@cortexkit/aft-opencode 0.21.0 → 0.22.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 +0 -2
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts +2 -0
- package/dist/configure-warnings.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +919 -281
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp-github-install.d.ts +12 -1
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/subagent-detect.d.ts +13 -0
- package/dist/shared/subagent-detect.d.ts.map +1 -0
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/imports.d.ts.map +1 -1
- package/dist/tools/permissions.d.ts +69 -0
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/refactoring.d.ts.map +1 -1
- package/dist/tools/safety.d.ts.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/structure.d.ts.map +1 -1
- package/dist/tui.js +7 -7
- package/package.json +7 -7
- package/src/shared/rpc-server.ts +5 -2
- package/src/shared/subagent-detect.ts +150 -0
- package/dist/shared/runtime.d.ts +0 -10
- package/dist/shared/runtime.d.ts.map +0 -1
- package/src/shared/runtime.ts +0 -26
package/dist/index.js
CHANGED
|
@@ -7803,20 +7803,27 @@ var require_src2 = __commonJS((exports, module) => {
|
|
|
7803
7803
|
});
|
|
7804
7804
|
|
|
7805
7805
|
// src/index.ts
|
|
7806
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
7806
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
7807
7807
|
import { createRequire as createRequire2 } from "module";
|
|
7808
7808
|
import { homedir as homedir11 } from "os";
|
|
7809
|
-
import { join as
|
|
7809
|
+
import { join as join21 } from "path";
|
|
7810
7810
|
|
|
7811
7811
|
// ../aft-bridge/dist/active-logger.js
|
|
7812
|
-
var active;
|
|
7812
|
+
var ACTIVE_LOGGER_SYMBOL = Symbol.for("aft-bridge-active-logger");
|
|
7813
|
+
function loggerGlobal() {
|
|
7814
|
+
return globalThis;
|
|
7815
|
+
}
|
|
7813
7816
|
function setActiveLogger(logger) {
|
|
7814
|
-
|
|
7817
|
+
loggerGlobal()[ACTIVE_LOGGER_SYMBOL] = logger;
|
|
7818
|
+
}
|
|
7819
|
+
function getActiveLogger() {
|
|
7820
|
+
return loggerGlobal()[ACTIVE_LOGGER_SYMBOL];
|
|
7815
7821
|
}
|
|
7816
7822
|
function getLogFilePath() {
|
|
7817
|
-
return
|
|
7823
|
+
return getActiveLogger()?.getLogFilePath?.();
|
|
7818
7824
|
}
|
|
7819
7825
|
function log(message, meta) {
|
|
7826
|
+
const active = getActiveLogger();
|
|
7820
7827
|
if (active) {
|
|
7821
7828
|
active.log(message, meta);
|
|
7822
7829
|
} else {
|
|
@@ -7824,6 +7831,7 @@ function log(message, meta) {
|
|
|
7824
7831
|
}
|
|
7825
7832
|
}
|
|
7826
7833
|
function warn(message, meta) {
|
|
7834
|
+
const active = getActiveLogger();
|
|
7827
7835
|
if (active) {
|
|
7828
7836
|
active.warn(message, meta);
|
|
7829
7837
|
} else {
|
|
@@ -7831,6 +7839,7 @@ function warn(message, meta) {
|
|
|
7831
7839
|
}
|
|
7832
7840
|
}
|
|
7833
7841
|
function error(message, meta) {
|
|
7842
|
+
const active = getActiveLogger();
|
|
7834
7843
|
if (active) {
|
|
7835
7844
|
active.error(message, meta);
|
|
7836
7845
|
} else {
|
|
@@ -7850,6 +7859,7 @@ function sessionError(sessionId, message) {
|
|
|
7850
7859
|
import { spawn } from "child_process";
|
|
7851
7860
|
import { homedir } from "os";
|
|
7852
7861
|
import { join } from "path";
|
|
7862
|
+
import { StringDecoder } from "string_decoder";
|
|
7853
7863
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
7854
7864
|
var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
|
|
7855
7865
|
var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
|
|
@@ -7946,6 +7956,7 @@ class BinaryBridge {
|
|
|
7946
7956
|
configureWarningClients = new Map;
|
|
7947
7957
|
restartResetTimer = null;
|
|
7948
7958
|
errorPrefix;
|
|
7959
|
+
logger;
|
|
7949
7960
|
constructor(binaryPath, cwd, options, configOverrides) {
|
|
7950
7961
|
this.binaryPath = binaryPath;
|
|
7951
7962
|
this.cwd = cwd;
|
|
@@ -7958,6 +7969,28 @@ class BinaryBridge {
|
|
|
7958
7969
|
this.onBashCompletion = options?.onBashCompletion;
|
|
7959
7970
|
this.onBashLongRunning = options?.onBashLongRunning;
|
|
7960
7971
|
this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
|
|
7972
|
+
this.logger = options?.logger;
|
|
7973
|
+
}
|
|
7974
|
+
logVia(message, meta) {
|
|
7975
|
+
const logger = this.logger ?? getActiveLogger();
|
|
7976
|
+
if (logger)
|
|
7977
|
+
logger.log(message, meta);
|
|
7978
|
+
else
|
|
7979
|
+
log(message, meta);
|
|
7980
|
+
}
|
|
7981
|
+
warnVia(message, meta) {
|
|
7982
|
+
const logger = this.logger ?? getActiveLogger();
|
|
7983
|
+
if (logger)
|
|
7984
|
+
logger.warn(message, meta);
|
|
7985
|
+
else
|
|
7986
|
+
warn(message, meta);
|
|
7987
|
+
}
|
|
7988
|
+
errorVia(message, meta) {
|
|
7989
|
+
const logger = this.logger ?? getActiveLogger();
|
|
7990
|
+
if (logger)
|
|
7991
|
+
logger.error(message, meta);
|
|
7992
|
+
else
|
|
7993
|
+
error(message, meta);
|
|
7961
7994
|
}
|
|
7962
7995
|
get restartCount() {
|
|
7963
7996
|
return this._restartCount;
|
|
@@ -8066,8 +8099,8 @@ class BinaryBridge {
|
|
|
8066
8099
|
return;
|
|
8067
8100
|
if (configResult.warnings.length === 0)
|
|
8068
8101
|
return;
|
|
8102
|
+
const sessionId = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
8069
8103
|
try {
|
|
8070
|
-
const sessionId = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
8071
8104
|
await this.onConfigureWarnings({
|
|
8072
8105
|
projectRoot: this.cwd,
|
|
8073
8106
|
sessionId,
|
|
@@ -8076,6 +8109,10 @@ class BinaryBridge {
|
|
|
8076
8109
|
});
|
|
8077
8110
|
} catch (err) {
|
|
8078
8111
|
warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8112
|
+
} finally {
|
|
8113
|
+
if (sessionId) {
|
|
8114
|
+
this.configureWarningClients.delete(sessionId);
|
|
8115
|
+
}
|
|
8079
8116
|
}
|
|
8080
8117
|
}
|
|
8081
8118
|
async handleConfigureWarningsFrame(frame) {
|
|
@@ -8087,16 +8124,23 @@ class BinaryBridge {
|
|
|
8087
8124
|
const projectRoot = typeof frame.project_root === "string" ? frame.project_root : this.cwd;
|
|
8088
8125
|
const rawSessionId = frame.session_id;
|
|
8089
8126
|
const sessionId = typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId : null;
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8127
|
+
try {
|
|
8128
|
+
await this.onConfigureWarnings({
|
|
8129
|
+
projectRoot,
|
|
8130
|
+
sessionId,
|
|
8131
|
+
client: sessionId ? this.configureWarningClients.get(sessionId) : undefined,
|
|
8132
|
+
warnings
|
|
8133
|
+
});
|
|
8134
|
+
} finally {
|
|
8135
|
+
if (sessionId) {
|
|
8136
|
+
this.configureWarningClients.delete(sessionId);
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8096
8139
|
}
|
|
8097
8140
|
async shutdown() {
|
|
8098
8141
|
this._shuttingDown = true;
|
|
8099
8142
|
this.clearRestartResetTimer();
|
|
8143
|
+
this.configureWarningClients.clear();
|
|
8100
8144
|
this.rejectAllPending(new Error(`${this.errorPrefix} Bridge shutting down`));
|
|
8101
8145
|
if (this.process) {
|
|
8102
8146
|
const proc = this.process;
|
|
@@ -8120,10 +8164,12 @@ class BinaryBridge {
|
|
|
8120
8164
|
return;
|
|
8121
8165
|
try {
|
|
8122
8166
|
const resp = await this.send("version");
|
|
8167
|
+
if (resp.success === false) {
|
|
8168
|
+
throw new Error(`Binary version check failed: ${String(resp.code ?? "unknown")} \u2014 likely too old`);
|
|
8169
|
+
}
|
|
8123
8170
|
const binaryVersion = resp.version;
|
|
8124
|
-
if (
|
|
8125
|
-
|
|
8126
|
-
return;
|
|
8171
|
+
if (typeof binaryVersion !== "string") {
|
|
8172
|
+
throw new Error(`Binary did not report a version \u2014 likely too old (minVersion: ${this.minVersion})`);
|
|
8127
8173
|
}
|
|
8128
8174
|
log(`Binary version: ${binaryVersion}`);
|
|
8129
8175
|
if (compareSemver(binaryVersion, this.minVersion) < 0) {
|
|
@@ -8132,6 +8178,7 @@ class BinaryBridge {
|
|
|
8132
8178
|
}
|
|
8133
8179
|
} catch (err) {
|
|
8134
8180
|
warn(`Version check failed: ${err.message}`);
|
|
8181
|
+
throw err;
|
|
8135
8182
|
}
|
|
8136
8183
|
}
|
|
8137
8184
|
ensureSpawned(triggeringSessionId) {
|
|
@@ -8173,19 +8220,23 @@ class BinaryBridge {
|
|
|
8173
8220
|
env
|
|
8174
8221
|
});
|
|
8175
8222
|
const currentChild = child;
|
|
8223
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
8176
8224
|
child.stdout?.on("data", (chunk) => {
|
|
8177
|
-
this.onStdoutData(
|
|
8225
|
+
this.onStdoutData(stdoutDecoder.write(chunk));
|
|
8226
|
+
});
|
|
8227
|
+
child.stdout?.on("end", () => {
|
|
8228
|
+
const remaining = stdoutDecoder.end();
|
|
8229
|
+
if (remaining)
|
|
8230
|
+
this.onStdoutData(remaining);
|
|
8178
8231
|
});
|
|
8232
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
8179
8233
|
child.stderr?.on("data", (chunk) => {
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
log(tagged);
|
|
8187
|
-
this.pushStderrLine(tagged);
|
|
8188
|
-
}
|
|
8234
|
+
this.onStderrData(stderrDecoder.write(chunk));
|
|
8235
|
+
});
|
|
8236
|
+
child.stderr?.on("end", () => {
|
|
8237
|
+
const remaining = stderrDecoder.end();
|
|
8238
|
+
if (remaining)
|
|
8239
|
+
this.onStderrData(remaining);
|
|
8189
8240
|
});
|
|
8190
8241
|
child.on("error", (err) => {
|
|
8191
8242
|
if (this.process !== currentChild)
|
|
@@ -8218,6 +8269,17 @@ class BinaryBridge {
|
|
|
8218
8269
|
this.stderrTail.shift();
|
|
8219
8270
|
}
|
|
8220
8271
|
}
|
|
8272
|
+
onStderrData(data) {
|
|
8273
|
+
const lines = data.trimEnd().split(`
|
|
8274
|
+
`);
|
|
8275
|
+
for (const line of lines) {
|
|
8276
|
+
if (!line)
|
|
8277
|
+
continue;
|
|
8278
|
+
const tagged = tagStderrLine(line);
|
|
8279
|
+
log(tagged);
|
|
8280
|
+
this.pushStderrLine(tagged);
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8221
8283
|
formatStderrTail() {
|
|
8222
8284
|
if (this.stderrTail.length === 0)
|
|
8223
8285
|
return "";
|
|
@@ -8297,6 +8359,7 @@ class BinaryBridge {
|
|
|
8297
8359
|
}
|
|
8298
8360
|
}
|
|
8299
8361
|
handleTimeout(triggeringSessionId) {
|
|
8362
|
+
this.rejectAllPending(new Error(`${this.errorPrefix} bridge killed during sibling timeout \u2014 request aborted`));
|
|
8300
8363
|
if (this.process) {
|
|
8301
8364
|
this.process.kill("SIGKILL");
|
|
8302
8365
|
this.process = null;
|
|
@@ -8625,23 +8688,23 @@ async function ensureOnnxRuntime(storageDir) {
|
|
|
8625
8688
|
const onnxBaseDir = join3(storageDir, "onnxruntime");
|
|
8626
8689
|
mkdirSync2(onnxBaseDir, { recursive: true });
|
|
8627
8690
|
const lockPath = join3(onnxBaseDir, ONNX_LOCK_FILE);
|
|
8628
|
-
|
|
8691
|
+
cleanupAbandonedStagingDirs(onnxBaseDir);
|
|
8629
8692
|
if (!acquireLock(lockPath)) {
|
|
8630
8693
|
warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
|
|
8631
8694
|
return null;
|
|
8632
8695
|
}
|
|
8633
8696
|
try {
|
|
8697
|
+
cleanupIncompleteTargetIfUnowned(ortDir);
|
|
8634
8698
|
return await downloadOnnxRuntime(info, ortDir);
|
|
8635
8699
|
} finally {
|
|
8636
8700
|
releaseLock(lockPath);
|
|
8637
8701
|
}
|
|
8638
8702
|
}
|
|
8639
|
-
function
|
|
8703
|
+
function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
8640
8704
|
try {
|
|
8641
8705
|
const entries = readdirSync(onnxBaseDir);
|
|
8642
|
-
const ortDirBaseName = ortDir.slice(onnxBaseDir.length + 1);
|
|
8643
8706
|
for (const entry of entries) {
|
|
8644
|
-
if (!entry.startsWith(`${
|
|
8707
|
+
if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
|
|
8645
8708
|
continue;
|
|
8646
8709
|
const stagingDir = join3(onnxBaseDir, entry);
|
|
8647
8710
|
const parts = entry.split(".");
|
|
@@ -8672,6 +8735,8 @@ function cleanupAbandonedOnnxAttempts(onnxBaseDir, ortDir) {
|
|
|
8672
8735
|
}
|
|
8673
8736
|
}
|
|
8674
8737
|
} catch {}
|
|
8738
|
+
}
|
|
8739
|
+
function cleanupIncompleteTargetIfUnowned(ortDir) {
|
|
8675
8740
|
try {
|
|
8676
8741
|
if (existsSync2(ortDir) && !existsSync2(join3(ortDir, ONNX_INSTALLED_META_FILE))) {
|
|
8677
8742
|
log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
|
|
@@ -8862,25 +8927,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
8862
8927
|
realFiles.push(libFile);
|
|
8863
8928
|
}
|
|
8864
8929
|
}
|
|
8865
|
-
|
|
8866
|
-
const src = join3(extractedDir, libFile);
|
|
8867
|
-
const dst = join3(targetDir, libFile);
|
|
8868
|
-
try {
|
|
8869
|
-
copyFileSync(src, dst);
|
|
8870
|
-
if (process.platform !== "win32") {
|
|
8871
|
-
chmodSync2(dst, 493);
|
|
8872
|
-
}
|
|
8873
|
-
} catch (copyErr) {
|
|
8874
|
-
log(`ORT extract: failed to copy ${libFile}: ${copyErr}`);
|
|
8875
|
-
}
|
|
8876
|
-
}
|
|
8877
|
-
for (const link of symlinks) {
|
|
8878
|
-
const dst = join3(targetDir, link.name);
|
|
8879
|
-
try {
|
|
8880
|
-
unlinkSync2(dst);
|
|
8881
|
-
} catch {}
|
|
8882
|
-
symlinkSync(link.target, dst);
|
|
8883
|
-
}
|
|
8930
|
+
copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks);
|
|
8884
8931
|
const libPath = join3(targetDir, info.libName);
|
|
8885
8932
|
let libHash = null;
|
|
8886
8933
|
try {
|
|
@@ -8903,6 +8950,45 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
8903
8950
|
return null;
|
|
8904
8951
|
}
|
|
8905
8952
|
}
|
|
8953
|
+
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync) {
|
|
8954
|
+
const requiredLibs = new Set([info.libName]);
|
|
8955
|
+
for (const libFile of realFiles) {
|
|
8956
|
+
const src = join3(extractedDir, libFile);
|
|
8957
|
+
const dst = join3(targetDir, libFile);
|
|
8958
|
+
try {
|
|
8959
|
+
copyFile(src, dst);
|
|
8960
|
+
if (process.platform !== "win32") {
|
|
8961
|
+
chmodSync2(dst, 493);
|
|
8962
|
+
}
|
|
8963
|
+
} catch (copyErr) {
|
|
8964
|
+
if (requiredLibs.has(libFile)) {
|
|
8965
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
8966
|
+
throw copyErr;
|
|
8967
|
+
}
|
|
8968
|
+
log(`ORT extract: failed to copy optional ${libFile}: ${copyErr}`);
|
|
8969
|
+
}
|
|
8970
|
+
}
|
|
8971
|
+
for (const link of symlinks) {
|
|
8972
|
+
const dst = join3(targetDir, link.name);
|
|
8973
|
+
try {
|
|
8974
|
+
unlinkSync2(dst);
|
|
8975
|
+
} catch {}
|
|
8976
|
+
try {
|
|
8977
|
+
symlinkSync(link.target, dst);
|
|
8978
|
+
} catch (symlinkErr) {
|
|
8979
|
+
if (requiredLibs.has(link.name)) {
|
|
8980
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
8981
|
+
throw symlinkErr;
|
|
8982
|
+
}
|
|
8983
|
+
log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
const requiredPath = join3(targetDir, info.libName);
|
|
8987
|
+
if (!existsSync2(requiredPath)) {
|
|
8988
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
8989
|
+
throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8906
8992
|
async function extractZipArchive(archivePath, destinationDir) {
|
|
8907
8993
|
if (process.platform === "win32") {
|
|
8908
8994
|
execFileSync("tar.exe", ["-xf", archivePath, "-C", destinationDir], {
|
|
@@ -9086,11 +9172,13 @@ class BridgePool {
|
|
|
9086
9172
|
idleTimeoutMs;
|
|
9087
9173
|
bridgeOptions;
|
|
9088
9174
|
configOverrides;
|
|
9175
|
+
logger;
|
|
9089
9176
|
cleanupTimer = null;
|
|
9090
9177
|
constructor(binaryPath, options = {}, configOverrides = {}) {
|
|
9091
9178
|
this.binaryPath = binaryPath;
|
|
9092
9179
|
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
9093
9180
|
this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
9181
|
+
this.logger = options.logger;
|
|
9094
9182
|
this.bridgeOptions = {
|
|
9095
9183
|
timeoutMs: options.timeoutMs,
|
|
9096
9184
|
maxRestarts: options.maxRestarts,
|
|
@@ -9134,8 +9222,10 @@ class BridgePool {
|
|
|
9134
9222
|
cleanup() {
|
|
9135
9223
|
const now = Date.now();
|
|
9136
9224
|
for (const [dir, entry] of this.bridges) {
|
|
9225
|
+
if (entry.bridge.hasPendingRequests())
|
|
9226
|
+
continue;
|
|
9137
9227
|
if (now - entry.lastUsed > this.idleTimeoutMs) {
|
|
9138
|
-
entry.bridge.shutdown().catch((err) => error("cleanup shutdown failed:", err));
|
|
9228
|
+
entry.bridge.shutdown().catch((err) => this.error("cleanup shutdown failed:", err));
|
|
9139
9229
|
this.bridges.delete(dir);
|
|
9140
9230
|
}
|
|
9141
9231
|
}
|
|
@@ -9144,6 +9234,8 @@ class BridgePool {
|
|
|
9144
9234
|
let oldestDir = null;
|
|
9145
9235
|
let oldestTime = Infinity;
|
|
9146
9236
|
for (const [dir, entry] of this.bridges) {
|
|
9237
|
+
if (entry.bridge.hasPendingRequests())
|
|
9238
|
+
continue;
|
|
9147
9239
|
if (entry.lastUsed < oldestTime) {
|
|
9148
9240
|
oldestTime = entry.lastUsed;
|
|
9149
9241
|
oldestDir = dir;
|
|
@@ -9151,7 +9243,7 @@ class BridgePool {
|
|
|
9151
9243
|
}
|
|
9152
9244
|
if (oldestDir) {
|
|
9153
9245
|
const entry = this.bridges.get(oldestDir);
|
|
9154
|
-
entry?.bridge.shutdown().catch((err) => error("eviction shutdown failed:", err));
|
|
9246
|
+
entry?.bridge.shutdown().catch((err) => this.error("eviction shutdown failed:", err));
|
|
9155
9247
|
this.bridges.delete(oldestDir);
|
|
9156
9248
|
}
|
|
9157
9249
|
}
|
|
@@ -9169,7 +9261,21 @@ class BridgePool {
|
|
|
9169
9261
|
const shutdowns = Array.from(this.bridges.values()).map((entry) => entry.bridge.shutdown());
|
|
9170
9262
|
this.bridges.clear();
|
|
9171
9263
|
await Promise.allSettled(shutdowns);
|
|
9172
|
-
log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
|
|
9264
|
+
this.log(`Binary path updated to ${newPath}. All bridges cleared \u2014 next calls will use the new binary.`);
|
|
9265
|
+
}
|
|
9266
|
+
log(message, meta) {
|
|
9267
|
+
const logger = this.logger ?? getActiveLogger();
|
|
9268
|
+
if (logger)
|
|
9269
|
+
logger.log(message, meta);
|
|
9270
|
+
else
|
|
9271
|
+
log(message, meta);
|
|
9272
|
+
}
|
|
9273
|
+
error(message, meta) {
|
|
9274
|
+
const logger = this.logger ?? getActiveLogger();
|
|
9275
|
+
if (logger)
|
|
9276
|
+
logger.error(message, meta);
|
|
9277
|
+
else
|
|
9278
|
+
error(message, meta);
|
|
9173
9279
|
}
|
|
9174
9280
|
setConfigureOverride(key, value) {
|
|
9175
9281
|
if (value === undefined) {
|
|
@@ -9289,7 +9395,7 @@ async function findBinary(expectedVersion) {
|
|
|
9289
9395
|
return syncResult;
|
|
9290
9396
|
}
|
|
9291
9397
|
log("Binary not found locally, attempting auto-download...");
|
|
9292
|
-
const downloaded = await ensureBinary();
|
|
9398
|
+
const downloaded = await ensureBinary(expectedVersion);
|
|
9293
9399
|
if (downloaded)
|
|
9294
9400
|
return downloaded;
|
|
9295
9401
|
throw new Error([
|
|
@@ -9853,16 +9959,6 @@ async function resolvePromptContext(client, sessionId) {
|
|
|
9853
9959
|
return null;
|
|
9854
9960
|
return result;
|
|
9855
9961
|
}
|
|
9856
|
-
async function getLastAssistantModel(client, sessionId) {
|
|
9857
|
-
const ctx = await resolvePromptContext(client, sessionId);
|
|
9858
|
-
if (!ctx?.model)
|
|
9859
|
-
return null;
|
|
9860
|
-
return {
|
|
9861
|
-
providerID: ctx.model.providerID,
|
|
9862
|
-
modelID: ctx.model.modelID,
|
|
9863
|
-
...ctx.variant ? { variant: ctx.variant } : {}
|
|
9864
|
-
};
|
|
9865
|
-
}
|
|
9866
9962
|
|
|
9867
9963
|
// src/bg-notifications.ts
|
|
9868
9964
|
var sessionBgStates = new Map;
|
|
@@ -9980,9 +10076,6 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
|
9980
10076
|
sessionWarn2(drainContext.sessionID, `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
9981
10077
|
});
|
|
9982
10078
|
}
|
|
9983
|
-
function resetBgWake(sessionID) {
|
|
9984
|
-
stateFor(sessionID).wakeFiredThisIdle = false;
|
|
9985
|
-
}
|
|
9986
10079
|
function formatSystemReminder(completions) {
|
|
9987
10080
|
const bullets = completions.map((completion) => formatCompletion(completion)).join(`
|
|
9988
10081
|
`);
|
|
@@ -10074,7 +10167,6 @@ function scheduleWake(state, sendWake, onSendFailure) {
|
|
|
10074
10167
|
state.pendingLongRunning = [];
|
|
10075
10168
|
sendWake(reminder).then(() => {
|
|
10076
10169
|
state.retryDelayMs = null;
|
|
10077
|
-
state.wakeFiredThisIdle = true;
|
|
10078
10170
|
}).catch((err) => {
|
|
10079
10171
|
state.pendingCompletions = [...pending, ...state.pendingCompletions];
|
|
10080
10172
|
state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
|
|
@@ -10096,7 +10188,6 @@ function stateFor(sessionID) {
|
|
|
10096
10188
|
pendingCompletions: [],
|
|
10097
10189
|
pendingLongRunning: [],
|
|
10098
10190
|
debounceTimer: null,
|
|
10099
|
-
wakeFiredThisIdle: false,
|
|
10100
10191
|
firstCompletionAt: null,
|
|
10101
10192
|
scheduledFireAt: null,
|
|
10102
10193
|
scheduledCompletionCount: 0,
|
|
@@ -21143,7 +21234,7 @@ function finalize(ctx, schema) {
|
|
|
21143
21234
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
21144
21235
|
} else if (ctx.target === "draft-04") {
|
|
21145
21236
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
21146
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
21237
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
21147
21238
|
if (ctx.external?.uri) {
|
|
21148
21239
|
const id = ctx.external.registry.get(schema)?.id;
|
|
21149
21240
|
if (!id)
|
|
@@ -21391,7 +21482,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
|
|
|
21391
21482
|
if (val === undefined) {
|
|
21392
21483
|
if (ctx.unrepresentable === "throw") {
|
|
21393
21484
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
21394
|
-
}
|
|
21485
|
+
}
|
|
21395
21486
|
} else if (typeof val === "bigint") {
|
|
21396
21487
|
if (ctx.unrepresentable === "throw") {
|
|
21397
21488
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -24220,7 +24311,8 @@ import { dirname as dirname4, join as join11 } from "path";
|
|
|
24220
24311
|
// src/hooks/auto-update-checker/cache.ts
|
|
24221
24312
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
24222
24313
|
import { spawn as spawn2 } from "child_process";
|
|
24223
|
-
import { existsSync as existsSync7, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
24314
|
+
import { cpSync, existsSync as existsSync7, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
24315
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
24224
24316
|
import { basename, dirname as dirname3, join as join10 } from "path";
|
|
24225
24317
|
|
|
24226
24318
|
// src/hooks/auto-update-checker/checker.ts
|
|
@@ -24471,6 +24563,42 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
24471
24563
|
}
|
|
24472
24564
|
|
|
24473
24565
|
// src/hooks/auto-update-checker/cache.ts
|
|
24566
|
+
var pendingSnapshots = new Map;
|
|
24567
|
+
function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
24568
|
+
const packageDir = join10(installDir, "node_modules", packageName);
|
|
24569
|
+
const lockfilePath = join10(installDir, "package-lock.json");
|
|
24570
|
+
const tempDir = mkdtempSync(join10(tmpdir2(), "aft-auto-update-"));
|
|
24571
|
+
const stagedPackageDir = existsSync7(packageDir) ? join10(tempDir, "package") : null;
|
|
24572
|
+
if (stagedPackageDir)
|
|
24573
|
+
cpSync(packageDir, stagedPackageDir, { recursive: true });
|
|
24574
|
+
return {
|
|
24575
|
+
packageJsonPath,
|
|
24576
|
+
packageJson: existsSync7(packageJsonPath) ? readFileSync5(packageJsonPath, "utf-8") : null,
|
|
24577
|
+
lockfilePath,
|
|
24578
|
+
lockfile: existsSync7(lockfilePath) ? readFileSync5(lockfilePath, "utf-8") : null,
|
|
24579
|
+
packageDir,
|
|
24580
|
+
stagedPackageDir,
|
|
24581
|
+
tempDir
|
|
24582
|
+
};
|
|
24583
|
+
}
|
|
24584
|
+
function restoreAutoUpdateSnapshot(snapshot) {
|
|
24585
|
+
try {
|
|
24586
|
+
if (snapshot.packageJson === null)
|
|
24587
|
+
rmSync2(snapshot.packageJsonPath, { force: true });
|
|
24588
|
+
else
|
|
24589
|
+
writeFileSync5(snapshot.packageJsonPath, snapshot.packageJson);
|
|
24590
|
+
if (snapshot.lockfile === null)
|
|
24591
|
+
rmSync2(snapshot.lockfilePath, { force: true });
|
|
24592
|
+
else
|
|
24593
|
+
writeFileSync5(snapshot.lockfilePath, snapshot.lockfile);
|
|
24594
|
+
rmSync2(snapshot.packageDir, { recursive: true, force: true });
|
|
24595
|
+
if (snapshot.stagedPackageDir) {
|
|
24596
|
+
cpSync(snapshot.stagedPackageDir, snapshot.packageDir, { recursive: true });
|
|
24597
|
+
}
|
|
24598
|
+
} finally {
|
|
24599
|
+
rmSync2(snapshot.tempDir, { recursive: true, force: true });
|
|
24600
|
+
}
|
|
24601
|
+
}
|
|
24474
24602
|
function stripPackageNameFromPath(pathValue, packageName) {
|
|
24475
24603
|
let current = pathValue;
|
|
24476
24604
|
for (const segment of [...packageName.split("/")].reverse()) {
|
|
@@ -24562,8 +24690,13 @@ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePacka
|
|
|
24562
24690
|
warn2("[auto-update-checker] No install context found for auto-update");
|
|
24563
24691
|
return null;
|
|
24564
24692
|
}
|
|
24565
|
-
|
|
24693
|
+
const snapshot = createAutoUpdateSnapshot(installContext.installDir, installContext.packageJsonPath, packageName);
|
|
24694
|
+
pendingSnapshots.set(installContext.installDir, snapshot);
|
|
24695
|
+
if (!ensureDependencyVersion(installContext.packageJsonPath, packageName, version2)) {
|
|
24696
|
+
pendingSnapshots.delete(installContext.installDir);
|
|
24697
|
+
restoreAutoUpdateSnapshot(snapshot);
|
|
24566
24698
|
return null;
|
|
24699
|
+
}
|
|
24567
24700
|
const packageRemoved = removeInstalledPackage(installContext.installDir, packageName);
|
|
24568
24701
|
const lockRemoved = removeFromPackageLock(installContext.installDir, packageName);
|
|
24569
24702
|
if (!packageRemoved && !lockRemoved) {
|
|
@@ -24582,7 +24715,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
24582
24715
|
return false;
|
|
24583
24716
|
const proc = spawn2("npm", ["install", "--no-audit", "--no-fund", "--no-progress"], {
|
|
24584
24717
|
cwd: installDir,
|
|
24585
|
-
stdio: "
|
|
24718
|
+
stdio: "ignore"
|
|
24586
24719
|
});
|
|
24587
24720
|
const abortProcess = () => {
|
|
24588
24721
|
try {
|
|
@@ -24601,10 +24734,27 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
24601
24734
|
options.signal?.removeEventListener("abort", abortProcess);
|
|
24602
24735
|
if (result === "timeout" || options.signal?.aborted) {
|
|
24603
24736
|
abortProcess();
|
|
24737
|
+
const snapshot2 = pendingSnapshots.get(installDir);
|
|
24738
|
+
if (snapshot2) {
|
|
24739
|
+
pendingSnapshots.delete(installDir);
|
|
24740
|
+
restoreAutoUpdateSnapshot(snapshot2);
|
|
24741
|
+
}
|
|
24604
24742
|
return false;
|
|
24605
24743
|
}
|
|
24744
|
+
const snapshot = pendingSnapshots.get(installDir);
|
|
24745
|
+
pendingSnapshots.delete(installDir);
|
|
24746
|
+
if (!result && snapshot) {
|
|
24747
|
+
restoreAutoUpdateSnapshot(snapshot);
|
|
24748
|
+
} else if (snapshot) {
|
|
24749
|
+
rmSync2(snapshot.tempDir, { recursive: true, force: true });
|
|
24750
|
+
}
|
|
24606
24751
|
return result;
|
|
24607
24752
|
} catch (err) {
|
|
24753
|
+
const snapshot = pendingSnapshots.get(installDir);
|
|
24754
|
+
if (snapshot) {
|
|
24755
|
+
pendingSnapshots.delete(installDir);
|
|
24756
|
+
restoreAutoUpdateSnapshot(snapshot);
|
|
24757
|
+
}
|
|
24608
24758
|
warn2(`[auto-update-checker] npm install error: ${String(err)}`);
|
|
24609
24759
|
return false;
|
|
24610
24760
|
} finally {
|
|
@@ -24768,7 +24918,8 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
24768
24918
|
// src/lsp-auto-install.ts
|
|
24769
24919
|
import { spawn as spawn3 } from "child_process";
|
|
24770
24920
|
import { createHash as createHash3 } from "crypto";
|
|
24771
|
-
import { createReadStream, statSync as statSync4 } from "fs";
|
|
24921
|
+
import { createReadStream, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync4, rmSync as rmSync3, statSync as statSync4 } from "fs";
|
|
24922
|
+
import { join as join14 } from "path";
|
|
24772
24923
|
|
|
24773
24924
|
// src/lsp-cache.ts
|
|
24774
24925
|
import {
|
|
@@ -25452,6 +25603,50 @@ function hashInstalledBinary(spec) {
|
|
|
25452
25603
|
stream.on("end", () => resolve3(hash2.digest("hex")));
|
|
25453
25604
|
});
|
|
25454
25605
|
}
|
|
25606
|
+
function installedBinaryPath(spec) {
|
|
25607
|
+
const candidates = process.platform === "win32" ? [
|
|
25608
|
+
lspBinaryPath(spec.npm, spec.binary),
|
|
25609
|
+
lspBinaryPath(spec.npm, `${spec.binary}.cmd`),
|
|
25610
|
+
lspBinaryPath(spec.npm, `${spec.binary}.exe`),
|
|
25611
|
+
lspBinaryPath(spec.npm, `${spec.binary}.bat`)
|
|
25612
|
+
] : [lspBinaryPath(spec.npm, spec.binary)];
|
|
25613
|
+
for (const candidate of candidates) {
|
|
25614
|
+
try {
|
|
25615
|
+
if (statSync4(candidate).isFile())
|
|
25616
|
+
return candidate;
|
|
25617
|
+
} catch {}
|
|
25618
|
+
}
|
|
25619
|
+
return null;
|
|
25620
|
+
}
|
|
25621
|
+
function sha256OfFileSync(path2) {
|
|
25622
|
+
return createHash3("sha256").update(readFileSync8(path2)).digest("hex");
|
|
25623
|
+
}
|
|
25624
|
+
function quarantineCachedNpmInstall(spec, reason) {
|
|
25625
|
+
const packageDir = lspPackageDir(spec.npm);
|
|
25626
|
+
const dest = join14(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
|
|
25627
|
+
warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
25628
|
+
try {
|
|
25629
|
+
mkdirSync7(join14(dest, ".."), { recursive: true });
|
|
25630
|
+
rmSync3(dest, { recursive: true, force: true });
|
|
25631
|
+
renameSync4(packageDir, dest);
|
|
25632
|
+
} catch (err) {
|
|
25633
|
+
warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
|
|
25634
|
+
}
|
|
25635
|
+
}
|
|
25636
|
+
function validateCachedNpmInstall(spec) {
|
|
25637
|
+
const meta3 = readInstalledMeta(spec.npm);
|
|
25638
|
+
const binaryPath = installedBinaryPath(spec);
|
|
25639
|
+
if (!meta3?.sha256 || !meta3.version || !isSafeVersion(meta3.version) || !binaryPath) {
|
|
25640
|
+
quarantineCachedNpmInstall(spec, "missing/unsafe metadata or binary");
|
|
25641
|
+
return false;
|
|
25642
|
+
}
|
|
25643
|
+
const currentHash = sha256OfFileSync(binaryPath);
|
|
25644
|
+
if (currentHash !== meta3.sha256) {
|
|
25645
|
+
quarantineCachedNpmInstall(spec, `recorded ${meta3.sha256}, current ${currentHash}`);
|
|
25646
|
+
return false;
|
|
25647
|
+
}
|
|
25648
|
+
return true;
|
|
25649
|
+
}
|
|
25455
25650
|
function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
25456
25651
|
const cachedBinDirs = [];
|
|
25457
25652
|
const skipped = [];
|
|
@@ -25464,7 +25659,7 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
25464
25659
|
return projectExtensions;
|
|
25465
25660
|
};
|
|
25466
25661
|
for (const spec of NPM_LSP_TABLE) {
|
|
25467
|
-
if (isInstalled(spec.npm, spec.binary)) {
|
|
25662
|
+
if (isInstalled(spec.npm, spec.binary) && validateCachedNpmInstall(spec)) {
|
|
25468
25663
|
cachedBinDirs.push(lspBinDir(spec.npm));
|
|
25469
25664
|
}
|
|
25470
25665
|
if (config2.disabled.has(spec.id)) {
|
|
@@ -25516,16 +25711,17 @@ import {
|
|
|
25516
25711
|
createWriteStream as createWriteStream2,
|
|
25517
25712
|
existsSync as existsSync10,
|
|
25518
25713
|
lstatSync as lstatSync2,
|
|
25519
|
-
mkdirSync as
|
|
25714
|
+
mkdirSync as mkdirSync8,
|
|
25520
25715
|
readdirSync as readdirSync4,
|
|
25716
|
+
readFileSync as readFileSync9,
|
|
25521
25717
|
readlinkSync as readlinkSync2,
|
|
25522
25718
|
realpathSync as realpathSync3,
|
|
25523
|
-
renameSync as
|
|
25524
|
-
rmSync as
|
|
25719
|
+
renameSync as renameSync5,
|
|
25720
|
+
rmSync as rmSync4,
|
|
25525
25721
|
statSync as statSync5,
|
|
25526
25722
|
unlinkSync as unlinkSync6
|
|
25527
25723
|
} from "fs";
|
|
25528
|
-
import { dirname as dirname5, join as
|
|
25724
|
+
import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve3 } from "path";
|
|
25529
25725
|
import { Readable as Readable2 } from "stream";
|
|
25530
25726
|
import { pipeline as pipeline2 } from "stream/promises";
|
|
25531
25727
|
|
|
@@ -25615,25 +25811,25 @@ function detectHostPlatform() {
|
|
|
25615
25811
|
|
|
25616
25812
|
// src/lsp-github-install.ts
|
|
25617
25813
|
function ghCacheRoot() {
|
|
25618
|
-
return
|
|
25814
|
+
return join15(aftCacheBase(), "lsp-binaries");
|
|
25619
25815
|
}
|
|
25620
25816
|
function ghPackageDir(spec) {
|
|
25621
|
-
return
|
|
25817
|
+
return join15(ghCacheRoot(), spec.id);
|
|
25622
25818
|
}
|
|
25623
25819
|
function ghBinDir(spec) {
|
|
25624
|
-
return
|
|
25820
|
+
return join15(ghPackageDir(spec), "bin");
|
|
25625
25821
|
}
|
|
25626
25822
|
function ghExtractDir(spec) {
|
|
25627
|
-
return
|
|
25823
|
+
return join15(ghPackageDir(spec), "extracted");
|
|
25628
25824
|
}
|
|
25629
25825
|
function ghBinaryPath(spec, platform2) {
|
|
25630
25826
|
const ext = platform2 === "win32" ? ".exe" : "";
|
|
25631
|
-
return
|
|
25827
|
+
return join15(ghBinDir(spec), `${spec.binary}${ext}`);
|
|
25632
25828
|
}
|
|
25633
25829
|
function isGithubInstalled(spec, platform2) {
|
|
25634
25830
|
for (const candidate of ghBinaryCandidates(spec, platform2)) {
|
|
25635
25831
|
try {
|
|
25636
|
-
if (statSync5(
|
|
25832
|
+
if (statSync5(join15(ghBinDir(spec), candidate)).isFile())
|
|
25637
25833
|
return true;
|
|
25638
25834
|
} catch {}
|
|
25639
25835
|
}
|
|
@@ -25655,6 +25851,9 @@ function sha256OfFile(path2) {
|
|
|
25655
25851
|
stream.on("end", () => resolve4(hash2.digest("hex")));
|
|
25656
25852
|
});
|
|
25657
25853
|
}
|
|
25854
|
+
function sha256OfFileSync2(path2) {
|
|
25855
|
+
return createHash4("sha256").update(readFileSync9(path2)).digest("hex");
|
|
25856
|
+
}
|
|
25658
25857
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
25659
25858
|
const candidates = [];
|
|
25660
25859
|
candidates.push(tag);
|
|
@@ -25675,11 +25874,7 @@ async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
|
25675
25874
|
const url2 = `https://api.github.com/repos/${githubRepo}/releases/tags/${encodeURIComponent(candidate)}`;
|
|
25676
25875
|
const timeout = controlledTimeoutSignal(15000, signal);
|
|
25677
25876
|
try {
|
|
25678
|
-
const res = await
|
|
25679
|
-
headers,
|
|
25680
|
-
redirect: "follow",
|
|
25681
|
-
signal: timeout.signal
|
|
25682
|
-
});
|
|
25877
|
+
const res = await fetchJsonFollowingRedirects(url2, headers, fetchImpl, timeout.signal);
|
|
25683
25878
|
if (res.status === 404)
|
|
25684
25879
|
continue;
|
|
25685
25880
|
if (!res.ok) {
|
|
@@ -25709,6 +25904,23 @@ async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
|
25709
25904
|
}
|
|
25710
25905
|
return null;
|
|
25711
25906
|
}
|
|
25907
|
+
async function fetchJsonFollowingRedirects(url2, headers, fetchImpl, signal) {
|
|
25908
|
+
const maxRedirects = 5;
|
|
25909
|
+
let currentUrl = url2;
|
|
25910
|
+
for (let i = 0;i <= maxRedirects; i += 1) {
|
|
25911
|
+
assertAllowedDownloadUrl(currentUrl);
|
|
25912
|
+
const res = await fetchImpl(currentUrl, { headers, redirect: "manual", signal });
|
|
25913
|
+
if (res.status >= 300 && res.status < 400) {
|
|
25914
|
+
const location = res.headers.get("location");
|
|
25915
|
+
if (!location)
|
|
25916
|
+
throw new Error(`redirect status ${res.status} without Location`);
|
|
25917
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
25918
|
+
continue;
|
|
25919
|
+
}
|
|
25920
|
+
return res;
|
|
25921
|
+
}
|
|
25922
|
+
throw new Error(`too many redirects (>${maxRedirects})`);
|
|
25923
|
+
}
|
|
25712
25924
|
async function resolveTargetTag(spec, config2, fetchImpl, signal) {
|
|
25713
25925
|
const pinned = config2.versions[spec.githubRepo];
|
|
25714
25926
|
if (pinned) {
|
|
@@ -25803,17 +26015,12 @@ function assertAllowedDownloadUrl(rawUrl) {
|
|
|
25803
26015
|
return parsed;
|
|
25804
26016
|
}
|
|
25805
26017
|
async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
25806
|
-
assertAllowedDownloadUrl(url2);
|
|
25807
26018
|
if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES2) {
|
|
25808
26019
|
throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES2} (set lsp.versions to pin a smaller release if this is wrong)`);
|
|
25809
26020
|
}
|
|
25810
26021
|
const timeout = controlledTimeoutSignal(120000, signal);
|
|
25811
26022
|
try {
|
|
25812
|
-
const res = await
|
|
25813
|
-
headers: { accept: "application/octet-stream" },
|
|
25814
|
-
redirect: "follow",
|
|
25815
|
-
signal: timeout.signal
|
|
25816
|
-
});
|
|
26023
|
+
const res = await fetchFollowingRedirects(url2, fetchImpl, timeout.signal);
|
|
25817
26024
|
if (!res.ok || !res.body) {
|
|
25818
26025
|
throw new Error(`download failed (${res.status})`);
|
|
25819
26026
|
}
|
|
@@ -25821,7 +26028,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
25821
26028
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
|
|
25822
26029
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
|
|
25823
26030
|
}
|
|
25824
|
-
|
|
26031
|
+
mkdirSync8(dirname5(destPath), { recursive: true });
|
|
25825
26032
|
let bytesWritten = 0;
|
|
25826
26033
|
const guard = new TransformStream({
|
|
25827
26034
|
transform(chunk, controller) {
|
|
@@ -25845,6 +26052,27 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
25845
26052
|
timeout.cleanup();
|
|
25846
26053
|
}
|
|
25847
26054
|
}
|
|
26055
|
+
async function fetchFollowingRedirects(url2, fetchImpl, signal) {
|
|
26056
|
+
const maxRedirects = 5;
|
|
26057
|
+
let currentUrl = url2;
|
|
26058
|
+
for (let i = 0;i <= maxRedirects; i += 1) {
|
|
26059
|
+
assertAllowedDownloadUrl(currentUrl);
|
|
26060
|
+
const res = await fetchImpl(currentUrl, {
|
|
26061
|
+
headers: { accept: "application/octet-stream" },
|
|
26062
|
+
redirect: "manual",
|
|
26063
|
+
signal
|
|
26064
|
+
});
|
|
26065
|
+
if (res.status >= 300 && res.status < 400) {
|
|
26066
|
+
const location = res.headers.get("location");
|
|
26067
|
+
if (!location)
|
|
26068
|
+
throw new Error(`redirect status ${res.status} without Location`);
|
|
26069
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
26070
|
+
continue;
|
|
26071
|
+
}
|
|
26072
|
+
return res;
|
|
26073
|
+
}
|
|
26074
|
+
throw new Error(`too many redirects (>${maxRedirects})`);
|
|
26075
|
+
}
|
|
25848
26076
|
function validateExtraction(stagingRoot) {
|
|
25849
26077
|
const realStagingRoot = realpathSync3(stagingRoot);
|
|
25850
26078
|
let totalBytes = 0;
|
|
@@ -25856,7 +26084,7 @@ function validateExtraction(stagingRoot) {
|
|
|
25856
26084
|
throw new Error(`failed to read staging dir ${dir}: ${err}`);
|
|
25857
26085
|
}
|
|
25858
26086
|
for (const entry of entries) {
|
|
25859
|
-
const full =
|
|
26087
|
+
const full = join15(dir, entry);
|
|
25860
26088
|
let lst;
|
|
25861
26089
|
try {
|
|
25862
26090
|
lst = lstatSync2(full);
|
|
@@ -25894,27 +26122,83 @@ function validateExtraction(stagingRoot) {
|
|
|
25894
26122
|
};
|
|
25895
26123
|
walk(realStagingRoot);
|
|
25896
26124
|
}
|
|
26125
|
+
function precheckArchiveSize(archivePath, archiveType) {
|
|
26126
|
+
let totalBytes = 0;
|
|
26127
|
+
if (archiveType === "zip") {
|
|
26128
|
+
const out = execFileSync2("unzip", ["-l", archivePath], { encoding: "utf8" });
|
|
26129
|
+
const match = out.match(/^\s*(\d+)\s+\d+\s+files?\s*$/m);
|
|
26130
|
+
if (match)
|
|
26131
|
+
totalBytes = Number.parseInt(match[1] ?? "0", 10);
|
|
26132
|
+
} else {
|
|
26133
|
+
const out = execFileSync2("tar", ["-tvf", archivePath], { encoding: "utf8" });
|
|
26134
|
+
for (const line of out.split(`
|
|
26135
|
+
`)) {
|
|
26136
|
+
const parts = line.trim().split(/\s+/);
|
|
26137
|
+
if (parts.length >= 6) {
|
|
26138
|
+
const numeric = parts.map((part) => Number.parseInt(part, 10)).filter((value) => Number.isFinite(value) && value >= 0);
|
|
26139
|
+
if (numeric.length > 0)
|
|
26140
|
+
totalBytes += Math.max(...numeric);
|
|
26141
|
+
}
|
|
26142
|
+
}
|
|
26143
|
+
}
|
|
26144
|
+
if (totalBytes > MAX_EXTRACT_BYTES2) {
|
|
26145
|
+
throw new Error(`archive uncompressed size ${totalBytes} exceeds ${MAX_EXTRACT_BYTES2}`);
|
|
26146
|
+
}
|
|
26147
|
+
}
|
|
25897
26148
|
function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
25898
26149
|
const suffix = randomBytes(8).toString("hex");
|
|
25899
26150
|
const stagingDir = `${destDir}.staging-${suffix}`;
|
|
25900
26151
|
try {
|
|
25901
|
-
|
|
26152
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
25902
26153
|
} catch {}
|
|
25903
|
-
|
|
26154
|
+
mkdirSync8(stagingDir, { recursive: true });
|
|
25904
26155
|
try {
|
|
26156
|
+
precheckArchiveSize(archivePath, archiveType);
|
|
25905
26157
|
runPlatformExtractor(archivePath, stagingDir, archiveType);
|
|
25906
26158
|
validateExtraction(stagingDir);
|
|
25907
26159
|
try {
|
|
25908
|
-
|
|
26160
|
+
rmSync4(destDir, { recursive: true, force: true });
|
|
25909
26161
|
} catch {}
|
|
25910
|
-
|
|
26162
|
+
renameSync5(stagingDir, destDir);
|
|
25911
26163
|
} catch (err) {
|
|
25912
26164
|
try {
|
|
25913
|
-
|
|
26165
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
25914
26166
|
} catch {}
|
|
25915
26167
|
throw err;
|
|
25916
26168
|
}
|
|
25917
26169
|
}
|
|
26170
|
+
function quarantineCachedGithubInstall(spec, reason) {
|
|
26171
|
+
const packageDir = ghPackageDir(spec);
|
|
26172
|
+
const dest = join15(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
|
|
26173
|
+
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
26174
|
+
try {
|
|
26175
|
+
mkdirSync8(dirname5(dest), { recursive: true });
|
|
26176
|
+
rmSync4(dest, { recursive: true, force: true });
|
|
26177
|
+
renameSync5(packageDir, dest);
|
|
26178
|
+
} catch (err) {
|
|
26179
|
+
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
26180
|
+
}
|
|
26181
|
+
}
|
|
26182
|
+
function validateCachedGithubInstall(spec, platform2) {
|
|
26183
|
+
const meta3 = readInstalledMetaIn(ghPackageDir(spec));
|
|
26184
|
+
const binaryPath = ghBinaryCandidates(spec, platform2).map((candidate) => join15(ghBinDir(spec), candidate)).find((candidate) => {
|
|
26185
|
+
try {
|
|
26186
|
+
return statSync5(candidate).isFile();
|
|
26187
|
+
} catch {
|
|
26188
|
+
return false;
|
|
26189
|
+
}
|
|
26190
|
+
});
|
|
26191
|
+
if (!meta3?.sha256 || !meta3.version || !isSafeVersion(meta3.version) || !binaryPath) {
|
|
26192
|
+
quarantineCachedGithubInstall(spec, "missing/unsafe metadata or binary");
|
|
26193
|
+
return false;
|
|
26194
|
+
}
|
|
26195
|
+
const currentHash = sha256OfFileSync2(binaryPath);
|
|
26196
|
+
if (currentHash !== meta3.sha256) {
|
|
26197
|
+
quarantineCachedGithubInstall(spec, `recorded ${meta3.sha256}, current ${currentHash}`);
|
|
26198
|
+
return false;
|
|
26199
|
+
}
|
|
26200
|
+
return true;
|
|
26201
|
+
}
|
|
25918
26202
|
function runPlatformExtractor(archivePath, destDir, archiveType) {
|
|
25919
26203
|
if (archiveType === "zip") {
|
|
25920
26204
|
if (process.platform === "win32") {
|
|
@@ -25960,7 +26244,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
25960
26244
|
}
|
|
25961
26245
|
const pkgDir = ghPackageDir(spec);
|
|
25962
26246
|
const extractDir = ghExtractDir(spec);
|
|
25963
|
-
const archivePath =
|
|
26247
|
+
const archivePath = join15(pkgDir, expected.name);
|
|
25964
26248
|
log2(`[lsp] downloading ${spec.id} ${tag} \u2192 ${matchingAsset.url}`);
|
|
25965
26249
|
try {
|
|
25966
26250
|
await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
|
|
@@ -25999,13 +26283,13 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
25999
26283
|
unlinkSync6(archivePath);
|
|
26000
26284
|
} catch {}
|
|
26001
26285
|
}
|
|
26002
|
-
const innerBinaryPath =
|
|
26286
|
+
const innerBinaryPath = join15(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
|
|
26003
26287
|
if (!existsSync10(innerBinaryPath)) {
|
|
26004
26288
|
error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
26005
26289
|
return null;
|
|
26006
26290
|
}
|
|
26007
26291
|
const targetBinary = ghBinaryPath(spec, platform2);
|
|
26008
|
-
|
|
26292
|
+
mkdirSync8(dirname5(targetBinary), { recursive: true });
|
|
26009
26293
|
try {
|
|
26010
26294
|
copyFileSync3(innerBinaryPath, targetBinary);
|
|
26011
26295
|
if (platform2 !== "win32") {
|
|
@@ -26096,7 +26380,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
26096
26380
|
};
|
|
26097
26381
|
}
|
|
26098
26382
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
26099
|
-
if (isGithubInstalled(spec, host.platform)) {
|
|
26383
|
+
if (isGithubInstalled(spec, host.platform) && validateCachedGithubInstall(spec, host.platform)) {
|
|
26100
26384
|
cachedBinDirs.push(ghBinDir(spec));
|
|
26101
26385
|
}
|
|
26102
26386
|
if (config2.disabled.has(spec.id)) {
|
|
@@ -26245,9 +26529,9 @@ function normalizeToolMap(tools) {
|
|
|
26245
26529
|
}
|
|
26246
26530
|
|
|
26247
26531
|
// src/notifications.ts
|
|
26248
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
26532
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync6, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
26249
26533
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
26250
|
-
import { join as
|
|
26534
|
+
import { join as join16 } from "path";
|
|
26251
26535
|
function isTuiMode() {
|
|
26252
26536
|
return process.env.OPENCODE_CLIENT === "cli";
|
|
26253
26537
|
}
|
|
@@ -26271,15 +26555,15 @@ function getDesktopStatePath() {
|
|
|
26271
26555
|
const os2 = platform2();
|
|
26272
26556
|
const home = homedir9();
|
|
26273
26557
|
if (os2 === "darwin") {
|
|
26274
|
-
return
|
|
26558
|
+
return join16(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
|
|
26275
26559
|
}
|
|
26276
26560
|
if (os2 === "linux") {
|
|
26277
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ||
|
|
26278
|
-
return
|
|
26561
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join16(home, ".config");
|
|
26562
|
+
return join16(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
|
|
26279
26563
|
}
|
|
26280
26564
|
if (os2 === "win32") {
|
|
26281
|
-
const appData = process.env.APPDATA ||
|
|
26282
|
-
return
|
|
26565
|
+
const appData = process.env.APPDATA || join16(home, "AppData", "Roaming");
|
|
26566
|
+
return join16(appData, "ai.opencode.desktop", "opencode.global.dat");
|
|
26283
26567
|
}
|
|
26284
26568
|
return null;
|
|
26285
26569
|
}
|
|
@@ -26289,7 +26573,7 @@ function readDesktopState(directory) {
|
|
|
26289
26573
|
return { sessionId: null, serverUrl: null };
|
|
26290
26574
|
}
|
|
26291
26575
|
try {
|
|
26292
|
-
const raw =
|
|
26576
|
+
const raw = readFileSync10(statePath, "utf-8");
|
|
26293
26577
|
const state = JSON.parse(raw);
|
|
26294
26578
|
let serverUrl = null;
|
|
26295
26579
|
const serverStr = state.server;
|
|
@@ -26383,10 +26667,10 @@ async function sendWarning(opts, message) {
|
|
|
26383
26667
|
}
|
|
26384
26668
|
async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
26385
26669
|
if (storageDir) {
|
|
26386
|
-
const versionFile =
|
|
26670
|
+
const versionFile = join16(storageDir, "last_announced_version");
|
|
26387
26671
|
try {
|
|
26388
26672
|
if (existsSync11(versionFile)) {
|
|
26389
|
-
const lastVersion =
|
|
26673
|
+
const lastVersion = readFileSync10(versionFile, "utf-8").trim();
|
|
26390
26674
|
if (lastVersion === version2)
|
|
26391
26675
|
return;
|
|
26392
26676
|
}
|
|
@@ -26406,17 +26690,17 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
|
26406
26690
|
}
|
|
26407
26691
|
if (storageDir) {
|
|
26408
26692
|
try {
|
|
26409
|
-
|
|
26410
|
-
writeFileSync8(
|
|
26693
|
+
mkdirSync9(storageDir, { recursive: true });
|
|
26694
|
+
writeFileSync8(join16(storageDir, "last_announced_version"), version2);
|
|
26411
26695
|
} catch {}
|
|
26412
26696
|
}
|
|
26413
26697
|
}
|
|
26414
26698
|
function readWarnedTools(storageDir) {
|
|
26415
26699
|
try {
|
|
26416
|
-
const warnedToolsPath =
|
|
26700
|
+
const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
|
|
26417
26701
|
if (!existsSync11(warnedToolsPath))
|
|
26418
26702
|
return {};
|
|
26419
|
-
const parsed = JSON.parse(
|
|
26703
|
+
const parsed = JSON.parse(readFileSync10(warnedToolsPath, "utf-8"));
|
|
26420
26704
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
26421
26705
|
return {};
|
|
26422
26706
|
const warned = {};
|
|
@@ -26432,12 +26716,34 @@ function readWarnedTools(storageDir) {
|
|
|
26432
26716
|
}
|
|
26433
26717
|
function writeWarnedTools(storageDir, warned) {
|
|
26434
26718
|
try {
|
|
26435
|
-
|
|
26436
|
-
const warnedToolsPath =
|
|
26437
|
-
|
|
26719
|
+
mkdirSync9(storageDir, { recursive: true });
|
|
26720
|
+
const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
|
|
26721
|
+
const tmpPath = join16(storageDir, `${WARNED_TOOLS_FILE}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
26722
|
+
writeFileSync8(tmpPath, `${JSON.stringify(warned, null, 2)}
|
|
26438
26723
|
`);
|
|
26724
|
+
renameSync6(tmpPath, warnedToolsPath);
|
|
26439
26725
|
} catch {}
|
|
26440
26726
|
}
|
|
26727
|
+
async function withWarnedToolsLock(storageDir, fn) {
|
|
26728
|
+
const lockDir = join16(storageDir, "warned_tools.lock");
|
|
26729
|
+
for (let attempt = 0;attempt < 5; attempt++) {
|
|
26730
|
+
try {
|
|
26731
|
+
mkdirSync9(storageDir, { recursive: true });
|
|
26732
|
+
mkdirSync9(lockDir, { mode: 448 });
|
|
26733
|
+
try {
|
|
26734
|
+
return await fn();
|
|
26735
|
+
} finally {
|
|
26736
|
+
rmSync5(lockDir, { recursive: true, force: true });
|
|
26737
|
+
}
|
|
26738
|
+
} catch (err) {
|
|
26739
|
+
const code = err.code;
|
|
26740
|
+
if (code !== "EEXIST")
|
|
26741
|
+
return null;
|
|
26742
|
+
await new Promise((resolve4) => setTimeout(resolve4, 10 * (attempt + 1)));
|
|
26743
|
+
}
|
|
26744
|
+
}
|
|
26745
|
+
return null;
|
|
26746
|
+
}
|
|
26441
26747
|
function warningKey(warning, projectRoot) {
|
|
26442
26748
|
const scope = warning.kind === "lsp_binary_missing" ? "_" : projectRoot ?? "_";
|
|
26443
26749
|
return [
|
|
@@ -26476,20 +26782,29 @@ ${warning.hint}`;
|
|
|
26476
26782
|
async function deliverConfigureWarnings(opts, warnings) {
|
|
26477
26783
|
if (warnings.length === 0)
|
|
26478
26784
|
return;
|
|
26479
|
-
const
|
|
26480
|
-
|
|
26785
|
+
const deliveredWithLock = await withWarnedToolsLock(opts.storageDir, async () => {
|
|
26786
|
+
const warned = readWarnedTools(opts.storageDir);
|
|
26787
|
+
let changed = false;
|
|
26788
|
+
for (const warning of warnings) {
|
|
26789
|
+
const key = warningKey(warning, opts.projectRoot);
|
|
26790
|
+
if (Object.hasOwn(warned, key))
|
|
26791
|
+
continue;
|
|
26792
|
+
const delivered = await sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning));
|
|
26793
|
+
if (!delivered)
|
|
26794
|
+
continue;
|
|
26795
|
+
warned[key] = opts.pluginVersion;
|
|
26796
|
+
changed = true;
|
|
26797
|
+
}
|
|
26798
|
+
if (changed) {
|
|
26799
|
+
const merged = { ...readWarnedTools(opts.storageDir), ...warned };
|
|
26800
|
+
writeWarnedTools(opts.storageDir, merged);
|
|
26801
|
+
}
|
|
26802
|
+
return true;
|
|
26803
|
+
});
|
|
26804
|
+
if (deliveredWithLock)
|
|
26805
|
+
return;
|
|
26481
26806
|
for (const warning of warnings) {
|
|
26482
|
-
|
|
26483
|
-
if (Object.hasOwn(warned, key))
|
|
26484
|
-
continue;
|
|
26485
|
-
const delivered = await sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning));
|
|
26486
|
-
if (!delivered)
|
|
26487
|
-
continue;
|
|
26488
|
-
warned[key] = opts.pluginVersion;
|
|
26489
|
-
changed = true;
|
|
26490
|
-
}
|
|
26491
|
-
if (changed) {
|
|
26492
|
-
writeWarnedTools(opts.storageDir, warned);
|
|
26807
|
+
await sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning));
|
|
26493
26808
|
}
|
|
26494
26809
|
}
|
|
26495
26810
|
async function cleanupWarnings(opts) {
|
|
@@ -26527,20 +26842,20 @@ async function cleanupWarnings(opts) {
|
|
|
26527
26842
|
|
|
26528
26843
|
// src/shared/rpc-server.ts
|
|
26529
26844
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
26530
|
-
import { mkdirSync as
|
|
26845
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
26531
26846
|
import { createServer } from "http";
|
|
26532
26847
|
import { dirname as dirname6 } from "path";
|
|
26533
26848
|
|
|
26534
26849
|
// src/shared/rpc-utils.ts
|
|
26535
26850
|
import { createHash as createHash5 } from "crypto";
|
|
26536
|
-
import { join as
|
|
26851
|
+
import { join as join17 } from "path";
|
|
26537
26852
|
function projectHash(directory) {
|
|
26538
26853
|
const normalized = directory.replace(/\/+$/, "");
|
|
26539
26854
|
return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
26540
26855
|
}
|
|
26541
26856
|
function rpcPortFilePath(storageDir, directory) {
|
|
26542
26857
|
const hash2 = projectHash(directory);
|
|
26543
|
-
return
|
|
26858
|
+
return join17(storageDir, "rpc", hash2, "port");
|
|
26544
26859
|
}
|
|
26545
26860
|
|
|
26546
26861
|
// src/shared/rpc-server.ts
|
|
@@ -26574,10 +26889,13 @@ class AftRpcServer {
|
|
|
26574
26889
|
this.server = server;
|
|
26575
26890
|
try {
|
|
26576
26891
|
const dir = dirname6(this.portFilePath);
|
|
26577
|
-
|
|
26892
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
26578
26893
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
26579
|
-
writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }),
|
|
26580
|
-
|
|
26894
|
+
writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
|
|
26895
|
+
encoding: "utf-8",
|
|
26896
|
+
mode: 384
|
|
26897
|
+
});
|
|
26898
|
+
renameSync7(tmpPath, this.portFilePath);
|
|
26581
26899
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
26582
26900
|
} catch (err) {
|
|
26583
26901
|
warn2(`Failed to write RPC port file: ${err}`);
|
|
@@ -26654,18 +26972,6 @@ class AftRpcServer {
|
|
|
26654
26972
|
}
|
|
26655
26973
|
}
|
|
26656
26974
|
|
|
26657
|
-
// src/shared/runtime.ts
|
|
26658
|
-
var GLOBAL_KEY = "__AFT_SHARED_BRIDGE_POOL__";
|
|
26659
|
-
function getGlobalState() {
|
|
26660
|
-
return globalThis;
|
|
26661
|
-
}
|
|
26662
|
-
function setSharedBridgePool(pool) {
|
|
26663
|
-
getGlobalState()[GLOBAL_KEY] = pool;
|
|
26664
|
-
}
|
|
26665
|
-
function clearSharedBridgePool() {
|
|
26666
|
-
getGlobalState()[GLOBAL_KEY] = null;
|
|
26667
|
-
}
|
|
26668
|
-
|
|
26669
26975
|
// src/shared/session-directory.ts
|
|
26670
26976
|
var CACHE_MAX_ENTRIES = 200;
|
|
26671
26977
|
var cache = new Map;
|
|
@@ -26872,21 +27178,21 @@ function formatStatusMarkdown(status) {
|
|
|
26872
27178
|
|
|
26873
27179
|
// src/shared/tui-config.ts
|
|
26874
27180
|
var import_comment_json4 = __toESM(require_src2(), 1);
|
|
26875
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
26876
|
-
import { dirname as dirname7, join as
|
|
27181
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
27182
|
+
import { dirname as dirname7, join as join19 } from "path";
|
|
26877
27183
|
|
|
26878
27184
|
// src/shared/opencode-config-dir.ts
|
|
26879
27185
|
import { homedir as homedir10 } from "os";
|
|
26880
|
-
import { join as
|
|
27186
|
+
import { join as join18, resolve as resolve4 } from "path";
|
|
26881
27187
|
function getCliConfigDir() {
|
|
26882
27188
|
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
26883
27189
|
if (envConfigDir) {
|
|
26884
27190
|
return resolve4(envConfigDir);
|
|
26885
27191
|
}
|
|
26886
27192
|
if (process.platform === "win32") {
|
|
26887
|
-
return
|
|
27193
|
+
return join18(homedir10(), ".config", "opencode");
|
|
26888
27194
|
}
|
|
26889
|
-
return
|
|
27195
|
+
return join18(process.env.XDG_CONFIG_HOME || join18(homedir10(), ".config"), "opencode");
|
|
26890
27196
|
}
|
|
26891
27197
|
function getOpenCodeConfigDir2(_options) {
|
|
26892
27198
|
return getCliConfigDir();
|
|
@@ -26895,10 +27201,10 @@ function getOpenCodeConfigPaths(options) {
|
|
|
26895
27201
|
const configDir = getOpenCodeConfigDir2(options);
|
|
26896
27202
|
return {
|
|
26897
27203
|
configDir,
|
|
26898
|
-
configJson:
|
|
26899
|
-
configJsonc:
|
|
26900
|
-
packageJson:
|
|
26901
|
-
omoConfig:
|
|
27204
|
+
configJson: join18(configDir, "opencode.json"),
|
|
27205
|
+
configJsonc: join18(configDir, "opencode.jsonc"),
|
|
27206
|
+
packageJson: join18(configDir, "package.json"),
|
|
27207
|
+
omoConfig: join18(configDir, "magic-context.jsonc")
|
|
26902
27208
|
};
|
|
26903
27209
|
}
|
|
26904
27210
|
|
|
@@ -26907,8 +27213,8 @@ var PLUGIN_NAME = "@cortexkit/aft-opencode";
|
|
|
26907
27213
|
var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
|
|
26908
27214
|
function resolveTuiConfigPath() {
|
|
26909
27215
|
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
|
|
26910
|
-
const jsoncPath =
|
|
26911
|
-
const jsonPath =
|
|
27216
|
+
const jsoncPath = join19(configDir, "tui.jsonc");
|
|
27217
|
+
const jsonPath = join19(configDir, "tui.json");
|
|
26912
27218
|
if (existsSync12(jsoncPath))
|
|
26913
27219
|
return jsoncPath;
|
|
26914
27220
|
if (existsSync12(jsonPath))
|
|
@@ -26920,7 +27226,7 @@ function ensureTuiPluginEntry() {
|
|
|
26920
27226
|
const configPath = resolveTuiConfigPath();
|
|
26921
27227
|
let config2 = {};
|
|
26922
27228
|
if (existsSync12(configPath)) {
|
|
26923
|
-
config2 = import_comment_json4.parse(
|
|
27229
|
+
config2 = import_comment_json4.parse(readFileSync11(configPath, "utf-8")) ?? {};
|
|
26924
27230
|
}
|
|
26925
27231
|
const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
|
|
26926
27232
|
if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
|
|
@@ -26928,7 +27234,7 @@ function ensureTuiPluginEntry() {
|
|
|
26928
27234
|
}
|
|
26929
27235
|
plugins.push(PLUGIN_ENTRY);
|
|
26930
27236
|
config2.plugin = plugins;
|
|
26931
|
-
|
|
27237
|
+
mkdirSync11(dirname7(configPath), { recursive: true });
|
|
26932
27238
|
writeFileSync10(configPath, `${import_comment_json4.stringify(config2, null, 2)}
|
|
26933
27239
|
`);
|
|
26934
27240
|
log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
|
|
@@ -26940,13 +27246,13 @@ function ensureTuiPluginEntry() {
|
|
|
26940
27246
|
}
|
|
26941
27247
|
|
|
26942
27248
|
// src/shutdown-hooks.ts
|
|
26943
|
-
var
|
|
27249
|
+
var GLOBAL_KEY = "__aftShutdownHooks__";
|
|
26944
27250
|
function getState() {
|
|
26945
27251
|
const g = globalThis;
|
|
26946
|
-
if (!g[
|
|
26947
|
-
g[
|
|
27252
|
+
if (!g[GLOBAL_KEY]) {
|
|
27253
|
+
g[GLOBAL_KEY] = { cleanups: new Set, installed: false };
|
|
26948
27254
|
}
|
|
26949
|
-
return g[
|
|
27255
|
+
return g[GLOBAL_KEY];
|
|
26950
27256
|
}
|
|
26951
27257
|
var shuttingDown = false;
|
|
26952
27258
|
async function runCleanups(reason) {
|
|
@@ -27048,8 +27354,10 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
27048
27354
|
}
|
|
27049
27355
|
|
|
27050
27356
|
// src/tools/permissions.ts
|
|
27357
|
+
import * as fs3 from "fs";
|
|
27051
27358
|
import * as path3 from "path";
|
|
27052
27359
|
import { Effect } from "effect";
|
|
27360
|
+
var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.14.39 or newer for permission asks; please upgrade OpenCode";
|
|
27053
27361
|
async function runAsk(maybe) {
|
|
27054
27362
|
await Effect.runPromise(maybe);
|
|
27055
27363
|
}
|
|
@@ -27077,6 +27385,8 @@ function workspacePattern(_context) {
|
|
|
27077
27385
|
return ".";
|
|
27078
27386
|
}
|
|
27079
27387
|
async function askEditPermission(context, patterns, metadata = {}) {
|
|
27388
|
+
if (typeof context.ask !== "function")
|
|
27389
|
+
return UNSUPPORTED_ASK_HOST;
|
|
27080
27390
|
try {
|
|
27081
27391
|
await runAsk(context.ask({
|
|
27082
27392
|
permission: "edit",
|
|
@@ -27092,6 +27402,110 @@ async function askEditPermission(context, patterns, metadata = {}) {
|
|
|
27092
27402
|
return "Permission denied.";
|
|
27093
27403
|
}
|
|
27094
27404
|
}
|
|
27405
|
+
function containsPath(parent, child) {
|
|
27406
|
+
if (!parent)
|
|
27407
|
+
return false;
|
|
27408
|
+
const rel = path3.relative(parent, child);
|
|
27409
|
+
return rel === "" || !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
27410
|
+
}
|
|
27411
|
+
function windowsPath(p) {
|
|
27412
|
+
if (process.platform !== "win32")
|
|
27413
|
+
return p;
|
|
27414
|
+
return p.replace(/^\/([a-zA-Z]):(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`).replace(/^\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`).replace(/^\/cygdrive\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`).replace(/^\/mnt\/([a-zA-Z])(?:\/|$)/, (_, drive) => `${drive.toUpperCase()}:/`);
|
|
27415
|
+
}
|
|
27416
|
+
function normalizePath(p) {
|
|
27417
|
+
if (process.platform !== "win32")
|
|
27418
|
+
return p;
|
|
27419
|
+
const resolved = path3.resolve(windowsPath(p));
|
|
27420
|
+
try {
|
|
27421
|
+
return fs3.realpathSync.native(resolved);
|
|
27422
|
+
} catch {
|
|
27423
|
+
return resolved;
|
|
27424
|
+
}
|
|
27425
|
+
}
|
|
27426
|
+
function normalizePathPattern(p) {
|
|
27427
|
+
if (process.platform !== "win32")
|
|
27428
|
+
return p;
|
|
27429
|
+
if (p === "*" || p === "**")
|
|
27430
|
+
return p;
|
|
27431
|
+
const match = p.match(/^(.*)[\\/](\*{1,2})$/);
|
|
27432
|
+
if (!match)
|
|
27433
|
+
return normalizePath(p);
|
|
27434
|
+
const dir = /^[A-Za-z]:$/.test(match[1]) ? `${match[1]}\\` : match[1];
|
|
27435
|
+
return path3.join(normalizePath(dir), match[2]);
|
|
27436
|
+
}
|
|
27437
|
+
async function assertExternalDirectoryPermission(context, target, options) {
|
|
27438
|
+
if (!target)
|
|
27439
|
+
return;
|
|
27440
|
+
if (typeof context.ask !== "function")
|
|
27441
|
+
return UNSUPPORTED_ASK_HOST;
|
|
27442
|
+
const resolved = path3.isAbsolute(target) ? target : path3.resolve(context.directory, target);
|
|
27443
|
+
const absoluteTarget = normalizePath(resolved);
|
|
27444
|
+
const directory = context.directory ? normalizePath(context.directory) : context.directory;
|
|
27445
|
+
const rawWorktree = context.worktree;
|
|
27446
|
+
const worktree = rawWorktree && rawWorktree !== "/" ? normalizePath(rawWorktree) : rawWorktree;
|
|
27447
|
+
if (directory && containsPath(directory, absoluteTarget))
|
|
27448
|
+
return;
|
|
27449
|
+
if (worktree && worktree !== "/" && worktree !== directory && containsPath(worktree, absoluteTarget)) {
|
|
27450
|
+
return;
|
|
27451
|
+
}
|
|
27452
|
+
const kind = options?.kind ?? "file";
|
|
27453
|
+
const parentDir = kind === "directory" ? absoluteTarget : path3.dirname(absoluteTarget);
|
|
27454
|
+
const rawGlob = process.platform === "win32" ? normalizePathPattern(path3.join(parentDir, "*")) : path3.join(parentDir, "*").replaceAll("\\", "/");
|
|
27455
|
+
try {
|
|
27456
|
+
await runAsk(context.ask({
|
|
27457
|
+
permission: "external_directory",
|
|
27458
|
+
patterns: [rawGlob],
|
|
27459
|
+
always: [rawGlob],
|
|
27460
|
+
metadata: {
|
|
27461
|
+
filepath: absoluteTarget,
|
|
27462
|
+
parentDir
|
|
27463
|
+
}
|
|
27464
|
+
}));
|
|
27465
|
+
return;
|
|
27466
|
+
} catch (error50) {
|
|
27467
|
+
if (error50 instanceof Error && error50.message) {
|
|
27468
|
+
return error50.message;
|
|
27469
|
+
}
|
|
27470
|
+
return "Permission denied (external directory).";
|
|
27471
|
+
}
|
|
27472
|
+
}
|
|
27473
|
+
async function askGrepPermission(context, pattern, metadata = {}) {
|
|
27474
|
+
if (typeof context.ask !== "function")
|
|
27475
|
+
return UNSUPPORTED_ASK_HOST;
|
|
27476
|
+
try {
|
|
27477
|
+
await runAsk(context.ask({
|
|
27478
|
+
permission: "grep",
|
|
27479
|
+
patterns: [pattern],
|
|
27480
|
+
always: ["*"],
|
|
27481
|
+
metadata: { pattern, ...metadata }
|
|
27482
|
+
}));
|
|
27483
|
+
return;
|
|
27484
|
+
} catch (error50) {
|
|
27485
|
+
if (error50 instanceof Error && error50.message) {
|
|
27486
|
+
return error50.message;
|
|
27487
|
+
}
|
|
27488
|
+
return "Permission denied (grep).";
|
|
27489
|
+
}
|
|
27490
|
+
}
|
|
27491
|
+
async function askGlobPermission(context, pattern, metadata = {}) {
|
|
27492
|
+
if (typeof context.ask !== "function")
|
|
27493
|
+
return UNSUPPORTED_ASK_HOST;
|
|
27494
|
+
try {
|
|
27495
|
+
await runAsk(context.ask({
|
|
27496
|
+
permission: "glob",
|
|
27497
|
+
patterns: [pattern],
|
|
27498
|
+
always: ["*"],
|
|
27499
|
+
metadata: { pattern, ...metadata }
|
|
27500
|
+
}));
|
|
27501
|
+
return;
|
|
27502
|
+
} catch (error50) {
|
|
27503
|
+
if (error50 instanceof Error && error50.message) {
|
|
27504
|
+
return error50.message;
|
|
27505
|
+
}
|
|
27506
|
+
return "Permission denied (glob).";
|
|
27507
|
+
}
|
|
27508
|
+
}
|
|
27095
27509
|
function permissionDeniedResponse(message) {
|
|
27096
27510
|
return JSON.stringify({
|
|
27097
27511
|
success: false,
|
|
@@ -27111,6 +27525,17 @@ function extractHint(response) {
|
|
|
27111
27525
|
const hint = response.hint;
|
|
27112
27526
|
return typeof hint === "string" && hint.length > 0 ? hint : null;
|
|
27113
27527
|
}
|
|
27528
|
+
async function checkAstPathsPermission(context, paths) {
|
|
27529
|
+
if (!Array.isArray(paths))
|
|
27530
|
+
return;
|
|
27531
|
+
const uniquePaths = Array.from(new Set(paths.filter((p) => typeof p === "string" && p.length > 0)));
|
|
27532
|
+
for (const p of uniquePaths) {
|
|
27533
|
+
const denial = await assertExternalDirectoryPermission(context, p, { kind: "directory" });
|
|
27534
|
+
if (denial)
|
|
27535
|
+
return denial;
|
|
27536
|
+
}
|
|
27537
|
+
return;
|
|
27538
|
+
}
|
|
27114
27539
|
var SUPPORTED_LANGS = ["typescript", "tsx", "javascript", "python", "rust", "go"];
|
|
27115
27540
|
function astTools(ctx) {
|
|
27116
27541
|
const searchTool = {
|
|
@@ -27131,6 +27556,9 @@ function astTools(ctx) {
|
|
|
27131
27556
|
contextLines: z2.number().optional().describe("Number of context lines to show around each match")
|
|
27132
27557
|
},
|
|
27133
27558
|
execute: async (args, context) => {
|
|
27559
|
+
const externalDenied = await checkAstPathsPermission(context, args.paths);
|
|
27560
|
+
if (externalDenied)
|
|
27561
|
+
return permissionDeniedResponse(externalDenied);
|
|
27134
27562
|
const params = {
|
|
27135
27563
|
pattern: args.pattern,
|
|
27136
27564
|
lang: args.lang
|
|
@@ -27224,16 +27652,32 @@ ${hint}`;
|
|
|
27224
27652
|
},
|
|
27225
27653
|
execute: async (args, context) => {
|
|
27226
27654
|
const isDryRun = args.dryRun === true;
|
|
27655
|
+
const externalDenied = await checkAstPathsPermission(context, args.paths);
|
|
27656
|
+
if (externalDenied)
|
|
27657
|
+
return permissionDeniedResponse(externalDenied);
|
|
27227
27658
|
if (!isDryRun) {
|
|
27659
|
+
const paths = Array.isArray(args.paths) ? args.paths : ["."];
|
|
27660
|
+
if (!Array.isArray(args.paths)) {
|
|
27661
|
+
const asked = new Set;
|
|
27662
|
+
for (const targetPath of paths) {
|
|
27663
|
+
const absPath = resolveAbsolutePath(context, targetPath);
|
|
27664
|
+
if (asked.has(absPath))
|
|
27665
|
+
continue;
|
|
27666
|
+
asked.add(absPath);
|
|
27667
|
+
const denial = await assertExternalDirectoryPermission(context, absPath, {
|
|
27668
|
+
kind: "directory"
|
|
27669
|
+
});
|
|
27670
|
+
if (denial)
|
|
27671
|
+
return permissionDeniedResponse(denial);
|
|
27672
|
+
}
|
|
27673
|
+
}
|
|
27228
27674
|
const explicitPaths = Array.isArray(args.paths) ? resolveRelativePatterns(context, args.paths) : [];
|
|
27229
27675
|
const positiveGlobs = Array.isArray(args.globs) ? args.globs.filter((glob) => !glob.startsWith("!")) : [];
|
|
27230
27676
|
const patterns = [...explicitPaths, ...positiveGlobs];
|
|
27231
27677
|
const metadata = explicitPaths.length === 1 && positiveGlobs.length === 0 && Array.isArray(args.paths) ? { filepath: resolveAbsolutePath(context, args.paths[0]) } : {};
|
|
27232
27678
|
const permissionError = await askEditPermission(context, patterns.length > 0 ? patterns : [workspacePattern(context)], metadata);
|
|
27233
27679
|
if (permissionError) {
|
|
27234
|
-
|
|
27235
|
-
showOutputToUser(context, output2);
|
|
27236
|
-
return output2;
|
|
27680
|
+
return permissionDeniedResponse(permissionError);
|
|
27237
27681
|
}
|
|
27238
27682
|
}
|
|
27239
27683
|
const params = {
|
|
@@ -27367,7 +27811,7 @@ function conflictTools(ctx) {
|
|
|
27367
27811
|
}
|
|
27368
27812
|
|
|
27369
27813
|
// src/tools/hoisted.ts
|
|
27370
|
-
import * as
|
|
27814
|
+
import * as fs4 from "fs";
|
|
27371
27815
|
import * as path4 from "path";
|
|
27372
27816
|
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
27373
27817
|
|
|
@@ -27664,11 +28108,64 @@ ${chunk.old_lines.join(`
|
|
|
27664
28108
|
|
|
27665
28109
|
// src/tools/bash.ts
|
|
27666
28110
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
28111
|
+
|
|
28112
|
+
// src/shared/subagent-detect.ts
|
|
28113
|
+
var CACHE_MAX_ENTRIES2 = 200;
|
|
28114
|
+
var cache2 = new Map;
|
|
28115
|
+
async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
28116
|
+
if (!sessionId) {
|
|
28117
|
+
sessionLog2(undefined, "[subagent-detect] no sessionId provided \u2192 primary");
|
|
28118
|
+
return false;
|
|
28119
|
+
}
|
|
28120
|
+
const cached2 = cache2.get(sessionId);
|
|
28121
|
+
if (cached2) {
|
|
28122
|
+
cache2.delete(sessionId);
|
|
28123
|
+
cache2.set(sessionId, cached2);
|
|
28124
|
+
return cached2.isSubagent;
|
|
28125
|
+
}
|
|
28126
|
+
const c = client;
|
|
28127
|
+
const sessionApi = c?.session;
|
|
28128
|
+
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
28129
|
+
sessionLog2(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) \u2192 caching as primary`);
|
|
28130
|
+
setCache2(sessionId, false);
|
|
28131
|
+
return false;
|
|
28132
|
+
}
|
|
28133
|
+
sessionLog2(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
28134
|
+
let isSubagent = false;
|
|
28135
|
+
let parentIdRaw;
|
|
28136
|
+
try {
|
|
28137
|
+
const result = await sessionApi.get({
|
|
28138
|
+
path: { id: sessionId }
|
|
28139
|
+
});
|
|
28140
|
+
const session = result?.data ?? result;
|
|
28141
|
+
parentIdRaw = session?.parentID;
|
|
28142
|
+
isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
28143
|
+
sessionLog2(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} \u2192 isSubagent=${isSubagent}`);
|
|
28144
|
+
} catch (err) {
|
|
28145
|
+
sessionWarn2(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28146
|
+
return false;
|
|
28147
|
+
}
|
|
28148
|
+
setCache2(sessionId, isSubagent);
|
|
28149
|
+
return isSubagent;
|
|
28150
|
+
}
|
|
28151
|
+
function setCache2(sessionId, isSubagent) {
|
|
28152
|
+
if (cache2.has(sessionId))
|
|
28153
|
+
cache2.delete(sessionId);
|
|
28154
|
+
cache2.set(sessionId, { isSubagent, recordedAt: Date.now() });
|
|
28155
|
+
if (cache2.size > CACHE_MAX_ENTRIES2) {
|
|
28156
|
+
const oldest = cache2.keys().next().value;
|
|
28157
|
+
if (oldest !== undefined)
|
|
28158
|
+
cache2.delete(oldest);
|
|
28159
|
+
}
|
|
28160
|
+
}
|
|
28161
|
+
|
|
28162
|
+
// src/tools/bash.ts
|
|
27667
28163
|
var z3 = tool3.schema;
|
|
27668
28164
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
27669
28165
|
var FOREGROUND_WAIT_WINDOW_MS = 5000;
|
|
27670
28166
|
var FOREGROUND_POLL_INTERVAL_MS = 100;
|
|
27671
28167
|
var BASH_TRANSPORT_TIMEOUT_MS = 30000;
|
|
28168
|
+
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
27672
28169
|
var BASH_DESCRIPTION = `Hoisted bash tool with output compression, command rewriting to AFT tools, and optional background execution. By default, output is compressed; pass compressed: false for raw output. Pass background: true to spawn in the background and get a task_id for bash_status/bash_kill.`;
|
|
27673
28170
|
async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
27674
28171
|
const first = await bridgeCall(ctx, runtime, "bash", params, options);
|
|
@@ -27709,6 +28206,12 @@ function createBashTool(ctx) {
|
|
|
27709
28206
|
const metadata = context.metadata;
|
|
27710
28207
|
const command = args.command;
|
|
27711
28208
|
const cwd = args.workdir ?? context.directory;
|
|
28209
|
+
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
28210
|
+
const requestedBackground = args.background === true;
|
|
28211
|
+
const effectiveBackground = isSubagent ? false : requestedBackground;
|
|
28212
|
+
if (isSubagent && requestedBackground) {
|
|
28213
|
+
sessionLog2(context.sessionID, "[bash] subagent + background:true \u2192 converting to foreground (subagent would lose task_id)");
|
|
28214
|
+
}
|
|
27712
28215
|
const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
|
|
27713
28216
|
const data = await withPermissionLoop(ctx, context, {
|
|
27714
28217
|
command,
|
|
@@ -27716,8 +28219,8 @@ function createBashTool(ctx) {
|
|
|
27716
28219
|
workdir: args.workdir,
|
|
27717
28220
|
env: shellEnv?.env ?? {},
|
|
27718
28221
|
description,
|
|
27719
|
-
background:
|
|
27720
|
-
notify_on_completion:
|
|
28222
|
+
background: effectiveBackground,
|
|
28223
|
+
notify_on_completion: effectiveBackground,
|
|
27721
28224
|
compressed: args.compressed,
|
|
27722
28225
|
permissions_requested: true
|
|
27723
28226
|
}, callBridge, {
|
|
@@ -27734,7 +28237,7 @@ function createBashTool(ctx) {
|
|
|
27734
28237
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
27735
28238
|
const callID2 = getCallID(context);
|
|
27736
28239
|
const taskId = data.task_id;
|
|
27737
|
-
if (
|
|
28240
|
+
if (effectiveBackground) {
|
|
27738
28241
|
trackBgTask(context.sessionID, taskId);
|
|
27739
28242
|
const startedLine = formatBackgroundLaunch(taskId);
|
|
27740
28243
|
const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
|
|
@@ -27748,7 +28251,7 @@ function createBashTool(ctx) {
|
|
|
27748
28251
|
return startedLine;
|
|
27749
28252
|
}
|
|
27750
28253
|
const argTimeout = args.timeout;
|
|
27751
|
-
const waitTimeoutMs = argTimeout !== undefined ? Math.min(argTimeout, FOREGROUND_WAIT_WINDOW_MS) : FOREGROUND_WAIT_WINDOW_MS;
|
|
28254
|
+
const waitTimeoutMs = isSubagent ? argTimeout ?? DEFAULT_HARD_TIMEOUT_MS : argTimeout !== undefined ? Math.min(argTimeout, FOREGROUND_WAIT_WINDOW_MS) : FOREGROUND_WAIT_WINDOW_MS;
|
|
27752
28255
|
const startedAt = Date.now();
|
|
27753
28256
|
while (true) {
|
|
27754
28257
|
const status = await callBridge(ctx, context, "bash_status", { task_id: taskId });
|
|
@@ -27829,7 +28332,7 @@ function createBashStatusTool(ctx) {
|
|
|
27829
28332
|
return {
|
|
27830
28333
|
description: "Check the status and captured output of a background bash task spawned with bash({ background: true }). Returns status (running | completed | failed | killed | timed_out), exit code, duration, and a preview of captured output.",
|
|
27831
28334
|
args: {
|
|
27832
|
-
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g.
|
|
28335
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
|
|
27833
28336
|
},
|
|
27834
28337
|
execute: async (args, context) => {
|
|
27835
28338
|
const data = await callBridge(ctx, context, "bash_status", {
|
|
@@ -27859,7 +28362,7 @@ function createBashKillTool(ctx) {
|
|
|
27859
28362
|
return {
|
|
27860
28363
|
description: "Terminate a running background bash task spawned with bash({ background: true }). Returns confirmation of kill or an error if the task already finished.",
|
|
27861
28364
|
args: {
|
|
27862
|
-
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g.
|
|
28365
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
|
|
27863
28366
|
},
|
|
27864
28367
|
execute: async (args, context) => {
|
|
27865
28368
|
const data = await callBridge(ctx, context, "bash_kill", {
|
|
@@ -28170,12 +28673,23 @@ function createReadTool(ctx) {
|
|
|
28170
28673
|
execute: async (args, context) => {
|
|
28171
28674
|
const file2 = args.filePath;
|
|
28172
28675
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28676
|
+
{
|
|
28677
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28678
|
+
if (denial)
|
|
28679
|
+
return permissionDeniedResponse(denial);
|
|
28680
|
+
}
|
|
28681
|
+
try {
|
|
28682
|
+
await runAsk(context.ask({
|
|
28683
|
+
permission: "read",
|
|
28684
|
+
patterns: [filePath],
|
|
28685
|
+
always: ["*"],
|
|
28686
|
+
metadata: {}
|
|
28687
|
+
}));
|
|
28688
|
+
} catch (error50) {
|
|
28689
|
+
if (error50 instanceof Error && error50.message)
|
|
28690
|
+
return permissionDeniedResponse(error50.message);
|
|
28691
|
+
return permissionDeniedResponse("Permission denied.");
|
|
28692
|
+
}
|
|
28179
28693
|
const ext = path4.extname(filePath).toLowerCase();
|
|
28180
28694
|
const mimeMap = {
|
|
28181
28695
|
".png": "image/png",
|
|
@@ -28198,7 +28712,7 @@ function createReadTool(ctx) {
|
|
|
28198
28712
|
const label = isImage ? "Image" : "PDF";
|
|
28199
28713
|
let fileSize = 0;
|
|
28200
28714
|
try {
|
|
28201
|
-
const stat = await import("fs/promises").then((
|
|
28715
|
+
const stat = await import("fs/promises").then((fs5) => fs5.stat(filePath));
|
|
28202
28716
|
fileSize = stat.size;
|
|
28203
28717
|
} catch {}
|
|
28204
28718
|
const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
|
|
@@ -28294,6 +28808,11 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
28294
28808
|
const content = args.content;
|
|
28295
28809
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28296
28810
|
const relPath = path4.relative(context.worktree, filePath);
|
|
28811
|
+
{
|
|
28812
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28813
|
+
if (denial)
|
|
28814
|
+
return permissionDeniedResponse(denial);
|
|
28815
|
+
}
|
|
28297
28816
|
await runAsk(context.ask({
|
|
28298
28817
|
permission: "edit",
|
|
28299
28818
|
patterns: [relPath],
|
|
@@ -28414,9 +28933,9 @@ Note: Modes 6 and 7 are options on mode 5 (find/replace) \u2014 they require \`o
|
|
|
28414
28933
|
- Auto-formats using project formatter if configured
|
|
28415
28934
|
- Tree-sitter syntax validation on all edits
|
|
28416
28935
|
- Symbol replace includes decorators, attributes, and doc comments in range
|
|
28417
|
-
- LSP error-level diagnostics are returned automatically after
|
|
28936
|
+
- LSP error-level diagnostics are returned automatically after edits
|
|
28418
28937
|
|
|
28419
|
-
Returns: JSON string for the selected edit mode.
|
|
28938
|
+
Returns: JSON string for the selected edit mode. Edits may append inline LSP error lines.
|
|
28420
28939
|
|
|
28421
28940
|
Common response fields: success (boolean), diff (object with before/after), backup_id (string), syntax_valid (boolean). Exact fields vary by mode.`;
|
|
28422
28941
|
}
|
|
@@ -28433,8 +28952,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28433
28952
|
content: z4.string().optional().describe("New content for symbol replace or file write"),
|
|
28434
28953
|
appendContent: z4.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
28435
28954
|
edits: z4.array(z4.record(z4.string(), z4.unknown())).optional().describe("Batch edits \u2014 array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
|
|
28436
|
-
operations: z4.array(z4.record(z4.string(), z4.unknown())).optional().describe("Transaction \u2014 array of { file: string, command: 'edit_match' | 'write', match?: string, replacement?: string, content?: string } for multi-file edits with rollback. Note: uses 'file'/'match'/'replacement' (not filePath/oldString/newString)")
|
|
28437
|
-
dryRun: z4.boolean().optional().describe("Preview changes without applying (returns diff, default: false)")
|
|
28955
|
+
operations: z4.array(z4.record(z4.string(), z4.unknown())).optional().describe("Transaction \u2014 array of { file: string, command: 'edit_match' | 'write', match?: string, replacement?: string, content?: string } for multi-file edits with rollback. Note: uses 'file'/'match'/'replacement' (not filePath/oldString/newString)")
|
|
28438
28956
|
},
|
|
28439
28957
|
execute: async (args, context) => {
|
|
28440
28958
|
const argsRecord = args;
|
|
@@ -28444,6 +28962,18 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28444
28962
|
if (Array.isArray(args.operations)) {
|
|
28445
28963
|
const ops = args.operations;
|
|
28446
28964
|
const files = ops.map((op) => op.file).filter(Boolean);
|
|
28965
|
+
{
|
|
28966
|
+
const asked = new Set;
|
|
28967
|
+
for (const file3 of files) {
|
|
28968
|
+
const absPath = path4.isAbsolute(file3) ? file3 : path4.resolve(context.directory, file3);
|
|
28969
|
+
if (asked.has(absPath))
|
|
28970
|
+
continue;
|
|
28971
|
+
asked.add(absPath);
|
|
28972
|
+
const denial = await assertExternalDirectoryPermission(context, absPath);
|
|
28973
|
+
if (denial)
|
|
28974
|
+
return permissionDeniedResponse(denial);
|
|
28975
|
+
}
|
|
28976
|
+
}
|
|
28447
28977
|
await runAsk(context.ask({
|
|
28448
28978
|
permission: "edit",
|
|
28449
28979
|
patterns: files.map((f) => path4.relative(context.worktree, path4.resolve(context.directory, f))),
|
|
@@ -28454,9 +28984,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28454
28984
|
...op,
|
|
28455
28985
|
file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
|
|
28456
28986
|
}));
|
|
28457
|
-
const
|
|
28458
|
-
params2.dry_run = args.dryRun === true;
|
|
28459
|
-
const data2 = await callBridge(ctx, context, "transaction", params2);
|
|
28987
|
+
const data2 = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
|
|
28460
28988
|
return JSON.stringify(data2);
|
|
28461
28989
|
}
|
|
28462
28990
|
const file2 = args.filePath;
|
|
@@ -28464,6 +28992,11 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28464
28992
|
throw new Error("'filePath' parameter is required");
|
|
28465
28993
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28466
28994
|
const relPath = path4.relative(context.worktree, filePath);
|
|
28995
|
+
{
|
|
28996
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28997
|
+
if (denial)
|
|
28998
|
+
return permissionDeniedResponse(denial);
|
|
28999
|
+
}
|
|
28467
29000
|
await runAsk(context.ask({
|
|
28468
29001
|
permission: "edit",
|
|
28469
29002
|
patterns: [relPath],
|
|
@@ -28512,14 +29045,10 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28512
29045
|
const hint = typeof args.content === "string" ? ` To write the whole file, use the '${writeToolName}' tool. To edit existing content, provide 'oldString' (and optionally 'newString'), 'symbol' + 'content', or an 'edits' array.` : " Provide 'oldString' (+ optional 'newString'), 'symbol' + 'content', 'edits' array, or 'operations' array.";
|
|
28513
29046
|
throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
|
|
28514
29047
|
}
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
if (!args.dryRun)
|
|
28518
|
-
params.diagnostics = true;
|
|
28519
|
-
if (!args.dryRun)
|
|
28520
|
-
params.include_diff = true;
|
|
29048
|
+
params.diagnostics = true;
|
|
29049
|
+
params.include_diff = true;
|
|
28521
29050
|
const data = await callBridge(ctx, context, command, params);
|
|
28522
|
-
if (
|
|
29051
|
+
if (data.success && data.diff) {
|
|
28523
29052
|
const diff = data.diff;
|
|
28524
29053
|
const callID = getCallID2(context);
|
|
28525
29054
|
if (callID) {
|
|
@@ -28548,31 +29077,29 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28548
29077
|
result += `
|
|
28549
29078
|
|
|
28550
29079
|
${globSkipNote}`;
|
|
28551
|
-
|
|
28552
|
-
|
|
28553
|
-
|
|
28554
|
-
|
|
28555
|
-
|
|
28556
|
-
const diagLines = errors3.map((d) => ` Line ${d.line}: ${d.message}`).join(`
|
|
29080
|
+
const diags = data.lsp_diagnostics;
|
|
29081
|
+
if (diags && diags.length > 0) {
|
|
29082
|
+
const errors3 = diags.filter((d) => d.severity === "error");
|
|
29083
|
+
if (errors3.length > 0) {
|
|
29084
|
+
const diagLines = errors3.map((d) => ` Line ${d.line}: ${d.message}`).join(`
|
|
28557
29085
|
`);
|
|
28558
|
-
|
|
29086
|
+
result += `
|
|
28559
29087
|
|
|
28560
29088
|
LSP errors detected, please fix:
|
|
28561
29089
|
${diagLines}`;
|
|
28562
|
-
}
|
|
28563
29090
|
}
|
|
28564
|
-
|
|
28565
|
-
|
|
28566
|
-
|
|
28567
|
-
|
|
29091
|
+
}
|
|
29092
|
+
const pendingServers = data.lsp_pending_servers;
|
|
29093
|
+
const exitedServers = data.lsp_exited_servers;
|
|
29094
|
+
if (pendingServers && pendingServers.length > 0) {
|
|
29095
|
+
result += `
|
|
28568
29096
|
|
|
28569
29097
|
Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete; rerun lsp_diagnostics later for a fresh check.`;
|
|
28570
|
-
|
|
28571
|
-
|
|
28572
|
-
|
|
29098
|
+
}
|
|
29099
|
+
if (exitedServers && exitedServers.length > 0) {
|
|
29100
|
+
result += `
|
|
28573
29101
|
|
|
28574
29102
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
28575
|
-
}
|
|
28576
29103
|
}
|
|
28577
29104
|
return result;
|
|
28578
29105
|
}
|
|
@@ -28659,13 +29186,24 @@ function createApplyPatchTool(ctx) {
|
|
|
28659
29186
|
if (h.type === "update" && h.move_path) {
|
|
28660
29187
|
const dstAbs = path4.resolve(context.directory, h.move_path);
|
|
28661
29188
|
affectedAbs.add(dstAbs);
|
|
28662
|
-
if (!
|
|
29189
|
+
if (!fs4.existsSync(dstAbs)) {
|
|
28663
29190
|
newlyCreatedAbs.add(dstAbs);
|
|
28664
29191
|
}
|
|
28665
29192
|
}
|
|
28666
29193
|
}
|
|
28667
29194
|
const relPaths = Array.from(affectedAbs).map((abs) => path4.relative(context.worktree, abs));
|
|
28668
29195
|
const multiFileWritePaths = Array.from(affectedAbs);
|
|
29196
|
+
{
|
|
29197
|
+
const asked = new Set;
|
|
29198
|
+
for (const filePath of multiFileWritePaths) {
|
|
29199
|
+
if (asked.has(filePath))
|
|
29200
|
+
continue;
|
|
29201
|
+
asked.add(filePath);
|
|
29202
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29203
|
+
if (denial)
|
|
29204
|
+
return permissionDeniedResponse(denial);
|
|
29205
|
+
}
|
|
29206
|
+
}
|
|
28669
29207
|
await runAsk(context.ask({
|
|
28670
29208
|
permission: "edit",
|
|
28671
29209
|
patterns: relPaths,
|
|
@@ -28691,7 +29229,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28691
29229
|
const filePath = path4.resolve(context.directory, hunk.path);
|
|
28692
29230
|
switch (hunk.type) {
|
|
28693
29231
|
case "add": {
|
|
28694
|
-
if (
|
|
29232
|
+
if (fs4.existsSync(filePath)) {
|
|
28695
29233
|
const msg = `Failed to create ${hunk.path}: file already exists. Use *** Update File: to modify, or *** Delete File: first if you want to replace it entirely.`;
|
|
28696
29234
|
results.push(msg);
|
|
28697
29235
|
failures.push(hunk.path);
|
|
@@ -28723,7 +29261,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28723
29261
|
results.push(msg);
|
|
28724
29262
|
failures.push(hunk.path);
|
|
28725
29263
|
const filePath2 = path4.resolve(context.directory, hunk.path);
|
|
28726
|
-
if (
|
|
29264
|
+
if (fs4.existsSync(filePath2)) {
|
|
28727
29265
|
try {
|
|
28728
29266
|
await callBridge(ctx, context, "delete_file", { file: filePath2 });
|
|
28729
29267
|
} catch {}
|
|
@@ -28733,7 +29271,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28733
29271
|
}
|
|
28734
29272
|
case "delete": {
|
|
28735
29273
|
try {
|
|
28736
|
-
const before = await
|
|
29274
|
+
const before = await fs4.promises.readFile(filePath, "utf-8").catch(() => "");
|
|
28737
29275
|
await callBridge(ctx, context, "delete_file", { file: filePath });
|
|
28738
29276
|
perFileDiffs.push({
|
|
28739
29277
|
filePath,
|
|
@@ -28751,7 +29289,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28751
29289
|
}
|
|
28752
29290
|
case "update": {
|
|
28753
29291
|
try {
|
|
28754
|
-
const original = await
|
|
29292
|
+
const original = await fs4.promises.readFile(filePath, "utf-8");
|
|
28755
29293
|
const newContent = applyUpdateChunks(original, filePath, hunk.chunks);
|
|
28756
29294
|
const targetPath = hunk.move_path ? path4.resolve(context.directory, hunk.move_path) : filePath;
|
|
28757
29295
|
const writeResult = await callBridge(ctx, context, "write", {
|
|
@@ -28806,7 +29344,7 @@ ${diagLines}`);
|
|
|
28806
29344
|
if (rollbackResult.success === false) {
|
|
28807
29345
|
throw new Error(rollbackResult.message ?? "checkpoint restore failed");
|
|
28808
29346
|
}
|
|
28809
|
-
if (newlyCreatedAbs.has(targetPath) &&
|
|
29347
|
+
if (newlyCreatedAbs.has(targetPath) && fs4.existsSync(targetPath)) {
|
|
28810
29348
|
const cleanupResult = await callBridge(ctx, context, "delete_file", {
|
|
28811
29349
|
file: targetPath
|
|
28812
29350
|
});
|
|
@@ -28901,6 +29439,17 @@ function createDeleteTool(ctx) {
|
|
|
28901
29439
|
execute: async (args, context) => {
|
|
28902
29440
|
const inputs = args.files;
|
|
28903
29441
|
const absolutePaths = inputs.map((f) => path4.isAbsolute(f) ? f : path4.resolve(context.directory, f));
|
|
29442
|
+
{
|
|
29443
|
+
const asked = new Set;
|
|
29444
|
+
for (const filePath of absolutePaths) {
|
|
29445
|
+
if (asked.has(filePath))
|
|
29446
|
+
continue;
|
|
29447
|
+
asked.add(filePath);
|
|
29448
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29449
|
+
if (denial)
|
|
29450
|
+
return permissionDeniedResponse(denial);
|
|
29451
|
+
}
|
|
29452
|
+
}
|
|
28904
29453
|
await runAsk(context.ask({
|
|
28905
29454
|
permission: "edit",
|
|
28906
29455
|
patterns: absolutePaths,
|
|
@@ -28946,6 +29495,18 @@ function createMoveTool(ctx) {
|
|
|
28946
29495
|
execute: async (args, context) => {
|
|
28947
29496
|
const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
|
|
28948
29497
|
const destPath = path4.isAbsolute(args.destination) ? args.destination : path4.resolve(context.directory, args.destination);
|
|
29498
|
+
{
|
|
29499
|
+
const sourceDenial = await assertExternalDirectoryPermission(context, filePath, {
|
|
29500
|
+
kind: "file"
|
|
29501
|
+
});
|
|
29502
|
+
if (sourceDenial)
|
|
29503
|
+
return permissionDeniedResponse(sourceDenial);
|
|
29504
|
+
if (destPath !== filePath) {
|
|
29505
|
+
const destDenial = await assertExternalDirectoryPermission(context, destPath);
|
|
29506
|
+
if (destDenial)
|
|
29507
|
+
return permissionDeniedResponse(destDenial);
|
|
29508
|
+
}
|
|
29509
|
+
}
|
|
28949
29510
|
await runAsk(context.ask({
|
|
28950
29511
|
permission: "edit",
|
|
28951
29512
|
patterns: [filePath, destPath],
|
|
@@ -29000,6 +29561,11 @@ function aftPrefixedTools(ctx) {
|
|
|
29000
29561
|
const file2 = normalizedArgs.filePath;
|
|
29001
29562
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
29002
29563
|
const relPath = path4.relative(context.worktree, filePath);
|
|
29564
|
+
{
|
|
29565
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29566
|
+
if (denial)
|
|
29567
|
+
return permissionDeniedResponse(denial);
|
|
29568
|
+
}
|
|
29003
29569
|
await runAsk(context.ask({
|
|
29004
29570
|
permission: "edit",
|
|
29005
29571
|
patterns: [relPath],
|
|
@@ -29012,9 +29578,6 @@ function aftPrefixedTools(ctx) {
|
|
|
29012
29578
|
create_dirs: normalizedArgs.create_dirs !== false,
|
|
29013
29579
|
diagnostics: true
|
|
29014
29580
|
};
|
|
29015
|
-
if (normalizedArgs.dryRun === true || normalizedArgs.dry_run === true) {
|
|
29016
|
-
writeParams.dry_run = true;
|
|
29017
|
-
}
|
|
29018
29581
|
const data = await callBridge(ctx, context, "write", writeParams);
|
|
29019
29582
|
return JSON.stringify(data);
|
|
29020
29583
|
}
|
|
@@ -29038,10 +29601,9 @@ function importTools(ctx) {
|
|
|
29038
29601
|
` + `Ops:
|
|
29039
29602
|
` + `- 'add': Add an import. Auto-detects group (stdlib/external/internal), deduplicates. Requires 'module'. Optional 'names', 'defaultImport', 'typeOnly'.
|
|
29040
29603
|
` + `- 'remove': Remove an import or a specific named import. Requires 'module'. Provide 'removeName' to remove a single named import; omit to remove the entire import.
|
|
29041
|
-
` + `- 'organize': Re-sort and re-group all imports by language convention, deduplicate. Requires only 'filePath'.
|
|
29604
|
+
` + `- 'organize': Re-sort and re-group all imports by language convention, deduplicate. Requires only 'filePath'. Use aft_safety checkpoint/undo for recovery before broad cleanup.
|
|
29042
29605
|
|
|
29043
29606
|
` + `Returns:
|
|
29044
|
-
` + `- Dry run (any op): { ok, dry_run, diff, syntax_valid? }
|
|
29045
29607
|
` + `- add: { file, added, module, group?, already_present?, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29046
29608
|
` + `- remove: { file, removed, module, name?, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29047
29609
|
` + "- organize: { file, groups: [{ name, count }], removed_duplicates, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
|
|
@@ -29053,21 +29615,22 @@ function importTools(ctx) {
|
|
|
29053
29615
|
defaultImport: z5.string().optional().describe("Default import name (e.g. 'React')"),
|
|
29054
29616
|
typeOnly: z5.boolean().optional().describe("Type-only import (TS only, default: false)"),
|
|
29055
29617
|
removeName: z5.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
|
|
29056
|
-
validate: z5.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
|
|
29057
|
-
dryRun: z5.boolean().optional().describe("Preview without modifying the file (default: false)")
|
|
29618
|
+
validate: z5.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
|
|
29058
29619
|
},
|
|
29059
29620
|
execute: async (args, context) => {
|
|
29060
29621
|
const op = args.op;
|
|
29061
|
-
const isDryRun = args.dryRun === true;
|
|
29062
29622
|
if ((op === "add" || op === "remove") && typeof args.module !== "string") {
|
|
29063
29623
|
throw new Error(`'module' is required for '${op}' op`);
|
|
29064
29624
|
}
|
|
29065
|
-
|
|
29066
|
-
|
|
29067
|
-
const
|
|
29068
|
-
if (
|
|
29069
|
-
return permissionDeniedResponse(
|
|
29625
|
+
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
29626
|
+
{
|
|
29627
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29628
|
+
if (denial)
|
|
29629
|
+
return permissionDeniedResponse(denial);
|
|
29070
29630
|
}
|
|
29631
|
+
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29632
|
+
if (permissionError)
|
|
29633
|
+
return permissionDeniedResponse(permissionError);
|
|
29071
29634
|
const commandMap = {
|
|
29072
29635
|
add: "add_import",
|
|
29073
29636
|
remove: "remove_import",
|
|
@@ -29086,8 +29649,6 @@ function importTools(ctx) {
|
|
|
29086
29649
|
params.name = args.removeName;
|
|
29087
29650
|
if (args.validate !== undefined)
|
|
29088
29651
|
params.validate = args.validate;
|
|
29089
|
-
if (args.dryRun !== undefined)
|
|
29090
|
-
params.dry_run = args.dryRun;
|
|
29091
29652
|
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
29092
29653
|
if (response.success === false) {
|
|
29093
29654
|
throw new Error(response.message || `${op} failed`);
|
|
@@ -29430,9 +29991,9 @@ function refactoringTools(ctx) {
|
|
|
29430
29991
|
|
|
29431
29992
|
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements.
|
|
29432
29993
|
|
|
29433
|
-
` + `All ops need 'filePath'. Use
|
|
29994
|
+
` + `All ops need 'filePath'. Use aft_safety checkpoint/undo before risky refactors.
|
|
29434
29995
|
|
|
29435
|
-
` + "Returns: move
|
|
29996
|
+
` + "Returns: move { ok, files_modified, consumers_updated, checkpoint_name, results }. extract returns { file, name, parameters, return_type, syntax_valid, formatted, ... }. inline returns { file, symbol, call_context, substitutions, conflicts, syntax_valid, formatted, ... }.",
|
|
29436
29997
|
args: {
|
|
29437
29998
|
op: z9.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
|
|
29438
29999
|
filePath: z9.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
@@ -29442,8 +30003,7 @@ function refactoringTools(ctx) {
|
|
|
29442
30003
|
name: z9.string().optional().describe("New function name \u2014 required for 'extract' op"),
|
|
29443
30004
|
startLine: z9.number().optional().describe("1-based start line \u2014 required for 'extract' op"),
|
|
29444
30005
|
endLine: z9.number().optional().describe("1-based end line (inclusive) \u2014 required for 'extract' op"),
|
|
29445
|
-
callSiteLine: z9.number().optional().describe("1-based call site line \u2014 required for 'inline' op")
|
|
29446
|
-
dryRun: z9.boolean().optional().describe("Preview changes as diff without modifying files (default: false)")
|
|
30006
|
+
callSiteLine: z9.number().optional().describe("1-based call site line \u2014 required for 'inline' op")
|
|
29447
30007
|
},
|
|
29448
30008
|
execute: async (args, context) => {
|
|
29449
30009
|
const op = args.op;
|
|
@@ -29471,6 +30031,18 @@ function refactoringTools(ctx) {
|
|
|
29471
30031
|
...typeof args.destination === "string" ? [args.destination] : []
|
|
29472
30032
|
]) : [resolveRelativePattern(context, args.filePath)];
|
|
29473
30033
|
const metadata = patterns.length === 1 ? { filepath: filePath } : {};
|
|
30034
|
+
{
|
|
30035
|
+
const affectedPaths = op === "move" && typeof args.destination === "string" ? [filePath, resolveAbsolutePath(context, args.destination)] : [filePath];
|
|
30036
|
+
const asked = new Set;
|
|
30037
|
+
for (const affectedPath of affectedPaths) {
|
|
30038
|
+
if (asked.has(affectedPath))
|
|
30039
|
+
continue;
|
|
30040
|
+
asked.add(affectedPath);
|
|
30041
|
+
const denial = await assertExternalDirectoryPermission(context, affectedPath);
|
|
30042
|
+
if (denial)
|
|
30043
|
+
return permissionDeniedResponse(denial);
|
|
30044
|
+
}
|
|
30045
|
+
}
|
|
29474
30046
|
const permissionError = await askEditPermission(context, patterns, metadata);
|
|
29475
30047
|
if (permissionError)
|
|
29476
30048
|
return permissionDeniedResponse(permissionError);
|
|
@@ -29480,8 +30052,6 @@ function refactoringTools(ctx) {
|
|
|
29480
30052
|
inline: "inline_symbol"
|
|
29481
30053
|
};
|
|
29482
30054
|
const params = { file: args.filePath };
|
|
29483
|
-
if (args.dryRun !== undefined)
|
|
29484
|
-
params.dry_run = args.dryRun;
|
|
29485
30055
|
switch (op) {
|
|
29486
30056
|
case "move":
|
|
29487
30057
|
params.symbol = args.symbol;
|
|
@@ -29513,6 +30083,7 @@ function refactoringTools(ctx) {
|
|
|
29513
30083
|
}
|
|
29514
30084
|
|
|
29515
30085
|
// src/tools/safety.ts
|
|
30086
|
+
import * as path5 from "path";
|
|
29516
30087
|
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
29517
30088
|
var z10 = tool10.schema;
|
|
29518
30089
|
function safetyTools(ctx) {
|
|
@@ -29550,10 +30121,30 @@ function safetyTools(ctx) {
|
|
|
29550
30121
|
}
|
|
29551
30122
|
if (op === "undo" && typeof args.filePath === "string") {
|
|
29552
30123
|
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
30124
|
+
{
|
|
30125
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
30126
|
+
if (denial)
|
|
30127
|
+
return permissionDeniedResponse(denial);
|
|
30128
|
+
}
|
|
29553
30129
|
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29554
30130
|
if (permissionError)
|
|
29555
30131
|
return permissionDeniedResponse(permissionError);
|
|
29556
30132
|
}
|
|
30133
|
+
if (op === "checkpoint" && Array.isArray(args.files)) {
|
|
30134
|
+
const uniqueParents = new Set;
|
|
30135
|
+
for (const file2 of args.files) {
|
|
30136
|
+
if (typeof file2 !== "string")
|
|
30137
|
+
continue;
|
|
30138
|
+
const abs = path5.isAbsolute(file2) ? file2 : path5.resolve(context.directory, file2);
|
|
30139
|
+
const parent = path5.dirname(abs);
|
|
30140
|
+
if (uniqueParents.has(parent))
|
|
30141
|
+
continue;
|
|
30142
|
+
uniqueParents.add(parent);
|
|
30143
|
+
const denial = await assertExternalDirectoryPermission(context, file2, { kind: "file" });
|
|
30144
|
+
if (denial)
|
|
30145
|
+
return permissionDeniedResponse(denial);
|
|
30146
|
+
}
|
|
30147
|
+
}
|
|
29557
30148
|
if (op === "restore") {
|
|
29558
30149
|
const permissionError = await askEditPermission(context, [workspacePattern(context)], {
|
|
29559
30150
|
checkpoint: args.name
|
|
@@ -29594,6 +30185,8 @@ function safetyTools(ctx) {
|
|
|
29594
30185
|
}
|
|
29595
30186
|
|
|
29596
30187
|
// src/tools/search.ts
|
|
30188
|
+
import * as fs5 from "fs";
|
|
30189
|
+
import * as path6 from "path";
|
|
29597
30190
|
function arg(schema) {
|
|
29598
30191
|
return schema;
|
|
29599
30192
|
}
|
|
@@ -29663,11 +30256,33 @@ function searchTools(ctx) {
|
|
|
29663
30256
|
path: arg(exports_external.string().optional().describe("Directory to search in, relative to project root"))
|
|
29664
30257
|
},
|
|
29665
30258
|
execute: async (args, context) => {
|
|
30259
|
+
const pattern = String(args.pattern);
|
|
30260
|
+
const includeArg = args.include ? String(args.include) : undefined;
|
|
30261
|
+
const pathArg = args.path ? String(args.path) : undefined;
|
|
30262
|
+
const grepDenied = await askGrepPermission(context, pattern, {
|
|
30263
|
+
path: pathArg,
|
|
30264
|
+
include: includeArg
|
|
30265
|
+
});
|
|
30266
|
+
if (grepDenied)
|
|
30267
|
+
return permissionDeniedResponse(grepDenied);
|
|
30268
|
+
if (pathArg) {
|
|
30269
|
+
let kind = "file";
|
|
30270
|
+
try {
|
|
30271
|
+
const abs = path6.isAbsolute(pathArg) ? pathArg : path6.resolve(context.directory, pathArg);
|
|
30272
|
+
if (fs5.lstatSync(abs).isDirectory())
|
|
30273
|
+
kind = "directory";
|
|
30274
|
+
} catch {}
|
|
30275
|
+
const externalDenied = await assertExternalDirectoryPermission(context, pathArg, {
|
|
30276
|
+
kind
|
|
30277
|
+
});
|
|
30278
|
+
if (externalDenied)
|
|
30279
|
+
return permissionDeniedResponse(externalDenied);
|
|
30280
|
+
}
|
|
29666
30281
|
const response = await callBridge(ctx, context, "grep", {
|
|
29667
|
-
pattern
|
|
30282
|
+
pattern,
|
|
29668
30283
|
case_sensitive: true,
|
|
29669
|
-
include:
|
|
29670
|
-
path:
|
|
30284
|
+
include: includeArg ? splitIncludeArg(includeArg).map(normalizeGlob).filter(Boolean) : undefined,
|
|
30285
|
+
path: pathArg,
|
|
29671
30286
|
max_results: 100
|
|
29672
30287
|
});
|
|
29673
30288
|
if (response.success === false) {
|
|
@@ -29695,6 +30310,22 @@ function searchTools(ctx) {
|
|
|
29695
30310
|
}
|
|
29696
30311
|
}
|
|
29697
30312
|
}
|
|
30313
|
+
const globDenied = await askGlobPermission(context, globPattern, { path: globPath });
|
|
30314
|
+
if (globDenied)
|
|
30315
|
+
return permissionDeniedResponse(globDenied);
|
|
30316
|
+
if (globPath) {
|
|
30317
|
+
let kind = "directory";
|
|
30318
|
+
try {
|
|
30319
|
+
const abs = path6.isAbsolute(globPath) ? globPath : path6.resolve(context.directory, globPath);
|
|
30320
|
+
if (fs5.lstatSync(abs).isFile())
|
|
30321
|
+
kind = "file";
|
|
30322
|
+
} catch {}
|
|
30323
|
+
const externalDenied = await assertExternalDirectoryPermission(context, globPath, {
|
|
30324
|
+
kind
|
|
30325
|
+
});
|
|
30326
|
+
if (externalDenied)
|
|
30327
|
+
return permissionDeniedResponse(externalDenied);
|
|
30328
|
+
}
|
|
29698
30329
|
const response = await callBridge(ctx, context, "glob", {
|
|
29699
30330
|
pattern: globPattern,
|
|
29700
30331
|
path: globPath
|
|
@@ -29786,10 +30417,9 @@ function structureTools(ctx) {
|
|
|
29786
30417
|
` + `- 'add_decorator': Add Python decorator to function/class. Requires 'target' and 'decorator' (without @). Optional 'position'.
|
|
29787
30418
|
` + `- 'add_struct_tags': Add/update Go struct field tags. Requires 'target' (struct name), 'field', 'tag', 'value'.
|
|
29788
30419
|
|
|
29789
|
-
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements.
|
|
30420
|
+
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements. Use aft_safety checkpoint/undo before risky transforms.
|
|
29790
30421
|
|
|
29791
30422
|
` + `Returns:
|
|
29792
|
-
` + `- Dry run (any op): { ok, dry_run, diff, syntax_valid? }
|
|
29793
30423
|
` + `- add_member: { file, scope, position, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29794
30424
|
` + `- add_derive: { file, target, derives, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29795
30425
|
` + `- wrap_try_catch: { file, target, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
@@ -29808,8 +30438,7 @@ function structureTools(ctx) {
|
|
|
29808
30438
|
field: z11.string().optional().describe("Struct field name (add_struct_tags)"),
|
|
29809
30439
|
tag: z11.string().optional().describe("Tag key (add_struct_tags \u2014 e.g. 'json')"),
|
|
29810
30440
|
value: z11.string().optional().describe("Tag value (add_struct_tags \u2014 e.g. 'user_name,omitempty')"),
|
|
29811
|
-
validate: z11.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
|
|
29812
|
-
dryRun: z11.boolean().optional().describe("Preview without modifying the file (default: false)")
|
|
30441
|
+
validate: z11.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
|
|
29813
30442
|
},
|
|
29814
30443
|
execute: async (args, context) => {
|
|
29815
30444
|
const op = args.op;
|
|
@@ -29838,14 +30467,17 @@ function structureTools(ctx) {
|
|
|
29838
30467
|
throw new Error("'value' is required for 'add_struct_tags' op");
|
|
29839
30468
|
}
|
|
29840
30469
|
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
30470
|
+
{
|
|
30471
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
30472
|
+
if (denial)
|
|
30473
|
+
return permissionDeniedResponse(denial);
|
|
30474
|
+
}
|
|
29841
30475
|
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29842
30476
|
if (permissionError)
|
|
29843
30477
|
return permissionDeniedResponse(permissionError);
|
|
29844
30478
|
const params = { file: args.filePath };
|
|
29845
30479
|
if (args.validate !== undefined)
|
|
29846
30480
|
params.validate = args.validate;
|
|
29847
|
-
if (args.dryRun !== undefined)
|
|
29848
|
-
params.dry_run = args.dryRun;
|
|
29849
30481
|
switch (op) {
|
|
29850
30482
|
case "add_member":
|
|
29851
30483
|
params.scope = args.container;
|
|
@@ -29939,6 +30571,7 @@ function buildHintsFromConfig(config2, disabledTools) {
|
|
|
29939
30571
|
}
|
|
29940
30572
|
|
|
29941
30573
|
// src/configure-warnings.ts
|
|
30574
|
+
var pendingEagerWarnings = new Map;
|
|
29942
30575
|
function isConfigureWarning(value) {
|
|
29943
30576
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29944
30577
|
return false;
|
|
@@ -29948,13 +30581,25 @@ function isConfigureWarning(value) {
|
|
|
29948
30581
|
function coerceConfigureWarnings(warnings) {
|
|
29949
30582
|
return warnings.filter(isConfigureWarning);
|
|
29950
30583
|
}
|
|
30584
|
+
function drainPendingEagerWarnings(projectRoot) {
|
|
30585
|
+
const pending = pendingEagerWarnings.get(projectRoot) ?? [];
|
|
30586
|
+
pendingEagerWarnings.delete(projectRoot);
|
|
30587
|
+
return pending;
|
|
30588
|
+
}
|
|
29951
30589
|
async function handleConfigureWarningsForSession(context) {
|
|
30590
|
+
const validWarnings = coerceConfigureWarnings(context.warnings);
|
|
29952
30591
|
if (!context.sessionId) {
|
|
29953
|
-
|
|
30592
|
+
if (validWarnings.length === 0)
|
|
30593
|
+
return;
|
|
30594
|
+
const pending = pendingEagerWarnings.get(context.projectRoot) ?? [];
|
|
30595
|
+
pending.push(...validWarnings);
|
|
30596
|
+
pendingEagerWarnings.set(context.projectRoot, pending);
|
|
30597
|
+
warn2(`[configure] deferred warnings for ${context.projectRoot} arrived without session_id; buffering until first session-bound call`);
|
|
29954
30598
|
return;
|
|
29955
30599
|
}
|
|
29956
|
-
const
|
|
29957
|
-
|
|
30600
|
+
const pendingWarnings = drainPendingEagerWarnings(context.projectRoot);
|
|
30601
|
+
const combinedWarnings = [...pendingWarnings, ...validWarnings];
|
|
30602
|
+
if (combinedWarnings.length === 0)
|
|
29958
30603
|
return;
|
|
29959
30604
|
await deliverConfigureWarnings({
|
|
29960
30605
|
client: context.client ?? context.fallbackClient,
|
|
@@ -29962,7 +30607,7 @@ async function handleConfigureWarningsForSession(context) {
|
|
|
29962
30607
|
storageDir: context.storageDir,
|
|
29963
30608
|
pluginVersion: context.pluginVersion,
|
|
29964
30609
|
projectRoot: context.projectRoot
|
|
29965
|
-
},
|
|
30610
|
+
}, combinedWarnings);
|
|
29966
30611
|
}
|
|
29967
30612
|
|
|
29968
30613
|
// src/index.ts
|
|
@@ -29977,16 +30622,10 @@ function throwSentinel(command) {
|
|
|
29977
30622
|
}
|
|
29978
30623
|
async function sendIgnoredMessage2(client, sessionID, text) {
|
|
29979
30624
|
const typedClient = client;
|
|
29980
|
-
const lastModel = await getLastAssistantModel(client, sessionID);
|
|
29981
30625
|
const body = {
|
|
29982
30626
|
noReply: true,
|
|
29983
30627
|
parts: [{ type: "text", text, ignored: true }]
|
|
29984
30628
|
};
|
|
29985
|
-
if (lastModel) {
|
|
29986
|
-
body.model = { providerID: lastModel.providerID, modelID: lastModel.modelID };
|
|
29987
|
-
if (lastModel.variant)
|
|
29988
|
-
body.variant = lastModel.variant;
|
|
29989
|
-
}
|
|
29990
30629
|
const promptInput = { path: { id: sessionID }, body };
|
|
29991
30630
|
if (typeof typedClient.session?.prompt === "function") {
|
|
29992
30631
|
await Promise.resolve(typedClient.session.prompt(promptInput));
|
|
@@ -30016,7 +30655,8 @@ var ANNOUNCEMENT_FEATURES = [
|
|
|
30016
30655
|
"Trigram grep/glob and semantic search (aft_search) graduated out of experimental.",
|
|
30017
30656
|
"Lots of bugfixes and new end-to-end test coverage."
|
|
30018
30657
|
];
|
|
30019
|
-
var plugin = async (input) =>
|
|
30658
|
+
var plugin = async (input) => initializePluginForDirectory(input);
|
|
30659
|
+
async function initializePluginForDirectory(input) {
|
|
30020
30660
|
const binaryPath = await findBinary();
|
|
30021
30661
|
const aftConfig = loadAftConfig(input.directory);
|
|
30022
30662
|
const autoUpdateAbort = new AbortController;
|
|
@@ -30044,8 +30684,8 @@ var plugin = async (input) => {
|
|
|
30044
30684
|
if (aftConfig.max_callgraph_files !== undefined)
|
|
30045
30685
|
configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
|
|
30046
30686
|
const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
|
|
30047
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
30048
|
-
configOverrides.storage_dir =
|
|
30687
|
+
const dataHome = process.env.XDG_DATA_HOME || join21(homedir11(), ".local", "share");
|
|
30688
|
+
configOverrides.storage_dir = join21(dataHome, "opencode", "storage", "plugin", "aft");
|
|
30049
30689
|
let onnxRuntimePromise = null;
|
|
30050
30690
|
if (aftConfig.semantic_search && isFastembedSemanticBackend) {
|
|
30051
30691
|
const storageDir2 = configOverrides.storage_dir;
|
|
@@ -30131,10 +30771,10 @@ ${lines}
|
|
|
30131
30771
|
}
|
|
30132
30772
|
versionUpgradeAttempted = binaryVersion;
|
|
30133
30773
|
warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
30134
|
-
ensureBinary(`v${minVersion}`).then((
|
|
30135
|
-
if (
|
|
30136
|
-
log2(`Found/downloaded compatible binary at ${
|
|
30137
|
-
pool.replaceBinary(
|
|
30774
|
+
ensureBinary(`v${minVersion}`).then((path7) => {
|
|
30775
|
+
if (path7) {
|
|
30776
|
+
log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
|
|
30777
|
+
pool.replaceBinary(path7).then(() => {
|
|
30138
30778
|
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
30139
30779
|
}, (err) => error2("Failed to replace binary:", err));
|
|
30140
30780
|
} else {
|
|
@@ -30145,11 +30785,12 @@ ${lines}
|
|
|
30145
30785
|
});
|
|
30146
30786
|
},
|
|
30147
30787
|
onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
|
|
30788
|
+
const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
|
|
30148
30789
|
await handleConfigureWarningsForSession({
|
|
30149
30790
|
projectRoot,
|
|
30150
30791
|
sessionId,
|
|
30151
30792
|
client,
|
|
30152
|
-
warnings,
|
|
30793
|
+
warnings: [...pendingWarnings, ...warnings],
|
|
30153
30794
|
fallbackClient: input.client,
|
|
30154
30795
|
storageDir: configOverrides.storage_dir,
|
|
30155
30796
|
pluginVersion: PLUGIN_VERSION
|
|
@@ -30184,7 +30825,6 @@ ${lines}
|
|
|
30184
30825
|
config: aftConfig,
|
|
30185
30826
|
storageDir: configOverrides.storage_dir
|
|
30186
30827
|
};
|
|
30187
|
-
setSharedBridgePool(pool);
|
|
30188
30828
|
if (onnxRuntimePromise) {
|
|
30189
30829
|
onnxRuntimePromise.then((ortDylibDir) => {
|
|
30190
30830
|
if (ortDylibDir) {
|
|
@@ -30206,7 +30846,7 @@ ${lines}
|
|
|
30206
30846
|
if (onnxRuntimePromise) {
|
|
30207
30847
|
await Promise.race([
|
|
30208
30848
|
onnxRuntimePromise,
|
|
30209
|
-
new Promise((
|
|
30849
|
+
new Promise((resolve11) => setTimeout(() => resolve11(null), 60000))
|
|
30210
30850
|
]);
|
|
30211
30851
|
}
|
|
30212
30852
|
const bridge = pool.getBridge(input.directory);
|
|
@@ -30252,10 +30892,10 @@ ${lines}
|
|
|
30252
30892
|
return { show: false };
|
|
30253
30893
|
}
|
|
30254
30894
|
if (storageDir) {
|
|
30255
|
-
const versionFile =
|
|
30895
|
+
const versionFile = join21(storageDir, "last_announced_version");
|
|
30256
30896
|
try {
|
|
30257
30897
|
if (existsSync14(versionFile)) {
|
|
30258
|
-
const lastVersion =
|
|
30898
|
+
const lastVersion = readFileSync12(versionFile, "utf-8").trim();
|
|
30259
30899
|
if (lastVersion === ANNOUNCEMENT_VERSION)
|
|
30260
30900
|
return { show: false };
|
|
30261
30901
|
}
|
|
@@ -30266,8 +30906,8 @@ ${lines}
|
|
|
30266
30906
|
rpcServer.handle("mark-announced", async () => {
|
|
30267
30907
|
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
30268
30908
|
try {
|
|
30269
|
-
|
|
30270
|
-
writeFileSync11(
|
|
30909
|
+
mkdirSync12(storageDir, { recursive: true });
|
|
30910
|
+
writeFileSync11(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
|
|
30271
30911
|
} catch {}
|
|
30272
30912
|
}
|
|
30273
30913
|
return { success: true };
|
|
@@ -30399,7 +31039,6 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
30399
31039
|
},
|
|
30400
31040
|
"chat.message": async (messageInput) => {
|
|
30401
31041
|
const sid = messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id;
|
|
30402
|
-
resetBgWake(sid);
|
|
30403
31042
|
warmSessionDirectory(input.client, sid, input.directory);
|
|
30404
31043
|
},
|
|
30405
31044
|
"command.execute.before": async (commandInput, _output) => {
|
|
@@ -30459,11 +31098,10 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
30459
31098
|
unregisterShutdown();
|
|
30460
31099
|
await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
|
|
30461
31100
|
rpcServer.stop();
|
|
30462
|
-
clearSharedBridgePool();
|
|
30463
31101
|
await pool.shutdown();
|
|
30464
31102
|
}
|
|
30465
31103
|
};
|
|
30466
|
-
}
|
|
31104
|
+
}
|
|
30467
31105
|
var src_default = plugin;
|
|
30468
31106
|
export {
|
|
30469
31107
|
src_default as default
|