@cortexkit/aft-opencode 0.20.1 → 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 +933 -284
- 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 +43 -18
- package/package.json +7 -7
- package/src/shared/rpc-server.ts +5 -2
- package/src/shared/subagent-detect.ts +150 -0
- package/src/tui/index.tsx +72 -11
- 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));
|
|
8178
8226
|
});
|
|
8227
|
+
child.stdout?.on("end", () => {
|
|
8228
|
+
const remaining = stdoutDecoder.end();
|
|
8229
|
+
if (remaining)
|
|
8230
|
+
this.onStdoutData(remaining);
|
|
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;
|
|
@@ -9932,6 +10028,13 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
9932
10028
|
output.output = appendReminder(output.output ?? "", reminder);
|
|
9933
10029
|
state.pendingCompletions = [];
|
|
9934
10030
|
state.pendingLongRunning = [];
|
|
10031
|
+
if (state.debounceTimer) {
|
|
10032
|
+
clearTimeout(state.debounceTimer);
|
|
10033
|
+
state.debounceTimer = null;
|
|
10034
|
+
state.firstCompletionAt = null;
|
|
10035
|
+
state.scheduledFireAt = null;
|
|
10036
|
+
state.scheduledCompletionCount = 0;
|
|
10037
|
+
}
|
|
9935
10038
|
}
|
|
9936
10039
|
async function handleIdleBgCompletions(drainContext) {
|
|
9937
10040
|
await triggerWakeIfPending(drainContext, false);
|
|
@@ -9973,9 +10076,6 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
|
9973
10076
|
sessionWarn2(drainContext.sessionID, `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
9974
10077
|
});
|
|
9975
10078
|
}
|
|
9976
|
-
function resetBgWake(sessionID) {
|
|
9977
|
-
stateFor(sessionID).wakeFiredThisIdle = false;
|
|
9978
|
-
}
|
|
9979
10079
|
function formatSystemReminder(completions) {
|
|
9980
10080
|
const bullets = completions.map((completion) => formatCompletion(completion)).join(`
|
|
9981
10081
|
`);
|
|
@@ -10056,16 +10156,17 @@ function scheduleWake(state, sendWake, onSendFailure) {
|
|
|
10056
10156
|
state.debounceTimer = setTimeout(() => {
|
|
10057
10157
|
const pending = state.pendingCompletions;
|
|
10058
10158
|
const pendingLongRunning = state.pendingLongRunning;
|
|
10059
|
-
const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
|
|
10060
|
-
state.pendingCompletions = [];
|
|
10061
|
-
state.pendingLongRunning = [];
|
|
10062
10159
|
state.debounceTimer = null;
|
|
10063
10160
|
state.firstCompletionAt = null;
|
|
10064
10161
|
state.scheduledFireAt = null;
|
|
10065
10162
|
state.scheduledCompletionCount = 0;
|
|
10163
|
+
if (pending.length === 0 && pendingLongRunning.length === 0)
|
|
10164
|
+
return;
|
|
10165
|
+
const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
|
|
10166
|
+
state.pendingCompletions = [];
|
|
10167
|
+
state.pendingLongRunning = [];
|
|
10066
10168
|
sendWake(reminder).then(() => {
|
|
10067
10169
|
state.retryDelayMs = null;
|
|
10068
|
-
state.wakeFiredThisIdle = true;
|
|
10069
10170
|
}).catch((err) => {
|
|
10070
10171
|
state.pendingCompletions = [...pending, ...state.pendingCompletions];
|
|
10071
10172
|
state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
|
|
@@ -10087,7 +10188,6 @@ function stateFor(sessionID) {
|
|
|
10087
10188
|
pendingCompletions: [],
|
|
10088
10189
|
pendingLongRunning: [],
|
|
10089
10190
|
debounceTimer: null,
|
|
10090
|
-
wakeFiredThisIdle: false,
|
|
10091
10191
|
firstCompletionAt: null,
|
|
10092
10192
|
scheduledFireAt: null,
|
|
10093
10193
|
scheduledCompletionCount: 0,
|
|
@@ -21134,7 +21234,7 @@ function finalize(ctx, schema) {
|
|
|
21134
21234
|
result.$schema = "http://json-schema.org/draft-07/schema#";
|
|
21135
21235
|
} else if (ctx.target === "draft-04") {
|
|
21136
21236
|
result.$schema = "http://json-schema.org/draft-04/schema#";
|
|
21137
|
-
} else if (ctx.target === "openapi-3.0") {}
|
|
21237
|
+
} else if (ctx.target === "openapi-3.0") {}
|
|
21138
21238
|
if (ctx.external?.uri) {
|
|
21139
21239
|
const id = ctx.external.registry.get(schema)?.id;
|
|
21140
21240
|
if (!id)
|
|
@@ -21382,7 +21482,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
|
|
|
21382
21482
|
if (val === undefined) {
|
|
21383
21483
|
if (ctx.unrepresentable === "throw") {
|
|
21384
21484
|
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
21385
|
-
}
|
|
21485
|
+
}
|
|
21386
21486
|
} else if (typeof val === "bigint") {
|
|
21387
21487
|
if (ctx.unrepresentable === "throw") {
|
|
21388
21488
|
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
@@ -24211,7 +24311,8 @@ import { dirname as dirname4, join as join11 } from "path";
|
|
|
24211
24311
|
// src/hooks/auto-update-checker/cache.ts
|
|
24212
24312
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
24213
24313
|
import { spawn as spawn2 } from "child_process";
|
|
24214
|
-
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";
|
|
24215
24316
|
import { basename, dirname as dirname3, join as join10 } from "path";
|
|
24216
24317
|
|
|
24217
24318
|
// src/hooks/auto-update-checker/checker.ts
|
|
@@ -24462,6 +24563,42 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
24462
24563
|
}
|
|
24463
24564
|
|
|
24464
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
|
+
}
|
|
24465
24602
|
function stripPackageNameFromPath(pathValue, packageName) {
|
|
24466
24603
|
let current = pathValue;
|
|
24467
24604
|
for (const segment of [...packageName.split("/")].reverse()) {
|
|
@@ -24553,8 +24690,13 @@ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePacka
|
|
|
24553
24690
|
warn2("[auto-update-checker] No install context found for auto-update");
|
|
24554
24691
|
return null;
|
|
24555
24692
|
}
|
|
24556
|
-
|
|
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);
|
|
24557
24698
|
return null;
|
|
24699
|
+
}
|
|
24558
24700
|
const packageRemoved = removeInstalledPackage(installContext.installDir, packageName);
|
|
24559
24701
|
const lockRemoved = removeFromPackageLock(installContext.installDir, packageName);
|
|
24560
24702
|
if (!packageRemoved && !lockRemoved) {
|
|
@@ -24573,7 +24715,7 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
24573
24715
|
return false;
|
|
24574
24716
|
const proc = spawn2("npm", ["install", "--no-audit", "--no-fund", "--no-progress"], {
|
|
24575
24717
|
cwd: installDir,
|
|
24576
|
-
stdio: "
|
|
24718
|
+
stdio: "ignore"
|
|
24577
24719
|
});
|
|
24578
24720
|
const abortProcess = () => {
|
|
24579
24721
|
try {
|
|
@@ -24592,10 +24734,27 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
24592
24734
|
options.signal?.removeEventListener("abort", abortProcess);
|
|
24593
24735
|
if (result === "timeout" || options.signal?.aborted) {
|
|
24594
24736
|
abortProcess();
|
|
24737
|
+
const snapshot2 = pendingSnapshots.get(installDir);
|
|
24738
|
+
if (snapshot2) {
|
|
24739
|
+
pendingSnapshots.delete(installDir);
|
|
24740
|
+
restoreAutoUpdateSnapshot(snapshot2);
|
|
24741
|
+
}
|
|
24595
24742
|
return false;
|
|
24596
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
|
+
}
|
|
24597
24751
|
return result;
|
|
24598
24752
|
} catch (err) {
|
|
24753
|
+
const snapshot = pendingSnapshots.get(installDir);
|
|
24754
|
+
if (snapshot) {
|
|
24755
|
+
pendingSnapshots.delete(installDir);
|
|
24756
|
+
restoreAutoUpdateSnapshot(snapshot);
|
|
24757
|
+
}
|
|
24599
24758
|
warn2(`[auto-update-checker] npm install error: ${String(err)}`);
|
|
24600
24759
|
return false;
|
|
24601
24760
|
} finally {
|
|
@@ -24759,7 +24918,8 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
|
24759
24918
|
// src/lsp-auto-install.ts
|
|
24760
24919
|
import { spawn as spawn3 } from "child_process";
|
|
24761
24920
|
import { createHash as createHash3 } from "crypto";
|
|
24762
|
-
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";
|
|
24763
24923
|
|
|
24764
24924
|
// src/lsp-cache.ts
|
|
24765
24925
|
import {
|
|
@@ -25443,6 +25603,50 @@ function hashInstalledBinary(spec) {
|
|
|
25443
25603
|
stream.on("end", () => resolve3(hash2.digest("hex")));
|
|
25444
25604
|
});
|
|
25445
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
|
+
}
|
|
25446
25650
|
function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
25447
25651
|
const cachedBinDirs = [];
|
|
25448
25652
|
const skipped = [];
|
|
@@ -25455,7 +25659,7 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
25455
25659
|
return projectExtensions;
|
|
25456
25660
|
};
|
|
25457
25661
|
for (const spec of NPM_LSP_TABLE) {
|
|
25458
|
-
if (isInstalled(spec.npm, spec.binary)) {
|
|
25662
|
+
if (isInstalled(spec.npm, spec.binary) && validateCachedNpmInstall(spec)) {
|
|
25459
25663
|
cachedBinDirs.push(lspBinDir(spec.npm));
|
|
25460
25664
|
}
|
|
25461
25665
|
if (config2.disabled.has(spec.id)) {
|
|
@@ -25507,16 +25711,17 @@ import {
|
|
|
25507
25711
|
createWriteStream as createWriteStream2,
|
|
25508
25712
|
existsSync as existsSync10,
|
|
25509
25713
|
lstatSync as lstatSync2,
|
|
25510
|
-
mkdirSync as
|
|
25714
|
+
mkdirSync as mkdirSync8,
|
|
25511
25715
|
readdirSync as readdirSync4,
|
|
25716
|
+
readFileSync as readFileSync9,
|
|
25512
25717
|
readlinkSync as readlinkSync2,
|
|
25513
25718
|
realpathSync as realpathSync3,
|
|
25514
|
-
renameSync as
|
|
25515
|
-
rmSync as
|
|
25719
|
+
renameSync as renameSync5,
|
|
25720
|
+
rmSync as rmSync4,
|
|
25516
25721
|
statSync as statSync5,
|
|
25517
25722
|
unlinkSync as unlinkSync6
|
|
25518
25723
|
} from "fs";
|
|
25519
|
-
import { dirname as dirname5, join as
|
|
25724
|
+
import { dirname as dirname5, join as join15, relative as relative2, resolve as resolve3 } from "path";
|
|
25520
25725
|
import { Readable as Readable2 } from "stream";
|
|
25521
25726
|
import { pipeline as pipeline2 } from "stream/promises";
|
|
25522
25727
|
|
|
@@ -25606,25 +25811,25 @@ function detectHostPlatform() {
|
|
|
25606
25811
|
|
|
25607
25812
|
// src/lsp-github-install.ts
|
|
25608
25813
|
function ghCacheRoot() {
|
|
25609
|
-
return
|
|
25814
|
+
return join15(aftCacheBase(), "lsp-binaries");
|
|
25610
25815
|
}
|
|
25611
25816
|
function ghPackageDir(spec) {
|
|
25612
|
-
return
|
|
25817
|
+
return join15(ghCacheRoot(), spec.id);
|
|
25613
25818
|
}
|
|
25614
25819
|
function ghBinDir(spec) {
|
|
25615
|
-
return
|
|
25820
|
+
return join15(ghPackageDir(spec), "bin");
|
|
25616
25821
|
}
|
|
25617
25822
|
function ghExtractDir(spec) {
|
|
25618
|
-
return
|
|
25823
|
+
return join15(ghPackageDir(spec), "extracted");
|
|
25619
25824
|
}
|
|
25620
25825
|
function ghBinaryPath(spec, platform2) {
|
|
25621
25826
|
const ext = platform2 === "win32" ? ".exe" : "";
|
|
25622
|
-
return
|
|
25827
|
+
return join15(ghBinDir(spec), `${spec.binary}${ext}`);
|
|
25623
25828
|
}
|
|
25624
25829
|
function isGithubInstalled(spec, platform2) {
|
|
25625
25830
|
for (const candidate of ghBinaryCandidates(spec, platform2)) {
|
|
25626
25831
|
try {
|
|
25627
|
-
if (statSync5(
|
|
25832
|
+
if (statSync5(join15(ghBinDir(spec), candidate)).isFile())
|
|
25628
25833
|
return true;
|
|
25629
25834
|
} catch {}
|
|
25630
25835
|
}
|
|
@@ -25646,6 +25851,9 @@ function sha256OfFile(path2) {
|
|
|
25646
25851
|
stream.on("end", () => resolve4(hash2.digest("hex")));
|
|
25647
25852
|
});
|
|
25648
25853
|
}
|
|
25854
|
+
function sha256OfFileSync2(path2) {
|
|
25855
|
+
return createHash4("sha256").update(readFileSync9(path2)).digest("hex");
|
|
25856
|
+
}
|
|
25649
25857
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
25650
25858
|
const candidates = [];
|
|
25651
25859
|
candidates.push(tag);
|
|
@@ -25666,11 +25874,7 @@ async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
|
25666
25874
|
const url2 = `https://api.github.com/repos/${githubRepo}/releases/tags/${encodeURIComponent(candidate)}`;
|
|
25667
25875
|
const timeout = controlledTimeoutSignal(15000, signal);
|
|
25668
25876
|
try {
|
|
25669
|
-
const res = await
|
|
25670
|
-
headers,
|
|
25671
|
-
redirect: "follow",
|
|
25672
|
-
signal: timeout.signal
|
|
25673
|
-
});
|
|
25877
|
+
const res = await fetchJsonFollowingRedirects(url2, headers, fetchImpl, timeout.signal);
|
|
25674
25878
|
if (res.status === 404)
|
|
25675
25879
|
continue;
|
|
25676
25880
|
if (!res.ok) {
|
|
@@ -25700,6 +25904,23 @@ async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
|
25700
25904
|
}
|
|
25701
25905
|
return null;
|
|
25702
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
|
+
}
|
|
25703
25924
|
async function resolveTargetTag(spec, config2, fetchImpl, signal) {
|
|
25704
25925
|
const pinned = config2.versions[spec.githubRepo];
|
|
25705
25926
|
if (pinned) {
|
|
@@ -25794,17 +26015,12 @@ function assertAllowedDownloadUrl(rawUrl) {
|
|
|
25794
26015
|
return parsed;
|
|
25795
26016
|
}
|
|
25796
26017
|
async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
25797
|
-
assertAllowedDownloadUrl(url2);
|
|
25798
26018
|
if (assetSize !== undefined && assetSize > MAX_DOWNLOAD_BYTES2) {
|
|
25799
26019
|
throw new Error(`asset size ${assetSize} exceeds max ${MAX_DOWNLOAD_BYTES2} (set lsp.versions to pin a smaller release if this is wrong)`);
|
|
25800
26020
|
}
|
|
25801
26021
|
const timeout = controlledTimeoutSignal(120000, signal);
|
|
25802
26022
|
try {
|
|
25803
|
-
const res = await
|
|
25804
|
-
headers: { accept: "application/octet-stream" },
|
|
25805
|
-
redirect: "follow",
|
|
25806
|
-
signal: timeout.signal
|
|
25807
|
-
});
|
|
26023
|
+
const res = await fetchFollowingRedirects(url2, fetchImpl, timeout.signal);
|
|
25808
26024
|
if (!res.ok || !res.body) {
|
|
25809
26025
|
throw new Error(`download failed (${res.status})`);
|
|
25810
26026
|
}
|
|
@@ -25812,7 +26028,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
25812
26028
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
|
|
25813
26029
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
|
|
25814
26030
|
}
|
|
25815
|
-
|
|
26031
|
+
mkdirSync8(dirname5(destPath), { recursive: true });
|
|
25816
26032
|
let bytesWritten = 0;
|
|
25817
26033
|
const guard = new TransformStream({
|
|
25818
26034
|
transform(chunk, controller) {
|
|
@@ -25836,6 +26052,27 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
25836
26052
|
timeout.cleanup();
|
|
25837
26053
|
}
|
|
25838
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
|
+
}
|
|
25839
26076
|
function validateExtraction(stagingRoot) {
|
|
25840
26077
|
const realStagingRoot = realpathSync3(stagingRoot);
|
|
25841
26078
|
let totalBytes = 0;
|
|
@@ -25847,7 +26084,7 @@ function validateExtraction(stagingRoot) {
|
|
|
25847
26084
|
throw new Error(`failed to read staging dir ${dir}: ${err}`);
|
|
25848
26085
|
}
|
|
25849
26086
|
for (const entry of entries) {
|
|
25850
|
-
const full =
|
|
26087
|
+
const full = join15(dir, entry);
|
|
25851
26088
|
let lst;
|
|
25852
26089
|
try {
|
|
25853
26090
|
lst = lstatSync2(full);
|
|
@@ -25885,27 +26122,83 @@ function validateExtraction(stagingRoot) {
|
|
|
25885
26122
|
};
|
|
25886
26123
|
walk(realStagingRoot);
|
|
25887
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
|
+
}
|
|
25888
26148
|
function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
25889
26149
|
const suffix = randomBytes(8).toString("hex");
|
|
25890
26150
|
const stagingDir = `${destDir}.staging-${suffix}`;
|
|
25891
26151
|
try {
|
|
25892
|
-
|
|
26152
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
25893
26153
|
} catch {}
|
|
25894
|
-
|
|
26154
|
+
mkdirSync8(stagingDir, { recursive: true });
|
|
25895
26155
|
try {
|
|
26156
|
+
precheckArchiveSize(archivePath, archiveType);
|
|
25896
26157
|
runPlatformExtractor(archivePath, stagingDir, archiveType);
|
|
25897
26158
|
validateExtraction(stagingDir);
|
|
25898
26159
|
try {
|
|
25899
|
-
|
|
26160
|
+
rmSync4(destDir, { recursive: true, force: true });
|
|
25900
26161
|
} catch {}
|
|
25901
|
-
|
|
26162
|
+
renameSync5(stagingDir, destDir);
|
|
25902
26163
|
} catch (err) {
|
|
25903
26164
|
try {
|
|
25904
|
-
|
|
26165
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
25905
26166
|
} catch {}
|
|
25906
26167
|
throw err;
|
|
25907
26168
|
}
|
|
25908
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
|
+
}
|
|
25909
26202
|
function runPlatformExtractor(archivePath, destDir, archiveType) {
|
|
25910
26203
|
if (archiveType === "zip") {
|
|
25911
26204
|
if (process.platform === "win32") {
|
|
@@ -25951,7 +26244,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
25951
26244
|
}
|
|
25952
26245
|
const pkgDir = ghPackageDir(spec);
|
|
25953
26246
|
const extractDir = ghExtractDir(spec);
|
|
25954
|
-
const archivePath =
|
|
26247
|
+
const archivePath = join15(pkgDir, expected.name);
|
|
25955
26248
|
log2(`[lsp] downloading ${spec.id} ${tag} \u2192 ${matchingAsset.url}`);
|
|
25956
26249
|
try {
|
|
25957
26250
|
await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
|
|
@@ -25990,13 +26283,13 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
25990
26283
|
unlinkSync6(archivePath);
|
|
25991
26284
|
} catch {}
|
|
25992
26285
|
}
|
|
25993
|
-
const innerBinaryPath =
|
|
26286
|
+
const innerBinaryPath = join15(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
|
|
25994
26287
|
if (!existsSync10(innerBinaryPath)) {
|
|
25995
26288
|
error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
25996
26289
|
return null;
|
|
25997
26290
|
}
|
|
25998
26291
|
const targetBinary = ghBinaryPath(spec, platform2);
|
|
25999
|
-
|
|
26292
|
+
mkdirSync8(dirname5(targetBinary), { recursive: true });
|
|
26000
26293
|
try {
|
|
26001
26294
|
copyFileSync3(innerBinaryPath, targetBinary);
|
|
26002
26295
|
if (platform2 !== "win32") {
|
|
@@ -26087,7 +26380,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
|
|
|
26087
26380
|
};
|
|
26088
26381
|
}
|
|
26089
26382
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
26090
|
-
if (isGithubInstalled(spec, host.platform)) {
|
|
26383
|
+
if (isGithubInstalled(spec, host.platform) && validateCachedGithubInstall(spec, host.platform)) {
|
|
26091
26384
|
cachedBinDirs.push(ghBinDir(spec));
|
|
26092
26385
|
}
|
|
26093
26386
|
if (config2.disabled.has(spec.id)) {
|
|
@@ -26236,9 +26529,9 @@ function normalizeToolMap(tools) {
|
|
|
26236
26529
|
}
|
|
26237
26530
|
|
|
26238
26531
|
// src/notifications.ts
|
|
26239
|
-
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";
|
|
26240
26533
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
26241
|
-
import { join as
|
|
26534
|
+
import { join as join16 } from "path";
|
|
26242
26535
|
function isTuiMode() {
|
|
26243
26536
|
return process.env.OPENCODE_CLIENT === "cli";
|
|
26244
26537
|
}
|
|
@@ -26262,15 +26555,15 @@ function getDesktopStatePath() {
|
|
|
26262
26555
|
const os2 = platform2();
|
|
26263
26556
|
const home = homedir9();
|
|
26264
26557
|
if (os2 === "darwin") {
|
|
26265
|
-
return
|
|
26558
|
+
return join16(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
|
|
26266
26559
|
}
|
|
26267
26560
|
if (os2 === "linux") {
|
|
26268
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ||
|
|
26269
|
-
return
|
|
26561
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join16(home, ".config");
|
|
26562
|
+
return join16(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
|
|
26270
26563
|
}
|
|
26271
26564
|
if (os2 === "win32") {
|
|
26272
|
-
const appData = process.env.APPDATA ||
|
|
26273
|
-
return
|
|
26565
|
+
const appData = process.env.APPDATA || join16(home, "AppData", "Roaming");
|
|
26566
|
+
return join16(appData, "ai.opencode.desktop", "opencode.global.dat");
|
|
26274
26567
|
}
|
|
26275
26568
|
return null;
|
|
26276
26569
|
}
|
|
@@ -26280,7 +26573,7 @@ function readDesktopState(directory) {
|
|
|
26280
26573
|
return { sessionId: null, serverUrl: null };
|
|
26281
26574
|
}
|
|
26282
26575
|
try {
|
|
26283
|
-
const raw =
|
|
26576
|
+
const raw = readFileSync10(statePath, "utf-8");
|
|
26284
26577
|
const state = JSON.parse(raw);
|
|
26285
26578
|
let serverUrl = null;
|
|
26286
26579
|
const serverStr = state.server;
|
|
@@ -26374,10 +26667,10 @@ async function sendWarning(opts, message) {
|
|
|
26374
26667
|
}
|
|
26375
26668
|
async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
26376
26669
|
if (storageDir) {
|
|
26377
|
-
const versionFile =
|
|
26670
|
+
const versionFile = join16(storageDir, "last_announced_version");
|
|
26378
26671
|
try {
|
|
26379
26672
|
if (existsSync11(versionFile)) {
|
|
26380
|
-
const lastVersion =
|
|
26673
|
+
const lastVersion = readFileSync10(versionFile, "utf-8").trim();
|
|
26381
26674
|
if (lastVersion === version2)
|
|
26382
26675
|
return;
|
|
26383
26676
|
}
|
|
@@ -26397,17 +26690,17 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
|
26397
26690
|
}
|
|
26398
26691
|
if (storageDir) {
|
|
26399
26692
|
try {
|
|
26400
|
-
|
|
26401
|
-
writeFileSync8(
|
|
26693
|
+
mkdirSync9(storageDir, { recursive: true });
|
|
26694
|
+
writeFileSync8(join16(storageDir, "last_announced_version"), version2);
|
|
26402
26695
|
} catch {}
|
|
26403
26696
|
}
|
|
26404
26697
|
}
|
|
26405
26698
|
function readWarnedTools(storageDir) {
|
|
26406
26699
|
try {
|
|
26407
|
-
const warnedToolsPath =
|
|
26700
|
+
const warnedToolsPath = join16(storageDir, WARNED_TOOLS_FILE);
|
|
26408
26701
|
if (!existsSync11(warnedToolsPath))
|
|
26409
26702
|
return {};
|
|
26410
|
-
const parsed = JSON.parse(
|
|
26703
|
+
const parsed = JSON.parse(readFileSync10(warnedToolsPath, "utf-8"));
|
|
26411
26704
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
26412
26705
|
return {};
|
|
26413
26706
|
const warned = {};
|
|
@@ -26423,12 +26716,34 @@ function readWarnedTools(storageDir) {
|
|
|
26423
26716
|
}
|
|
26424
26717
|
function writeWarnedTools(storageDir, warned) {
|
|
26425
26718
|
try {
|
|
26426
|
-
|
|
26427
|
-
const warnedToolsPath =
|
|
26428
|
-
|
|
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)}
|
|
26429
26723
|
`);
|
|
26724
|
+
renameSync6(tmpPath, warnedToolsPath);
|
|
26430
26725
|
} catch {}
|
|
26431
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
|
+
}
|
|
26432
26747
|
function warningKey(warning, projectRoot) {
|
|
26433
26748
|
const scope = warning.kind === "lsp_binary_missing" ? "_" : projectRoot ?? "_";
|
|
26434
26749
|
return [
|
|
@@ -26467,20 +26782,29 @@ ${warning.hint}`;
|
|
|
26467
26782
|
async function deliverConfigureWarnings(opts, warnings) {
|
|
26468
26783
|
if (warnings.length === 0)
|
|
26469
26784
|
return;
|
|
26470
|
-
const
|
|
26471
|
-
|
|
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;
|
|
26472
26806
|
for (const warning of warnings) {
|
|
26473
|
-
|
|
26474
|
-
if (Object.hasOwn(warned, key))
|
|
26475
|
-
continue;
|
|
26476
|
-
const delivered = await sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning));
|
|
26477
|
-
if (!delivered)
|
|
26478
|
-
continue;
|
|
26479
|
-
warned[key] = opts.pluginVersion;
|
|
26480
|
-
changed = true;
|
|
26481
|
-
}
|
|
26482
|
-
if (changed) {
|
|
26483
|
-
writeWarnedTools(opts.storageDir, warned);
|
|
26807
|
+
await sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning));
|
|
26484
26808
|
}
|
|
26485
26809
|
}
|
|
26486
26810
|
async function cleanupWarnings(opts) {
|
|
@@ -26518,20 +26842,20 @@ async function cleanupWarnings(opts) {
|
|
|
26518
26842
|
|
|
26519
26843
|
// src/shared/rpc-server.ts
|
|
26520
26844
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
26521
|
-
import { mkdirSync as
|
|
26845
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
26522
26846
|
import { createServer } from "http";
|
|
26523
26847
|
import { dirname as dirname6 } from "path";
|
|
26524
26848
|
|
|
26525
26849
|
// src/shared/rpc-utils.ts
|
|
26526
26850
|
import { createHash as createHash5 } from "crypto";
|
|
26527
|
-
import { join as
|
|
26851
|
+
import { join as join17 } from "path";
|
|
26528
26852
|
function projectHash(directory) {
|
|
26529
26853
|
const normalized = directory.replace(/\/+$/, "");
|
|
26530
26854
|
return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
26531
26855
|
}
|
|
26532
26856
|
function rpcPortFilePath(storageDir, directory) {
|
|
26533
26857
|
const hash2 = projectHash(directory);
|
|
26534
|
-
return
|
|
26858
|
+
return join17(storageDir, "rpc", hash2, "port");
|
|
26535
26859
|
}
|
|
26536
26860
|
|
|
26537
26861
|
// src/shared/rpc-server.ts
|
|
@@ -26565,10 +26889,13 @@ class AftRpcServer {
|
|
|
26565
26889
|
this.server = server;
|
|
26566
26890
|
try {
|
|
26567
26891
|
const dir = dirname6(this.portFilePath);
|
|
26568
|
-
|
|
26892
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
26569
26893
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
26570
|
-
writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }),
|
|
26571
|
-
|
|
26894
|
+
writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
|
|
26895
|
+
encoding: "utf-8",
|
|
26896
|
+
mode: 384
|
|
26897
|
+
});
|
|
26898
|
+
renameSync7(tmpPath, this.portFilePath);
|
|
26572
26899
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
26573
26900
|
} catch (err) {
|
|
26574
26901
|
warn2(`Failed to write RPC port file: ${err}`);
|
|
@@ -26645,18 +26972,6 @@ class AftRpcServer {
|
|
|
26645
26972
|
}
|
|
26646
26973
|
}
|
|
26647
26974
|
|
|
26648
|
-
// src/shared/runtime.ts
|
|
26649
|
-
var GLOBAL_KEY = "__AFT_SHARED_BRIDGE_POOL__";
|
|
26650
|
-
function getGlobalState() {
|
|
26651
|
-
return globalThis;
|
|
26652
|
-
}
|
|
26653
|
-
function setSharedBridgePool(pool) {
|
|
26654
|
-
getGlobalState()[GLOBAL_KEY] = pool;
|
|
26655
|
-
}
|
|
26656
|
-
function clearSharedBridgePool() {
|
|
26657
|
-
getGlobalState()[GLOBAL_KEY] = null;
|
|
26658
|
-
}
|
|
26659
|
-
|
|
26660
26975
|
// src/shared/session-directory.ts
|
|
26661
26976
|
var CACHE_MAX_ENTRIES = 200;
|
|
26662
26977
|
var cache = new Map;
|
|
@@ -26863,21 +27178,21 @@ function formatStatusMarkdown(status) {
|
|
|
26863
27178
|
|
|
26864
27179
|
// src/shared/tui-config.ts
|
|
26865
27180
|
var import_comment_json4 = __toESM(require_src2(), 1);
|
|
26866
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
26867
|
-
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";
|
|
26868
27183
|
|
|
26869
27184
|
// src/shared/opencode-config-dir.ts
|
|
26870
27185
|
import { homedir as homedir10 } from "os";
|
|
26871
|
-
import { join as
|
|
27186
|
+
import { join as join18, resolve as resolve4 } from "path";
|
|
26872
27187
|
function getCliConfigDir() {
|
|
26873
27188
|
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
26874
27189
|
if (envConfigDir) {
|
|
26875
27190
|
return resolve4(envConfigDir);
|
|
26876
27191
|
}
|
|
26877
27192
|
if (process.platform === "win32") {
|
|
26878
|
-
return
|
|
27193
|
+
return join18(homedir10(), ".config", "opencode");
|
|
26879
27194
|
}
|
|
26880
|
-
return
|
|
27195
|
+
return join18(process.env.XDG_CONFIG_HOME || join18(homedir10(), ".config"), "opencode");
|
|
26881
27196
|
}
|
|
26882
27197
|
function getOpenCodeConfigDir2(_options) {
|
|
26883
27198
|
return getCliConfigDir();
|
|
@@ -26886,10 +27201,10 @@ function getOpenCodeConfigPaths(options) {
|
|
|
26886
27201
|
const configDir = getOpenCodeConfigDir2(options);
|
|
26887
27202
|
return {
|
|
26888
27203
|
configDir,
|
|
26889
|
-
configJson:
|
|
26890
|
-
configJsonc:
|
|
26891
|
-
packageJson:
|
|
26892
|
-
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")
|
|
26893
27208
|
};
|
|
26894
27209
|
}
|
|
26895
27210
|
|
|
@@ -26898,8 +27213,8 @@ var PLUGIN_NAME = "@cortexkit/aft-opencode";
|
|
|
26898
27213
|
var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
|
|
26899
27214
|
function resolveTuiConfigPath() {
|
|
26900
27215
|
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
|
|
26901
|
-
const jsoncPath =
|
|
26902
|
-
const jsonPath =
|
|
27216
|
+
const jsoncPath = join19(configDir, "tui.jsonc");
|
|
27217
|
+
const jsonPath = join19(configDir, "tui.json");
|
|
26903
27218
|
if (existsSync12(jsoncPath))
|
|
26904
27219
|
return jsoncPath;
|
|
26905
27220
|
if (existsSync12(jsonPath))
|
|
@@ -26911,7 +27226,7 @@ function ensureTuiPluginEntry() {
|
|
|
26911
27226
|
const configPath = resolveTuiConfigPath();
|
|
26912
27227
|
let config2 = {};
|
|
26913
27228
|
if (existsSync12(configPath)) {
|
|
26914
|
-
config2 = import_comment_json4.parse(
|
|
27229
|
+
config2 = import_comment_json4.parse(readFileSync11(configPath, "utf-8")) ?? {};
|
|
26915
27230
|
}
|
|
26916
27231
|
const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
|
|
26917
27232
|
if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
|
|
@@ -26919,7 +27234,7 @@ function ensureTuiPluginEntry() {
|
|
|
26919
27234
|
}
|
|
26920
27235
|
plugins.push(PLUGIN_ENTRY);
|
|
26921
27236
|
config2.plugin = plugins;
|
|
26922
|
-
|
|
27237
|
+
mkdirSync11(dirname7(configPath), { recursive: true });
|
|
26923
27238
|
writeFileSync10(configPath, `${import_comment_json4.stringify(config2, null, 2)}
|
|
26924
27239
|
`);
|
|
26925
27240
|
log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
|
|
@@ -26931,13 +27246,13 @@ function ensureTuiPluginEntry() {
|
|
|
26931
27246
|
}
|
|
26932
27247
|
|
|
26933
27248
|
// src/shutdown-hooks.ts
|
|
26934
|
-
var
|
|
27249
|
+
var GLOBAL_KEY = "__aftShutdownHooks__";
|
|
26935
27250
|
function getState() {
|
|
26936
27251
|
const g = globalThis;
|
|
26937
|
-
if (!g[
|
|
26938
|
-
g[
|
|
27252
|
+
if (!g[GLOBAL_KEY]) {
|
|
27253
|
+
g[GLOBAL_KEY] = { cleanups: new Set, installed: false };
|
|
26939
27254
|
}
|
|
26940
|
-
return g[
|
|
27255
|
+
return g[GLOBAL_KEY];
|
|
26941
27256
|
}
|
|
26942
27257
|
var shuttingDown = false;
|
|
26943
27258
|
async function runCleanups(reason) {
|
|
@@ -27039,8 +27354,10 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
27039
27354
|
}
|
|
27040
27355
|
|
|
27041
27356
|
// src/tools/permissions.ts
|
|
27357
|
+
import * as fs3 from "fs";
|
|
27042
27358
|
import * as path3 from "path";
|
|
27043
27359
|
import { Effect } from "effect";
|
|
27360
|
+
var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.14.39 or newer for permission asks; please upgrade OpenCode";
|
|
27044
27361
|
async function runAsk(maybe) {
|
|
27045
27362
|
await Effect.runPromise(maybe);
|
|
27046
27363
|
}
|
|
@@ -27068,6 +27385,8 @@ function workspacePattern(_context) {
|
|
|
27068
27385
|
return ".";
|
|
27069
27386
|
}
|
|
27070
27387
|
async function askEditPermission(context, patterns, metadata = {}) {
|
|
27388
|
+
if (typeof context.ask !== "function")
|
|
27389
|
+
return UNSUPPORTED_ASK_HOST;
|
|
27071
27390
|
try {
|
|
27072
27391
|
await runAsk(context.ask({
|
|
27073
27392
|
permission: "edit",
|
|
@@ -27083,6 +27402,110 @@ async function askEditPermission(context, patterns, metadata = {}) {
|
|
|
27083
27402
|
return "Permission denied.";
|
|
27084
27403
|
}
|
|
27085
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
|
+
}
|
|
27086
27509
|
function permissionDeniedResponse(message) {
|
|
27087
27510
|
return JSON.stringify({
|
|
27088
27511
|
success: false,
|
|
@@ -27102,6 +27525,17 @@ function extractHint(response) {
|
|
|
27102
27525
|
const hint = response.hint;
|
|
27103
27526
|
return typeof hint === "string" && hint.length > 0 ? hint : null;
|
|
27104
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
|
+
}
|
|
27105
27539
|
var SUPPORTED_LANGS = ["typescript", "tsx", "javascript", "python", "rust", "go"];
|
|
27106
27540
|
function astTools(ctx) {
|
|
27107
27541
|
const searchTool = {
|
|
@@ -27122,6 +27556,9 @@ function astTools(ctx) {
|
|
|
27122
27556
|
contextLines: z2.number().optional().describe("Number of context lines to show around each match")
|
|
27123
27557
|
},
|
|
27124
27558
|
execute: async (args, context) => {
|
|
27559
|
+
const externalDenied = await checkAstPathsPermission(context, args.paths);
|
|
27560
|
+
if (externalDenied)
|
|
27561
|
+
return permissionDeniedResponse(externalDenied);
|
|
27125
27562
|
const params = {
|
|
27126
27563
|
pattern: args.pattern,
|
|
27127
27564
|
lang: args.lang
|
|
@@ -27215,16 +27652,32 @@ ${hint}`;
|
|
|
27215
27652
|
},
|
|
27216
27653
|
execute: async (args, context) => {
|
|
27217
27654
|
const isDryRun = args.dryRun === true;
|
|
27655
|
+
const externalDenied = await checkAstPathsPermission(context, args.paths);
|
|
27656
|
+
if (externalDenied)
|
|
27657
|
+
return permissionDeniedResponse(externalDenied);
|
|
27218
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
|
+
}
|
|
27219
27674
|
const explicitPaths = Array.isArray(args.paths) ? resolveRelativePatterns(context, args.paths) : [];
|
|
27220
27675
|
const positiveGlobs = Array.isArray(args.globs) ? args.globs.filter((glob) => !glob.startsWith("!")) : [];
|
|
27221
27676
|
const patterns = [...explicitPaths, ...positiveGlobs];
|
|
27222
27677
|
const metadata = explicitPaths.length === 1 && positiveGlobs.length === 0 && Array.isArray(args.paths) ? { filepath: resolveAbsolutePath(context, args.paths[0]) } : {};
|
|
27223
27678
|
const permissionError = await askEditPermission(context, patterns.length > 0 ? patterns : [workspacePattern(context)], metadata);
|
|
27224
27679
|
if (permissionError) {
|
|
27225
|
-
|
|
27226
|
-
showOutputToUser(context, output2);
|
|
27227
|
-
return output2;
|
|
27680
|
+
return permissionDeniedResponse(permissionError);
|
|
27228
27681
|
}
|
|
27229
27682
|
}
|
|
27230
27683
|
const params = {
|
|
@@ -27358,7 +27811,7 @@ function conflictTools(ctx) {
|
|
|
27358
27811
|
}
|
|
27359
27812
|
|
|
27360
27813
|
// src/tools/hoisted.ts
|
|
27361
|
-
import * as
|
|
27814
|
+
import * as fs4 from "fs";
|
|
27362
27815
|
import * as path4 from "path";
|
|
27363
27816
|
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
27364
27817
|
|
|
@@ -27655,11 +28108,65 @@ ${chunk.old_lines.join(`
|
|
|
27655
28108
|
|
|
27656
28109
|
// src/tools/bash.ts
|
|
27657
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
|
|
27658
28164
|
var z3 = tool3.schema;
|
|
27659
28165
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
27660
28166
|
var FOREGROUND_WAIT_WINDOW_MS = 5000;
|
|
27661
28167
|
var FOREGROUND_POLL_INTERVAL_MS = 100;
|
|
27662
28168
|
var BASH_TRANSPORT_TIMEOUT_MS = 30000;
|
|
28169
|
+
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
27663
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.`;
|
|
27664
28171
|
async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
27665
28172
|
const first = await bridgeCall(ctx, runtime, "bash", params, options);
|
|
@@ -27700,6 +28207,13 @@ function createBashTool(ctx) {
|
|
|
27700
28207
|
const metadata = context.metadata;
|
|
27701
28208
|
const command = args.command;
|
|
27702
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
|
+
}
|
|
27703
28217
|
const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
|
|
27704
28218
|
const data = await withPermissionLoop(ctx, context, {
|
|
27705
28219
|
command,
|
|
@@ -27707,8 +28221,8 @@ function createBashTool(ctx) {
|
|
|
27707
28221
|
workdir: args.workdir,
|
|
27708
28222
|
env: shellEnv?.env ?? {},
|
|
27709
28223
|
description,
|
|
27710
|
-
background:
|
|
27711
|
-
notify_on_completion:
|
|
28224
|
+
background: effectiveBackground,
|
|
28225
|
+
notify_on_completion: effectiveBackground,
|
|
27712
28226
|
compressed: args.compressed,
|
|
27713
28227
|
permissions_requested: true
|
|
27714
28228
|
}, callBridge, {
|
|
@@ -27725,7 +28239,7 @@ function createBashTool(ctx) {
|
|
|
27725
28239
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
27726
28240
|
const callID2 = getCallID(context);
|
|
27727
28241
|
const taskId = data.task_id;
|
|
27728
|
-
if (
|
|
28242
|
+
if (effectiveBackground) {
|
|
27729
28243
|
trackBgTask(context.sessionID, taskId);
|
|
27730
28244
|
const startedLine = formatBackgroundLaunch(taskId);
|
|
27731
28245
|
const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
|
|
@@ -27739,7 +28253,7 @@ function createBashTool(ctx) {
|
|
|
27739
28253
|
return startedLine;
|
|
27740
28254
|
}
|
|
27741
28255
|
const argTimeout = args.timeout;
|
|
27742
|
-
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;
|
|
27743
28257
|
const startedAt = Date.now();
|
|
27744
28258
|
while (true) {
|
|
27745
28259
|
const status = await callBridge(ctx, context, "bash_status", { task_id: taskId });
|
|
@@ -27820,7 +28334,7 @@ function createBashStatusTool(ctx) {
|
|
|
27820
28334
|
return {
|
|
27821
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.",
|
|
27822
28336
|
args: {
|
|
27823
|
-
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.")
|
|
27824
28338
|
},
|
|
27825
28339
|
execute: async (args, context) => {
|
|
27826
28340
|
const data = await callBridge(ctx, context, "bash_status", {
|
|
@@ -27850,7 +28364,7 @@ function createBashKillTool(ctx) {
|
|
|
27850
28364
|
return {
|
|
27851
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.",
|
|
27852
28366
|
args: {
|
|
27853
|
-
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.")
|
|
27854
28368
|
},
|
|
27855
28369
|
execute: async (args, context) => {
|
|
27856
28370
|
const data = await callBridge(ctx, context, "bash_kill", {
|
|
@@ -28161,12 +28675,23 @@ function createReadTool(ctx) {
|
|
|
28161
28675
|
execute: async (args, context) => {
|
|
28162
28676
|
const file2 = args.filePath;
|
|
28163
28677
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28164
|
-
|
|
28165
|
-
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
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
|
+
}
|
|
28170
28695
|
const ext = path4.extname(filePath).toLowerCase();
|
|
28171
28696
|
const mimeMap = {
|
|
28172
28697
|
".png": "image/png",
|
|
@@ -28189,7 +28714,7 @@ function createReadTool(ctx) {
|
|
|
28189
28714
|
const label = isImage ? "Image" : "PDF";
|
|
28190
28715
|
let fileSize = 0;
|
|
28191
28716
|
try {
|
|
28192
|
-
const stat = await import("fs/promises").then((
|
|
28717
|
+
const stat = await import("fs/promises").then((fs5) => fs5.stat(filePath));
|
|
28193
28718
|
fileSize = stat.size;
|
|
28194
28719
|
} catch {}
|
|
28195
28720
|
const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
|
|
@@ -28285,6 +28810,11 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
28285
28810
|
const content = args.content;
|
|
28286
28811
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28287
28812
|
const relPath = path4.relative(context.worktree, filePath);
|
|
28813
|
+
{
|
|
28814
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28815
|
+
if (denial)
|
|
28816
|
+
return permissionDeniedResponse(denial);
|
|
28817
|
+
}
|
|
28288
28818
|
await runAsk(context.ask({
|
|
28289
28819
|
permission: "edit",
|
|
28290
28820
|
patterns: [relPath],
|
|
@@ -28405,9 +28935,9 @@ Note: Modes 6 and 7 are options on mode 5 (find/replace) \u2014 they require \`o
|
|
|
28405
28935
|
- Auto-formats using project formatter if configured
|
|
28406
28936
|
- Tree-sitter syntax validation on all edits
|
|
28407
28937
|
- Symbol replace includes decorators, attributes, and doc comments in range
|
|
28408
|
-
- LSP error-level diagnostics are returned automatically after
|
|
28938
|
+
- LSP error-level diagnostics are returned automatically after edits
|
|
28409
28939
|
|
|
28410
|
-
Returns: JSON string for the selected edit mode.
|
|
28940
|
+
Returns: JSON string for the selected edit mode. Edits may append inline LSP error lines.
|
|
28411
28941
|
|
|
28412
28942
|
Common response fields: success (boolean), diff (object with before/after), backup_id (string), syntax_valid (boolean). Exact fields vary by mode.`;
|
|
28413
28943
|
}
|
|
@@ -28424,8 +28954,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28424
28954
|
content: z4.string().optional().describe("New content for symbol replace or file write"),
|
|
28425
28955
|
appendContent: z4.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
28426
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 }"),
|
|
28427
|
-
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)")
|
|
28428
|
-
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)")
|
|
28429
28958
|
},
|
|
28430
28959
|
execute: async (args, context) => {
|
|
28431
28960
|
const argsRecord = args;
|
|
@@ -28435,6 +28964,18 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28435
28964
|
if (Array.isArray(args.operations)) {
|
|
28436
28965
|
const ops = args.operations;
|
|
28437
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
|
+
}
|
|
28438
28979
|
await runAsk(context.ask({
|
|
28439
28980
|
permission: "edit",
|
|
28440
28981
|
patterns: files.map((f) => path4.relative(context.worktree, path4.resolve(context.directory, f))),
|
|
@@ -28445,9 +28986,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28445
28986
|
...op,
|
|
28446
28987
|
file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
|
|
28447
28988
|
}));
|
|
28448
|
-
const
|
|
28449
|
-
params2.dry_run = args.dryRun === true;
|
|
28450
|
-
const data2 = await callBridge(ctx, context, "transaction", params2);
|
|
28989
|
+
const data2 = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
|
|
28451
28990
|
return JSON.stringify(data2);
|
|
28452
28991
|
}
|
|
28453
28992
|
const file2 = args.filePath;
|
|
@@ -28455,6 +28994,11 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28455
28994
|
throw new Error("'filePath' parameter is required");
|
|
28456
28995
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28457
28996
|
const relPath = path4.relative(context.worktree, filePath);
|
|
28997
|
+
{
|
|
28998
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
28999
|
+
if (denial)
|
|
29000
|
+
return permissionDeniedResponse(denial);
|
|
29001
|
+
}
|
|
28458
29002
|
await runAsk(context.ask({
|
|
28459
29003
|
permission: "edit",
|
|
28460
29004
|
patterns: [relPath],
|
|
@@ -28503,14 +29047,10 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28503
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.";
|
|
28504
29048
|
throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
|
|
28505
29049
|
}
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
if (!args.dryRun)
|
|
28509
|
-
params.diagnostics = true;
|
|
28510
|
-
if (!args.dryRun)
|
|
28511
|
-
params.include_diff = true;
|
|
29050
|
+
params.diagnostics = true;
|
|
29051
|
+
params.include_diff = true;
|
|
28512
29052
|
const data = await callBridge(ctx, context, command, params);
|
|
28513
|
-
if (
|
|
29053
|
+
if (data.success && data.diff) {
|
|
28514
29054
|
const diff = data.diff;
|
|
28515
29055
|
const callID = getCallID2(context);
|
|
28516
29056
|
if (callID) {
|
|
@@ -28539,31 +29079,29 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
28539
29079
|
result += `
|
|
28540
29080
|
|
|
28541
29081
|
${globSkipNote}`;
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
28546
|
-
|
|
28547
|
-
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(`
|
|
28548
29087
|
`);
|
|
28549
|
-
|
|
29088
|
+
result += `
|
|
28550
29089
|
|
|
28551
29090
|
LSP errors detected, please fix:
|
|
28552
29091
|
${diagLines}`;
|
|
28553
|
-
}
|
|
28554
29092
|
}
|
|
28555
|
-
|
|
28556
|
-
|
|
28557
|
-
|
|
28558
|
-
|
|
29093
|
+
}
|
|
29094
|
+
const pendingServers = data.lsp_pending_servers;
|
|
29095
|
+
const exitedServers = data.lsp_exited_servers;
|
|
29096
|
+
if (pendingServers && pendingServers.length > 0) {
|
|
29097
|
+
result += `
|
|
28559
29098
|
|
|
28560
29099
|
Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete; rerun lsp_diagnostics later for a fresh check.`;
|
|
28561
|
-
|
|
28562
|
-
|
|
28563
|
-
|
|
29100
|
+
}
|
|
29101
|
+
if (exitedServers && exitedServers.length > 0) {
|
|
29102
|
+
result += `
|
|
28564
29103
|
|
|
28565
29104
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
28566
|
-
}
|
|
28567
29105
|
}
|
|
28568
29106
|
return result;
|
|
28569
29107
|
}
|
|
@@ -28650,13 +29188,24 @@ function createApplyPatchTool(ctx) {
|
|
|
28650
29188
|
if (h.type === "update" && h.move_path) {
|
|
28651
29189
|
const dstAbs = path4.resolve(context.directory, h.move_path);
|
|
28652
29190
|
affectedAbs.add(dstAbs);
|
|
28653
|
-
if (!
|
|
29191
|
+
if (!fs4.existsSync(dstAbs)) {
|
|
28654
29192
|
newlyCreatedAbs.add(dstAbs);
|
|
28655
29193
|
}
|
|
28656
29194
|
}
|
|
28657
29195
|
}
|
|
28658
29196
|
const relPaths = Array.from(affectedAbs).map((abs) => path4.relative(context.worktree, abs));
|
|
28659
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
|
+
}
|
|
28660
29209
|
await runAsk(context.ask({
|
|
28661
29210
|
permission: "edit",
|
|
28662
29211
|
patterns: relPaths,
|
|
@@ -28682,7 +29231,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28682
29231
|
const filePath = path4.resolve(context.directory, hunk.path);
|
|
28683
29232
|
switch (hunk.type) {
|
|
28684
29233
|
case "add": {
|
|
28685
|
-
if (
|
|
29234
|
+
if (fs4.existsSync(filePath)) {
|
|
28686
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.`;
|
|
28687
29236
|
results.push(msg);
|
|
28688
29237
|
failures.push(hunk.path);
|
|
@@ -28714,7 +29263,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28714
29263
|
results.push(msg);
|
|
28715
29264
|
failures.push(hunk.path);
|
|
28716
29265
|
const filePath2 = path4.resolve(context.directory, hunk.path);
|
|
28717
|
-
if (
|
|
29266
|
+
if (fs4.existsSync(filePath2)) {
|
|
28718
29267
|
try {
|
|
28719
29268
|
await callBridge(ctx, context, "delete_file", { file: filePath2 });
|
|
28720
29269
|
} catch {}
|
|
@@ -28724,7 +29273,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28724
29273
|
}
|
|
28725
29274
|
case "delete": {
|
|
28726
29275
|
try {
|
|
28727
|
-
const before = await
|
|
29276
|
+
const before = await fs4.promises.readFile(filePath, "utf-8").catch(() => "");
|
|
28728
29277
|
await callBridge(ctx, context, "delete_file", { file: filePath });
|
|
28729
29278
|
perFileDiffs.push({
|
|
28730
29279
|
filePath,
|
|
@@ -28742,7 +29291,7 @@ function createApplyPatchTool(ctx) {
|
|
|
28742
29291
|
}
|
|
28743
29292
|
case "update": {
|
|
28744
29293
|
try {
|
|
28745
|
-
const original = await
|
|
29294
|
+
const original = await fs4.promises.readFile(filePath, "utf-8");
|
|
28746
29295
|
const newContent = applyUpdateChunks(original, filePath, hunk.chunks);
|
|
28747
29296
|
const targetPath = hunk.move_path ? path4.resolve(context.directory, hunk.move_path) : filePath;
|
|
28748
29297
|
const writeResult = await callBridge(ctx, context, "write", {
|
|
@@ -28797,7 +29346,7 @@ ${diagLines}`);
|
|
|
28797
29346
|
if (rollbackResult.success === false) {
|
|
28798
29347
|
throw new Error(rollbackResult.message ?? "checkpoint restore failed");
|
|
28799
29348
|
}
|
|
28800
|
-
if (newlyCreatedAbs.has(targetPath) &&
|
|
29349
|
+
if (newlyCreatedAbs.has(targetPath) && fs4.existsSync(targetPath)) {
|
|
28801
29350
|
const cleanupResult = await callBridge(ctx, context, "delete_file", {
|
|
28802
29351
|
file: targetPath
|
|
28803
29352
|
});
|
|
@@ -28892,6 +29441,17 @@ function createDeleteTool(ctx) {
|
|
|
28892
29441
|
execute: async (args, context) => {
|
|
28893
29442
|
const inputs = args.files;
|
|
28894
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
|
+
}
|
|
28895
29455
|
await runAsk(context.ask({
|
|
28896
29456
|
permission: "edit",
|
|
28897
29457
|
patterns: absolutePaths,
|
|
@@ -28937,6 +29497,18 @@ function createMoveTool(ctx) {
|
|
|
28937
29497
|
execute: async (args, context) => {
|
|
28938
29498
|
const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
|
|
28939
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
|
+
}
|
|
28940
29512
|
await runAsk(context.ask({
|
|
28941
29513
|
permission: "edit",
|
|
28942
29514
|
patterns: [filePath, destPath],
|
|
@@ -28991,6 +29563,11 @@ function aftPrefixedTools(ctx) {
|
|
|
28991
29563
|
const file2 = normalizedArgs.filePath;
|
|
28992
29564
|
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
28993
29565
|
const relPath = path4.relative(context.worktree, filePath);
|
|
29566
|
+
{
|
|
29567
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29568
|
+
if (denial)
|
|
29569
|
+
return permissionDeniedResponse(denial);
|
|
29570
|
+
}
|
|
28994
29571
|
await runAsk(context.ask({
|
|
28995
29572
|
permission: "edit",
|
|
28996
29573
|
patterns: [relPath],
|
|
@@ -29003,9 +29580,6 @@ function aftPrefixedTools(ctx) {
|
|
|
29003
29580
|
create_dirs: normalizedArgs.create_dirs !== false,
|
|
29004
29581
|
diagnostics: true
|
|
29005
29582
|
};
|
|
29006
|
-
if (normalizedArgs.dryRun === true || normalizedArgs.dry_run === true) {
|
|
29007
|
-
writeParams.dry_run = true;
|
|
29008
|
-
}
|
|
29009
29583
|
const data = await callBridge(ctx, context, "write", writeParams);
|
|
29010
29584
|
return JSON.stringify(data);
|
|
29011
29585
|
}
|
|
@@ -29029,10 +29603,9 @@ function importTools(ctx) {
|
|
|
29029
29603
|
` + `Ops:
|
|
29030
29604
|
` + `- 'add': Add an import. Auto-detects group (stdlib/external/internal), deduplicates. Requires 'module'. Optional 'names', 'defaultImport', 'typeOnly'.
|
|
29031
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.
|
|
29032
|
-
` + `- '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.
|
|
29033
29607
|
|
|
29034
29608
|
` + `Returns:
|
|
29035
|
-
` + `- Dry run (any op): { ok, dry_run, diff, syntax_valid? }
|
|
29036
29609
|
` + `- add: { file, added, module, group?, already_present?, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29037
29610
|
` + `- remove: { file, removed, module, name?, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29038
29611
|
` + "- organize: { file, groups: [{ name, count }], removed_duplicates, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
|
|
@@ -29044,21 +29617,22 @@ function importTools(ctx) {
|
|
|
29044
29617
|
defaultImport: z5.string().optional().describe("Default import name (e.g. 'React')"),
|
|
29045
29618
|
typeOnly: z5.boolean().optional().describe("Type-only import (TS only, default: false)"),
|
|
29046
29619
|
removeName: z5.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
|
|
29047
|
-
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)")
|
|
29048
|
-
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)")
|
|
29049
29621
|
},
|
|
29050
29622
|
execute: async (args, context) => {
|
|
29051
29623
|
const op = args.op;
|
|
29052
|
-
const isDryRun = args.dryRun === true;
|
|
29053
29624
|
if ((op === "add" || op === "remove") && typeof args.module !== "string") {
|
|
29054
29625
|
throw new Error(`'module' is required for '${op}' op`);
|
|
29055
29626
|
}
|
|
29056
|
-
|
|
29057
|
-
|
|
29058
|
-
const
|
|
29059
|
-
if (
|
|
29060
|
-
return permissionDeniedResponse(
|
|
29627
|
+
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
29628
|
+
{
|
|
29629
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
29630
|
+
if (denial)
|
|
29631
|
+
return permissionDeniedResponse(denial);
|
|
29061
29632
|
}
|
|
29633
|
+
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29634
|
+
if (permissionError)
|
|
29635
|
+
return permissionDeniedResponse(permissionError);
|
|
29062
29636
|
const commandMap = {
|
|
29063
29637
|
add: "add_import",
|
|
29064
29638
|
remove: "remove_import",
|
|
@@ -29077,8 +29651,6 @@ function importTools(ctx) {
|
|
|
29077
29651
|
params.name = args.removeName;
|
|
29078
29652
|
if (args.validate !== undefined)
|
|
29079
29653
|
params.validate = args.validate;
|
|
29080
|
-
if (args.dryRun !== undefined)
|
|
29081
|
-
params.dry_run = args.dryRun;
|
|
29082
29654
|
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
29083
29655
|
if (response.success === false) {
|
|
29084
29656
|
throw new Error(response.message || `${op} failed`);
|
|
@@ -29421,9 +29993,9 @@ function refactoringTools(ctx) {
|
|
|
29421
29993
|
|
|
29422
29994
|
` + `Each op requires specific parameters \u2014 see parameter descriptions for requirements.
|
|
29423
29995
|
|
|
29424
|
-
` + `All ops need 'filePath'. Use
|
|
29996
|
+
` + `All ops need 'filePath'. Use aft_safety checkpoint/undo before risky refactors.
|
|
29425
29997
|
|
|
29426
|
-
` + "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, ... }.",
|
|
29427
29999
|
args: {
|
|
29428
30000
|
op: z9.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
|
|
29429
30001
|
filePath: z9.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
@@ -29433,8 +30005,7 @@ function refactoringTools(ctx) {
|
|
|
29433
30005
|
name: z9.string().optional().describe("New function name \u2014 required for 'extract' op"),
|
|
29434
30006
|
startLine: z9.number().optional().describe("1-based start line \u2014 required for 'extract' op"),
|
|
29435
30007
|
endLine: z9.number().optional().describe("1-based end line (inclusive) \u2014 required for 'extract' op"),
|
|
29436
|
-
callSiteLine: z9.number().optional().describe("1-based call site line \u2014 required for 'inline' op")
|
|
29437
|
-
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")
|
|
29438
30009
|
},
|
|
29439
30010
|
execute: async (args, context) => {
|
|
29440
30011
|
const op = args.op;
|
|
@@ -29462,6 +30033,18 @@ function refactoringTools(ctx) {
|
|
|
29462
30033
|
...typeof args.destination === "string" ? [args.destination] : []
|
|
29463
30034
|
]) : [resolveRelativePattern(context, args.filePath)];
|
|
29464
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
|
+
}
|
|
29465
30048
|
const permissionError = await askEditPermission(context, patterns, metadata);
|
|
29466
30049
|
if (permissionError)
|
|
29467
30050
|
return permissionDeniedResponse(permissionError);
|
|
@@ -29471,8 +30054,6 @@ function refactoringTools(ctx) {
|
|
|
29471
30054
|
inline: "inline_symbol"
|
|
29472
30055
|
};
|
|
29473
30056
|
const params = { file: args.filePath };
|
|
29474
|
-
if (args.dryRun !== undefined)
|
|
29475
|
-
params.dry_run = args.dryRun;
|
|
29476
30057
|
switch (op) {
|
|
29477
30058
|
case "move":
|
|
29478
30059
|
params.symbol = args.symbol;
|
|
@@ -29504,6 +30085,7 @@ function refactoringTools(ctx) {
|
|
|
29504
30085
|
}
|
|
29505
30086
|
|
|
29506
30087
|
// src/tools/safety.ts
|
|
30088
|
+
import * as path5 from "path";
|
|
29507
30089
|
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
29508
30090
|
var z10 = tool10.schema;
|
|
29509
30091
|
function safetyTools(ctx) {
|
|
@@ -29541,10 +30123,30 @@ function safetyTools(ctx) {
|
|
|
29541
30123
|
}
|
|
29542
30124
|
if (op === "undo" && typeof args.filePath === "string") {
|
|
29543
30125
|
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
30126
|
+
{
|
|
30127
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
30128
|
+
if (denial)
|
|
30129
|
+
return permissionDeniedResponse(denial);
|
|
30130
|
+
}
|
|
29544
30131
|
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29545
30132
|
if (permissionError)
|
|
29546
30133
|
return permissionDeniedResponse(permissionError);
|
|
29547
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
|
+
}
|
|
29548
30150
|
if (op === "restore") {
|
|
29549
30151
|
const permissionError = await askEditPermission(context, [workspacePattern(context)], {
|
|
29550
30152
|
checkpoint: args.name
|
|
@@ -29585,6 +30187,8 @@ function safetyTools(ctx) {
|
|
|
29585
30187
|
}
|
|
29586
30188
|
|
|
29587
30189
|
// src/tools/search.ts
|
|
30190
|
+
import * as fs5 from "fs";
|
|
30191
|
+
import * as path6 from "path";
|
|
29588
30192
|
function arg(schema) {
|
|
29589
30193
|
return schema;
|
|
29590
30194
|
}
|
|
@@ -29654,11 +30258,33 @@ function searchTools(ctx) {
|
|
|
29654
30258
|
path: arg(exports_external.string().optional().describe("Directory to search in, relative to project root"))
|
|
29655
30259
|
},
|
|
29656
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
|
+
}
|
|
29657
30283
|
const response = await callBridge(ctx, context, "grep", {
|
|
29658
|
-
pattern
|
|
30284
|
+
pattern,
|
|
29659
30285
|
case_sensitive: true,
|
|
29660
|
-
include:
|
|
29661
|
-
path:
|
|
30286
|
+
include: includeArg ? splitIncludeArg(includeArg).map(normalizeGlob).filter(Boolean) : undefined,
|
|
30287
|
+
path: pathArg,
|
|
29662
30288
|
max_results: 100
|
|
29663
30289
|
});
|
|
29664
30290
|
if (response.success === false) {
|
|
@@ -29686,6 +30312,22 @@ function searchTools(ctx) {
|
|
|
29686
30312
|
}
|
|
29687
30313
|
}
|
|
29688
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
|
+
}
|
|
29689
30331
|
const response = await callBridge(ctx, context, "glob", {
|
|
29690
30332
|
pattern: globPattern,
|
|
29691
30333
|
path: globPath
|
|
@@ -29777,10 +30419,9 @@ function structureTools(ctx) {
|
|
|
29777
30419
|
` + `- 'add_decorator': Add Python decorator to function/class. Requires 'target' and 'decorator' (without @). Optional 'position'.
|
|
29778
30420
|
` + `- 'add_struct_tags': Add/update Go struct field tags. Requires 'target' (struct name), 'field', 'tag', 'value'.
|
|
29779
30421
|
|
|
29780
|
-
` + `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.
|
|
29781
30423
|
|
|
29782
30424
|
` + `Returns:
|
|
29783
|
-
` + `- Dry run (any op): { ok, dry_run, diff, syntax_valid? }
|
|
29784
30425
|
` + `- add_member: { file, scope, position, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29785
30426
|
` + `- add_derive: { file, target, derives, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
29786
30427
|
` + `- wrap_try_catch: { file, target, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
@@ -29799,8 +30440,7 @@ function structureTools(ctx) {
|
|
|
29799
30440
|
field: z11.string().optional().describe("Struct field name (add_struct_tags)"),
|
|
29800
30441
|
tag: z11.string().optional().describe("Tag key (add_struct_tags \u2014 e.g. 'json')"),
|
|
29801
30442
|
value: z11.string().optional().describe("Tag value (add_struct_tags \u2014 e.g. 'user_name,omitempty')"),
|
|
29802
|
-
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)")
|
|
29803
|
-
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)")
|
|
29804
30444
|
},
|
|
29805
30445
|
execute: async (args, context) => {
|
|
29806
30446
|
const op = args.op;
|
|
@@ -29829,14 +30469,17 @@ function structureTools(ctx) {
|
|
|
29829
30469
|
throw new Error("'value' is required for 'add_struct_tags' op");
|
|
29830
30470
|
}
|
|
29831
30471
|
const filePath = resolveAbsolutePath(context, args.filePath);
|
|
30472
|
+
{
|
|
30473
|
+
const denial = await assertExternalDirectoryPermission(context, filePath);
|
|
30474
|
+
if (denial)
|
|
30475
|
+
return permissionDeniedResponse(denial);
|
|
30476
|
+
}
|
|
29832
30477
|
const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
|
|
29833
30478
|
if (permissionError)
|
|
29834
30479
|
return permissionDeniedResponse(permissionError);
|
|
29835
30480
|
const params = { file: args.filePath };
|
|
29836
30481
|
if (args.validate !== undefined)
|
|
29837
30482
|
params.validate = args.validate;
|
|
29838
|
-
if (args.dryRun !== undefined)
|
|
29839
|
-
params.dry_run = args.dryRun;
|
|
29840
30483
|
switch (op) {
|
|
29841
30484
|
case "add_member":
|
|
29842
30485
|
params.scope = args.container;
|
|
@@ -29930,6 +30573,7 @@ function buildHintsFromConfig(config2, disabledTools) {
|
|
|
29930
30573
|
}
|
|
29931
30574
|
|
|
29932
30575
|
// src/configure-warnings.ts
|
|
30576
|
+
var pendingEagerWarnings = new Map;
|
|
29933
30577
|
function isConfigureWarning(value) {
|
|
29934
30578
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29935
30579
|
return false;
|
|
@@ -29939,13 +30583,25 @@ function isConfigureWarning(value) {
|
|
|
29939
30583
|
function coerceConfigureWarnings(warnings) {
|
|
29940
30584
|
return warnings.filter(isConfigureWarning);
|
|
29941
30585
|
}
|
|
30586
|
+
function drainPendingEagerWarnings(projectRoot) {
|
|
30587
|
+
const pending = pendingEagerWarnings.get(projectRoot) ?? [];
|
|
30588
|
+
pendingEagerWarnings.delete(projectRoot);
|
|
30589
|
+
return pending;
|
|
30590
|
+
}
|
|
29942
30591
|
async function handleConfigureWarningsForSession(context) {
|
|
30592
|
+
const validWarnings = coerceConfigureWarnings(context.warnings);
|
|
29943
30593
|
if (!context.sessionId) {
|
|
29944
|
-
|
|
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`);
|
|
29945
30600
|
return;
|
|
29946
30601
|
}
|
|
29947
|
-
const
|
|
29948
|
-
|
|
30602
|
+
const pendingWarnings = drainPendingEagerWarnings(context.projectRoot);
|
|
30603
|
+
const combinedWarnings = [...pendingWarnings, ...validWarnings];
|
|
30604
|
+
if (combinedWarnings.length === 0)
|
|
29949
30605
|
return;
|
|
29950
30606
|
await deliverConfigureWarnings({
|
|
29951
30607
|
client: context.client ?? context.fallbackClient,
|
|
@@ -29953,7 +30609,7 @@ async function handleConfigureWarningsForSession(context) {
|
|
|
29953
30609
|
storageDir: context.storageDir,
|
|
29954
30610
|
pluginVersion: context.pluginVersion,
|
|
29955
30611
|
projectRoot: context.projectRoot
|
|
29956
|
-
},
|
|
30612
|
+
}, combinedWarnings);
|
|
29957
30613
|
}
|
|
29958
30614
|
|
|
29959
30615
|
// src/index.ts
|
|
@@ -29968,16 +30624,10 @@ function throwSentinel(command) {
|
|
|
29968
30624
|
}
|
|
29969
30625
|
async function sendIgnoredMessage2(client, sessionID, text) {
|
|
29970
30626
|
const typedClient = client;
|
|
29971
|
-
const lastModel = await getLastAssistantModel(client, sessionID);
|
|
29972
30627
|
const body = {
|
|
29973
30628
|
noReply: true,
|
|
29974
30629
|
parts: [{ type: "text", text, ignored: true }]
|
|
29975
30630
|
};
|
|
29976
|
-
if (lastModel) {
|
|
29977
|
-
body.model = { providerID: lastModel.providerID, modelID: lastModel.modelID };
|
|
29978
|
-
if (lastModel.variant)
|
|
29979
|
-
body.variant = lastModel.variant;
|
|
29980
|
-
}
|
|
29981
30631
|
const promptInput = { path: { id: sessionID }, body };
|
|
29982
30632
|
if (typeof typedClient.session?.prompt === "function") {
|
|
29983
30633
|
await Promise.resolve(typedClient.session.prompt(promptInput));
|
|
@@ -30007,7 +30657,8 @@ var ANNOUNCEMENT_FEATURES = [
|
|
|
30007
30657
|
"Trigram grep/glob and semantic search (aft_search) graduated out of experimental.",
|
|
30008
30658
|
"Lots of bugfixes and new end-to-end test coverage."
|
|
30009
30659
|
];
|
|
30010
|
-
var plugin = async (input) =>
|
|
30660
|
+
var plugin = async (input) => initializePluginForDirectory(input);
|
|
30661
|
+
async function initializePluginForDirectory(input) {
|
|
30011
30662
|
const binaryPath = await findBinary();
|
|
30012
30663
|
const aftConfig = loadAftConfig(input.directory);
|
|
30013
30664
|
const autoUpdateAbort = new AbortController;
|
|
@@ -30035,8 +30686,8 @@ var plugin = async (input) => {
|
|
|
30035
30686
|
if (aftConfig.max_callgraph_files !== undefined)
|
|
30036
30687
|
configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
|
|
30037
30688
|
const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
|
|
30038
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
30039
|
-
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");
|
|
30040
30691
|
let onnxRuntimePromise = null;
|
|
30041
30692
|
if (aftConfig.semantic_search && isFastembedSemanticBackend) {
|
|
30042
30693
|
const storageDir2 = configOverrides.storage_dir;
|
|
@@ -30122,10 +30773,10 @@ ${lines}
|
|
|
30122
30773
|
}
|
|
30123
30774
|
versionUpgradeAttempted = binaryVersion;
|
|
30124
30775
|
warn2(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
30125
|
-
ensureBinary(`v${minVersion}`).then((
|
|
30126
|
-
if (
|
|
30127
|
-
log2(`Found/downloaded compatible binary at ${
|
|
30128
|
-
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(() => {
|
|
30129
30780
|
log2("Binary replaced successfully. New bridges will use the updated binary.");
|
|
30130
30781
|
}, (err) => error2("Failed to replace binary:", err));
|
|
30131
30782
|
} else {
|
|
@@ -30136,11 +30787,12 @@ ${lines}
|
|
|
30136
30787
|
});
|
|
30137
30788
|
},
|
|
30138
30789
|
onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
|
|
30790
|
+
const pendingWarnings = sessionId ? drainPendingEagerWarnings(projectRoot) : [];
|
|
30139
30791
|
await handleConfigureWarningsForSession({
|
|
30140
30792
|
projectRoot,
|
|
30141
30793
|
sessionId,
|
|
30142
30794
|
client,
|
|
30143
|
-
warnings,
|
|
30795
|
+
warnings: [...pendingWarnings, ...warnings],
|
|
30144
30796
|
fallbackClient: input.client,
|
|
30145
30797
|
storageDir: configOverrides.storage_dir,
|
|
30146
30798
|
pluginVersion: PLUGIN_VERSION
|
|
@@ -30175,7 +30827,6 @@ ${lines}
|
|
|
30175
30827
|
config: aftConfig,
|
|
30176
30828
|
storageDir: configOverrides.storage_dir
|
|
30177
30829
|
};
|
|
30178
|
-
setSharedBridgePool(pool);
|
|
30179
30830
|
if (onnxRuntimePromise) {
|
|
30180
30831
|
onnxRuntimePromise.then((ortDylibDir) => {
|
|
30181
30832
|
if (ortDylibDir) {
|
|
@@ -30197,7 +30848,7 @@ ${lines}
|
|
|
30197
30848
|
if (onnxRuntimePromise) {
|
|
30198
30849
|
await Promise.race([
|
|
30199
30850
|
onnxRuntimePromise,
|
|
30200
|
-
new Promise((
|
|
30851
|
+
new Promise((resolve11) => setTimeout(() => resolve11(null), 60000))
|
|
30201
30852
|
]);
|
|
30202
30853
|
}
|
|
30203
30854
|
const bridge = pool.getBridge(input.directory);
|
|
@@ -30243,10 +30894,10 @@ ${lines}
|
|
|
30243
30894
|
return { show: false };
|
|
30244
30895
|
}
|
|
30245
30896
|
if (storageDir) {
|
|
30246
|
-
const versionFile =
|
|
30897
|
+
const versionFile = join21(storageDir, "last_announced_version");
|
|
30247
30898
|
try {
|
|
30248
30899
|
if (existsSync14(versionFile)) {
|
|
30249
|
-
const lastVersion =
|
|
30900
|
+
const lastVersion = readFileSync12(versionFile, "utf-8").trim();
|
|
30250
30901
|
if (lastVersion === ANNOUNCEMENT_VERSION)
|
|
30251
30902
|
return { show: false };
|
|
30252
30903
|
}
|
|
@@ -30257,8 +30908,8 @@ ${lines}
|
|
|
30257
30908
|
rpcServer.handle("mark-announced", async () => {
|
|
30258
30909
|
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
30259
30910
|
try {
|
|
30260
|
-
|
|
30261
|
-
writeFileSync11(
|
|
30911
|
+
mkdirSync12(storageDir, { recursive: true });
|
|
30912
|
+
writeFileSync11(join21(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
|
|
30262
30913
|
} catch {}
|
|
30263
30914
|
}
|
|
30264
30915
|
return { success: true };
|
|
@@ -30390,7 +31041,6 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
30390
31041
|
},
|
|
30391
31042
|
"chat.message": async (messageInput) => {
|
|
30392
31043
|
const sid = messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id;
|
|
30393
|
-
resetBgWake(sid);
|
|
30394
31044
|
warmSessionDirectory(input.client, sid, input.directory);
|
|
30395
31045
|
},
|
|
30396
31046
|
"command.execute.before": async (commandInput, _output) => {
|
|
@@ -30450,11 +31100,10 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
30450
31100
|
unregisterShutdown();
|
|
30451
31101
|
await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
|
|
30452
31102
|
rpcServer.stop();
|
|
30453
|
-
clearSharedBridgePool();
|
|
30454
31103
|
await pool.shutdown();
|
|
30455
31104
|
}
|
|
30456
31105
|
};
|
|
30457
|
-
}
|
|
31106
|
+
}
|
|
30458
31107
|
var src_default = plugin;
|
|
30459
31108
|
export {
|
|
30460
31109
|
src_default as default
|