@cortexkit/aft-opencode 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +921 -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,65 @@ ${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
|
+
sessionLog2(sessionId, `[subagent-detect] cache hit: isSubagent=${cached2.isSubagent}`);
|
|
28125
|
+
return cached2.isSubagent;
|
|
28126
|
+
}
|
|
28127
|
+
const c = client;
|
|
28128
|
+
const sessionApi = c?.session;
|
|
28129
|
+
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
28130
|
+
sessionLog2(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) \u2192 caching as primary`);
|
|
28131
|
+
setCache2(sessionId, false);
|
|
28132
|
+
return false;
|
|
28133
|
+
}
|
|
28134
|
+
sessionLog2(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
28135
|
+
let isSubagent = false;
|
|
28136
|
+
let parentIdRaw;
|
|
28137
|
+
try {
|
|
28138
|
+
const result = await sessionApi.get({
|
|
28139
|
+
path: { id: sessionId }
|
|
28140
|
+
});
|
|
28141
|
+
const session = result?.data ?? result;
|
|
28142
|
+
parentIdRaw = session?.parentID;
|
|
28143
|
+
isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
28144
|
+
sessionLog2(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} \u2192 isSubagent=${isSubagent}`);
|
|
28145
|
+
} catch (err) {
|
|
28146
|
+
sessionWarn2(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
28147
|
+
return false;
|
|
28148
|
+
}
|
|
28149
|
+
setCache2(sessionId, isSubagent);
|
|
28150
|
+
return isSubagent;
|
|
28151
|
+
}
|
|
28152
|
+
function setCache2(sessionId, isSubagent) {
|
|
28153
|
+
if (cache2.has(sessionId))
|
|
28154
|
+
cache2.delete(sessionId);
|
|
28155
|
+
cache2.set(sessionId, { isSubagent, recordedAt: Date.now() });
|
|
28156
|
+
if (cache2.size > CACHE_MAX_ENTRIES2) {
|
|
28157
|
+
const oldest = cache2.keys().next().value;
|
|
28158
|
+
if (oldest !== undefined)
|
|
28159
|
+
cache2.delete(oldest);
|
|
28160
|
+
}
|
|
28161
|
+
}
|
|
28162
|
+
|
|
28163
|
+
// src/tools/bash.ts
|
|
27667
28164
|
var z3 = tool3.schema;
|
|
27668
28165
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
27669
28166
|
var FOREGROUND_WAIT_WINDOW_MS = 5000;
|
|
27670
28167
|
var FOREGROUND_POLL_INTERVAL_MS = 100;
|
|
27671
28168
|
var BASH_TRANSPORT_TIMEOUT_MS = 30000;
|
|
28169
|
+
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
27672
28170
|
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
28171
|
async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
27674
28172
|
const first = await bridgeCall(ctx, runtime, "bash", params, options);
|
|
@@ -27709,6 +28207,13 @@ function createBashTool(ctx) {
|
|
|
27709
28207
|
const metadata = context.metadata;
|
|
27710
28208
|
const command = args.command;
|
|
27711
28209
|
const cwd = args.workdir ?? context.directory;
|
|
28210
|
+
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
28211
|
+
const requestedBackground = args.background === true;
|
|
28212
|
+
const effectiveBackground = isSubagent ? false : requestedBackground;
|
|
28213
|
+
sessionLog2(context.sessionID, `[bash] subagent gate: isSubagent=${isSubagent}, requestedBackground=${requestedBackground}, effectiveBackground=${effectiveBackground}, hasTimeout=${typeof args.timeout === "number"}`);
|
|
28214
|
+
if (isSubagent && requestedBackground) {
|
|
28215
|
+
sessionLog2(context.sessionID, "[bash] subagent + background:true \u2192 converting to foreground (subagent would lose task_id)");
|
|
28216
|
+
}
|
|
27712
28217
|
const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
|
|
27713
28218
|
const data = await withPermissionLoop(ctx, context, {
|
|
27714
28219
|
command,
|
|
@@ -27716,8 +28221,8 @@ function createBashTool(ctx) {
|
|
|
27716
28221
|
workdir: args.workdir,
|
|
27717
28222
|
env: shellEnv?.env ?? {},
|
|
27718
28223
|
description,
|
|
27719
|
-
background:
|
|
27720
|
-
notify_on_completion:
|
|
28224
|
+
background: effectiveBackground,
|
|
28225
|
+
notify_on_completion: effectiveBackground,
|
|
27721
28226
|
compressed: args.compressed,
|
|
27722
28227
|
permissions_requested: true
|
|
27723
28228
|
}, callBridge, {
|
|
@@ -27734,7 +28239,7 @@ function createBashTool(ctx) {
|
|
|
27734
28239
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
27735
28240
|
const callID2 = getCallID(context);
|
|
27736
28241
|
const taskId = data.task_id;
|
|
27737
|
-
if (
|
|
28242
|
+
if (effectiveBackground) {
|
|
27738
28243
|
trackBgTask(context.sessionID, taskId);
|
|
27739
28244
|
const startedLine = formatBackgroundLaunch(taskId);
|
|
27740
28245
|
const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
|
|
@@ -27748,7 +28253,7 @@ function createBashTool(ctx) {
|
|
|
27748
28253
|
return startedLine;
|
|
27749
28254
|
}
|
|
27750
28255
|
const argTimeout = args.timeout;
|
|
27751
|
-
const waitTimeoutMs = argTimeout !== undefined ? Math.min(argTimeout, FOREGROUND_WAIT_WINDOW_MS) : FOREGROUND_WAIT_WINDOW_MS;
|
|
28256
|
+
const waitTimeoutMs = isSubagent ? argTimeout ?? DEFAULT_HARD_TIMEOUT_MS : argTimeout !== undefined ? Math.min(argTimeout, FOREGROUND_WAIT_WINDOW_MS) : FOREGROUND_WAIT_WINDOW_MS;
|
|
27752
28257
|
const startedAt = Date.now();
|
|
27753
28258
|
while (true) {
|
|
27754
28259
|
const status = await callBridge(ctx, context, "bash_status", { task_id: taskId });
|
|
@@ -27829,7 +28334,7 @@ function createBashStatusTool(ctx) {
|
|
|
27829
28334
|
return {
|
|
27830
28335
|
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
28336
|
args: {
|
|
27832
|
-
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g.
|
|
28337
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
|
|
27833
28338
|
},
|
|
27834
28339
|
execute: async (args, context) => {
|
|
27835
28340
|
const data = await callBridge(ctx, context, "bash_status", {
|
|
@@ -27859,7 +28364,7 @@ function createBashKillTool(ctx) {
|
|
|
27859
28364
|
return {
|
|
27860
28365
|
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
28366
|
args: {
|
|
27862
|
-
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g.
|
|
28367
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
|
|
27863
28368
|
},
|
|
27864
28369
|
execute: async (args, context) => {
|
|
27865
28370
|
const data = await callBridge(ctx, context, "bash_kill", {
|
|
@@ -28170,12 +28675,23 @@ function createReadTool(ctx) {
|
|
|
28170
28675
|
execute: async (args, context) => {
|
|
28171
28676
|
const file2 = args.filePath;
|
|
28172
28677
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28678
|
+
{
|
|
28679
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28680
|
+
if (denial)
|
|
28681
|
+
return permissionDeniedResponse(denial);
|
|
28682
|
+
}
|
|
28683
|
+
try {
|
|
28684
|
+
await runAsk(context.ask({
|
|
28685
|
+
permission: "read",
|
|
28686
|
+
patterns: [filePath],
|
|
28687
|
+
always: ["*"],
|
|
28688
|
+
metadata: {}
|
|
28689
|
+
}));
|
|
28690
|
+
} catch (error50) {
|
|
28691
|
+
if (error50 instanceof Error && error50.message)
|
|
28692
|
+
return permissionDeniedResponse(error50.message);
|
|
28693
|
+
return permissionDeniedResponse("Permission denied.");
|
|
28694
|
+
}
|
|
28179
28695
|
const ext = path4.extname(filePath).toLowerCase();
|
|
28180
28696
|
const mimeMap = {
|
|
28181
28697
|
".png": "image/png",
|
|
@@ -28198,7 +28714,7 @@ function createReadTool(ctx) {
|
|
|
28198
28714
|
const label = isImage ? "Image" : "PDF";
|
|
28199
28715
|
let fileSize = 0;
|
|
28200
28716
|
try {
|
|
28201
|
-
const stat = await import("fs/promises").then((
|
|
28717
|
+
const stat = await import("fs/promises").then((fs5) => fs5.stat(filePath));
|
|
28202
28718
|
fileSize = stat.size;
|
|
28203
28719
|
} catch {}
|
|
28204
28720
|
const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
|
|
@@ -28294,6 +28810,11 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
28294
28810
|
const content = args.content;
|
|
28295
28811
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28296
28812
|
const relPath = path4.relative(context.worktree, filePath);
|
|
28813
|
+
{
|
|
28814
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28815
|
+
if (denial)
|
|
28816
|
+
return permissionDeniedResponse(denial);
|
|
28817
|
+
}
|
|
28297
28818
|
await runAsk(context.ask({
|
|
28298
28819
|
permission: "edit",
|
|
28299
28820
|
patterns: [relPath],
|
|
@@ -28414,9 +28935,9 @@ Note: Modes 6 and 7 are options on mode 5 (find/replace) \u2014 they require \`o
|
|
|
28414
28935
|
- Auto-formats using project formatter if configured
|
|
28415
28936
|
- Tree-sitter syntax validation on all edits
|
|
28416
28937
|
- Symbol replace includes decorators, attributes, and doc comments in range
|
|
28417
|
-
- LSP error-level diagnostics are returned automatically after
|
|
28938
|
+
- LSP error-level diagnostics are returned automatically after edits
|
|
28418
28939
|
|
|
28419
|
-
Returns: JSON string for the selected edit mode.
|
|
28940
|
+
Returns: JSON string for the selected edit mode. Edits may append inline LSP error lines.
|
|
28420
28941
|
|
|
28421
28942
|
Common response fields: success (boolean), diff (object with before/after), backup_id (string), syntax_valid (boolean). Exact fields vary by mode.`;
|
|
28422
28943
|
}
|
|
@@ -28433,8 +28954,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28433
28954
|
content: z4.string().optional().describe("New content for symbol replace or file write"),
|
|
28434
28955
|
appendContent: z4.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
28435
28956
|
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)")
|
|
28957
|
+
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
28958
|
},
|
|
28439
28959
|
execute: async (args, context) => {
|
|
28440
28960
|
const argsRecord = args;
|
|
@@ -28444,6 +28964,18 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28444
28964
|
if (Array.isArray(args.operations)) {
|
|
28445
28965
|
const ops = args.operations;
|
|
28446
28966
|
const files = ops.map((op) => op.file).filter(Boolean);
|
|
28967
|
+
{
|
|
28968
|
+
const asked = new Set;
|
|
28969
|
+
for (const file3 of files) {
|
|
28970
|
+
const absPath = path4.isAbsolute(file3) ? file3 : path4.resolve(context.directory, file3);
|
|
28971
|
+
if (asked.has(absPath))
|
|
28972
|
+
continue;
|
|
28973
|
+
asked.add(absPath);
|
|
28974
|
+
const denial = await assertExternalDirectoryPermission(context, absPath);
|
|
28975
|
+
if (denial)
|
|
28976
|
+
return permissionDeniedResponse(denial);
|
|
28977
|
+
}
|
|
28978
|
+
}
|
|
28447
28979
|
await runAsk(context.ask({
|
|
28448
28980
|
permission: "edit",
|
|
28449
28981
|
patterns: files.map((f) => path4.relative(context.worktree, path4.resolve(context.directory, f))),
|
|
@@ -28454,9 +28986,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28454
28986
|
...op,
|
|
28455
28987
|
file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
|
|
28456
28988
|
}));
|
|
28457
|
-
const
|
|
28458
|
-
params2.dry_run = args.dryRun === true;
|
|
28459
|
-
const data2 = await callBridge(ctx, context, "transaction", params2);
|
|
28989
|
+
const data2 = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
|
|
28460
28990
|
return JSON.stringify(data2);
|
|
28461
28991
|
}
|
|
28462
28992
|
const file2 = args.filePath;
|
|
@@ -28464,6 +28994,11 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28464
28994
|
throw new Error("'filePath' parameter is required");
|
|
28465
28995
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28466
28996
|
const relPath = path4.relative(context.worktree, filePath);
|
|
28997
|
+
{
|
|
28998
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28999
|
+
if (denial)
|
|
29000
|
+
return permissionDeniedResponse(denial);
|
|
29001
|
+
}
|
|
28467
29002
|
await runAsk(context.ask({
|
|
28468
29003
|
permission: "edit",
|
|
28469
29004
|
patterns: [relPath],
|
|
@@ -28512,14 +29047,10 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28512
29047
|
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
29048
|
throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
|
|
28514
29049
|
}
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
if (!args.dryRun)
|
|
28518
|
-
params.diagnostics = true;
|
|
28519
|
-
if (!args.dryRun)
|
|
28520
|
-
params.include_diff = true;
|
|
29050
|
+
params.diagnostics = true;
|
|
29051
|
+
params.include_diff = true;
|
|
28521
29052
|
const data = await callBridge(ctx, context, command, params);
|
|
28522
|
-
if (
|
|
29053
|
+
if (data.success && data.diff) {
|
|
28523
29054
|
const diff = data.diff;
|
|
28524
29055
|
const callID = getCallID2(context);
|
|
28525
29056
|
if (callID) {
|
|
@@ -28548,31 +29079,29 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28548
29079
|
result += `
|
|
28549
29080
|
|
|
28550
29081
|
${globSkipNote}`;
|
|
28551
|
-
|
|
28552
|
-
|
|
28553
|
-
|
|
28554
|
-
|
|
28555
|
-
|
|
28556
|
-
const diagLines = errors3.map((d) => ` Line ${d.line}: ${d.message}`).join(`
|
|
29082
|
+
const diags = data.lsp_diagnostics;
|
|
29083
|
+
if (diags && diags.length > 0) {
|
|
29084
|
+
const errors3 = diags.filter((d) => d.severity === "error");
|
|
29085
|
+
if (errors3.length > 0) {
|
|
29086
|
+
const diagLines = errors3.map((d) => ` Line ${d.line}: ${d.message}`).join(`
|
|
28557
29087
|
`);
|
|
28558
|
-
|
|
29088
|
+
result += `
|
|
28559
29089
|
|
|
28560
29090
|
LSP errors detected, please fix:
|
|
28561
29091
|
${diagLines}`;
|
|
28562
|
-
}
|
|
28563
29092
|
}
|
|
28564
|
-
|
|
28565
|
-
|
|
28566
|
-
|
|
28567
|
-
|
|
29093
|
+
}
|
|
29094
|
+
const pendingServers = data.lsp_pending_servers;
|
|
29095
|
+
const exitedServers = data.lsp_exited_servers;
|
|
29096
|
+
if (pendingServers && pendingServers.length > 0) {
|
|
29097
|
+
result += `
|
|
28568
29098
|
|
|
28569
29099
|
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
|
-
|
|
29100
|
+
}
|
|
29101
|
+
if (exitedServers && exitedServers.length > 0) {
|
|
29102
|
+
result += `
|
|
28573
29103
|
|
|
28574
29104
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
28575
|
-
}
|
|
28576
29105
|
}
|
|
28577
29106
|
return result;
|
|
28578
29107
|
}
|
|
@@ -28659,13 +29188,24 @@ function createApplyPatchTool(ctx) {
|
|
|
28659
29188
|
if (h.type === "update" && h.move_path) {
|
|
28660
29189
|
const dstAbs = path4.resolve(context.directory, h.move_path);
|
|
28661
29190
|
affectedAbs.add(dstAbs);
|
|
28662
|
-
if (!
|
|
29191
|
+
if (!fs4.existsSync(dstAbs)) {
|
|
28663
29192
|
newlyCreatedAbs.add(dstAbs);
|
|
28664
29193
|
}
|
|
28665
29194
|
}
|
|
28666
29195
|
}
|
|
28667
29196
|
const relPaths = Array.from(affectedAbs).map((abs) => path4.relative(context.worktree, abs));
|
|
28668
29197
|
const multiFileWritePaths = Array.from(affectedAbs);
|
|
29198
|
+
{
|
|
29199
|
+
const asked = new Set;
|
|
29200
|
+
for (const filePath of multiFileWritePaths) {
|
|
29201
|
+
if (asked.has(filePath))
|
|
29202
|
+
continue;
|
|
29203
|
+
asked.add(filePath);
|
|
29204
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29205
|
+
if (denial)
|
|
29206
|
+
return permissionDeniedResponse(denial);
|
|
29207
|
+
}
|
|
29208
|
+
}
|
|
28669
29209
|
await runAsk(context.ask({
|
|
28670
29210
|
permission: "edit",
|
|
28671
29211
|
patterns: relPaths,
|
|
@@ -28691,7 +29231,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28691
29231
|
const filePath = path4.resolve(context.directory, hunk.path);
|
|
28692
29232
|
switch (hunk.type) {
|
|
28693
29233
|
case "add": {
|
|
28694
|
-
if (
|
|
29234
|
+
if (fs4.existsSync(filePath)) {
|
|
28695
29235
|
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
29236
|
results.push(msg);
|
|
28697
29237
|
failures.push(hunk.path);
|
|
@@ -28723,7 +29263,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28723
29263
|
results.push(msg);
|
|
28724
29264
|
failures.push(hunk.path);
|
|
28725
29265
|
const filePath2 = path4.resolve(context.directory, hunk.path);
|
|
28726
|
-
if (
|
|
29266
|
+
if (fs4.existsSync(filePath2)) {
|
|
28727
29267
|
try {
|
|
28728
29268
|
await callBridge(ctx, context, "delete_file", { file: filePath2 });
|
|
28729
29269
|
} catch {}
|
|
@@ -28733,7 +29273,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28733
29273
|
}
|
|
28734
29274
|
case "delete": {
|
|
28735
29275
|
try {
|
|
28736
|
-
const before = await
|
|
29276
|
+
const before = await fs4.promises.readFile(filePath, "utf-8").catch(() => "");
|
|
28737
29277
|
await callBridge(ctx, context, "delete_file", { file: filePath });
|
|
28738
29278
|
perFileDiffs.push({
|
|
28739
29279
|
filePath,
|
|
@@ -28751,7 +29291,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28751
29291
|
}
|
|
28752
29292
|
case "update": {
|
|
28753
29293
|
try {
|
|
28754
|
-
const original = await
|
|
29294
|
+
const original = await fs4.promises.readFile(filePath, "utf-8");
|
|
28755
29295
|
const newContent = applyUpdateChunks(original, filePath, hunk.chunks);
|
|
28756
29296
|
const targetPath = hunk.move_path ? path4.resolve(context.directory, hunk.move_path) : filePath;
|
|
28757
29297
|
const writeResult = await callBridge(ctx, context, "write", {
|
|
@@ -28806,7 +29346,7 @@ ${diagLines}`);
|
|
|
28806
29346
|
if (rollbackResult.success === false) {
|
|
28807
29347
|
throw new Error(rollbackResult.message ?? "checkpoint restore failed");
|
|
28808
29348
|
}
|
|
28809
|
-
if (newlyCreatedAbs.has(targetPath) &&
|
|
29349
|
+
if (newlyCreatedAbs.has(targetPath) && fs4.existsSync(targetPath)) {
|
|
28810
29350
|
const cleanupResult = await callBridge(ctx, context, "delete_file", {
|
|
28811
29351
|
file: targetPath
|
|
28812
29352
|
});
|
|
@@ -28901,6 +29441,17 @@ function createDeleteTool(ctx) {
|
|
|
28901
29441
|
execute: async (args, context) => {
|
|
28902
29442
|
const inputs = args.files;
|
|
28903
29443
|
const absolutePaths = inputs.map((f) => path4.isAbsolute(f) ? f : path4.resolve(context.directory, f));
|
|
29444
|
+
{
|
|
29445
|
+
const asked = new Set;
|
|
29446
|
+
for (const filePath of absolutePaths) {
|
|
29447
|
+
if (asked.has(filePath))
|
|
29448
|
+
continue;
|
|
29449
|
+
asked.add(filePath);
|
|
29450
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29451
|
+
if (denial)
|
|
29452
|
+
return permissionDeniedResponse(denial);
|
|
29453
|
+
}
|
|
29454
|
+
}
|
|
28904
29455
|
await runAsk(context.ask({
|
|
28905
29456
|
permission: "edit",
|
|
28906
29457
|
patterns: absolutePaths,
|
|
@@ -28946,6 +29497,18 @@ function createMoveTool(ctx) {
|
|
|
28946
29497
|
execute: async (args, context) => {
|
|
28947
29498
|
const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
|
|
28948
29499
|
const destPath = path4.isAbsolute(args.destination) ? args.destination : path4.resolve(context.directory, args.destination);
|
|
29500
|
+
{
|
|
29501
|
+
const sourceDenial = await assertExternalDirectoryPermission(context, filePath, {
|
|
29502
|
+
kind: "file"
|
|
29503
|
+
});
|
|
29504
|
+
if (sourceDenial)
|
|
29505
|
+
return permissionDeniedResponse(sourceDenial);
|
|
29506
|
+
if (destPath !== filePath) {
|
|
29507
|
+
const destDenial = await assertExternalDirectoryPermission(context, destPath);
|
|
29508
|
+
if (destDenial)
|
|
29509
|
+
return permissionDeniedResponse(destDenial);
|
|
29510
|
+
}
|
|
29511
|
+
}
|
|
28949
29512
|
await runAsk(context.ask({
|
|
28950
29513
|
permission: "edit",
|
|
28951
29514
|
patterns: [filePath, destPath],
|
|
@@ -29000,6 +29563,11 @@ function aftPrefixedTools(ctx) {
|
|
|
29000
29563
|
const file2 = normalizedArgs.filePath;
|
|
29001
29564
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
29002
29565
|
const relPath = path4.relative(context.worktree, filePath);
|
|
29566
|
+
{
|
|
29567
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29568
|
+
if (denial)
|
|
29569
|
+
return permissionDeniedResponse(denial);
|
|
29570
|
+
}
|
|
29003
29571
|
await runAsk(context.ask({
|
|
29004
29572
|
permission: "edit",
|
|
29005
29573
|
patterns: [relPath],
|
|
@@ -29012,9 +29580,6 @@ function aftPrefixedTools(ctx) {
|
|
|
29012
29580
|
create_dirs: normalizedArgs.create_dirs !== false,
|
|
29013
29581
|
diagnostics: true
|
|
29014
29582
|
};
|
|
29015
|
-
if (normalizedArgs.dryRun === true || normalizedArgs.dry_run === true) {
|
|
29016
|
-
writeParams.dry_run = true;
|
|
29017
|
-
}
|
|
29018
29583
|
const data = await callBridge(ctx, context, "write", writeParams);
|
|
29019
29584
|
return JSON.stringify(data);
|
|
29020
29585
|
}
|
|
@@ -29038,10 +29603,9 @@ function importTools(ctx) {
|
|
|
29038
29603
|
` + `Ops:
|
|
29039
29604
|
` + `- 'add': Add an import. Auto-detects group (stdlib/external/internal), deduplicates. Requires 'module'. Optional 'names', 'defaultImport', 'typeOnly'.
|
|
29040
29605
|
` + `- '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'.
|
|
29606
|
+
` + `- '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
29607
|
|
|
29043
29608
|
` + `Returns:
|
|
29044
|
-
` + `- Dry run (any op): { ok, dry_run, diff, syntax_valid? }
|
|
29045
29609
|
` + `- add: { file, added, module, group?, already_present?, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29046
29610
|
` + `- remove: { file, removed, module, name?, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29047
29611
|
` + "- organize: { file, groups: [{ name, count }], removed_duplicates, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
|
|
@@ -29053,21 +29617,22 @@ function importTools(ctx) {
|
|
|
29053
29617
|
defaultImport: z5.string().optional().describe("Default import name (e.g. 'React')"),
|
|
29054
29618
|
typeOnly: z5.boolean().optional().describe("Type-only import (TS only, default: false)"),
|
|
29055
29619
|
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)")
|
|
29620
|
+
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
29621
|
},
|
|
29059
29622
|
execute: async (args, context) => {
|
|
29060
29623
|
const op = args.op;
|
|
29061
|
-
const isDryRun = args.dryRun === true;
|
|
29062
29624
|
if ((op === "add" || op === "remove") && typeof args.module !== "string") {
|
|
29063
29625
|
throw new Error(`'module' is required for '${op}' op`);
|
|
29064
29626
|
}
|
|
29065
|
-
|
|
29066
|
-
|
|
29067
|
-
const
|
|
29068
|
-
if (
|
|
29069
|
-
return permissionDeniedResponse(
|
|
29627
|
+
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
29628
|
+
{
|
|
29629
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29630
|
+
if (denial)
|
|
29631
|
+
return permissionDeniedResponse(denial);
|
|
29070
29632
|
}
|
|
29633
|
+
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29634
|
+
if (permissionError)
|
|
29635
|
+
return permissionDeniedResponse(permissionError);
|
|
29071
29636
|
const commandMap = {
|
|
29072
29637
|
add: "add_import",
|
|
29073
29638
|
remove: "remove_import",
|
|
@@ -29086,8 +29651,6 @@ function importTools(ctx) {
|
|
|
29086
29651
|
params.name = args.removeName;
|
|
29087
29652
|
if (args.validate !== undefined)
|
|
29088
29653
|
params.validate = args.validate;
|
|
29089
|
-
if (args.dryRun !== undefined)
|
|
29090
|
-
params.dry_run = args.dryRun;
|
|
29091
29654
|
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
29092
29655
|
if (response.success === false) {
|
|
29093
29656
|
throw new Error(response.message || `${op} failed`);
|
|
@@ -29430,9 +29993,9 @@ function refactoringTools(ctx) {
|
|
|
29430
29993
|
|
|
29431
29994
|
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements.
|
|
29432
29995
|
|
|
29433
|
-
` + `All ops need 'filePath'. Use
|
|
29996
|
+
` + `All ops need 'filePath'. Use aft_safety checkpoint/undo before risky refactors.
|
|
29434
29997
|
|
|
29435
|
-
` + "Returns: move
|
|
29998
|
+
` + "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
29999
|
args: {
|
|
29437
30000
|
op: z9.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
|
|
29438
30001
|
filePath: z9.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
@@ -29442,8 +30005,7 @@ function refactoringTools(ctx) {
|
|
|
29442
30005
|
name: z9.string().optional().describe("New function name \u2014 required for 'extract' op"),
|
|
29443
30006
|
startLine: z9.number().optional().describe("1-based start line \u2014 required for 'extract' op"),
|
|
29444
30007
|
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)")
|
|
30008
|
+
callSiteLine: z9.number().optional().describe("1-based call site line \u2014 required for 'inline' op")
|
|
29447
30009
|
},
|
|
29448
30010
|
execute: async (args, context) => {
|
|
29449
30011
|
const op = args.op;
|
|
@@ -29471,6 +30033,18 @@ function refactoringTools(ctx) {
|
|
|
29471
30033
|
...typeof args.destination === "string" ? [args.destination] : []
|
|
29472
30034
|
]) : [resolveRelativePattern(context, args.filePath)];
|
|
29473
30035
|
const metadata = patterns.length === 1 ? { filepath: filePath } : {};
|
|
30036
|
+
{
|
|
30037
|
+
const affectedPaths = op === "move" && typeof args.destination === "string" ? [filePath, resolveAbsolutePath(context, args.destination)] : [filePath];
|
|
30038
|
+
const asked = new Set;
|
|
30039
|
+
for (const affectedPath of affectedPaths) {
|
|
30040
|
+
if (asked.has(affectedPath))
|
|
30041
|
+
continue;
|
|
30042
|
+
asked.add(affectedPath);
|
|
30043
|
+
const denial = await assertExternalDirectoryPermission(context, affectedPath);
|
|
30044
|
+
if (denial)
|
|
30045
|
+
return permissionDeniedResponse(denial);
|
|
30046
|
+
}
|
|
30047
|
+
}
|
|
29474
30048
|
const permissionError = await askEditPermission(context, patterns, metadata);
|
|
29475
30049
|
if (permissionError)
|
|
29476
30050
|
return permissionDeniedResponse(permissionError);
|
|
@@ -29480,8 +30054,6 @@ function refactoringTools(ctx) {
|
|
|
29480
30054
|
inline: "inline_symbol"
|
|
29481
30055
|
};
|
|
29482
30056
|
const params = { file: args.filePath };
|
|
29483
|
-
if (args.dryRun !== undefined)
|
|
29484
|
-
params.dry_run = args.dryRun;
|
|
29485
30057
|
switch (op) {
|
|
29486
30058
|
case "move":
|
|
29487
30059
|
params.symbol = args.symbol;
|
|
@@ -29513,6 +30085,7 @@ function refactoringTools(ctx) {
|
|
|
29513
30085
|
}
|
|
29514
30086
|
|
|
29515
30087
|
// src/tools/safety.ts
|
|
30088
|
+
import * as path5 from "path";
|
|
29516
30089
|
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
29517
30090
|
var z10 = tool10.schema;
|
|
29518
30091
|
function safetyTools(ctx) {
|
|
@@ -29550,10 +30123,30 @@ function safetyTools(ctx) {
|
|
|
29550
30123
|
}
|
|
29551
30124
|
if (op === "undo" && typeof args.filePath === "string") {
|
|
29552
30125
|
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
30126
|
+
{
|
|
30127
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
30128
|
+
if (denial)
|
|
30129
|
+
return permissionDeniedResponse(denial);
|
|
30130
|
+
}
|
|
29553
30131
|
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29554
30132
|
if (permissionError)
|
|
29555
30133
|
return permissionDeniedResponse(permissionError);
|
|
29556
30134
|
}
|
|
30135
|
+
if (op === "checkpoint" && Array.isArray(args.files)) {
|
|
30136
|
+
const uniqueParents = new Set;
|
|
30137
|
+
for (const file2 of args.files) {
|
|
30138
|
+
if (typeof file2 !== "string")
|
|
30139
|
+
continue;
|
|
30140
|
+
const abs = path5.isAbsolute(file2) ? file2 : path5.resolve(context.directory, file2);
|
|
30141
|
+
const parent = path5.dirname(abs);
|
|
30142
|
+
if (uniqueParents.has(parent))
|
|
30143
|
+
continue;
|
|
30144
|
+
uniqueParents.add(parent);
|
|
30145
|
+
const denial = await assertExternalDirectoryPermission(context, file2, { kind: "file" });
|
|
30146
|
+
if (denial)
|
|
30147
|
+
return permissionDeniedResponse(denial);
|
|
30148
|
+
}
|
|
30149
|
+
}
|
|
29557
30150
|
if (op === "restore") {
|
|
29558
30151
|
const permissionError = await askEditPermission(context, [workspacePattern(context)], {
|
|
29559
30152
|
checkpoint: args.name
|
|
@@ -29594,6 +30187,8 @@ function safetyTools(ctx) {
|
|
|
29594
30187
|
}
|
|
29595
30188
|
|
|
29596
30189
|
// src/tools/search.ts
|
|
30190
|
+
import * as fs5 from "fs";
|
|
30191
|
+
import * as path6 from "path";
|
|
29597
30192
|
function arg(schema) {
|
|
29598
30193
|
return schema;
|
|
29599
30194
|
}
|
|
@@ -29663,11 +30258,33 @@ function searchTools(ctx) {
|
|
|
29663
30258
|
path: arg(exports_external.string().optional().describe("Directory to search in, relative to project root"))
|
|
29664
30259
|
},
|
|
29665
30260
|
execute: async (args, context) => {
|
|
30261
|
+
const pattern = String(args.pattern);
|
|
30262
|
+
const includeArg = args.include ? String(args.include) : undefined;
|
|
30263
|
+
const pathArg = args.path ? String(args.path) : undefined;
|
|
30264
|
+
const grepDenied = await askGrepPermission(context, pattern, {
|
|
30265
|
+
path: pathArg,
|
|
30266
|
+
include: includeArg
|
|
30267
|
+
});
|
|
30268
|
+
if (grepDenied)
|
|
30269
|
+
return permissionDeniedResponse(grepDenied);
|
|
30270
|
+
if (pathArg) {
|
|
30271
|
+
let kind = "file";
|
|
30272
|
+
try {
|
|
30273
|
+
const abs = path6.isAbsolute(pathArg) ? pathArg : path6.resolve(context.directory, pathArg);
|
|
30274
|
+
if (fs5.lstatSync(abs).isDirectory())
|
|
30275
|
+
kind = "directory";
|
|
30276
|
+
} catch {}
|
|
30277
|
+
const externalDenied = await assertExternalDirectoryPermission(context, pathArg, {
|
|
30278
|
+
kind
|
|
30279
|
+
});
|
|
30280
|
+
if (externalDenied)
|
|
30281
|
+
return permissionDeniedResponse(externalDenied);
|
|
30282
|
+
}
|
|
29666
30283
|
const response = await callBridge(ctx, context, "grep", {
|
|
29667
|
-
pattern
|
|
30284
|
+
pattern,
|
|
29668
30285
|
case_sensitive: true,
|
|
29669
|
-
include:
|
|
29670
|
-
path:
|
|
30286
|
+
include: includeArg ? splitIncludeArg(includeArg).map(normalizeGlob).filter(Boolean) : undefined,
|
|
30287
|
+
path: pathArg,
|
|
29671
30288
|
max_results: 100
|
|
29672
30289
|
});
|
|
29673
30290
|
if (response.success === false) {
|
|
@@ -29695,6 +30312,22 @@ function searchTools(ctx) {
|
|
|
29695
30312
|
}
|
|
29696
30313
|
}
|
|
29697
30314
|
}
|
|
30315
|
+
const globDenied = await askGlobPermission(context, globPattern, { path: globPath });
|
|
30316
|
+
if (globDenied)
|
|
30317
|
+
return permissionDeniedResponse(globDenied);
|
|
30318
|
+
if (globPath) {
|
|
30319
|
+
let kind = "directory";
|
|
30320
|
+
try {
|
|
30321
|
+
const abs = path6.isAbsolute(globPath) ? globPath : path6.resolve(context.directory, globPath);
|
|
30322
|
+
if (fs5.lstatSync(abs).isFile())
|
|
30323
|
+
kind = "file";
|
|
30324
|
+
} catch {}
|
|
30325
|
+
const externalDenied = await assertExternalDirectoryPermission(context, globPath, {
|
|
30326
|
+
kind
|
|
30327
|
+
});
|
|
30328
|
+
if (externalDenied)
|
|
30329
|
+
return permissionDeniedResponse(externalDenied);
|
|
30330
|
+
}
|
|
29698
30331
|
const response = await callBridge(ctx, context, "glob", {
|
|
29699
30332
|
pattern: globPattern,
|
|
29700
30333
|
path: globPath
|
|
@@ -29786,10 +30419,9 @@ function structureTools(ctx) {
|
|
|
29786
30419
|
` + `- 'add_decorator': Add Python decorator to function/class. Requires 'target' and 'decorator' (without @). Optional 'position'.
|
|
29787
30420
|
` + `- 'add_struct_tags': Add/update Go struct field tags. Requires 'target' (struct name), 'field', 'tag', 'value'.
|
|
29788
30421
|
|
|
29789
|
-
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements.
|
|
30422
|
+
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements. Use aft_safety checkpoint/undo before risky transforms.
|
|
29790
30423
|
|
|
29791
30424
|
` + `Returns:
|
|
29792
|
-
` + `- Dry run (any op): { ok, dry_run, diff, syntax_valid? }
|
|
29793
30425
|
` + `- add_member: { file, scope, position, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29794
30426
|
` + `- add_derive: { file, target, derives, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29795
30427
|
` + `- wrap_try_catch: { file, target, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
@@ -29808,8 +30440,7 @@ function structureTools(ctx) {
|
|
|
29808
30440
|
field: z11.string().optional().describe("Struct field name (add_struct_tags)"),
|
|
29809
30441
|
tag: z11.string().optional().describe("Tag key (add_struct_tags \u2014 e.g. 'json')"),
|
|
29810
30442
|
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)")
|
|
30443
|
+
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
30444
|
},
|
|
29814
30445
|
execute: async (args, context) => {
|
|
29815
30446
|
const op = args.op;
|
|
@@ -29838,14 +30469,17 @@ function structureTools(ctx) {
|
|
|
29838
30469
|
throw new Error("'value' is required for 'add_struct_tags' op");
|
|
29839
30470
|
}
|
|
29840
30471
|
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
30472
|
+
{
|
|
30473
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
30474
|
+
if (denial)
|
|
30475
|
+
return permissionDeniedResponse(denial);
|
|
30476
|
+
}
|
|
29841
30477
|
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29842
30478
|
if (permissionError)
|
|
29843
30479
|
return permissionDeniedResponse(permissionError);
|
|
29844
30480
|
const params = { file: args.filePath };
|
|
29845
30481
|
if (args.validate !== undefined)
|
|
29846
30482
|
params.validate = args.validate;
|
|
29847
|
-
if (args.dryRun !== undefined)
|
|
29848
|
-
params.dry_run = args.dryRun;
|
|
29849
30483
|
switch (op) {
|
|
29850
30484
|
case "add_member":
|
|
29851
30485
|
params.scope = args.container;
|
|
@@ -29939,6 +30573,7 @@ function buildHintsFromConfig(config2, disabledTools) {
|
|
|
29939
30573
|
}
|
|
29940
30574
|
|
|
29941
30575
|
// src/configure-warnings.ts
|
|
30576
|
+
var pendingEagerWarnings = new Map;
|
|
29942
30577
|
function isConfigureWarning(value) {
|
|
29943
30578
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29944
30579
|
return false;
|
|
@@ -29948,13 +30583,25 @@ function isConfigureWarning(value) {
|
|
|
29948
30583
|
function coerceConfigureWarnings(warnings) {
|
|
29949
30584
|
return warnings.filter(isConfigureWarning);
|
|
29950
30585
|
}
|
|
30586
|
+
function drainPendingEagerWarnings(projectRoot) {
|
|
30587
|
+
const pending = pendingEagerWarnings.get(projectRoot) ?? [];
|
|
30588
|
+
pendingEagerWarnings.delete(projectRoot);
|
|
30589
|
+
return pending;
|
|
30590
|
+
}
|
|
29951
30591
|
async function handleConfigureWarningsForSession(context) {
|
|
30592
|
+
const validWarnings = coerceConfigureWarnings(context.warnings);
|
|
29952
30593
|
if (!context.sessionId) {
|
|
29953
|
-
|
|
30594
|
+
if (validWarnings.length === 0)
|
|
30595
|
+
return;
|
|
30596
|
+
const pending = pendingEagerWarnings.get(context.projectRoot) ?? [];
|
|
30597
|
+
pending.push(...validWarnings);
|
|
30598
|
+
pendingEagerWarnings.set(context.projectRoot, pending);
|
|
30599
|
+
warn2(`[configure] deferred warnings for ${context.projectRoot} arrived without session_id; buffering until first session-bound call`);
|
|
29954
30600
|
return;
|
|
29955
30601
|
}
|
|
29956
|
-
const
|
|
29957
|
-
|
|
30602
|
+
const pendingWarnings = drainPendingEagerWarnings(context.projectRoot);
|
|
30603
|
+
const combinedWarnings = [...pendingWarnings, ...validWarnings];
|
|
30604
|
+
if (combinedWarnings.length === 0)
|
|
29958
30605
|
return;
|
|
29959
30606
|
await deliverConfigureWarnings({
|
|
29960
30607
|
client: context.client ?? context.fallbackClient,
|
|
@@ -29962,7 +30609,7 @@ async function handleConfigureWarningsForSession(context) {
|
|
|
29962
30609
|
storageDir: context.storageDir,
|
|
29963
30610
|
pluginVersion: context.pluginVersion,
|
|
29964
30611
|
projectRoot: context.projectRoot
|
|
29965
|
-
},
|
|
30612
|
+
}, combinedWarnings);
|
|
29966
30613
|
}
|
|
29967
30614
|
|
|
29968
30615
|
// src/index.ts
|
|
@@ -29977,16 +30624,10 @@ function throwSentinel(command) {
|
|
|
29977
30624
|
}
|
|
29978
30625
|
async function sendIgnoredMessage2(client, sessionID, text) {
|
|
29979
30626
|
const typedClient = client;
|
|
29980
|
-
const lastModel = await getLastAssistantModel(client, sessionID);
|
|
29981
30627
|
const body = {
|
|
29982
30628
|
noReply: true,
|
|
29983
30629
|
parts: [{ type: "text", text, ignored: true }]
|
|
29984
30630
|
};
|
|
29985
|
-
if (lastModel) {
|
|
29986
|
-
body.model = { providerID: lastModel.providerID, modelID: lastModel.modelID };
|
|
29987
|
-
if (lastModel.variant)
|
|
29988
|
-
body.variant = lastModel.variant;
|
|
29989
|
-
}
|
|
29990
30631
|
const promptInput = { path: { id: sessionID }, body };
|
|
29991
30632
|
if (typeof typedClient.session?.prompt === "function") {
|
|
29992
30633
|
await Promise.resolve(typedClient.session.prompt(promptInput));
|
|
@@ -30016,7 +30657,8 @@ var ANNOUNCEMENT_FEATURES = [
|
|
|
30016
30657
|
"Trigram grep/glob and semantic search (aft_search) graduated out of experimental.",
|
|
30017
30658
|
"Lots of bugfixes and new end-to-end test coverage."
|
|
30018
30659
|
];
|
|
30019
|
-
var plugin = async (input) =>
|
|
30660
|
+
var plugin = async (input) => initializePluginForDirectory(input);
|
|
30661
|
+
async function initializePluginForDirectory(input) {
|
|
30020
30662
|
const binaryPath = await findBinary();
|
|
30021
30663
|
const aftConfig = loadAftConfig(input.directory);
|
|
30022
30664
|
const autoUpdateAbort = new AbortController;
|
|
@@ -30044,8 +30686,8 @@ var plugin = async (input) => {
|
|
|
30044
30686
|
if (aftConfig.max_callgraph_files !== undefined)
|
|
30045
30687
|
configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
|
|
30046
30688
|
const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
|
|
30047
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
30048
|
-
configOverrides.storage_dir =
|
|
30689
|
+
const dataHome = process.env.XDG_DATA_HOME || join21(homedir11(), ".local", "share");
|
|
30690
|
+
configOverrides.storage_dir = join21(dataHome, "opencode", "storage", "plugin", "aft");
|
|
30049
30691
|
let onnxRuntimePromise = null;
|
|
30050
30692
|
if (aftConfig.semantic_search && isFastembedSemanticBackend) {
|
|
30051
30693
|
const storageDir2 = configOverrides.storage_dir;
|
|
@@ -30131,10 +30773,10 @@ ${lines}
|
|
|
30131
30773
|
}
|
|
30132
30774
|
versionUpgradeAttempted = binaryVersion;
|
|
30133
30775
|
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(
|
|
30776
|
+
ensureBinary(`v${minVersion}`).then((path7) => {
|
|
30777
|
+
if (path7) {
|
|
30778
|
+
log2(`Found/downloaded compatible binary at ${path7}. Replacing running bridges...`);
|
|
30779
|
+
pool.replaceBinary(path7).then(() => {
|
|
30138
30780
|
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
30139
30781
|
}, (err) => error2("Failed to replace binary:", err));
|
|
30140
30782
|
} else {
|
|
@@ -30145,11 +30787,12 @@ ${lines}
|
|
|
30145
30787
|
});
|
|
30146
30788
|
},
|
|
30147
30789
|
onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
|
|
30790
|
+
const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
|
|
30148
30791
|
await handleConfigureWarningsForSession({
|
|
30149
30792
|
projectRoot,
|
|
30150
30793
|
sessionId,
|
|
30151
30794
|
client,
|
|
30152
|
-
warnings,
|
|
30795
|
+
warnings: [...pendingWarnings, ...warnings],
|
|
30153
30796
|
fallbackClient: input.client,
|
|
30154
30797
|
storageDir: configOverrides.storage_dir,
|
|
30155
30798
|
pluginVersion: PLUGIN_VERSION
|
|
@@ -30184,7 +30827,6 @@ ${lines}
|
|
|
30184
30827
|
config: aftConfig,
|
|
30185
30828
|
storageDir: configOverrides.storage_dir
|
|
30186
30829
|
};
|
|
30187
|
-
setSharedBridgePool(pool);
|
|
30188
30830
|
if (onnxRuntimePromise) {
|
|
30189
30831
|
onnxRuntimePromise.then((ortDylibDir) => {
|
|
30190
30832
|
if (ortDylibDir) {
|
|
@@ -30206,7 +30848,7 @@ ${lines}
|
|
|
30206
30848
|
if (onnxRuntimePromise) {
|
|
30207
30849
|
await Promise.race([
|
|
30208
30850
|
onnxRuntimePromise,
|
|
30209
|
-
new Promise((
|
|
30851
|
+
new Promise((resolve11) => setTimeout(() => resolve11(null), 60000))
|
|
30210
30852
|
]);
|
|
30211
30853
|
}
|
|
30212
30854
|
const bridge = pool.getBridge(input.directory);
|
|
@@ -30252,10 +30894,10 @@ ${lines}
|
|
|
30252
30894
|
return { show: false };
|
|
30253
30895
|
}
|
|
30254
30896
|
if (storageDir) {
|
|
30255
|
-
const versionFile =
|
|
30897
|
+
const versionFile = join21(storageDir, "last_announced_version");
|
|
30256
30898
|
try {
|
|
30257
30899
|
if (existsSync14(versionFile)) {
|
|
30258
|
-
const lastVersion =
|
|
30900
|
+
const lastVersion = readFileSync12(versionFile, "utf-8").trim();
|
|
30259
30901
|
if (lastVersion === ANNOUNCEMENT_VERSION)
|
|
30260
30902
|
return { show: false };
|
|
30261
30903
|
}
|
|
@@ -30266,8 +30908,8 @@ ${lines}
|
|
|
30266
30908
|
rpcServer.handle("mark-announced", async () => {
|
|
30267
30909
|
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
30268
30910
|
try {
|
|
30269
|
-
|
|
30270
|
-
writeFileSync11(
|
|
30911
|
+
mkdirSync12(storageDir, { recursive: true });
|
|
30912
|
+
writeFileSync11(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
|
|
30271
30913
|
} catch {}
|
|
30272
30914
|
}
|
|
30273
30915
|
return { success: true };
|
|
@@ -30399,7 +31041,6 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
30399
31041
|
},
|
|
30400
31042
|
"chat.message": async (messageInput) => {
|
|
30401
31043
|
const sid = messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id;
|
|
30402
|
-
resetBgWake(sid);
|
|
30403
31044
|
warmSessionDirectory(input.client, sid, input.directory);
|
|
30404
31045
|
},
|
|
30405
31046
|
"command.execute.before": async (commandInput, _output) => {
|
|
@@ -30459,11 +31100,10 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
30459
31100
|
unregisterShutdown();
|
|
30460
31101
|
await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
|
|
30461
31102
|
rpcServer.stop();
|
|
30462
|
-
clearSharedBridgePool();
|
|
30463
31103
|
await pool.shutdown();
|
|
30464
31104
|
}
|
|
30465
31105
|
};
|
|
30466
|
-
}
|
|
31106
|
+
}
|
|
30467
31107
|
var src_default = plugin;
|
|
30468
31108
|
export {
|
|
30469
31109
|
src_default as default
|