@cortexkit/aft-opencode 0.31.1 → 0.33.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/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +635 -204
- package/dist/notifications.d.ts.map +1 -1
- package/dist/patch-parser.d.ts.map +1 -1
- package/dist/shared/status.d.ts +4 -1
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/shutdown-hooks.d.ts +1 -0
- package/dist/shutdown-hooks.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +13 -0
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts +30 -0
- package/dist/tools/inspect.d.ts.map +1 -0
- package/dist/tools/navigation.d.ts +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/refactoring.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui.js +39 -3
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/shared/status.ts +28 -5
- package/src/tui/index.tsx +18 -2
- package/src/tui/sidebar.tsx +23 -3
- package/dist/tools/lsp.d.ts +0 -7
- package/dist/tools/lsp.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -11599,6 +11599,7 @@ import { homedir } from "node:os";
|
|
|
11599
11599
|
import { join } from "node:path";
|
|
11600
11600
|
import { StringDecoder } from "node:string_decoder";
|
|
11601
11601
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
11602
|
+
var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
|
|
11602
11603
|
var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
|
|
11603
11604
|
var MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
|
|
11604
11605
|
function tagStderrLine(line) {
|
|
@@ -11706,8 +11707,11 @@ class BinaryBridge {
|
|
|
11706
11707
|
statusListeners = new Set;
|
|
11707
11708
|
configureWarningClients = new Map;
|
|
11708
11709
|
restartResetTimer = null;
|
|
11710
|
+
lastChildActivityAt = 0;
|
|
11711
|
+
consecutiveRequestTimeouts = 0;
|
|
11709
11712
|
errorPrefix;
|
|
11710
11713
|
logger;
|
|
11714
|
+
childEnv;
|
|
11711
11715
|
constructor(binaryPath, cwd, options, configOverrides) {
|
|
11712
11716
|
this.binaryPath = binaryPath;
|
|
11713
11717
|
this.cwd = cwd;
|
|
@@ -11722,6 +11726,7 @@ class BinaryBridge {
|
|
|
11722
11726
|
this.onBashPatternMatch = options?.onBashPatternMatch;
|
|
11723
11727
|
this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
|
|
11724
11728
|
this.logger = options?.logger;
|
|
11729
|
+
this.childEnv = options?.childEnv;
|
|
11725
11730
|
}
|
|
11726
11731
|
logVia(message, meta) {
|
|
11727
11732
|
const logger = this.logger ?? getActiveLogger();
|
|
@@ -11873,20 +11878,41 @@ class BinaryBridge {
|
|
|
11873
11878
|
const line = `${JSON.stringify(request)}
|
|
11874
11879
|
`;
|
|
11875
11880
|
const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
|
|
11881
|
+
let requestSentAt = Date.now();
|
|
11876
11882
|
const response = await new Promise((resolve, reject) => {
|
|
11877
11883
|
const timer = setTimeout(() => {
|
|
11884
|
+
const entry = this.pending.get(id);
|
|
11885
|
+
if (!entry)
|
|
11886
|
+
return;
|
|
11878
11887
|
this.pending.delete(id);
|
|
11879
|
-
|
|
11888
|
+
clearTimeout(entry.timer);
|
|
11889
|
+
if (keepBridgeOnTimeout) {
|
|
11890
|
+
const timeoutMsg2 = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`;
|
|
11891
|
+
if (requestSessionId) {
|
|
11892
|
+
this.sessionWarnVia(requestSessionId, timeoutMsg2);
|
|
11893
|
+
} else {
|
|
11894
|
+
this.warnVia(timeoutMsg2);
|
|
11895
|
+
}
|
|
11896
|
+
entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
11897
|
+
return;
|
|
11898
|
+
}
|
|
11899
|
+
const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
|
|
11900
|
+
const consecutiveTimeouts = this.consecutiveRequestTimeouts + 1;
|
|
11901
|
+
this.consecutiveRequestTimeouts = consecutiveTimeouts;
|
|
11902
|
+
const keepWarm = childActiveSinceRequest || consecutiveTimeouts < BRIDGE_HANG_TIMEOUT_THRESHOLD;
|
|
11903
|
+
const restartSuffix = keepWarm ? " — bridge kept warm" : " — restarting bridge";
|
|
11880
11904
|
const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
|
|
11881
11905
|
if (requestSessionId) {
|
|
11882
11906
|
this.sessionWarnVia(requestSessionId, timeoutMsg);
|
|
11883
11907
|
} else {
|
|
11884
11908
|
this.warnVia(timeoutMsg);
|
|
11885
11909
|
}
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11910
|
+
if (keepWarm) {
|
|
11911
|
+
entry.reject(new Error(`${this.errorPrefix} request "${command}" timed out after ${effectiveTimeoutMs}ms (bridge busy/under load); bridge kept warm — retry`));
|
|
11912
|
+
return;
|
|
11889
11913
|
}
|
|
11914
|
+
entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
11915
|
+
this.handleTimeout(requestSessionId);
|
|
11890
11916
|
}, effectiveTimeoutMs);
|
|
11891
11917
|
this.pending.set(id, { resolve, reject, timer, onProgress: options?.onProgress });
|
|
11892
11918
|
if (!this.process?.stdin?.writable) {
|
|
@@ -11895,6 +11921,7 @@ class BinaryBridge {
|
|
|
11895
11921
|
reject(new Error(`${this.errorPrefix} stdin not writable for command "${command}"`));
|
|
11896
11922
|
return;
|
|
11897
11923
|
}
|
|
11924
|
+
requestSentAt = Date.now();
|
|
11898
11925
|
this.process.stdin.write(line, (err) => {
|
|
11899
11926
|
if (err) {
|
|
11900
11927
|
const entry = this.pending.get(id);
|
|
@@ -12089,6 +12116,15 @@ class BinaryBridge {
|
|
|
12089
12116
|
this.logVia(`ORT_DYLIB_PATH set from managed ONNX Runtime: ${ortLibraryPath}`);
|
|
12090
12117
|
}
|
|
12091
12118
|
}
|
|
12119
|
+
if (this.childEnv) {
|
|
12120
|
+
for (const [key, value] of Object.entries(this.childEnv)) {
|
|
12121
|
+
if (value === undefined) {
|
|
12122
|
+
delete env[key];
|
|
12123
|
+
} else {
|
|
12124
|
+
env[key] = value;
|
|
12125
|
+
}
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
12092
12128
|
const child = spawn(this.binaryPath, [], {
|
|
12093
12129
|
cwd: this.cwd,
|
|
12094
12130
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -12138,6 +12174,8 @@ class BinaryBridge {
|
|
|
12138
12174
|
this.process = child;
|
|
12139
12175
|
this.stdoutBuffer = "";
|
|
12140
12176
|
this.stderrBuffer = "";
|
|
12177
|
+
this.lastChildActivityAt = 0;
|
|
12178
|
+
this.consecutiveRequestTimeouts = 0;
|
|
12141
12179
|
this.stderrTail = [];
|
|
12142
12180
|
}
|
|
12143
12181
|
pushStderrLine(line) {
|
|
@@ -12193,6 +12231,7 @@ class BinaryBridge {
|
|
|
12193
12231
|
continue;
|
|
12194
12232
|
try {
|
|
12195
12233
|
const response = JSON.parse(line);
|
|
12234
|
+
this.lastChildActivityAt = Date.now();
|
|
12196
12235
|
if (response.type === "progress") {
|
|
12197
12236
|
const requestId = response.request_id;
|
|
12198
12237
|
const entry = requestId ? this.pending.get(requestId) : undefined;
|
|
@@ -12245,6 +12284,7 @@ class BinaryBridge {
|
|
|
12245
12284
|
continue;
|
|
12246
12285
|
this.pending.delete(id);
|
|
12247
12286
|
clearTimeout(entry.timer);
|
|
12287
|
+
this.consecutiveRequestTimeouts = 0;
|
|
12248
12288
|
this.scheduleRestartCountReset();
|
|
12249
12289
|
entry.resolve(response);
|
|
12250
12290
|
} else if (typeof response.type === "string") {
|
|
@@ -12256,6 +12296,7 @@ class BinaryBridge {
|
|
|
12256
12296
|
}
|
|
12257
12297
|
}
|
|
12258
12298
|
handleTimeout(triggeringSessionId) {
|
|
12299
|
+
this.consecutiveRequestTimeouts = 0;
|
|
12259
12300
|
this.rejectAllPending(new Error(`${this.errorPrefix} bridge killed during sibling timeout — request aborted`));
|
|
12260
12301
|
if (this.process) {
|
|
12261
12302
|
this.process.kill("SIGKILL");
|
|
@@ -12334,7 +12375,7 @@ class BinaryBridge {
|
|
|
12334
12375
|
// ../aft-bridge/dist/downloader.js
|
|
12335
12376
|
import { spawnSync } from "node:child_process";
|
|
12336
12377
|
import { createHash, randomUUID } from "node:crypto";
|
|
12337
|
-
import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
12378
|
+
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
12338
12379
|
import { homedir as homedir2 } from "node:os";
|
|
12339
12380
|
import { join as join2 } from "node:path";
|
|
12340
12381
|
import { Readable } from "node:stream";
|
|
@@ -12501,10 +12542,18 @@ async function downloadBinary(version) {
|
|
|
12501
12542
|
throw new Error(`Checksum mismatch for ${assetName}: expected ${expectedHash}, got ${actualHash}. ` + "The binary may have been tampered with.");
|
|
12502
12543
|
}
|
|
12503
12544
|
log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
|
|
12504
|
-
if (process.platform
|
|
12545
|
+
if (process.platform === "win32") {
|
|
12546
|
+
copyFileSync(tmpPath, binaryPath);
|
|
12547
|
+
} else {
|
|
12505
12548
|
chmodSync(tmpPath, 493);
|
|
12549
|
+
renameSync(tmpPath, binaryPath);
|
|
12550
|
+
}
|
|
12551
|
+
try {
|
|
12552
|
+
if (existsSync(tmpPath))
|
|
12553
|
+
unlinkSync(tmpPath);
|
|
12554
|
+
} catch {
|
|
12555
|
+
warn(`Could not clean up temporary download file ${tmpPath} — it can be removed manually.`);
|
|
12506
12556
|
}
|
|
12507
|
-
renameSync(tmpPath, binaryPath);
|
|
12508
12557
|
log(`AFT binary ready at ${binaryPath}`);
|
|
12509
12558
|
return binaryPath;
|
|
12510
12559
|
} catch (err) {
|
|
@@ -12668,7 +12717,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
|
|
|
12668
12717
|
|
|
12669
12718
|
// ../aft-bridge/dist/resolver.js
|
|
12670
12719
|
import { execSync } from "node:child_process";
|
|
12671
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync3 } from "node:fs";
|
|
12720
|
+
import { chmodSync as chmodSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
12672
12721
|
import { createRequire as createRequire2 } from "node:module";
|
|
12673
12722
|
import { homedir as homedir3 } from "node:os";
|
|
12674
12723
|
import { join as join4 } from "node:path";
|
|
@@ -12691,10 +12740,15 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
12691
12740
|
}
|
|
12692
12741
|
mkdirSync3(versionedDir, { recursive: true });
|
|
12693
12742
|
const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
|
|
12694
|
-
|
|
12743
|
+
copyFileSync2(npmBinaryPath, tmpPath);
|
|
12695
12744
|
if (process.platform !== "win32") {
|
|
12696
12745
|
chmodSync2(tmpPath, 493);
|
|
12697
12746
|
}
|
|
12747
|
+
if (process.platform === "win32" && existsSync3(cachedPath)) {
|
|
12748
|
+
try {
|
|
12749
|
+
unlinkSync2(cachedPath);
|
|
12750
|
+
} catch {}
|
|
12751
|
+
}
|
|
12698
12752
|
renameSync3(tmpPath, cachedPath);
|
|
12699
12753
|
log(`Copied npm binary to versioned cache: ${cachedPath}`);
|
|
12700
12754
|
return cachedPath;
|
|
@@ -12946,7 +13000,7 @@ async function ensureStorageMigrated(opts) {
|
|
|
12946
13000
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
12947
13001
|
import { execFileSync } from "node:child_process";
|
|
12948
13002
|
import { createHash as createHash2 } from "node:crypto";
|
|
12949
|
-
import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as
|
|
13003
|
+
import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, openSync as openSync2, readdirSync, readFileSync as readFileSync3, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
12950
13004
|
import { basename, dirname as dirname3, isAbsolute, join as join6, relative, resolve, win32 } from "node:path";
|
|
12951
13005
|
import { Readable as Readable2 } from "node:stream";
|
|
12952
13006
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
@@ -13185,20 +13239,64 @@ function findSystemOnnxRuntime(libName) {
|
|
|
13185
13239
|
} else if (process.platform === "linux") {
|
|
13186
13240
|
searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
|
|
13187
13241
|
} else if (process.platform === "win32") {
|
|
13242
|
+
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
13243
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
13244
|
+
searchPaths.push(join6(programFiles, "onnxruntime", "lib"), join6(programFiles, "Microsoft ONNX Runtime", "lib"), join6(programFiles, "Microsoft Machine Learning", "lib"), join6(programFilesX86, "onnxruntime", "lib"), ...(() => {
|
|
13245
|
+
const nugetPaths = [];
|
|
13246
|
+
const userProfile = process.env.USERPROFILE ?? "";
|
|
13247
|
+
if (!userProfile)
|
|
13248
|
+
return nugetPaths;
|
|
13249
|
+
const nugetPackageDir = join6(userProfile, ".nuget", "packages", "microsoft.ml.onnxruntime");
|
|
13250
|
+
if (!existsSync5(nugetPackageDir))
|
|
13251
|
+
return nugetPaths;
|
|
13252
|
+
try {
|
|
13253
|
+
for (const entry of readdirSync(nugetPackageDir, { withFileTypes: true })) {
|
|
13254
|
+
if (!entry.isDirectory())
|
|
13255
|
+
continue;
|
|
13256
|
+
if (entry.name === "__globalPackagesFolder" || entry.name.startsWith("."))
|
|
13257
|
+
continue;
|
|
13258
|
+
nugetPaths.push(join6(nugetPackageDir, entry.name, "runtimes", "win-x64", "native"), join6(nugetPackageDir, entry.name, "runtimes", "win-arm64", "native"));
|
|
13259
|
+
}
|
|
13260
|
+
} catch (err) {
|
|
13261
|
+
warn(`Failed to scan NuGet ONNX Runtime cache ${nugetPackageDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13262
|
+
}
|
|
13263
|
+
return nugetPaths;
|
|
13264
|
+
})());
|
|
13188
13265
|
searchPaths.push(...pathEntriesForPlatform());
|
|
13189
13266
|
}
|
|
13267
|
+
const normalizeCase = process.platform === "win32" || process.platform === "darwin";
|
|
13190
13268
|
const seen = new Set;
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
if (
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13269
|
+
const uniquePaths = searchPaths.filter((p) => {
|
|
13270
|
+
let key = resolve(p).replace(/[/\\]+$/, "");
|
|
13271
|
+
if (normalizeCase)
|
|
13272
|
+
key = key.toLowerCase();
|
|
13273
|
+
if (seen.has(key))
|
|
13274
|
+
return false;
|
|
13275
|
+
seen.add(key);
|
|
13276
|
+
return true;
|
|
13277
|
+
});
|
|
13278
|
+
for (const dir of uniquePaths) {
|
|
13279
|
+
const libPath = join6(dir, libName);
|
|
13280
|
+
if (process.platform === "win32") {
|
|
13281
|
+
if (!directoryContainsLibrary(dir, libName))
|
|
13282
|
+
continue;
|
|
13283
|
+
} else if (!existsSync5(libPath)) {
|
|
13200
13284
|
continue;
|
|
13201
13285
|
}
|
|
13286
|
+
let skipVersionCheck = false;
|
|
13287
|
+
if (process.platform === "win32") {
|
|
13288
|
+
const dirLower = dir.toLowerCase();
|
|
13289
|
+
const isCommonPath = dirLower.includes("program files") || dirLower.includes("onnxruntime");
|
|
13290
|
+
if (!isCommonPath)
|
|
13291
|
+
skipVersionCheck = true;
|
|
13292
|
+
}
|
|
13293
|
+
if (!skipVersionCheck) {
|
|
13294
|
+
const version = detectOnnxVersion(dir, libName);
|
|
13295
|
+
if (version && !isOnnxVersionCompatible(version)) {
|
|
13296
|
+
warn(`Skipping system ONNX Runtime at ${dir} (v${version}); AFT requires ` + `v${REQUIRED_ORT_MAJOR}.${REQUIRED_ORT_MIN_MINOR}+. Falling through to AFT-managed download.`);
|
|
13297
|
+
continue;
|
|
13298
|
+
}
|
|
13299
|
+
}
|
|
13202
13300
|
return dir;
|
|
13203
13301
|
}
|
|
13204
13302
|
return null;
|
|
@@ -13236,7 +13334,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
13236
13334
|
await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
|
|
13237
13335
|
} catch (err) {
|
|
13238
13336
|
try {
|
|
13239
|
-
|
|
13337
|
+
unlinkSync3(destPath);
|
|
13240
13338
|
} catch {}
|
|
13241
13339
|
throw err;
|
|
13242
13340
|
} finally {
|
|
@@ -13297,7 +13395,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
13297
13395
|
await extractZipArchive(archivePath, tmpDir);
|
|
13298
13396
|
}
|
|
13299
13397
|
try {
|
|
13300
|
-
|
|
13398
|
+
unlinkSync3(archivePath);
|
|
13301
13399
|
} catch {}
|
|
13302
13400
|
validateExtractedTree(tmpDir);
|
|
13303
13401
|
const extractedDir = join6(tmpDir, info.assetName, "lib");
|
|
@@ -13346,7 +13444,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
13346
13444
|
return null;
|
|
13347
13445
|
}
|
|
13348
13446
|
}
|
|
13349
|
-
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile =
|
|
13447
|
+
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync3) {
|
|
13350
13448
|
const requiredLibs = new Set([info.libName]);
|
|
13351
13449
|
for (const libFile of realFiles) {
|
|
13352
13450
|
const src = join6(extractedDir, libFile);
|
|
@@ -13367,7 +13465,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
13367
13465
|
for (const link of symlinks) {
|
|
13368
13466
|
const dst = join6(targetDir, link.name);
|
|
13369
13467
|
try {
|
|
13370
|
-
|
|
13468
|
+
unlinkSync3(dst);
|
|
13371
13469
|
} catch {}
|
|
13372
13470
|
try {
|
|
13373
13471
|
symlinkSync(link.target, dst);
|
|
@@ -13478,7 +13576,7 @@ ${new Date().toISOString()}
|
|
|
13478
13576
|
}
|
|
13479
13577
|
log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
13480
13578
|
try {
|
|
13481
|
-
|
|
13579
|
+
unlinkSync3(lockPath);
|
|
13482
13580
|
} catch {}
|
|
13483
13581
|
return tryClaim();
|
|
13484
13582
|
}
|
|
@@ -13503,7 +13601,7 @@ function releaseLock(lockPath) {
|
|
|
13503
13601
|
return;
|
|
13504
13602
|
}
|
|
13505
13603
|
try {
|
|
13506
|
-
|
|
13604
|
+
unlinkSync3(lockPath);
|
|
13507
13605
|
} catch (unlinkErr) {
|
|
13508
13606
|
const code = unlinkErr.code;
|
|
13509
13607
|
if (code !== "ENOENT") {
|
|
@@ -14738,7 +14836,7 @@ function formatDuration(completion) {
|
|
|
14738
14836
|
|
|
14739
14837
|
// src/config.ts
|
|
14740
14838
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
14741
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as
|
|
14839
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
14742
14840
|
import { homedir as homedir5 } from "node:os";
|
|
14743
14841
|
import { join as join8 } from "node:path";
|
|
14744
14842
|
|
|
@@ -28326,6 +28424,7 @@ var LspConfigSchema = exports_external.object({
|
|
|
28326
28424
|
servers: exports_external.record(exports_external.string().trim().min(1), LspServerEntrySchema).optional(),
|
|
28327
28425
|
disabled: exports_external.array(exports_external.string().trim().min(1)).optional(),
|
|
28328
28426
|
python: exports_external.enum(["pyright", "ty", "auto"]).optional(),
|
|
28427
|
+
diagnostics_on_edit: exports_external.boolean().optional(),
|
|
28329
28428
|
auto_install: exports_external.boolean().optional(),
|
|
28330
28429
|
grace_days: exports_external.number().int().positive().optional(),
|
|
28331
28430
|
versions: exports_external.record(exports_external.string().trim().min(1), exports_external.string().trim().min(1)).optional()
|
|
@@ -28349,6 +28448,24 @@ var BashFeaturesSchema = exports_external.object({
|
|
|
28349
28448
|
long_running_reminder_interval_ms: exports_external.number().int().positive().optional()
|
|
28350
28449
|
});
|
|
28351
28450
|
var BashConfigSchema = exports_external.union([exports_external.boolean(), BashFeaturesSchema]);
|
|
28451
|
+
var InspectConfigSchema = exports_external.object({
|
|
28452
|
+
enabled: exports_external.boolean().optional(),
|
|
28453
|
+
tier2_idle_minutes: exports_external.number().min(0).optional(),
|
|
28454
|
+
categories: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
|
|
28455
|
+
tier2_soft_deadline_ms: exports_external.number().int().positive().optional(),
|
|
28456
|
+
max_drill_down_items: exports_external.number().int().positive().max(100).optional(),
|
|
28457
|
+
duplicates: exports_external.object({
|
|
28458
|
+
lower_bound: exports_external.number().int().positive().optional(),
|
|
28459
|
+
discard_cost: exports_external.number().int().min(0).optional(),
|
|
28460
|
+
anonymize: exports_external.object({
|
|
28461
|
+
variables: exports_external.boolean().optional(),
|
|
28462
|
+
fields: exports_external.boolean().optional(),
|
|
28463
|
+
methods: exports_external.boolean().optional(),
|
|
28464
|
+
types: exports_external.boolean().optional(),
|
|
28465
|
+
literals: exports_external.boolean().optional()
|
|
28466
|
+
}).optional()
|
|
28467
|
+
}).optional()
|
|
28468
|
+
});
|
|
28352
28469
|
var AftConfigSchema = exports_external.object({
|
|
28353
28470
|
$schema: exports_external.string().optional(),
|
|
28354
28471
|
format_on_edit: exports_external.boolean().optional(),
|
|
@@ -28362,6 +28479,7 @@ var AftConfigSchema = exports_external.object({
|
|
|
28362
28479
|
restrict_to_project_root: exports_external.boolean().optional(),
|
|
28363
28480
|
search_index: exports_external.boolean().optional(),
|
|
28364
28481
|
semantic_search: exports_external.boolean().optional(),
|
|
28482
|
+
inspect: InspectConfigSchema.optional(),
|
|
28365
28483
|
bash: BashConfigSchema.optional(),
|
|
28366
28484
|
experimental: ExperimentalConfigSchema.optional(),
|
|
28367
28485
|
lsp: LspConfigSchema.optional(),
|
|
@@ -28438,6 +28556,8 @@ function resolveProjectOverridesForConfigure(config2) {
|
|
|
28438
28556
|
Object.assign(overrides, resolveLspConfigForConfigure(config2));
|
|
28439
28557
|
if (config2.semantic !== undefined)
|
|
28440
28558
|
overrides.semantic = config2.semantic;
|
|
28559
|
+
if (config2.inspect !== undefined)
|
|
28560
|
+
overrides.inspect = config2.inspect;
|
|
28441
28561
|
if (config2.max_callgraph_files !== undefined)
|
|
28442
28562
|
overrides.max_callgraph_files = config2.max_callgraph_files;
|
|
28443
28563
|
return overrides;
|
|
@@ -28653,7 +28773,7 @@ ${serialized}` : serialized;
|
|
|
28653
28773
|
} catch (err) {
|
|
28654
28774
|
if (tmpPath) {
|
|
28655
28775
|
try {
|
|
28656
|
-
|
|
28776
|
+
unlinkSync4(tmpPath);
|
|
28657
28777
|
} catch {}
|
|
28658
28778
|
}
|
|
28659
28779
|
if (isWritableMigrationError(err)) {
|
|
@@ -28746,6 +28866,9 @@ function mergeLspConfig(baseLsp, overrideLsp) {
|
|
|
28746
28866
|
const projectLsp = {};
|
|
28747
28867
|
if (overrideLsp?.python !== undefined)
|
|
28748
28868
|
projectLsp.python = overrideLsp.python;
|
|
28869
|
+
if (overrideLsp?.diagnostics_on_edit !== undefined) {
|
|
28870
|
+
projectLsp.diagnostics_on_edit = overrideLsp.diagnostics_on_edit;
|
|
28871
|
+
}
|
|
28749
28872
|
const userDisabled = baseLsp?.disabled ?? [];
|
|
28750
28873
|
const lsp = {
|
|
28751
28874
|
...baseLsp,
|
|
@@ -28757,6 +28880,27 @@ function mergeLspConfig(baseLsp, overrideLsp) {
|
|
|
28757
28880
|
}
|
|
28758
28881
|
return Object.fromEntries(Object.entries(lsp).filter(([, value]) => value !== undefined));
|
|
28759
28882
|
}
|
|
28883
|
+
function mergeInspectConfig(baseInspect, overrideInspect) {
|
|
28884
|
+
const inspect = {
|
|
28885
|
+
...baseInspect,
|
|
28886
|
+
...overrideInspect,
|
|
28887
|
+
duplicates: baseInspect?.duplicates || overrideInspect?.duplicates ? {
|
|
28888
|
+
...baseInspect?.duplicates,
|
|
28889
|
+
...overrideInspect?.duplicates,
|
|
28890
|
+
anonymize: baseInspect?.duplicates?.anonymize || overrideInspect?.duplicates?.anonymize ? {
|
|
28891
|
+
...baseInspect?.duplicates?.anonymize,
|
|
28892
|
+
...overrideInspect?.duplicates?.anonymize
|
|
28893
|
+
} : undefined
|
|
28894
|
+
} : undefined
|
|
28895
|
+
};
|
|
28896
|
+
if (inspect.duplicates && inspect.duplicates.anonymize === undefined) {
|
|
28897
|
+
delete inspect.duplicates.anonymize;
|
|
28898
|
+
}
|
|
28899
|
+
if (Object.values(inspect).every((value) => value === undefined)) {
|
|
28900
|
+
return;
|
|
28901
|
+
}
|
|
28902
|
+
return Object.fromEntries(Object.entries(inspect).filter(([, value]) => value !== undefined));
|
|
28903
|
+
}
|
|
28760
28904
|
function mergeBashConfig(baseBash, overrideBash) {
|
|
28761
28905
|
if (baseBash === undefined && overrideBash === undefined)
|
|
28762
28906
|
return;
|
|
@@ -28816,6 +28960,7 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
|
|
|
28816
28960
|
"validate_on_edit",
|
|
28817
28961
|
"search_index",
|
|
28818
28962
|
"semantic_search",
|
|
28963
|
+
"inspect",
|
|
28819
28964
|
"experimental",
|
|
28820
28965
|
"bash"
|
|
28821
28966
|
]);
|
|
@@ -28848,8 +28993,10 @@ function mergeConfigs(base, override) {
|
|
|
28848
28993
|
const lsp = mergeLspConfig(base.lsp, override.lsp);
|
|
28849
28994
|
const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
|
|
28850
28995
|
const bash = mergeBashConfig(base.bash, override.bash);
|
|
28996
|
+
const inspect = mergeInspectConfig(base.inspect, override.inspect);
|
|
28851
28997
|
const safeOverride = pickProjectSafeFields(override);
|
|
28852
28998
|
delete safeOverride.bash;
|
|
28999
|
+
delete safeOverride.inspect;
|
|
28853
29000
|
return {
|
|
28854
29001
|
...base,
|
|
28855
29002
|
...safeOverride,
|
|
@@ -28857,6 +29004,7 @@ function mergeConfigs(base, override) {
|
|
|
28857
29004
|
...Object.keys(checker).length > 0 ? { checker } : {},
|
|
28858
29005
|
...lsp ? { lsp } : {},
|
|
28859
29006
|
...bash !== undefined ? { bash } : {},
|
|
29007
|
+
...inspect !== undefined ? { inspect } : {},
|
|
28860
29008
|
experimental,
|
|
28861
29009
|
semantic,
|
|
28862
29010
|
...disabledTools.length > 0 ? { disabled_tools: [...new Set(disabledTools)] } : {}
|
|
@@ -29603,7 +29751,7 @@ import {
|
|
|
29603
29751
|
openSync as openSync4,
|
|
29604
29752
|
readFileSync as readFileSync8,
|
|
29605
29753
|
statSync as statSync4,
|
|
29606
|
-
unlinkSync as
|
|
29754
|
+
unlinkSync as unlinkSync5,
|
|
29607
29755
|
writeFileSync as writeFileSync7
|
|
29608
29756
|
} from "node:fs";
|
|
29609
29757
|
import { homedir as homedir8 } from "node:os";
|
|
@@ -29733,7 +29881,7 @@ ${new Date().toISOString()}
|
|
|
29733
29881
|
}
|
|
29734
29882
|
log2(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
|
|
29735
29883
|
try {
|
|
29736
|
-
|
|
29884
|
+
unlinkSync5(lock);
|
|
29737
29885
|
} catch {}
|
|
29738
29886
|
return tryClaim();
|
|
29739
29887
|
}
|
|
@@ -29770,7 +29918,7 @@ function releaseInstallLock(lockKey) {
|
|
|
29770
29918
|
return;
|
|
29771
29919
|
}
|
|
29772
29920
|
try {
|
|
29773
|
-
|
|
29921
|
+
unlinkSync5(lock);
|
|
29774
29922
|
} catch (unlinkErr) {
|
|
29775
29923
|
const code = unlinkErr.code;
|
|
29776
29924
|
if (code !== "ENOENT") {
|
|
@@ -30427,7 +30575,7 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
|
|
|
30427
30575
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
30428
30576
|
import { createHash as createHash5, randomBytes } from "node:crypto";
|
|
30429
30577
|
import {
|
|
30430
|
-
copyFileSync as
|
|
30578
|
+
copyFileSync as copyFileSync4,
|
|
30431
30579
|
createReadStream as createReadStream2,
|
|
30432
30580
|
createWriteStream as createWriteStream3,
|
|
30433
30581
|
existsSync as existsSync11,
|
|
@@ -30440,7 +30588,7 @@ import {
|
|
|
30440
30588
|
renameSync as renameSync7,
|
|
30441
30589
|
rmSync as rmSync6,
|
|
30442
30590
|
statSync as statSync6,
|
|
30443
|
-
unlinkSync as
|
|
30591
|
+
unlinkSync as unlinkSync6,
|
|
30444
30592
|
writeFileSync as writeFileSync8
|
|
30445
30593
|
} from "node:fs";
|
|
30446
30594
|
import { dirname as dirname7, join as join15, relative as relative2, resolve as resolve3 } from "node:path";
|
|
@@ -30802,7 +30950,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
|
|
|
30802
30950
|
await pipeline3(nodeStream, createWriteStream3(destPath), { signal: timeout.signal });
|
|
30803
30951
|
} catch (err) {
|
|
30804
30952
|
try {
|
|
30805
|
-
|
|
30953
|
+
unlinkSync6(destPath);
|
|
30806
30954
|
} catch {}
|
|
30807
30955
|
throw err;
|
|
30808
30956
|
} finally {
|
|
@@ -31034,7 +31182,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
31034
31182
|
} catch (err) {
|
|
31035
31183
|
error2(`[lsp] hash ${spec.id} failed: ${err}`);
|
|
31036
31184
|
try {
|
|
31037
|
-
|
|
31185
|
+
unlinkSync6(archivePath);
|
|
31038
31186
|
} catch {}
|
|
31039
31187
|
return null;
|
|
31040
31188
|
}
|
|
@@ -31045,7 +31193,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
31045
31193
|
if (previousArchiveSha256 !== archiveSha256) {
|
|
31046
31194
|
error2(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch — refusing install. ` + `Previously installed archive sha256=${previousArchiveSha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
|
|
31047
31195
|
try {
|
|
31048
|
-
|
|
31196
|
+
unlinkSync6(archivePath);
|
|
31049
31197
|
} catch {}
|
|
31050
31198
|
return null;
|
|
31051
31199
|
}
|
|
@@ -31057,7 +31205,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
31057
31205
|
return null;
|
|
31058
31206
|
} finally {
|
|
31059
31207
|
try {
|
|
31060
|
-
|
|
31208
|
+
unlinkSync6(archivePath);
|
|
31061
31209
|
} catch {}
|
|
31062
31210
|
}
|
|
31063
31211
|
const innerBinaryPath = join15(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
|
|
@@ -31068,7 +31216,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
|
|
|
31068
31216
|
const targetBinary = ghBinaryPath(spec, platform2);
|
|
31069
31217
|
mkdirSync9(dirname7(targetBinary), { recursive: true });
|
|
31070
31218
|
try {
|
|
31071
|
-
|
|
31219
|
+
copyFileSync4(innerBinaryPath, targetBinary);
|
|
31072
31220
|
if (platform2 !== "win32") {
|
|
31073
31221
|
const { chmodSync: chmodSync4 } = await import("node:fs");
|
|
31074
31222
|
chmodSync4(targetBinary, 493);
|
|
@@ -31426,13 +31574,21 @@ async function getSessionMessages(client, sessionId) {
|
|
|
31426
31574
|
async function sendIgnoredMessage(client, sessionId, text) {
|
|
31427
31575
|
try {
|
|
31428
31576
|
const c = client;
|
|
31429
|
-
const
|
|
31577
|
+
const promptContext = await resolvePromptContext(c, sessionId);
|
|
31430
31578
|
const body = {
|
|
31431
31579
|
noReply: true,
|
|
31432
31580
|
parts: [{ type: "text", text, ignored: true }]
|
|
31433
31581
|
};
|
|
31434
|
-
if (agent)
|
|
31435
|
-
body.agent = agent;
|
|
31582
|
+
if (promptContext?.agent)
|
|
31583
|
+
body.agent = promptContext.agent;
|
|
31584
|
+
if (promptContext?.model) {
|
|
31585
|
+
body.model = {
|
|
31586
|
+
providerID: promptContext.model.providerID,
|
|
31587
|
+
modelID: promptContext.model.modelID
|
|
31588
|
+
};
|
|
31589
|
+
}
|
|
31590
|
+
if (promptContext?.variant)
|
|
31591
|
+
body.variant = promptContext.variant;
|
|
31436
31592
|
const promptInput = {
|
|
31437
31593
|
path: { id: sessionId },
|
|
31438
31594
|
body
|
|
@@ -31450,14 +31606,6 @@ async function sendIgnoredMessage(client, sessionId, text) {
|
|
|
31450
31606
|
}
|
|
31451
31607
|
return false;
|
|
31452
31608
|
}
|
|
31453
|
-
async function resolveCurrentAgent(client, sessionId) {
|
|
31454
|
-
try {
|
|
31455
|
-
const ctx = await resolvePromptContext(client, sessionId);
|
|
31456
|
-
return ctx?.agent;
|
|
31457
|
-
} catch {
|
|
31458
|
-
return;
|
|
31459
|
-
}
|
|
31460
|
-
}
|
|
31461
31609
|
async function deleteMessage(serverUrl, sessionId, messageId) {
|
|
31462
31610
|
const auth = getServerAuth();
|
|
31463
31611
|
const url2 = `${serverUrl}/session/${encodeURIComponent(sessionId)}/message/${encodeURIComponent(messageId)}`;
|
|
@@ -31534,13 +31682,18 @@ function persistAnnouncedVersion(storageDir, version2) {
|
|
|
31534
31682
|
markAnnouncementSeen(storageDir, "opencode", version2);
|
|
31535
31683
|
}
|
|
31536
31684
|
async function readWarnedTools(bridge) {
|
|
31685
|
+
let resp;
|
|
31686
|
+
try {
|
|
31687
|
+
resp = await bridge.send("db_get_state", { key: "warned_tools" });
|
|
31688
|
+
} catch {
|
|
31689
|
+
return null;
|
|
31690
|
+
}
|
|
31691
|
+
if (resp.success === false)
|
|
31692
|
+
return null;
|
|
31693
|
+
const value = resp.data?.value;
|
|
31694
|
+
if (typeof value !== "string")
|
|
31695
|
+
return {};
|
|
31537
31696
|
try {
|
|
31538
|
-
const resp = await bridge.send("db_get_state", { key: "warned_tools" });
|
|
31539
|
-
if (resp.success === false)
|
|
31540
|
-
return {};
|
|
31541
|
-
const value = resp.data?.value;
|
|
31542
|
-
if (typeof value !== "string")
|
|
31543
|
-
return {};
|
|
31544
31697
|
const parsed = JSON.parse(value);
|
|
31545
31698
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
31546
31699
|
return {};
|
|
@@ -31549,12 +31702,16 @@ async function readWarnedTools(bridge) {
|
|
|
31549
31702
|
return {};
|
|
31550
31703
|
}
|
|
31551
31704
|
}
|
|
31552
|
-
async function
|
|
31705
|
+
async function warnedStatus(bridge, key) {
|
|
31553
31706
|
const warned = await readWarnedTools(bridge);
|
|
31554
|
-
|
|
31707
|
+
if (warned === null)
|
|
31708
|
+
return "unknown";
|
|
31709
|
+
return warned[key] === true || typeof warned[key] === "string" ? "warned" : "fresh";
|
|
31555
31710
|
}
|
|
31556
31711
|
async function recordWarning(bridge, key) {
|
|
31557
31712
|
const warned = await readWarnedTools(bridge);
|
|
31713
|
+
if (warned === null)
|
|
31714
|
+
return;
|
|
31558
31715
|
warned[key] = true;
|
|
31559
31716
|
try {
|
|
31560
31717
|
await bridge.send("db_set_state", {
|
|
@@ -31606,7 +31763,8 @@ async function deliverConfigureWarnings(opts, warnings) {
|
|
|
31606
31763
|
return;
|
|
31607
31764
|
for (const warning of warnings) {
|
|
31608
31765
|
const key = warningKey(warning, opts.projectRoot);
|
|
31609
|
-
|
|
31766
|
+
const status = await warnedStatus(opts.bridge, key);
|
|
31767
|
+
if (status !== "fresh")
|
|
31610
31768
|
continue;
|
|
31611
31769
|
const delivered = await sendIgnoredMessage(opts.client, opts.sessionId, formatConfigureWarning(warning));
|
|
31612
31770
|
if (!delivered)
|
|
@@ -31755,7 +31913,7 @@ function writeTerminal(terminal, data) {
|
|
|
31755
31913
|
|
|
31756
31914
|
// src/shared/rpc-server.ts
|
|
31757
31915
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
31758
|
-
import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as
|
|
31916
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
31759
31917
|
import { createServer } from "node:http";
|
|
31760
31918
|
import { dirname as dirname8, join as join18 } from "node:path";
|
|
31761
31919
|
|
|
@@ -31829,7 +31987,7 @@ class AftRpcServer {
|
|
|
31829
31987
|
}
|
|
31830
31988
|
this.token = null;
|
|
31831
31989
|
try {
|
|
31832
|
-
|
|
31990
|
+
unlinkSync7(this.portFilePath);
|
|
31833
31991
|
} catch {}
|
|
31834
31992
|
}
|
|
31835
31993
|
dispatch(req, res) {
|
|
@@ -31990,6 +32148,19 @@ function formatFlag(enabled) {
|
|
|
31990
32148
|
function formatCount(value) {
|
|
31991
32149
|
return value == null ? "—" : value.toLocaleString("en-US");
|
|
31992
32150
|
}
|
|
32151
|
+
function formatSemanticIndexStatus(status, stage) {
|
|
32152
|
+
if ((status === "loading" || status === "building") && stage === "fingerprint_change") {
|
|
32153
|
+
return "Rebuilding (model changed)";
|
|
32154
|
+
}
|
|
32155
|
+
return status;
|
|
32156
|
+
}
|
|
32157
|
+
function formatSemanticRefreshing(refreshingCount) {
|
|
32158
|
+
if (!Number.isFinite(refreshingCount) || refreshingCount <= 0)
|
|
32159
|
+
return null;
|
|
32160
|
+
if (refreshingCount > 20)
|
|
32161
|
+
return "Ready (many files refreshing)";
|
|
32162
|
+
return `Ready (${refreshingCount} file(s) refreshing)`;
|
|
32163
|
+
}
|
|
31993
32164
|
function formatBytes(bytes) {
|
|
31994
32165
|
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
31995
32166
|
return "0 B";
|
|
@@ -32041,6 +32212,7 @@ function coerceAftStatus(response) {
|
|
|
32041
32212
|
files: readOptionalNumber(semanticIndex.files),
|
|
32042
32213
|
entries_done: readOptionalNumber(semanticIndex.entries_done),
|
|
32043
32214
|
entries_total: readOptionalNumber(semanticIndex.entries_total),
|
|
32215
|
+
refreshing_count: readNumber(semanticIndex.refreshing_count),
|
|
32044
32216
|
entries: readOptionalNumber(semanticIndex.entries),
|
|
32045
32217
|
dimension: readOptionalNumber(semanticIndex.dimension),
|
|
32046
32218
|
error: readNullableString(semanticIndex.error)
|
|
@@ -32086,9 +32258,13 @@ function formatStatusMarkdown(status) {
|
|
|
32086
32258
|
`- **Trigrams:** ${formatCount(status.search_index.trigrams)}`,
|
|
32087
32259
|
"",
|
|
32088
32260
|
"### Semantic index",
|
|
32089
|
-
`- **Status:** \`${status.semantic_index.status}
|
|
32090
|
-
`- **Entries:** ${formatCount(status.semantic_index.entries)}`
|
|
32261
|
+
`- **Status:** \`${formatSemanticIndexStatus(status.semantic_index.status, status.semantic_index.stage)}\``
|
|
32091
32262
|
];
|
|
32263
|
+
const refreshing = formatSemanticRefreshing(status.semantic_index.refreshing_count);
|
|
32264
|
+
if (refreshing) {
|
|
32265
|
+
lines.push(`- **Refresh:** ${refreshing}`);
|
|
32266
|
+
}
|
|
32267
|
+
lines.push(`- **Entries:** ${formatCount(status.semantic_index.entries)}`);
|
|
32092
32268
|
if (status.semantic_index.backend) {
|
|
32093
32269
|
lines.push(`- **Backend:** ${status.semantic_index.backend}`);
|
|
32094
32270
|
}
|
|
@@ -32197,24 +32373,28 @@ function getState() {
|
|
|
32197
32373
|
}
|
|
32198
32374
|
return g[GLOBAL_KEY];
|
|
32199
32375
|
}
|
|
32200
|
-
var
|
|
32376
|
+
var runningCleanups = false;
|
|
32201
32377
|
async function runCleanups(reason) {
|
|
32202
|
-
if (
|
|
32203
|
-
return;
|
|
32204
|
-
shuttingDown = true;
|
|
32205
|
-
const state = getState();
|
|
32206
|
-
if (state.cleanups.size === 0)
|
|
32378
|
+
if (runningCleanups)
|
|
32207
32379
|
return;
|
|
32208
|
-
|
|
32209
|
-
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
|
|
32213
|
-
|
|
32214
|
-
|
|
32215
|
-
|
|
32216
|
-
|
|
32217
|
-
|
|
32380
|
+
runningCleanups = true;
|
|
32381
|
+
try {
|
|
32382
|
+
const state = getState();
|
|
32383
|
+
if (state.cleanups.size === 0)
|
|
32384
|
+
return;
|
|
32385
|
+
log2(`Shutdown triggered by ${reason} — running ${state.cleanups.size} cleanup(s)`);
|
|
32386
|
+
const cleanups = Array.from(state.cleanups);
|
|
32387
|
+
state.cleanups.clear();
|
|
32388
|
+
await Promise.allSettled(cleanups.map(async (fn) => {
|
|
32389
|
+
try {
|
|
32390
|
+
await fn();
|
|
32391
|
+
} catch (err) {
|
|
32392
|
+
log2(`Cleanup error: ${err.message}`);
|
|
32393
|
+
}
|
|
32394
|
+
}));
|
|
32395
|
+
} finally {
|
|
32396
|
+
runningCleanups = false;
|
|
32397
|
+
}
|
|
32218
32398
|
}
|
|
32219
32399
|
function installProcessHandlers() {
|
|
32220
32400
|
const state = getState();
|
|
@@ -32249,6 +32429,17 @@ import * as path2 from "node:path";
|
|
|
32249
32429
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
32250
32430
|
var z2 = tool2.schema;
|
|
32251
32431
|
var optionalInt = (min, max) => z2.number().int().min(min).max(max).optional();
|
|
32432
|
+
function isEmptyParam(value) {
|
|
32433
|
+
if (value === undefined || value === null)
|
|
32434
|
+
return true;
|
|
32435
|
+
if (typeof value === "string")
|
|
32436
|
+
return value.length === 0;
|
|
32437
|
+
if (Array.isArray(value))
|
|
32438
|
+
return value.length === 0;
|
|
32439
|
+
if (typeof value === "object")
|
|
32440
|
+
return Object.keys(value).length === 0;
|
|
32441
|
+
return false;
|
|
32442
|
+
}
|
|
32252
32443
|
var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
|
|
32253
32444
|
callers: 60000,
|
|
32254
32445
|
trace_to: 60000,
|
|
@@ -32257,7 +32448,7 @@ var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
|
|
|
32257
32448
|
impact: 60000,
|
|
32258
32449
|
grep: 60000,
|
|
32259
32450
|
glob: 60000,
|
|
32260
|
-
semantic_search:
|
|
32451
|
+
semantic_search: 60000
|
|
32261
32452
|
};
|
|
32262
32453
|
function timeoutForCommand(command) {
|
|
32263
32454
|
return LONG_RUNNING_COMMAND_TIMEOUT_MS[command];
|
|
@@ -32569,9 +32760,9 @@ function astTools(ctx) {
|
|
|
32569
32760
|
pattern: args.pattern,
|
|
32570
32761
|
lang: args.lang
|
|
32571
32762
|
};
|
|
32572
|
-
if (args.paths)
|
|
32763
|
+
if (!isEmptyParam(args.paths))
|
|
32573
32764
|
params.paths = args.paths;
|
|
32574
|
-
if (args.globs)
|
|
32765
|
+
if (!isEmptyParam(args.globs))
|
|
32575
32766
|
params.globs = args.globs;
|
|
32576
32767
|
if (args.contextLines !== undefined)
|
|
32577
32768
|
params.context = Number(args.contextLines);
|
|
@@ -32689,9 +32880,9 @@ ${hint}`;
|
|
|
32689
32880
|
rewrite: args.rewrite,
|
|
32690
32881
|
lang: args.lang
|
|
32691
32882
|
};
|
|
32692
|
-
if (args.paths)
|
|
32883
|
+
if (!isEmptyParam(args.paths))
|
|
32693
32884
|
params.paths = args.paths;
|
|
32694
|
-
if (args.globs)
|
|
32885
|
+
if (!isEmptyParam(args.globs))
|
|
32695
32886
|
params.globs = args.globs;
|
|
32696
32887
|
params.dry_run = args.dryRun === true;
|
|
32697
32888
|
const response = await callBridge(ctx, context, "ast_replace", params);
|
|
@@ -33053,7 +33244,14 @@ function applyUpdateChunks(originalContent, filePath, chunks) {
|
|
|
33053
33244
|
lineIndex = contextIdx + 1;
|
|
33054
33245
|
}
|
|
33055
33246
|
if (chunk.old_lines.length === 0) {
|
|
33056
|
-
|
|
33247
|
+
let insertionIdx;
|
|
33248
|
+
if (chunk.change_context) {
|
|
33249
|
+
insertionIdx = lineIndex;
|
|
33250
|
+
} else if (originalLines.length > 0 && originalLines[originalLines.length - 1] === "") {
|
|
33251
|
+
insertionIdx = originalLines.length - 1;
|
|
33252
|
+
} else {
|
|
33253
|
+
insertionIdx = originalLines.length;
|
|
33254
|
+
}
|
|
33057
33255
|
replacements.push([insertionIdx, 0, chunk.new_lines]);
|
|
33058
33256
|
continue;
|
|
33059
33257
|
}
|
|
@@ -34038,6 +34236,10 @@ function inferBeforeStart(ops, from, beforeLen) {
|
|
|
34038
34236
|
return beforeLen;
|
|
34039
34237
|
}
|
|
34040
34238
|
var z7 = tool7.schema;
|
|
34239
|
+
var DIAGNOSTICS_PARAM_DESCRIPTION = "When true, wait up to 3 seconds for fresh LSP diagnostics on the edited file and include them in the result. Defaults to the configured `lsp.diagnostics_on_edit` value (false unless configured); per-call true/false overrides. Use aft_inspect to check diagnostics across a batch of edits or before tests/commits.";
|
|
34240
|
+
function diagnosticsOnEditDefault(ctx) {
|
|
34241
|
+
return ctx.config.lsp?.diagnostics_on_edit ?? false;
|
|
34242
|
+
}
|
|
34041
34243
|
var READ_DESCRIPTION = `Read file contents or list directory entries.
|
|
34042
34244
|
|
|
34043
34245
|
Use either startLine/endLine OR offset/limit to read a section of a file.
|
|
@@ -34055,8 +34257,7 @@ Examples:
|
|
|
34055
34257
|
Read lines 50-100: { "filePath": "src/app.ts", "startLine": 50, "endLine": 100 }
|
|
34056
34258
|
Read 30 lines from line 200: { "filePath": "src/app.ts", "offset": 200, "limit": 30 }
|
|
34057
34259
|
List directory: { "filePath": "src/" }
|
|
34058
|
-
|
|
34059
|
-
Returns: Line-numbered file content string. For directories: newline-joined sorted entries. For binary files: size/message string.`;
|
|
34260
|
+
`;
|
|
34060
34261
|
function createReadTool(ctx) {
|
|
34061
34262
|
return {
|
|
34062
34263
|
description: READ_DESCRIPTION,
|
|
@@ -34181,24 +34382,26 @@ function getWriteDescription(editToolName) {
|
|
|
34181
34382
|
|
|
34182
34383
|
Automatically creates parent directories. Backs up existing files before overwriting.
|
|
34183
34384
|
If the project has a formatter configured (biome, prettier, rustfmt, etc.), the file
|
|
34184
|
-
is auto-formatted after writing.
|
|
34385
|
+
is auto-formatted after writing. Edits return as soon as the write completes unless
|
|
34386
|
+
the configured \`lsp.diagnostics_on_edit\` default or a per-call \`diagnostics: true\`
|
|
34387
|
+
asks for legacy sync-wait behavior. Call \`aft_inspect\` afterward to check
|
|
34388
|
+
diagnostics across a batch of edits.
|
|
34185
34389
|
|
|
34186
34390
|
**Behavior:**
|
|
34187
34391
|
- Creates parent directories automatically (no need to mkdir first)
|
|
34188
34392
|
- Existing files are backed up before overwriting (recoverable via aft_safety undo)
|
|
34189
34393
|
- Auto-formats using project formatter if configured (biome.json, .prettierrc, etc.)
|
|
34190
|
-
-
|
|
34394
|
+
- LSP diagnostics follow \`lsp.diagnostics_on_edit\` by default; pass \`diagnostics\` to override per call
|
|
34191
34395
|
- Use this for creating new files or completely replacing file contents
|
|
34192
|
-
- For partial edits (find/replace), use the \`${editToolName}\` tool instead
|
|
34193
|
-
|
|
34194
|
-
Returns: Status message string (for example: "Created new file. Auto-formatted.") with optional inline LSP error lines.`;
|
|
34396
|
+
- For partial edits (find/replace), use the \`${editToolName}\` tool instead`;
|
|
34195
34397
|
}
|
|
34196
34398
|
function createWriteTool(ctx, editToolName = "edit") {
|
|
34197
34399
|
return {
|
|
34198
34400
|
description: getWriteDescription(editToolName),
|
|
34199
34401
|
args: {
|
|
34200
34402
|
filePath: z7.string().describe("Path to the file to write (absolute or relative to project root)"),
|
|
34201
|
-
content: z7.string().describe("The full content to write to the file")
|
|
34403
|
+
content: z7.string().describe("The full content to write to the file"),
|
|
34404
|
+
diagnostics: z7.boolean().optional().describe(DIAGNOSTICS_PARAM_DESCRIPTION)
|
|
34202
34405
|
},
|
|
34203
34406
|
execute: async (args, context) => {
|
|
34204
34407
|
const file2 = args.filePath;
|
|
@@ -34220,8 +34423,8 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
34220
34423
|
file: filePath,
|
|
34221
34424
|
content,
|
|
34222
34425
|
create_dirs: true,
|
|
34223
|
-
diagnostics:
|
|
34224
|
-
|
|
34426
|
+
diagnostics: args.diagnostics ?? diagnosticsOnEditDefault(ctx),
|
|
34427
|
+
include_diff_content: true
|
|
34225
34428
|
});
|
|
34226
34429
|
if (data.success === false) {
|
|
34227
34430
|
throw new Error(data.message || "write failed");
|
|
@@ -34251,7 +34454,7 @@ LSP errors detected, please fix:
|
|
|
34251
34454
|
if (pendingServers && pendingServers.length > 0) {
|
|
34252
34455
|
output += `
|
|
34253
34456
|
|
|
34254
|
-
Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete;
|
|
34457
|
+
Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete; call aft_inspect for a checkpoint diagnostics snapshot.`;
|
|
34255
34458
|
}
|
|
34256
34459
|
if (exitedServers && exitedServers.length > 0) {
|
|
34257
34460
|
output += `
|
|
@@ -34333,11 +34536,8 @@ Note: Modes 6 and 7 are options on mode 5 (find/replace) — they require \`oldS
|
|
|
34333
34536
|
- Auto-formats using project formatter if configured
|
|
34334
34537
|
- Tree-sitter syntax validation on all edits
|
|
34335
34538
|
- Symbol replace includes decorators, attributes, and doc comments in range
|
|
34336
|
-
-
|
|
34337
|
-
|
|
34338
|
-
Returns: JSON string for the selected edit mode. Edits may append inline LSP error lines.
|
|
34339
|
-
|
|
34340
|
-
Common response fields: success (boolean), diff (object with before/after), backup_id (string), syntax_valid (boolean). Exact fields vary by mode.`;
|
|
34539
|
+
- Edits return as soon as the write completes unless \`lsp.diagnostics_on_edit\` or a per-call \`diagnostics: true\` requests legacy sync-wait behavior. Call \`aft_inspect\` afterward to check diagnostics across a batch of edits.
|
|
34540
|
+
- Response is a JSON string for the selected edit mode; key fields include success, diff, backup_id, syntax_valid, and mode-specific fields.`;
|
|
34341
34541
|
}
|
|
34342
34542
|
function createEditTool(ctx, writeToolName = "write") {
|
|
34343
34543
|
return {
|
|
@@ -34352,7 +34552,8 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
34352
34552
|
content: z7.string().optional().describe("Replacement content for symbol mode or operations[].command='write'. For whole-file writes, use the `write` tool."),
|
|
34353
34553
|
appendContent: z7.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
34354
34554
|
edits: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Batch edits — array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
|
|
34355
|
-
operations: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Transaction — 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)")
|
|
34555
|
+
operations: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Transaction — 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)"),
|
|
34556
|
+
diagnostics: z7.boolean().optional().describe(DIAGNOSTICS_PARAM_DESCRIPTION)
|
|
34356
34557
|
},
|
|
34357
34558
|
execute: async (args, context) => {
|
|
34358
34559
|
const argsRecord = args;
|
|
@@ -34448,8 +34649,8 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
34448
34649
|
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.";
|
|
34449
34650
|
throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
|
|
34450
34651
|
}
|
|
34451
|
-
params.diagnostics =
|
|
34452
|
-
params.
|
|
34652
|
+
params.diagnostics = args.diagnostics ?? diagnosticsOnEditDefault(ctx);
|
|
34653
|
+
params.include_diff_content = true;
|
|
34453
34654
|
const data = await callBridge(ctx, context, command, params);
|
|
34454
34655
|
if (data.success && data.diff) {
|
|
34455
34656
|
const diff = data.diff;
|
|
@@ -34474,7 +34675,12 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
34474
34675
|
});
|
|
34475
34676
|
}
|
|
34476
34677
|
}
|
|
34477
|
-
|
|
34678
|
+
const agentData = { ...data };
|
|
34679
|
+
if (agentData.diff && typeof agentData.diff === "object") {
|
|
34680
|
+
const d = agentData.diff;
|
|
34681
|
+
agentData.diff = { additions: d.additions ?? 0, deletions: d.deletions ?? 0 };
|
|
34682
|
+
}
|
|
34683
|
+
let result = JSON.stringify(agentData);
|
|
34478
34684
|
const globSkipNote = formatGlobSkipReasonsNote(data.format_skip_reasons);
|
|
34479
34685
|
if (globSkipNote)
|
|
34480
34686
|
result += `
|
|
@@ -34502,7 +34708,7 @@ ${diagLines}`;
|
|
|
34502
34708
|
if (pendingServers && pendingServers.length > 0) {
|
|
34503
34709
|
result += `
|
|
34504
34710
|
|
|
34505
|
-
Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete;
|
|
34711
|
+
Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete; call aft_inspect for a checkpoint diagnostics snapshot.`;
|
|
34506
34712
|
}
|
|
34507
34713
|
if (exitedServers && exitedServers.length > 0) {
|
|
34508
34714
|
result += `
|
|
@@ -34563,15 +34769,17 @@ Example patch:
|
|
|
34563
34769
|
- You must include a header with your intended action (Add/Delete/Update)
|
|
34564
34770
|
- You must prefix new lines with \`+\` even when creating a new file
|
|
34565
34771
|
|
|
34566
|
-
|
|
34772
|
+
Edits return as soon as the write completes unless \`lsp.diagnostics_on_edit\` or a per-call \`diagnostics: true\` requests legacy sync-wait behavior. Call \`aft_inspect\` afterward to check diagnostics across a batch of edits.`;
|
|
34567
34773
|
function createApplyPatchTool(ctx) {
|
|
34568
34774
|
return {
|
|
34569
34775
|
description: APPLY_PATCH_DESCRIPTION,
|
|
34570
34776
|
args: {
|
|
34571
|
-
patchText: z7.string().describe("The full patch text including Begin/End markers")
|
|
34777
|
+
patchText: z7.string().describe("The full patch text including Begin/End markers"),
|
|
34778
|
+
diagnostics: z7.boolean().optional().describe(DIAGNOSTICS_PARAM_DESCRIPTION)
|
|
34572
34779
|
},
|
|
34573
34780
|
execute: async (args, context) => {
|
|
34574
34781
|
const patchText = args.patchText;
|
|
34782
|
+
const diagnostics = args.diagnostics ?? diagnosticsOnEditDefault(ctx);
|
|
34575
34783
|
if (!patchText)
|
|
34576
34784
|
throw new Error("'patchText' is required");
|
|
34577
34785
|
let hunks;
|
|
@@ -34651,8 +34859,8 @@ function createApplyPatchTool(ctx) {
|
|
|
34651
34859
|
file: filePath,
|
|
34652
34860
|
content,
|
|
34653
34861
|
create_dirs: true,
|
|
34654
|
-
diagnostics
|
|
34655
|
-
|
|
34862
|
+
diagnostics,
|
|
34863
|
+
include_diff_content: true,
|
|
34656
34864
|
multi_file_write_paths: multiFileWritePaths
|
|
34657
34865
|
});
|
|
34658
34866
|
const wrDiff = writeResult.diff;
|
|
@@ -34671,7 +34879,7 @@ function createApplyPatchTool(ctx) {
|
|
|
34671
34879
|
const filePath2 = path4.resolve(context.directory, hunk.path);
|
|
34672
34880
|
if (fs6.existsSync(filePath2)) {
|
|
34673
34881
|
try {
|
|
34674
|
-
|
|
34882
|
+
fs6.rmSync(filePath2, { force: true });
|
|
34675
34883
|
} catch {}
|
|
34676
34884
|
}
|
|
34677
34885
|
}
|
|
@@ -34704,8 +34912,8 @@ function createApplyPatchTool(ctx) {
|
|
|
34704
34912
|
file: targetPath,
|
|
34705
34913
|
content: newContent,
|
|
34706
34914
|
create_dirs: true,
|
|
34707
|
-
diagnostics
|
|
34708
|
-
|
|
34915
|
+
diagnostics,
|
|
34916
|
+
include_diff_content: true,
|
|
34709
34917
|
multi_file_write_paths: multiFileWritePaths
|
|
34710
34918
|
});
|
|
34711
34919
|
const diags = writeResult.lsp_diagnostics;
|
|
@@ -34978,7 +35186,7 @@ function aftPrefixedTools(ctx) {
|
|
|
34978
35186
|
file: filePath,
|
|
34979
35187
|
content: normalizedArgs.content,
|
|
34980
35188
|
create_dirs: normalizedArgs.create_dirs !== false,
|
|
34981
|
-
diagnostics:
|
|
35189
|
+
diagnostics: normalizedArgs.diagnostics ?? diagnosticsOnEditDefault(ctx)
|
|
34982
35190
|
};
|
|
34983
35191
|
const response = await callBridge(ctx, context, "write", writeParams);
|
|
34984
35192
|
if (response.success === false) {
|
|
@@ -35067,44 +35275,169 @@ function importTools(ctx) {
|
|
|
35067
35275
|
};
|
|
35068
35276
|
}
|
|
35069
35277
|
|
|
35070
|
-
// src/tools/
|
|
35278
|
+
// src/tools/inspect.ts
|
|
35071
35279
|
import { tool as tool9 } from "@opencode-ai/plugin";
|
|
35072
35280
|
var z9 = tool9.schema;
|
|
35073
|
-
function
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35281
|
+
function asRecord2(value) {
|
|
35282
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
35283
|
+
return;
|
|
35284
|
+
return value;
|
|
35285
|
+
}
|
|
35286
|
+
function asNumber(value) {
|
|
35287
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
35288
|
+
}
|
|
35289
|
+
function asString(value) {
|
|
35290
|
+
return typeof value === "string" ? value : undefined;
|
|
35291
|
+
}
|
|
35292
|
+
function asStringArray(value) {
|
|
35293
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
35294
|
+
}
|
|
35295
|
+
function diagnosticsServerSummary(section) {
|
|
35296
|
+
const pending = asStringArray(section.servers_pending);
|
|
35297
|
+
const notInstalled = asStringArray(section.servers_not_installed);
|
|
35298
|
+
const parts = [];
|
|
35299
|
+
if (pending.length > 0)
|
|
35300
|
+
parts.push(`pending: ${pending.join(", ")}`);
|
|
35301
|
+
if (notInstalled.length > 0)
|
|
35302
|
+
parts.push(`not installed: ${notInstalled.join(", ")}`);
|
|
35303
|
+
return parts.length > 0 ? parts.join("; ") : "none reported";
|
|
35304
|
+
}
|
|
35305
|
+
function formatDiagnosticsSummary(summary) {
|
|
35306
|
+
const section = asRecord2(summary?.diagnostics);
|
|
35307
|
+
if (!section)
|
|
35308
|
+
return;
|
|
35309
|
+
const errors3 = asNumber(section.errors);
|
|
35310
|
+
const warnings = asNumber(section.warnings);
|
|
35311
|
+
const info = asNumber(section.info);
|
|
35312
|
+
const hints = asNumber(section.hints);
|
|
35313
|
+
const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
|
|
35314
|
+
const counts = `${errors3 ?? 0} errors, ${warnings ?? 0} warnings, ${info ?? 0} info, ${hints ?? 0} hints`;
|
|
35315
|
+
const status = asString(section.status);
|
|
35316
|
+
if (status === "pending") {
|
|
35317
|
+
return hasCounts ? `diagnostics: ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics: pending (servers: ${diagnosticsServerSummary(section)})`;
|
|
35318
|
+
}
|
|
35319
|
+
if (status === "incomplete") {
|
|
35320
|
+
return hasCounts ? `diagnostics: ${counts} (incomplete — servers: ${diagnosticsServerSummary(section)})` : `diagnostics: unavailable (status incomplete; servers: ${diagnosticsServerSummary(section)})`;
|
|
35321
|
+
}
|
|
35322
|
+
if (hasCounts) {
|
|
35323
|
+
return `diagnostics: ${counts}`;
|
|
35324
|
+
}
|
|
35325
|
+
return;
|
|
35326
|
+
}
|
|
35327
|
+
function formatDiagnosticLocation(diagnostic) {
|
|
35328
|
+
const file2 = asString(diagnostic.file) ?? "(unknown file)";
|
|
35329
|
+
const line = asNumber(diagnostic.line);
|
|
35330
|
+
const column = asNumber(diagnostic.column);
|
|
35331
|
+
if (line === undefined)
|
|
35332
|
+
return file2;
|
|
35333
|
+
if (column === undefined)
|
|
35334
|
+
return `${file2}:${line}`;
|
|
35335
|
+
return `${file2}:${line}:${column}`;
|
|
35336
|
+
}
|
|
35337
|
+
function formatDiagnosticsDetails(details) {
|
|
35338
|
+
const diagnostics = Array.isArray(details?.diagnostics) ? details.diagnostics.map(asRecord2).filter(Boolean) : [];
|
|
35339
|
+
return diagnostics.map((diagnostic) => {
|
|
35340
|
+
const severity = asString(diagnostic.severity) ?? "information";
|
|
35341
|
+
const message = asString(diagnostic.message) ?? "(no message)";
|
|
35342
|
+
const source = asString(diagnostic.source);
|
|
35343
|
+
const suffix = source ? ` [${source}]` : "";
|
|
35344
|
+
return `${formatDiagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`;
|
|
35345
|
+
});
|
|
35346
|
+
}
|
|
35347
|
+
function renderInspectDiagnostics(response) {
|
|
35348
|
+
const lines = [];
|
|
35349
|
+
const summaryLine = formatDiagnosticsSummary(asRecord2(response.summary));
|
|
35350
|
+
if (summaryLine)
|
|
35351
|
+
lines.push(summaryLine);
|
|
35352
|
+
const detailLines = formatDiagnosticsDetails(asRecord2(response.details));
|
|
35353
|
+
if (detailLines.length > 0) {
|
|
35354
|
+
lines.push("diagnostics details:", ...detailLines.map((line) => `- ${line}`));
|
|
35355
|
+
}
|
|
35356
|
+
return lines.join(`
|
|
35357
|
+
`);
|
|
35358
|
+
}
|
|
35359
|
+
function appendRenderedDiagnostics(text, response) {
|
|
35360
|
+
if (/^diagnostics[: ]/im.test(text))
|
|
35361
|
+
return text;
|
|
35362
|
+
const diagnostics = renderInspectDiagnostics(response);
|
|
35363
|
+
if (!diagnostics)
|
|
35364
|
+
return text;
|
|
35365
|
+
return text ? `${text}
|
|
35366
|
+
|
|
35367
|
+
${diagnostics}` : diagnostics;
|
|
35368
|
+
}
|
|
35369
|
+
function arg(schema) {
|
|
35370
|
+
return schema;
|
|
35371
|
+
}
|
|
35372
|
+
function normalizeStringOrArray(value) {
|
|
35373
|
+
return isEmptyParam(value) ? undefined : value;
|
|
35374
|
+
}
|
|
35375
|
+
function inspectToolSurfaceEnabled(config2) {
|
|
35376
|
+
return (config2.tool_surface ?? "recommended") !== "minimal" && config2.inspect?.enabled !== false;
|
|
35377
|
+
}
|
|
35378
|
+
function createInspectTier2IdleScheduler(options) {
|
|
35379
|
+
const timers = new Map;
|
|
35380
|
+
const setTimer = options.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
35381
|
+
const clearTimer = options.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
35382
|
+
const clear = (sessionID) => {
|
|
35383
|
+
const timer = timers.get(sessionID);
|
|
35384
|
+
if (!timer)
|
|
35385
|
+
return;
|
|
35386
|
+
clearTimer(timer);
|
|
35387
|
+
timers.delete(sessionID);
|
|
35388
|
+
};
|
|
35389
|
+
const clearAll = () => {
|
|
35390
|
+
for (const timer of timers.values()) {
|
|
35391
|
+
clearTimer(timer);
|
|
35392
|
+
}
|
|
35393
|
+
timers.clear();
|
|
35394
|
+
};
|
|
35395
|
+
const schedule = (sessionID) => {
|
|
35396
|
+
if (!options.isEnabled())
|
|
35397
|
+
return;
|
|
35398
|
+
clear(sessionID);
|
|
35399
|
+
const idleMinutes = options.idleMinutes() ?? 4;
|
|
35400
|
+
const delayMs = Math.max(0, idleMinutes * 60 * 1000);
|
|
35401
|
+
const timer = setTimer(() => {
|
|
35402
|
+
timers.delete(sessionID);
|
|
35403
|
+
options.run(sessionID).catch((err) => {
|
|
35404
|
+
options.warn?.(`inspect_tier2_run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
35405
|
+
});
|
|
35406
|
+
}, delayMs);
|
|
35407
|
+
timers.set(sessionID, timer);
|
|
35408
|
+
};
|
|
35409
|
+
return { schedule, clear, clearAll };
|
|
35410
|
+
}
|
|
35411
|
+
function inspectTools(ctx) {
|
|
35412
|
+
const inspectTool = {
|
|
35413
|
+
description: "Codebase health snapshot. One call returns summary stats for: TODOs, diagnostics, file/symbol metrics, dead code, unused exports, code duplicates. Pass `sections` for per-category drill-down details.\n\n" + "Categories run in tiers — Tier 1 (todos, metrics) return synchronously from cache. Tier 2 (dead_code, unused_exports, duplicates) run as background scans triggered on session idle; calls may return cached `stale_categories: [...]` results or `pending_categories: [...]` while a refresh is in progress (waits up to 1s for fresh data before falling back to cached).\n\n" + `Use when: starting work on unfamiliar code, after multi-edit batches to check diagnostics, before a refactor, before review, or to verify cleanup completeness.
|
|
35414
|
+
|
|
35415
|
+
` + "Treat `dead_code` as a hint, not proof: reachability is call-based, so symbols reached only via method dispatch or referenced only in type position may be false positives — verify before deleting.",
|
|
35077
35416
|
args: {
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
waitMs: optionalInt(1, 1e4).describe("Wait up to N ms (max 10000) for push diagnostics. Push-only servers like bash-language-server and yaml-language-server — use after an edit.")
|
|
35417
|
+
sections: arg(z9.union([z9.string(), z9.array(z9.string())]).optional().describe("Categories to include in detailed drill-down (e.g. 'todos' or ['todos', 'dead_code']). Use 'all' for every active category. Omit for summary-only mode.")),
|
|
35418
|
+
scope: arg(z9.union([z9.string(), z9.array(z9.string())]).optional().describe("Restrict scan/results to paths under this scope (file or directory, absolute or relative to project root). Tier 1 scopes the scan; Tier 2 scans project-wide and applies scope as a result filter.")),
|
|
35419
|
+
topK: arg(z9.number().int().positive().max(100).optional().describe("Max drill-down items per category. Default 20, max 100."))
|
|
35082
35420
|
},
|
|
35083
35421
|
execute: async (args, context) => {
|
|
35084
|
-
const
|
|
35085
|
-
const
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
if (filePath !== undefined)
|
|
35091
|
-
params.file = filePath;
|
|
35092
|
-
if (directory !== undefined)
|
|
35093
|
-
params.directory = directory;
|
|
35094
|
-
if (args.severity !== undefined)
|
|
35095
|
-
params.severity = args.severity;
|
|
35096
|
-
if (args.waitMs !== undefined)
|
|
35097
|
-
params.wait_ms = args.waitMs;
|
|
35098
|
-
const result = await callBridge(ctx, context, "lsp_diagnostics", params);
|
|
35099
|
-
if (result.success === false) {
|
|
35100
|
-
throw new Error(result.message || "lsp_diagnostics failed");
|
|
35422
|
+
const sections = normalizeStringOrArray(args.sections);
|
|
35423
|
+
const scope = normalizeStringOrArray(args.scope);
|
|
35424
|
+
const topK = args.topK === undefined || args.topK === null ? undefined : args.topK;
|
|
35425
|
+
const response = await callBridge(ctx, context, "inspect", { sections, scope, topK });
|
|
35426
|
+
if (response.success === false) {
|
|
35427
|
+
throw new Error(response.message || "inspect failed");
|
|
35101
35428
|
}
|
|
35102
|
-
|
|
35429
|
+
if (typeof response.text === "string") {
|
|
35430
|
+
return appendRenderedDiagnostics(response.text, response);
|
|
35431
|
+
}
|
|
35432
|
+
const diagnostics = renderInspectDiagnostics(response);
|
|
35433
|
+
const json2 = JSON.stringify(response, null, 2);
|
|
35434
|
+
return diagnostics ? `${json2}
|
|
35435
|
+
|
|
35436
|
+
${diagnostics}` : json2;
|
|
35103
35437
|
}
|
|
35104
35438
|
};
|
|
35105
|
-
const hoisting = ctx.config.hoist_builtin_tools !== false;
|
|
35106
35439
|
return {
|
|
35107
|
-
|
|
35440
|
+
aft_inspect: inspectTool
|
|
35108
35441
|
};
|
|
35109
35442
|
}
|
|
35110
35443
|
|
|
@@ -35113,18 +35446,18 @@ import { tool as tool10 } from "@opencode-ai/plugin";
|
|
|
35113
35446
|
var z10 = tool10.schema;
|
|
35114
35447
|
function navigationTools(ctx) {
|
|
35115
35448
|
return {
|
|
35116
|
-
|
|
35117
|
-
description: `
|
|
35449
|
+
aft_callgraph: {
|
|
35450
|
+
description: `Answer code-relationship questions from a real call graph — instead of grep + read chains. Reach for this whenever the question is about how symbols connect: who calls X, what X calls, what breaks if X changes, how execution reaches X, or how a value flows.
|
|
35118
35451
|
|
|
35119
35452
|
` + `Ops:
|
|
35120
|
-
` + `- 'call_tree': See what a function calls (forward traversal). Use to understand dependencies before modifying a function.
|
|
35121
35453
|
` + `- 'callers': Find all call sites of a symbol. Use before renaming or changing a function's signature.
|
|
35122
|
-
` + `- '
|
|
35123
|
-
` + `- '
|
|
35124
|
-
` + `- '
|
|
35454
|
+
` + `- 'impact': What breaks if a symbol changes — affected callers with signatures and entry-point status (blast radius). Use before a risky edit.
|
|
35455
|
+
` + `- 'call_tree': What a function calls (forward traversal). Use to understand a function's dependencies before modifying it.
|
|
35456
|
+
` + `- 'trace_to': How execution reaches a function from entry points (routes, exports, main). Use to understand context around deeply-nested code.
|
|
35457
|
+
` + `- 'trace_to_symbol': Shortest call path from one symbol to another. Requires 'toSymbol'. If multiple targets match, the error returns candidate files; retry with 'toFile' to disambiguate.
|
|
35125
35458
|
` + `- 'trace_data': Follow a value through variable assignments and function parameters across files. Requires 'symbol' (scope to trace from) and 'expression'.
|
|
35126
35459
|
|
|
35127
|
-
` + `All ops require both 'filePath' and 'symbol'.
|
|
35460
|
+
` + `All ops require both 'filePath' and 'symbol'. 'expression' is additionally required for trace_data; 'toSymbol' for trace_to_symbol.
|
|
35128
35461
|
|
|
35129
35462
|
`,
|
|
35130
35463
|
args: {
|
|
@@ -35174,7 +35507,7 @@ function getCallID3(ctx) {
|
|
|
35174
35507
|
return c.callID ?? c.callId ?? c.call_id;
|
|
35175
35508
|
}
|
|
35176
35509
|
function buildZoomTitle(args) {
|
|
35177
|
-
if (args.targets
|
|
35510
|
+
if (!isEmptyParam(args.targets)) {
|
|
35178
35511
|
if (Array.isArray(args.targets)) {
|
|
35179
35512
|
if (args.targets.length === 1 && args.targets[0]) {
|
|
35180
35513
|
return `${args.targets[0].filePath}#${args.targets[0].symbol}`;
|
|
@@ -35303,14 +35636,42 @@ function readingTools(ctx) {
|
|
|
35303
35636
|
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)")
|
|
35304
35637
|
},
|
|
35305
35638
|
execute: async (args, context) => {
|
|
35306
|
-
const
|
|
35307
|
-
|
|
35308
|
-
|
|
35309
|
-
|
|
35639
|
+
const hasTargetsProvided = (t) => {
|
|
35640
|
+
if (isEmptyParam(t))
|
|
35641
|
+
return false;
|
|
35642
|
+
const entryEmpty = (entry) => {
|
|
35643
|
+
if (!entry || typeof entry !== "object")
|
|
35644
|
+
return true;
|
|
35645
|
+
const fp = entry.filePath;
|
|
35646
|
+
const sym = entry.symbol;
|
|
35647
|
+
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
35648
|
+
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
35649
|
+
return fpEmpty && symEmpty;
|
|
35650
|
+
};
|
|
35651
|
+
if (Array.isArray(t))
|
|
35652
|
+
return !t.every(entryEmpty);
|
|
35653
|
+
return !entryEmpty(t);
|
|
35654
|
+
};
|
|
35655
|
+
const hasFilePath = !isEmptyParam(args.filePath);
|
|
35656
|
+
const hasUrl = !isEmptyParam(args.url);
|
|
35657
|
+
const hasTargets = hasTargetsProvided(args.targets);
|
|
35658
|
+
const hasSymbols = !isEmptyParam(args.symbols);
|
|
35310
35659
|
const zoomCallID = getCallID3(context);
|
|
35311
35660
|
if (zoomCallID) {
|
|
35312
35661
|
const title = buildZoomTitle(args);
|
|
35313
|
-
|
|
35662
|
+
const display = { title };
|
|
35663
|
+
if (hasFilePath)
|
|
35664
|
+
display.filePath = args.filePath;
|
|
35665
|
+
if (hasUrl)
|
|
35666
|
+
display.url = args.url;
|
|
35667
|
+
if (hasSymbols) {
|
|
35668
|
+
display.symbols = typeof args.symbols === "string" ? args.symbols : JSON.stringify(args.symbols);
|
|
35669
|
+
}
|
|
35670
|
+
if (hasTargets)
|
|
35671
|
+
display.targets = JSON.stringify(args.targets);
|
|
35672
|
+
if (args.contextLines !== undefined)
|
|
35673
|
+
display.contextLines = args.contextLines;
|
|
35674
|
+
storeToolMetadata(context.sessionID, zoomCallID, { title, metadata: display });
|
|
35314
35675
|
}
|
|
35315
35676
|
if (hasTargets) {
|
|
35316
35677
|
if (hasFilePath || hasUrl || hasSymbols) {
|
|
@@ -35560,14 +35921,14 @@ function refactoringTools(ctx) {
|
|
|
35560
35921
|
},
|
|
35561
35922
|
execute: async (args, context) => {
|
|
35562
35923
|
const op = args.op;
|
|
35563
|
-
if ((op === "move" || op === "inline") &&
|
|
35924
|
+
if ((op === "move" || op === "inline") && isEmptyParam(args.symbol)) {
|
|
35564
35925
|
throw new Error(`'symbol' is required for '${op}' op`);
|
|
35565
35926
|
}
|
|
35566
|
-
if (op === "move" &&
|
|
35927
|
+
if (op === "move" && isEmptyParam(args.destination)) {
|
|
35567
35928
|
throw new Error("'destination' is required for 'move' op");
|
|
35568
35929
|
}
|
|
35569
35930
|
if (op === "extract") {
|
|
35570
|
-
if (
|
|
35931
|
+
if (isEmptyParam(args.name))
|
|
35571
35932
|
throw new Error("'name' is required for 'extract' op");
|
|
35572
35933
|
if (args.startLine === undefined)
|
|
35573
35934
|
throw new Error("'startLine' is required for 'extract' op");
|
|
@@ -35749,7 +36110,7 @@ function expandTilde(input) {
|
|
|
35749
36110
|
}
|
|
35750
36111
|
return input;
|
|
35751
36112
|
}
|
|
35752
|
-
function
|
|
36113
|
+
function arg2(schema) {
|
|
35753
36114
|
return schema;
|
|
35754
36115
|
}
|
|
35755
36116
|
function formatGrepOutput(response) {
|
|
@@ -35847,9 +36208,9 @@ function searchTools(ctx) {
|
|
|
35847
36208
|
const grepTool = {
|
|
35848
36209
|
description: "Search file contents using regular expressions. Returns matching lines with file paths and line numbers (no surrounding context lines — use `read` for that). Always case-sensitive. Capped at 100 matches; if you hit the cap, narrow with `path` or `include` and re-run.",
|
|
35849
36210
|
args: {
|
|
35850
|
-
pattern:
|
|
35851
|
-
include:
|
|
35852
|
-
path:
|
|
36211
|
+
pattern: arg2(exports_external.string().describe("Regular expression pattern to search for")),
|
|
36212
|
+
include: arg2(exports_external.string().optional().describe("File pattern to include (e.g. '*.ts', '*.{ts,tsx}')")),
|
|
36213
|
+
path: arg2(exports_external.string().optional().describe("Directory to search (absolute or relative to project root)"))
|
|
35853
36214
|
},
|
|
35854
36215
|
execute: async (args, context) => {
|
|
35855
36216
|
const pattern = String(args.pattern);
|
|
@@ -35886,8 +36247,8 @@ function searchTools(ctx) {
|
|
|
35886
36247
|
const globTool = {
|
|
35887
36248
|
description: "Find files matching a glob pattern. Returns matching file paths sorted by modification time.",
|
|
35888
36249
|
args: {
|
|
35889
|
-
pattern:
|
|
35890
|
-
path:
|
|
36250
|
+
pattern: arg2(exports_external.string().describe("Glob pattern to match (e.g. '**/*.ts', 'src/**/*.test.*')")),
|
|
36251
|
+
path: arg2(exports_external.string().optional().describe("Directory to search (absolute or relative to project root)"))
|
|
35891
36252
|
},
|
|
35892
36253
|
execute: async (args, context) => {
|
|
35893
36254
|
let globPattern = expandTilde(String(args.pattern));
|
|
@@ -35944,48 +36305,87 @@ function searchTools(ctx) {
|
|
|
35944
36305
|
// src/tools/semantic.ts
|
|
35945
36306
|
import { tool as tool14 } from "@opencode-ai/plugin";
|
|
35946
36307
|
var z14 = tool14.schema;
|
|
35947
|
-
function
|
|
36308
|
+
function semanticHonestyNote(response) {
|
|
36309
|
+
const notes = [];
|
|
36310
|
+
if (response.more_available === true)
|
|
36311
|
+
notes.push("more results available");
|
|
36312
|
+
if (response.engine_capped === true)
|
|
36313
|
+
notes.push("enumeration capped");
|
|
36314
|
+
if (response.fully_degraded === true)
|
|
36315
|
+
notes.push("fully degraded");
|
|
36316
|
+
if (response.complete === false)
|
|
36317
|
+
notes.push("partial/incomplete");
|
|
36318
|
+
return notes.length > 0 ? `Search status: ${notes.join("; ")}.` : undefined;
|
|
36319
|
+
}
|
|
36320
|
+
function arg3(schema) {
|
|
35948
36321
|
return schema;
|
|
35949
36322
|
}
|
|
35950
36323
|
function semanticTools(ctx) {
|
|
35951
36324
|
const searchTool = {
|
|
35952
36325
|
description: [
|
|
35953
|
-
"Find
|
|
36326
|
+
"Find code with unified semantic, lexical, literal, and regex search. Returns ranked symbol/file results or exact matching lines, with routing metadata.",
|
|
35954
36327
|
"",
|
|
35955
36328
|
"When to reach for it:",
|
|
35956
36329
|
"- Exploring an unfamiliar area: 'where is rate limiting handled', 'how does auth flow work'",
|
|
35957
36330
|
"- Concept doesn't appear as a literal string: 'retry logic', 'cache invalidation', 'graceful shutdown'",
|
|
35958
36331
|
"- Filename-shaped concepts: 'the bridge spawn helper', 'the session detection module'",
|
|
35959
|
-
"-
|
|
36332
|
+
"- Regex-shaped or exact text queries when you want AFT to classify and route automatically",
|
|
35960
36333
|
"- You know roughly what the function does but not what it's named",
|
|
35961
36334
|
"",
|
|
35962
36335
|
"When NOT to use:",
|
|
35963
|
-
"- You
|
|
36336
|
+
"- You need exhaustive literal enumeration → use grep directly",
|
|
35964
36337
|
"- You want the file/module structure → use aft_outline",
|
|
35965
|
-
"- You're following a call chain → use
|
|
36338
|
+
"- You're following a call chain → use aft_callgraph",
|
|
35966
36339
|
"",
|
|
35967
|
-
"
|
|
36340
|
+
"Set hint to 'regex', 'literal', 'semantic', or 'auto' to override or document routing intent."
|
|
35968
36341
|
].join(`
|
|
35969
36342
|
`),
|
|
35970
36343
|
args: {
|
|
35971
|
-
query:
|
|
35972
|
-
topK:
|
|
36344
|
+
query: arg3(z14.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
|
|
36345
|
+
topK: arg3(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)")),
|
|
36346
|
+
hint: arg3(z14.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'."))
|
|
35973
36347
|
},
|
|
35974
36348
|
execute: async (args, context) => {
|
|
35975
|
-
|
|
35976
|
-
|
|
36349
|
+
if (isEmptyParam(args.query) || typeof args.query !== "string" || args.query.trim().length === 0) {
|
|
36350
|
+
throw new Error("semantic_search: invalid params: `query` must be a non-empty string");
|
|
36351
|
+
}
|
|
36352
|
+
const query = args.query;
|
|
36353
|
+
const hint = typeof args.hint === "string" ? args.hint : undefined;
|
|
36354
|
+
if (hint !== "semantic") {
|
|
36355
|
+
const denied = await askGrepPermission(context, query);
|
|
36356
|
+
if (denied)
|
|
36357
|
+
return permissionDeniedResponse(denied);
|
|
36358
|
+
}
|
|
36359
|
+
const bridgeParams = {
|
|
36360
|
+
query,
|
|
35977
36361
|
top_k: args.topK ?? 10
|
|
35978
|
-
}
|
|
36362
|
+
};
|
|
36363
|
+
if (hint)
|
|
36364
|
+
bridgeParams.hint = hint;
|
|
36365
|
+
const response = await callBridge(ctx, context, "semantic_search", bridgeParams);
|
|
35979
36366
|
if (response.success === false) {
|
|
35980
|
-
|
|
35981
|
-
|
|
35982
|
-
}
|
|
35983
|
-
|
|
35984
|
-
|
|
35985
|
-
if (typeof response.text === "string") {
|
|
35986
|
-
return response.text
|
|
36367
|
+
const message = typeof response.message === "string" && response.message.length > 0 ? response.message : "semantic_search failed";
|
|
36368
|
+
const code = typeof response.code === "string" && response.code.length > 0 ? response.code : undefined;
|
|
36369
|
+
throw new Error(code ? `semantic_search: ${code} — ${message}` : message);
|
|
36370
|
+
}
|
|
36371
|
+
const honestyNote = semanticHonestyNote(response);
|
|
36372
|
+
if (typeof response.text === "string" && response.status === "disabled" && Array.isArray(response.results) && response.results.length === 0) {
|
|
36373
|
+
return honestyNote ? `${response.text}
|
|
36374
|
+
${honestyNote}` : response.text;
|
|
36375
|
+
}
|
|
36376
|
+
const structured = JSON.stringify(response, null, 2);
|
|
36377
|
+
if (typeof response.text === "string" && response.text.length > 0) {
|
|
36378
|
+
const display = honestyNote ? `${response.text}
|
|
36379
|
+
${honestyNote}` : response.text;
|
|
36380
|
+
return `${display}
|
|
36381
|
+
|
|
36382
|
+
Structured response:
|
|
36383
|
+
${structured}`;
|
|
35987
36384
|
}
|
|
35988
|
-
return
|
|
36385
|
+
return honestyNote ? `${honestyNote}
|
|
36386
|
+
|
|
36387
|
+
Structured response:
|
|
36388
|
+
${structured}` : structured;
|
|
35989
36389
|
}
|
|
35990
36390
|
};
|
|
35991
36391
|
return {
|
|
@@ -36115,19 +36515,27 @@ function buildWorkflowHints(opts) {
|
|
|
36115
36515
|
const hasZoom = !opts.disabledTools.has("aft_zoom");
|
|
36116
36516
|
const hasGrep = opts.toolSurface !== "minimal" && !opts.disabledTools.has(grepName);
|
|
36117
36517
|
const hasSearch = opts.toolSurface !== "minimal" && opts.semanticEnabled && !opts.disabledTools.has("aft_search");
|
|
36118
|
-
const hasNavigate = opts.toolSurface === "all" && !opts.disabledTools.has("
|
|
36518
|
+
const hasNavigate = opts.toolSurface === "all" && !opts.disabledTools.has("aft_callgraph");
|
|
36519
|
+
const hasInspect = opts.toolSurface !== "minimal" && !opts.disabledTools.has("aft_inspect");
|
|
36119
36520
|
const hasBash = !opts.disabledTools.has(bashName);
|
|
36120
36521
|
const hasBgBash = hasBash && opts.bashBackgroundEnabled && !opts.disabledTools.has(bashStatusName);
|
|
36121
36522
|
if (hasOutline && hasZoom) {
|
|
36122
36523
|
sections.push(`**Web/URL access**: \`aft_outline({ target: url })\` first for structure, then \`aft_zoom({ url, symbols: "<heading>" })\` for the specific section.`);
|
|
36123
36524
|
}
|
|
36124
36525
|
if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
|
|
36125
|
-
|
|
36126
|
-
|
|
36526
|
+
if (hasSearch) {
|
|
36527
|
+
const grepFallback = hasGrep ? ` Use \`${grepName}\` directly only when you need exhaustive enumeration of literal text (every TODO, every import of X) without ranking.` : "";
|
|
36528
|
+
sections.push(`**Code exploration**: \`aft_search\` is the primary code-search tool. It auto-routes by query shape — exact identifiers, regex, error messages, natural language all use the same call. Very short queries fall back to literal scans; pass \`hint: "regex"\` / \`hint: "literal"\` / \`hint: "semantic"\` to override routing if needed. Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).${grepFallback}`);
|
|
36529
|
+
} else {
|
|
36530
|
+
sections.push(`**Code exploration**: \`${grepName}\` to locate → \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).`);
|
|
36531
|
+
}
|
|
36532
|
+
}
|
|
36533
|
+
if (hasInspect) {
|
|
36534
|
+
sections.push("**Codebase health & diagnostics**: AFT does not surface compile/type errors automatically after edits — pull them with `aft_inspect`. Run it after a batch of edits and before you run tests or commit, when starting in unfamiliar code, or before a refactor/review. One call summarizes diagnostics (compile/type errors), TODOs, metrics, dead code, unused exports, and duplicates; pass `sections` for focused drill-down and `scope` to actively pull diagnostics for a specific file or directory. Its diagnostics are a fast checkpoint, not the authority — a clean `tsc` / `cargo check` / `pyright` run is the real gate. Treat `stale_categories` as a genuine stale-cache signal while an async Tier 2 refresh catches up.");
|
|
36127
36535
|
}
|
|
36128
36536
|
if (hasNavigate) {
|
|
36129
36537
|
sections.push([
|
|
36130
|
-
"Use `
|
|
36538
|
+
"Use `aft_callgraph` for code-relationship questions instead of grep + read chains:",
|
|
36131
36539
|
"- `callers` — find all call sites before changing a function signature",
|
|
36132
36540
|
"- `impact` — blast radius (which functions/files will need updates)",
|
|
36133
36541
|
"- `trace_to` — how execution reaches this code from entry points (routes, exports, main)",
|
|
@@ -36244,13 +36652,13 @@ var PLUGIN_VERSION = (() => {
|
|
|
36244
36652
|
return "0.0.0";
|
|
36245
36653
|
}
|
|
36246
36654
|
})();
|
|
36247
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
36655
|
+
var ANNOUNCEMENT_VERSION = "0.33.0";
|
|
36248
36656
|
var ANNOUNCEMENT_FEATURES = [
|
|
36249
|
-
"
|
|
36250
|
-
|
|
36251
|
-
|
|
36252
|
-
"
|
|
36253
|
-
"
|
|
36657
|
+
"New `aft_inspect` — one call for codebase health: diagnostics, metrics, TODOs, dead code, unused exports, and duplicates.",
|
|
36658
|
+
"Diagnostics now flow through `aft_inspect` (run it after a batch of edits) instead of arriving automatically on every edit.",
|
|
36659
|
+
"`aft_navigate` is renamed to `aft_callgraph`; the Rust call graph now resolves cross-file callers.",
|
|
36660
|
+
"Edits no longer echo the whole file back to the agent — much lower token cost per edit.",
|
|
36661
|
+
"Batch of `aft_search` correctness fixes and undo-history/SSRF/Windows hardening."
|
|
36254
36662
|
];
|
|
36255
36663
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU";
|
|
36256
36664
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -36462,7 +36870,10 @@ ${lines}
|
|
|
36462
36870
|
});
|
|
36463
36871
|
}
|
|
36464
36872
|
const rpcServer = new AftRpcServer(configOverrides.storage_dir, input.directory);
|
|
36465
|
-
|
|
36873
|
+
let clearInspectTier2Idle = () => {};
|
|
36874
|
+
registerShutdownCleanup(async () => {
|
|
36875
|
+
autoUpdateAbort.abort();
|
|
36876
|
+
clearInspectTier2Idle();
|
|
36466
36877
|
await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
|
|
36467
36878
|
try {
|
|
36468
36879
|
rpcServer.stop();
|
|
@@ -36562,7 +36973,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36562
36973
|
}
|
|
36563
36974
|
const surface = aftConfig.tool_surface ?? "recommended";
|
|
36564
36975
|
const ALL_ONLY_TOOLS = new Set([
|
|
36565
|
-
"
|
|
36976
|
+
"aft_callgraph",
|
|
36566
36977
|
"aft_delete",
|
|
36567
36978
|
"aft_move",
|
|
36568
36979
|
"aft_transform",
|
|
@@ -36577,9 +36988,9 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36577
36988
|
...navigationTools(ctx),
|
|
36578
36989
|
...surface !== "minimal" && astTools(ctx),
|
|
36579
36990
|
...surface !== "minimal" && aftConfig.semantic_search === true && semanticTools(ctx),
|
|
36991
|
+
...inspectToolSurfaceEnabled(aftConfig) && inspectTools(ctx),
|
|
36580
36992
|
...surface !== "minimal" && aftConfig.search_index === true && searchTools(ctx),
|
|
36581
36993
|
...refactoringTools(ctx),
|
|
36582
|
-
...surface !== "minimal" && lspTools(ctx),
|
|
36583
36994
|
...surface !== "minimal" && conflictTools(ctx)
|
|
36584
36995
|
});
|
|
36585
36996
|
if (surface !== "all") {
|
|
@@ -36610,7 +37021,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36610
37021
|
"aft_outline",
|
|
36611
37022
|
"aft_zoom",
|
|
36612
37023
|
"aft_search",
|
|
36613
|
-
"
|
|
37024
|
+
"aft_callgraph",
|
|
37025
|
+
"aft_inspect",
|
|
36614
37026
|
"grep",
|
|
36615
37027
|
"aft_grep",
|
|
36616
37028
|
"bash",
|
|
@@ -36627,6 +37039,20 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36627
37039
|
if (hintsBlock) {
|
|
36628
37040
|
log2(`Workflow hints injected (${hintsBlock.length} chars)`);
|
|
36629
37041
|
}
|
|
37042
|
+
const inspectTier2Idle = createInspectTier2IdleScheduler({
|
|
37043
|
+
isEnabled: () => registeredTools.has("aft_inspect"),
|
|
37044
|
+
idleMinutes: () => aftConfig.inspect?.tier2_idle_minutes,
|
|
37045
|
+
warn: warn2,
|
|
37046
|
+
run: async (sessionID) => {
|
|
37047
|
+
const sessionDir = await getSessionDirectory(input.client, sessionID, input.directory) ?? input.directory;
|
|
37048
|
+
const bridge = ctx.pool.getActiveBridgeForRoot(sessionDir) ?? ctx.pool.getBridge(sessionDir);
|
|
37049
|
+
const response = await bridge.send("inspect_tier2_run", { session_id: sessionID });
|
|
37050
|
+
if (response.success === false) {
|
|
37051
|
+
warn2(response.message || "inspect_tier2_run failed");
|
|
37052
|
+
}
|
|
37053
|
+
}
|
|
37054
|
+
});
|
|
37055
|
+
clearInspectTier2Idle = () => inspectTier2Idle.clearAll();
|
|
36630
37056
|
return {
|
|
36631
37057
|
tool: allTools,
|
|
36632
37058
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
@@ -36636,11 +37062,17 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36636
37062
|
},
|
|
36637
37063
|
event: async (eventInput) => {
|
|
36638
37064
|
await autoUpdateEventHook(eventInput);
|
|
36639
|
-
|
|
36640
|
-
return;
|
|
37065
|
+
const eventType = eventInput.event.type;
|
|
36641
37066
|
const sessionID = extractSessionID(eventInput.event.properties);
|
|
37067
|
+
if ((eventType === "session.deleted" || eventType === "session.shutdown") && sessionID) {
|
|
37068
|
+
inspectTier2Idle.clear(sessionID);
|
|
37069
|
+
return;
|
|
37070
|
+
}
|
|
37071
|
+
if (eventType !== "session.idle")
|
|
37072
|
+
return;
|
|
36642
37073
|
if (!sessionID)
|
|
36643
37074
|
return;
|
|
37075
|
+
inspectTier2Idle.schedule(sessionID);
|
|
36644
37076
|
const sessionDir = await getSessionDirectory(input.client, sessionID, input.directory) ?? input.directory;
|
|
36645
37077
|
await handleIdleBgCompletions({
|
|
36646
37078
|
ctx,
|
|
@@ -36654,6 +37086,10 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36654
37086
|
const sid = messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id;
|
|
36655
37087
|
warmSessionDirectory(input.client, sid, input.directory);
|
|
36656
37088
|
},
|
|
37089
|
+
"tool.execute.before": async (toolInput) => {
|
|
37090
|
+
if (toolInput.sessionID)
|
|
37091
|
+
inspectTier2Idle.clear(toolInput.sessionID);
|
|
37092
|
+
},
|
|
36657
37093
|
"command.execute.before": async (commandInput, _output) => {
|
|
36658
37094
|
if (isTuiMode2() || commandInput.command !== STATUS_COMMAND) {
|
|
36659
37095
|
return;
|
|
@@ -36704,12 +37140,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
36704
37140
|
};
|
|
36705
37141
|
},
|
|
36706
37142
|
dispose: async () => {
|
|
36707
|
-
|
|
36708
|
-
unregisterShutdown();
|
|
36709
|
-
await Promise.allSettled([abortInFlightAutoInstalls(), abortInFlightGithubInstalls()]);
|
|
36710
|
-
rpcServer.stop();
|
|
36711
|
-
await disposeAllPtyTerminals();
|
|
36712
|
-
await pool.shutdown();
|
|
37143
|
+
await runCleanups("dispose");
|
|
36713
37144
|
}
|
|
36714
37145
|
};
|
|
36715
37146
|
}
|