@cortexkit/aft-opencode 0.48.1 → 0.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +944 -428
- package/dist/normalize-schemas.d.ts +6 -4
- package/dist/normalize-schemas.d.ts.map +1 -1
- package/dist/tool-registration.d.ts +12 -0
- package/dist/tool-registration.d.ts.map +1 -0
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/imports.d.ts.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/permissions.d.ts +1 -0
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/reading.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/semantic.d.ts.map +1 -1
- package/dist/workflow-hints.d.ts +10 -0
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -7871,6 +7871,7 @@ function sleep(ms) {
|
|
|
7871
7871
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7872
7872
|
}
|
|
7873
7873
|
// ../aft-bridge/dist/bash-hints.js
|
|
7874
|
+
import * as fs from "node:fs";
|
|
7874
7875
|
import * as os from "node:os";
|
|
7875
7876
|
import * as path from "node:path";
|
|
7876
7877
|
var CONFLICT_HINT = `
|
|
@@ -7879,6 +7880,7 @@ var CONFLICT_HINT = `
|
|
|
7879
7880
|
var GREP_SEARCH_AFT_SEARCH_HINT = "DO NOT search code by running grep/rg in bash — it is unindexed, unranked, and serial. Use the `aft_search` tool instead (it auto-routes concepts, identifiers, regex, and literals).";
|
|
7880
7881
|
var GREP_SEARCH_GREP_HINT = "DO NOT search code by running grep/rg in bash — it is unindexed, unranked, and serial. Use the `grep` tool instead (indexed and ranked).";
|
|
7881
7882
|
var GREP_SEARCH_HINT_PREFIX = "DO NOT search code by running grep/rg in bash —";
|
|
7883
|
+
var GREP_SEARCH_FRESHNESS_WINDOW_MS = 60000;
|
|
7882
7884
|
function maybeAppendConflictsHint(output) {
|
|
7883
7885
|
if (!output.includes("Automatic merge failed; fix conflicts"))
|
|
7884
7886
|
return output;
|
|
@@ -7950,12 +7952,19 @@ function shouldSuppressGrepSearchHint(command, projectRoot) {
|
|
|
7950
7952
|
const operands = collectPathOperands(firstStage, firstToken.end);
|
|
7951
7953
|
if (operands.length === 0)
|
|
7952
7954
|
return false;
|
|
7955
|
+
let sawInProjectOperand = false;
|
|
7953
7956
|
for (const operand of operands) {
|
|
7954
7957
|
if (isDynamicPathOperand(operand))
|
|
7955
7958
|
continue;
|
|
7956
|
-
|
|
7957
|
-
|
|
7959
|
+
const resolvedOperand = resolvePathOperand(effectiveCwd, operand);
|
|
7960
|
+
if (!isPathInsideProject(resolvedRoot, effectiveCwd, operand))
|
|
7961
|
+
continue;
|
|
7962
|
+
sawInProjectOperand = true;
|
|
7963
|
+
if (shouldSuppressResolvedPath(resolvedOperand))
|
|
7964
|
+
return true;
|
|
7958
7965
|
}
|
|
7966
|
+
if (sawInProjectOperand)
|
|
7967
|
+
return false;
|
|
7959
7968
|
}
|
|
7960
7969
|
return sawCodeSearchStatement;
|
|
7961
7970
|
}
|
|
@@ -8054,6 +8063,17 @@ function isPathInsideProject(resolvedRoot, baseCwd, operand) {
|
|
|
8054
8063
|
const rel = path.relative(resolvedRoot, resolved);
|
|
8055
8064
|
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
8056
8065
|
}
|
|
8066
|
+
function shouldSuppressResolvedPath(resolved) {
|
|
8067
|
+
try {
|
|
8068
|
+
const stats = fs.statSync(resolved);
|
|
8069
|
+
if (stats.isFile())
|
|
8070
|
+
return true;
|
|
8071
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
8072
|
+
return ageMs >= 0 && ageMs < GREP_SEARCH_FRESHNESS_WINDOW_MS;
|
|
8073
|
+
} catch {
|
|
8074
|
+
return false;
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8057
8077
|
function splitTopLevelStatements(command) {
|
|
8058
8078
|
const statements = [];
|
|
8059
8079
|
let start = 0;
|
|
@@ -9388,7 +9408,7 @@ function formatDroppedKeyWarnings(dropped) {
|
|
|
9388
9408
|
// ../aft-bridge/dist/downloader.js
|
|
9389
9409
|
import { spawnSync } from "node:child_process";
|
|
9390
9410
|
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
9391
|
-
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
9411
|
+
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync2, mkdirSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, statSync as statSync2, unlinkSync, writeSync } from "node:fs";
|
|
9392
9412
|
import { homedir as homedir3 } from "node:os";
|
|
9393
9413
|
import { join as join3 } from "node:path";
|
|
9394
9414
|
import { Readable } from "node:stream";
|
|
@@ -9421,7 +9441,7 @@ function readBinaryVersion(binaryPath) {
|
|
|
9421
9441
|
const result = spawnSync(binaryPath, ["--version"], {
|
|
9422
9442
|
encoding: "utf-8",
|
|
9423
9443
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9424
|
-
timeout:
|
|
9444
|
+
timeout: 60000
|
|
9425
9445
|
});
|
|
9426
9446
|
const stdoutVersion = result.stdout?.trim();
|
|
9427
9447
|
const stderrVersion = result.stderr?.trim();
|
|
@@ -9626,7 +9646,7 @@ async function acquireDownloadLock(lockPath) {
|
|
|
9626
9646
|
if (code !== "EEXIST")
|
|
9627
9647
|
throw err;
|
|
9628
9648
|
try {
|
|
9629
|
-
const ageMs = Date.now() -
|
|
9649
|
+
const ageMs = Date.now() - statSync2(lockPath).mtimeMs;
|
|
9630
9650
|
if (ageMs > DOWNLOAD_LOCK_STALE_MS) {
|
|
9631
9651
|
rmSync(lockPath, { force: true });
|
|
9632
9652
|
continue;
|
|
@@ -9783,6 +9803,23 @@ async function renameIfPresent(from, to) {
|
|
|
9783
9803
|
throw error2;
|
|
9784
9804
|
}
|
|
9785
9805
|
}
|
|
9806
|
+
// ../aft-bridge/dist/error-contract.js
|
|
9807
|
+
class AftToolError extends Error {
|
|
9808
|
+
code;
|
|
9809
|
+
response;
|
|
9810
|
+
constructor(message, code, response) {
|
|
9811
|
+
const cause = { code, message, response };
|
|
9812
|
+
super(message, { cause });
|
|
9813
|
+
this.name = "AftToolError";
|
|
9814
|
+
this.code = code;
|
|
9815
|
+
this.response = response;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
function toolErrorFromResponse(command, response) {
|
|
9819
|
+
const code = typeof response.code === "string" && response.code.length > 0 ? response.code : "unknown_error";
|
|
9820
|
+
const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
|
|
9821
|
+
return new AftToolError(message, code, response);
|
|
9822
|
+
}
|
|
9786
9823
|
// ../aft-bridge/dist/jsonc.js
|
|
9787
9824
|
function stripJsoncSymbols(value) {
|
|
9788
9825
|
if (Array.isArray(value)) {
|
|
@@ -9799,7 +9836,7 @@ function stripJsoncSymbols(value) {
|
|
|
9799
9836
|
}
|
|
9800
9837
|
// ../aft-bridge/dist/migration.js
|
|
9801
9838
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9802
|
-
import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as
|
|
9839
|
+
import { closeSync as closeSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9803
9840
|
import { homedir as homedir7, tmpdir } from "node:os";
|
|
9804
9841
|
import { basename, dirname as dirname3, join as join7, resolve as resolve4 } from "node:path";
|
|
9805
9842
|
|
|
@@ -10299,7 +10336,7 @@ function acquireConfigMigrationLock(lockDir) {
|
|
|
10299
10336
|
if (code !== "EEXIST")
|
|
10300
10337
|
throw err;
|
|
10301
10338
|
try {
|
|
10302
|
-
const ageMs = Date.now() -
|
|
10339
|
+
const ageMs = Date.now() - statSync3(lockDir).mtimeMs;
|
|
10303
10340
|
if (ageMs > 60000) {
|
|
10304
10341
|
rmSync2(lockDir, { recursive: true, force: true });
|
|
10305
10342
|
continue;
|
|
@@ -10585,7 +10622,7 @@ async function ensureStorageMigrated(opts) {
|
|
|
10585
10622
|
throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
|
|
10586
10623
|
}
|
|
10587
10624
|
// ../aft-bridge/dist/npm-resolver.js
|
|
10588
|
-
import { readdirSync, statSync as
|
|
10625
|
+
import { readdirSync, statSync as statSync4 } from "node:fs";
|
|
10589
10626
|
import { homedir as homedir8 } from "node:os";
|
|
10590
10627
|
import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join8 } from "node:path";
|
|
10591
10628
|
function defaultDeps() {
|
|
@@ -10601,7 +10638,7 @@ function npmBinaryName(platform) {
|
|
|
10601
10638
|
}
|
|
10602
10639
|
function isFile(p) {
|
|
10603
10640
|
try {
|
|
10604
|
-
return
|
|
10641
|
+
return statSync4(p).isFile();
|
|
10605
10642
|
} catch {
|
|
10606
10643
|
return false;
|
|
10607
10644
|
}
|
|
@@ -10704,7 +10741,7 @@ function isNpmAvailable(deps = defaultDeps()) {
|
|
|
10704
10741
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
10705
10742
|
import { execFileSync } from "node:child_process";
|
|
10706
10743
|
import { createHash as createHash3 } from "node:crypto";
|
|
10707
|
-
import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, readlinkSync, realpathSync, rmSync as rmSync3, statSync as
|
|
10744
|
+
import { chmodSync as chmodSync3, closeSync as closeSync4, copyFileSync as copyFileSync3, createWriteStream as createWriteStream2, existsSync as existsSync6, lstatSync, mkdirSync as mkdirSync5, openSync as openSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, readlinkSync, realpathSync, rmSync as rmSync3, statSync as statSync5, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
10708
10745
|
import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute4, join as join9, relative as relative2, resolve as resolve5, win32 } from "node:path";
|
|
10709
10746
|
import { Readable as Readable2 } from "node:stream";
|
|
10710
10747
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
@@ -10837,7 +10874,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
10837
10874
|
abandoned = true;
|
|
10838
10875
|
} else {
|
|
10839
10876
|
try {
|
|
10840
|
-
const ageMs = Date.now() -
|
|
10877
|
+
const ageMs = Date.now() - statSync5(stagingDir).mtimeMs;
|
|
10841
10878
|
abandoned = ageMs > STALE_LOCK_MS;
|
|
10842
10879
|
} catch {
|
|
10843
10880
|
abandoned = true;
|
|
@@ -11270,7 +11307,7 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
|
11270
11307
|
function readOnnxInstalledMeta(installDir) {
|
|
11271
11308
|
const path2 = join9(installDir, ONNX_INSTALLED_META_FILE);
|
|
11272
11309
|
try {
|
|
11273
|
-
if (!
|
|
11310
|
+
if (!statSync5(path2).isFile())
|
|
11274
11311
|
return null;
|
|
11275
11312
|
const raw = readFileSync6(path2, "utf8");
|
|
11276
11313
|
const parsed = JSON.parse(raw);
|
|
@@ -11321,7 +11358,7 @@ ${new Date().toISOString()}
|
|
|
11321
11358
|
const parsed = Number.parseInt(firstLine, 10);
|
|
11322
11359
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
11323
11360
|
owningPid = parsed;
|
|
11324
|
-
lockMtimeMs =
|
|
11361
|
+
lockMtimeMs = statSync5(lockPath).mtimeMs;
|
|
11325
11362
|
} catch {
|
|
11326
11363
|
return tryClaim();
|
|
11327
11364
|
}
|
|
@@ -11404,6 +11441,488 @@ function isProcessAlive(pid) {
|
|
|
11404
11441
|
return true;
|
|
11405
11442
|
}
|
|
11406
11443
|
}
|
|
11444
|
+
// ../aft-bridge/dist/path-aliases.js
|
|
11445
|
+
class InvalidRequestError extends AftToolError {
|
|
11446
|
+
constructor(message) {
|
|
11447
|
+
super(message, "invalid_request", {
|
|
11448
|
+
success: false,
|
|
11449
|
+
code: "invalid_request",
|
|
11450
|
+
message
|
|
11451
|
+
});
|
|
11452
|
+
this.name = "InvalidRequestError";
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
function isWellFormedUnicodeString(value) {
|
|
11456
|
+
for (let index = 0;index < value.length; index++) {
|
|
11457
|
+
const codeUnit = value.charCodeAt(index);
|
|
11458
|
+
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
11459
|
+
const next = value.charCodeAt(index + 1);
|
|
11460
|
+
if (next < 56320 || next > 57343 || Number.isNaN(next))
|
|
11461
|
+
return false;
|
|
11462
|
+
index++;
|
|
11463
|
+
} else if (codeUnit >= 56320 && codeUnit <= 57343) {
|
|
11464
|
+
return false;
|
|
11465
|
+
}
|
|
11466
|
+
}
|
|
11467
|
+
return true;
|
|
11468
|
+
}
|
|
11469
|
+
function hasOwn(record, key) {
|
|
11470
|
+
return Object.hasOwn(record, key);
|
|
11471
|
+
}
|
|
11472
|
+
function invalidPathValue(property) {
|
|
11473
|
+
throw new InvalidRequestError(`'${property}' must be a non-empty well-formed Unicode string`);
|
|
11474
|
+
}
|
|
11475
|
+
function pathValue(record, property) {
|
|
11476
|
+
const value = record[property];
|
|
11477
|
+
if (typeof value !== "string" || value.length === 0 || !isWellFormedUnicodeString(value)) {
|
|
11478
|
+
invalidPathValue(property);
|
|
11479
|
+
}
|
|
11480
|
+
return value;
|
|
11481
|
+
}
|
|
11482
|
+
function normalizeAliasPair(record, canonical, legacy, required) {
|
|
11483
|
+
const hasCanonical = hasOwn(record, canonical);
|
|
11484
|
+
const hasLegacy = hasOwn(record, legacy);
|
|
11485
|
+
if (!hasCanonical && !hasLegacy) {
|
|
11486
|
+
if (required) {
|
|
11487
|
+
throw new InvalidRequestError(`'${canonical}' is required`);
|
|
11488
|
+
}
|
|
11489
|
+
return;
|
|
11490
|
+
}
|
|
11491
|
+
if (hasCanonical && hasLegacy) {
|
|
11492
|
+
let canonicalValue;
|
|
11493
|
+
let legacyValue;
|
|
11494
|
+
try {
|
|
11495
|
+
canonicalValue = pathValue(record, canonical);
|
|
11496
|
+
legacyValue = pathValue(record, legacy);
|
|
11497
|
+
} catch {
|
|
11498
|
+
throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must both be non-empty well-formed Unicode strings`);
|
|
11499
|
+
}
|
|
11500
|
+
if (canonicalValue !== legacyValue) {
|
|
11501
|
+
throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must contain equal decoded strings`);
|
|
11502
|
+
}
|
|
11503
|
+
delete record[legacy];
|
|
11504
|
+
return;
|
|
11505
|
+
}
|
|
11506
|
+
if (hasCanonical) {
|
|
11507
|
+
pathValue(record, canonical);
|
|
11508
|
+
return;
|
|
11509
|
+
}
|
|
11510
|
+
record[canonical] = pathValue(record, legacy);
|
|
11511
|
+
delete record[legacy];
|
|
11512
|
+
}
|
|
11513
|
+
function validateOptionalCanonicalPath(record, property) {
|
|
11514
|
+
if (hasOwn(record, property))
|
|
11515
|
+
pathValue(record, property);
|
|
11516
|
+
}
|
|
11517
|
+
function normalizeZoomTargets(record) {
|
|
11518
|
+
if (!hasOwn(record, "targets"))
|
|
11519
|
+
return;
|
|
11520
|
+
const targets = record.targets;
|
|
11521
|
+
const normalizeTarget = (target, index) => {
|
|
11522
|
+
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
11523
|
+
throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
|
|
11524
|
+
}
|
|
11525
|
+
const source = target;
|
|
11526
|
+
const emptyTarget = source.symbol === "" && (hasOwn(source, "path") && source.path === "" || hasOwn(source, "filePath") && source.filePath === "");
|
|
11527
|
+
if (emptyTarget)
|
|
11528
|
+
return { ...source };
|
|
11529
|
+
const normalized = { ...source };
|
|
11530
|
+
try {
|
|
11531
|
+
normalizeAliasPair(normalized, "path", "filePath", true);
|
|
11532
|
+
} catch (error2) {
|
|
11533
|
+
if (error2 instanceof InvalidRequestError) {
|
|
11534
|
+
throw new InvalidRequestError(error2.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
11535
|
+
}
|
|
11536
|
+
throw error2;
|
|
11537
|
+
}
|
|
11538
|
+
return normalized;
|
|
11539
|
+
};
|
|
11540
|
+
if (Array.isArray(targets)) {
|
|
11541
|
+
if (targets.length === 0)
|
|
11542
|
+
return;
|
|
11543
|
+
record.targets = targets.map(normalizeTarget);
|
|
11544
|
+
return;
|
|
11545
|
+
}
|
|
11546
|
+
if (targets && typeof targets === "object") {
|
|
11547
|
+
record.targets = normalizeTarget(targets, 0);
|
|
11548
|
+
}
|
|
11549
|
+
}
|
|
11550
|
+
function bareToolName(toolName) {
|
|
11551
|
+
const bare = toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
|
|
11552
|
+
if (bare === "read" || bare === "write" || bare === "edit" || bare === "zoom" || bare === "callgraph" || bare === "safety" || bare === "move" || bare === "import" || bare === "refactor" || bare === "grep" || bare === "search" || bare === "conflicts") {
|
|
11553
|
+
return bare;
|
|
11554
|
+
}
|
|
11555
|
+
return;
|
|
11556
|
+
}
|
|
11557
|
+
function prepareCanonicalPathArguments(toolName, rawArguments) {
|
|
11558
|
+
if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
|
|
11559
|
+
throw new InvalidRequestError("tool arguments must be an object");
|
|
11560
|
+
}
|
|
11561
|
+
const tool = bareToolName(toolName);
|
|
11562
|
+
const record = { ...rawArguments };
|
|
11563
|
+
if (!tool)
|
|
11564
|
+
return record;
|
|
11565
|
+
switch (tool) {
|
|
11566
|
+
case "read":
|
|
11567
|
+
case "write":
|
|
11568
|
+
case "edit":
|
|
11569
|
+
case "move":
|
|
11570
|
+
case "import":
|
|
11571
|
+
case "refactor":
|
|
11572
|
+
normalizeAliasPair(record, "path", "filePath", true);
|
|
11573
|
+
break;
|
|
11574
|
+
case "zoom":
|
|
11575
|
+
normalizeAliasPair(record, "path", "filePath", false);
|
|
11576
|
+
normalizeZoomTargets(record);
|
|
11577
|
+
break;
|
|
11578
|
+
case "callgraph":
|
|
11579
|
+
normalizeAliasPair(record, "path", "filePath", true);
|
|
11580
|
+
normalizeAliasPair(record, "toPath", "toFile", false);
|
|
11581
|
+
break;
|
|
11582
|
+
case "safety":
|
|
11583
|
+
normalizeAliasPair(record, "path", "filePath", false);
|
|
11584
|
+
break;
|
|
11585
|
+
case "grep":
|
|
11586
|
+
case "search":
|
|
11587
|
+
case "conflicts":
|
|
11588
|
+
validateOptionalCanonicalPath(record, "path");
|
|
11589
|
+
break;
|
|
11590
|
+
}
|
|
11591
|
+
return record;
|
|
11592
|
+
}
|
|
11593
|
+
var EDIT_ROOT_COMPATIBILITY_KEYS = new Set([
|
|
11594
|
+
"oldString",
|
|
11595
|
+
"newString",
|
|
11596
|
+
"replaceAll",
|
|
11597
|
+
"occurrence"
|
|
11598
|
+
]);
|
|
11599
|
+
var EDIT_ROOT_CANONICAL_KEYS = new Set(["path", "appendContent", "edits", "symbol", "content"]);
|
|
11600
|
+
var EDIT_ITEM_KEYS = new Set([
|
|
11601
|
+
"oldString",
|
|
11602
|
+
"newString",
|
|
11603
|
+
"replaceAll",
|
|
11604
|
+
"occurrence",
|
|
11605
|
+
"startLine",
|
|
11606
|
+
"endLine",
|
|
11607
|
+
"content"
|
|
11608
|
+
]);
|
|
11609
|
+
var ASCII_WHITESPACE = /^[\t\n\v\f\r ]+$/;
|
|
11610
|
+
var ASCII_TRIM = /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g;
|
|
11611
|
+
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
11612
|
+
function prepareCanonicalEditArguments(toolName, rawArguments) {
|
|
11613
|
+
if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
|
|
11614
|
+
throw new InvalidRequestError("tool arguments must be an object");
|
|
11615
|
+
}
|
|
11616
|
+
const raw = rawArguments;
|
|
11617
|
+
const record = copyOwnProperties(raw);
|
|
11618
|
+
normalizeEditPathAlias(record);
|
|
11619
|
+
const suppliedLineFields = ["startLine", "endLine"].filter((key) => hasOwn(record, key));
|
|
11620
|
+
if (suppliedLineFields.length > 0) {
|
|
11621
|
+
throw new InvalidRequestError(`edit: top-level ${suppliedLineFields.map((key) => `'${key}'`).join(" and ")} are invalid; ` + "line-range fields are valid only inside 'edits[]'. " + "Use edits: [{ startLine, endLine, content }].");
|
|
11622
|
+
}
|
|
11623
|
+
const isOpenCodeRetiredBoundary = toolName === "aft_edit";
|
|
11624
|
+
const retiredFields = ["file", "mode"].filter((key) => hasOwn(record, key));
|
|
11625
|
+
if (retiredFields.length > 0 && isOpenCodeRetiredBoundary) {
|
|
11626
|
+
throw new InvalidRequestError("aft_edit: the retired `mode`/`file` edit form is no longer supported; use `path` with " + "exactly one of `appendContent`, `edits`, or `symbol` plus `content`.");
|
|
11627
|
+
}
|
|
11628
|
+
const unknownRootKeys = Object.getOwnPropertyNames(record).filter((key) => !EDIT_ROOT_CANONICAL_KEYS.has(key) && !EDIT_ROOT_COMPATIBILITY_KEYS.has(key) && key !== "filePath").sort();
|
|
11629
|
+
if (unknownRootKeys.length > 0) {
|
|
11630
|
+
throw new InvalidRequestError(formatUnknownKeys(unknownRootKeys));
|
|
11631
|
+
}
|
|
11632
|
+
const modes = editModesPresent(record);
|
|
11633
|
+
if (hasOrphanedSymbolContent(record)) {
|
|
11634
|
+
throw new InvalidRequestError("edit: 'content' requires a non-empty string 'symbol' when symbol mode is selected");
|
|
11635
|
+
}
|
|
11636
|
+
if (modes.length > 1) {
|
|
11637
|
+
throw new InvalidRequestError(`edit: conflicting modes: ${modes.join(", ")}. ` + OMIT_OPTIONAL_FIELDS_STEERING);
|
|
11638
|
+
}
|
|
11639
|
+
if (modes.length === 0) {
|
|
11640
|
+
throw new InvalidRequestError("edit: exactly one of `appendContent`, `edits`, or `symbol` plus `content` is required. " + OMIT_OPTIONAL_FIELDS_STEERING);
|
|
11641
|
+
}
|
|
11642
|
+
const mode = modes[0];
|
|
11643
|
+
if (mode === "appendContent") {
|
|
11644
|
+
if (typeof record.appendContent !== "string") {
|
|
11645
|
+
throw new InvalidRequestError("edit: 'appendContent' must be a string");
|
|
11646
|
+
}
|
|
11647
|
+
} else if (mode === "edits") {
|
|
11648
|
+
const parsedEdits = parseEditArray(record.edits);
|
|
11649
|
+
record.edits = parsedEdits.map((item, index) => normalizeEditItem(item, index));
|
|
11650
|
+
} else if (mode === "symbol/content") {
|
|
11651
|
+
if (!hasOwn(record, "symbol") || typeof record.symbol !== "string") {
|
|
11652
|
+
throw new InvalidRequestError("edit: 'symbol' must be a string when symbol mode is selected");
|
|
11653
|
+
}
|
|
11654
|
+
if (!hasOwn(record, "content") || typeof record.content !== "string") {
|
|
11655
|
+
throw new InvalidRequestError("edit: symbol mode requires both 'symbol' and 'content' string properties");
|
|
11656
|
+
}
|
|
11657
|
+
} else {
|
|
11658
|
+
const item = {};
|
|
11659
|
+
for (const key of EDIT_ROOT_COMPATIBILITY_KEYS) {
|
|
11660
|
+
if (hasOwn(record, key))
|
|
11661
|
+
item[key] = record[key];
|
|
11662
|
+
}
|
|
11663
|
+
record.edits = [normalizeEditItem(item, 0)];
|
|
11664
|
+
for (const key of EDIT_ROOT_COMPATIBILITY_KEYS)
|
|
11665
|
+
delete record[key];
|
|
11666
|
+
}
|
|
11667
|
+
validateEditPath(record);
|
|
11668
|
+
return record;
|
|
11669
|
+
}
|
|
11670
|
+
function normalizeEditPathAlias(record) {
|
|
11671
|
+
const hasCanonical = hasOwn(record, "path");
|
|
11672
|
+
const hasLegacy = hasOwn(record, "filePath");
|
|
11673
|
+
if (!hasCanonical && !hasLegacy)
|
|
11674
|
+
return;
|
|
11675
|
+
if (hasCanonical && hasLegacy) {
|
|
11676
|
+
let canonical;
|
|
11677
|
+
let legacy;
|
|
11678
|
+
try {
|
|
11679
|
+
canonical = pathValue(record, "path");
|
|
11680
|
+
legacy = pathValue(record, "filePath");
|
|
11681
|
+
} catch {
|
|
11682
|
+
throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must both be non-empty well-formed Unicode strings");
|
|
11683
|
+
}
|
|
11684
|
+
if (canonical !== legacy) {
|
|
11685
|
+
throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must contain equal decoded strings");
|
|
11686
|
+
}
|
|
11687
|
+
delete record.filePath;
|
|
11688
|
+
return;
|
|
11689
|
+
}
|
|
11690
|
+
if (!hasCanonical) {
|
|
11691
|
+
record.path = pathValue(record, "filePath");
|
|
11692
|
+
delete record.filePath;
|
|
11693
|
+
}
|
|
11694
|
+
}
|
|
11695
|
+
function validateEditPath(record) {
|
|
11696
|
+
if (!hasOwn(record, "path")) {
|
|
11697
|
+
throw new InvalidRequestError("'path' is required");
|
|
11698
|
+
}
|
|
11699
|
+
pathValue(record, "path");
|
|
11700
|
+
}
|
|
11701
|
+
function formatUnknownKeys(keys) {
|
|
11702
|
+
return `Unrecognized keys: ${keys.map((key) => `"${key}"`).join(", ")}`;
|
|
11703
|
+
}
|
|
11704
|
+
function editModesPresent(record) {
|
|
11705
|
+
const hasAppendContent = isNonEmptyString(record.appendContent);
|
|
11706
|
+
if (!hasAppendContent)
|
|
11707
|
+
delete record.appendContent;
|
|
11708
|
+
const hasEdits = normalizeEditArraySentinels(record);
|
|
11709
|
+
if (!hasEdits)
|
|
11710
|
+
delete record.edits;
|
|
11711
|
+
const hasSymbol = isNonEmptyString(record.symbol);
|
|
11712
|
+
if (!hasSymbol) {
|
|
11713
|
+
delete record.symbol;
|
|
11714
|
+
if (record.content === null || record.content === "")
|
|
11715
|
+
delete record.content;
|
|
11716
|
+
} else if (record.content === null) {
|
|
11717
|
+
delete record.content;
|
|
11718
|
+
}
|
|
11719
|
+
const hasSingleEdit = isNonEmptyString(record.oldString);
|
|
11720
|
+
if (!hasSingleEdit) {
|
|
11721
|
+
for (const key of EDIT_ROOT_COMPATIBILITY_KEYS)
|
|
11722
|
+
delete record[key];
|
|
11723
|
+
} else {
|
|
11724
|
+
for (const key of ["newString", "replaceAll", "occurrence"]) {
|
|
11725
|
+
if (record[key] === null)
|
|
11726
|
+
delete record[key];
|
|
11727
|
+
}
|
|
11728
|
+
}
|
|
11729
|
+
const modes = [];
|
|
11730
|
+
if (hasAppendContent)
|
|
11731
|
+
modes.push("appendContent");
|
|
11732
|
+
if (hasEdits)
|
|
11733
|
+
modes.push("edits");
|
|
11734
|
+
if (hasSymbol)
|
|
11735
|
+
modes.push("symbol/content");
|
|
11736
|
+
if (hasSingleEdit)
|
|
11737
|
+
modes.push("oldString/newString");
|
|
11738
|
+
return modes;
|
|
11739
|
+
}
|
|
11740
|
+
function isNonEmptyString(value) {
|
|
11741
|
+
return typeof value === "string" && value.length > 0;
|
|
11742
|
+
}
|
|
11743
|
+
var OMIT_OPTIONAL_FIELDS_STEERING = "Omit unused optional fields entirely; do not send empty strings or empty arrays for them.";
|
|
11744
|
+
function isEditSentinelItem(item) {
|
|
11745
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
11746
|
+
return false;
|
|
11747
|
+
const record = item;
|
|
11748
|
+
if (record.oldString !== "")
|
|
11749
|
+
return false;
|
|
11750
|
+
const newStringEmpty = !hasOwn(record, "newString") || record.newString === "" || record.newString === null;
|
|
11751
|
+
if (!newStringEmpty)
|
|
11752
|
+
return false;
|
|
11753
|
+
return !hasOwn(record, "content") || record.content === "" || record.content === null;
|
|
11754
|
+
}
|
|
11755
|
+
function normalizeEditArraySentinels(record) {
|
|
11756
|
+
const value = record.edits;
|
|
11757
|
+
if (Array.isArray(value)) {
|
|
11758
|
+
const survivors = value.filter((item) => !isEditSentinelItem(item));
|
|
11759
|
+
if (survivors.length === 0)
|
|
11760
|
+
return false;
|
|
11761
|
+
record.edits = survivors;
|
|
11762
|
+
return true;
|
|
11763
|
+
}
|
|
11764
|
+
if (typeof value !== "string" || value.length === 0)
|
|
11765
|
+
return false;
|
|
11766
|
+
try {
|
|
11767
|
+
const parsed = JSON.parse(value);
|
|
11768
|
+
if (!Array.isArray(parsed))
|
|
11769
|
+
return true;
|
|
11770
|
+
const survivors = parsed.filter((item) => !isEditSentinelItem(item));
|
|
11771
|
+
if (survivors.length === 0)
|
|
11772
|
+
return false;
|
|
11773
|
+
record.edits = survivors;
|
|
11774
|
+
return true;
|
|
11775
|
+
} catch {
|
|
11776
|
+
return true;
|
|
11777
|
+
}
|
|
11778
|
+
}
|
|
11779
|
+
function hasOrphanedSymbolContent(record) {
|
|
11780
|
+
return isNonEmptyString(record.content) && !isNonEmptyString(record.symbol);
|
|
11781
|
+
}
|
|
11782
|
+
function parseEditArray(value) {
|
|
11783
|
+
if (typeof value === "string") {
|
|
11784
|
+
let parsed;
|
|
11785
|
+
try {
|
|
11786
|
+
parsed = JSON.parse(value);
|
|
11787
|
+
} catch {
|
|
11788
|
+
throw new InvalidRequestError("edit: 'edits' must contain valid JSON representing an array");
|
|
11789
|
+
}
|
|
11790
|
+
if (!Array.isArray(parsed)) {
|
|
11791
|
+
throw new InvalidRequestError("edit: 'edits' JSON must have an array root");
|
|
11792
|
+
}
|
|
11793
|
+
if (parsed.length === 0) {
|
|
11794
|
+
throw new InvalidRequestError("edit: 'edits' array must not be empty");
|
|
11795
|
+
}
|
|
11796
|
+
return parsed;
|
|
11797
|
+
}
|
|
11798
|
+
if (!Array.isArray(value)) {
|
|
11799
|
+
throw new InvalidRequestError("edit: 'edits' must be a non-empty array");
|
|
11800
|
+
}
|
|
11801
|
+
if (value.length === 0) {
|
|
11802
|
+
throw new InvalidRequestError("edit: 'edits' array must not be empty");
|
|
11803
|
+
}
|
|
11804
|
+
return value;
|
|
11805
|
+
}
|
|
11806
|
+
function normalizeEditItem(value, index) {
|
|
11807
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11808
|
+
throw new InvalidRequestError(`edit: edits[${index}] must be an object`);
|
|
11809
|
+
}
|
|
11810
|
+
const source = value;
|
|
11811
|
+
const item = copyOwnProperties(source);
|
|
11812
|
+
normalizeItemAlias(item, "oldString", "oldText");
|
|
11813
|
+
normalizeItemAlias(item, "newString", "newText");
|
|
11814
|
+
const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
|
|
11815
|
+
const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
|
|
11816
|
+
if (hasFindField && hasRangeField) {
|
|
11817
|
+
throw new InvalidRequestError(`edit: edits[${index}] mixes find/replace and line-range fields`);
|
|
11818
|
+
}
|
|
11819
|
+
if (hasFindField) {
|
|
11820
|
+
if (!hasOwn(item, "oldString") || typeof item.oldString !== "string") {
|
|
11821
|
+
throw new InvalidRequestError(`edit: edits[${index}] requires string 'oldString'`);
|
|
11822
|
+
}
|
|
11823
|
+
if (hasOwn(item, "newString") && typeof item.newString !== "string") {
|
|
11824
|
+
throw new InvalidRequestError(`edit: edits[${index}].newString must be a string`);
|
|
11825
|
+
}
|
|
11826
|
+
coerceEditScalars(item, index);
|
|
11827
|
+
validateEditItemKeys(item, index);
|
|
11828
|
+
return item;
|
|
11829
|
+
}
|
|
11830
|
+
if (hasRangeField) {
|
|
11831
|
+
for (const key of ["startLine", "endLine"]) {
|
|
11832
|
+
const value2 = item[key];
|
|
11833
|
+
if (typeof value2 === "string" && /^[0-9]+$/.test(value2.trim())) {
|
|
11834
|
+
item[key] = Number(value2.trim());
|
|
11835
|
+
}
|
|
11836
|
+
if (!hasOwn(item, key) || !isPositiveSafeInteger(item[key])) {
|
|
11837
|
+
throw new InvalidRequestError(`edit: edits[${index}].${key} must be a positive integer`);
|
|
11838
|
+
}
|
|
11839
|
+
}
|
|
11840
|
+
if (item.startLine > item.endLine) {
|
|
11841
|
+
throw new InvalidRequestError(`edit: edits[${index}] requires startLine <= endLine`);
|
|
11842
|
+
}
|
|
11843
|
+
if (!hasOwn(item, "content") || typeof item.content !== "string") {
|
|
11844
|
+
throw new InvalidRequestError(`edit: edits[${index}] requires string 'content'`);
|
|
11845
|
+
}
|
|
11846
|
+
validateEditItemKeys(item, index);
|
|
11847
|
+
return item;
|
|
11848
|
+
}
|
|
11849
|
+
throw new InvalidRequestError(`edit: edits[${index}] must be a find/replace or line-range item`);
|
|
11850
|
+
}
|
|
11851
|
+
function normalizeItemAlias(item, canonical, legacy) {
|
|
11852
|
+
if (hasOwn(item, legacy)) {
|
|
11853
|
+
if (!hasOwn(item, canonical))
|
|
11854
|
+
item[canonical] = item[legacy];
|
|
11855
|
+
delete item[legacy];
|
|
11856
|
+
}
|
|
11857
|
+
}
|
|
11858
|
+
function validateEditItemKeys(item, index) {
|
|
11859
|
+
const unknown = Object.getOwnPropertyNames(item).filter((key) => !EDIT_ITEM_KEYS.has(key)).sort();
|
|
11860
|
+
if (unknown.length > 0) {
|
|
11861
|
+
throw new InvalidRequestError(`edit: edits[${index}] contains ${formatUnknownKeys(unknown)}`);
|
|
11862
|
+
}
|
|
11863
|
+
}
|
|
11864
|
+
function coerceEditScalars(item, index) {
|
|
11865
|
+
if (hasOwn(item, "replaceAll") && hasOwn(item, "occurrence")) {
|
|
11866
|
+
throw new InvalidRequestError(`edit: edits[${index}] cannot contain both 'replaceAll' and 'occurrence'`);
|
|
11867
|
+
}
|
|
11868
|
+
if (hasOwn(item, "replaceAll"))
|
|
11869
|
+
item.replaceAll = coerceEditBoolean(item.replaceAll, index);
|
|
11870
|
+
if (hasOwn(item, "occurrence")) {
|
|
11871
|
+
const occurrence = coerceEditOccurrence(item.occurrence, index);
|
|
11872
|
+
if (occurrence === undefined)
|
|
11873
|
+
delete item.occurrence;
|
|
11874
|
+
else
|
|
11875
|
+
item.occurrence = occurrence;
|
|
11876
|
+
}
|
|
11877
|
+
}
|
|
11878
|
+
function coerceEditBoolean(value, index) {
|
|
11879
|
+
if (typeof value === "boolean")
|
|
11880
|
+
return value;
|
|
11881
|
+
if (typeof value === "number" && Number.isFinite(value) && (value === 0 || value === 1)) {
|
|
11882
|
+
return value === 1;
|
|
11883
|
+
}
|
|
11884
|
+
if (typeof value === "string") {
|
|
11885
|
+
if (value === "1")
|
|
11886
|
+
return true;
|
|
11887
|
+
if (value === "0")
|
|
11888
|
+
return false;
|
|
11889
|
+
if (/^(?:true|false)$/i.test(value))
|
|
11890
|
+
return value.toLowerCase() === "true";
|
|
11891
|
+
}
|
|
11892
|
+
throw new InvalidRequestError(`edit: edits[${index}].replaceAll must be a boolean, true/false string, or 0/1`);
|
|
11893
|
+
}
|
|
11894
|
+
function coerceEditOccurrence(value, index) {
|
|
11895
|
+
if (value === null)
|
|
11896
|
+
return;
|
|
11897
|
+
if (typeof value === "string") {
|
|
11898
|
+
const trimmed = value.replace(ASCII_TRIM, "");
|
|
11899
|
+
if (trimmed.length === 0 || ASCII_WHITESPACE.test(trimmed))
|
|
11900
|
+
return;
|
|
11901
|
+
if (!/^[+]?[0-9]+$/.test(trimmed)) {
|
|
11902
|
+
throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
|
|
11903
|
+
}
|
|
11904
|
+
try {
|
|
11905
|
+
const parsed = BigInt(trimmed);
|
|
11906
|
+
if (parsed < 1n || parsed > BigInt(MAX_SAFE_INTEGER))
|
|
11907
|
+
throw new Error("out of range");
|
|
11908
|
+
return Number(parsed);
|
|
11909
|
+
} catch {
|
|
11910
|
+
throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
|
|
11911
|
+
}
|
|
11912
|
+
}
|
|
11913
|
+
if (typeof value === "number" && isPositiveSafeInteger(value))
|
|
11914
|
+
return value;
|
|
11915
|
+
throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
|
|
11916
|
+
}
|
|
11917
|
+
function isPositiveSafeInteger(value) {
|
|
11918
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
11919
|
+
}
|
|
11920
|
+
function copyOwnProperties(source) {
|
|
11921
|
+
const copy = Object.create(null);
|
|
11922
|
+
for (const key of Object.getOwnPropertyNames(source))
|
|
11923
|
+
copy[key] = source[key];
|
|
11924
|
+
return copy;
|
|
11925
|
+
}
|
|
11407
11926
|
// ../aft-bridge/dist/project-identity.js
|
|
11408
11927
|
import { createHash as createHash4 } from "node:crypto";
|
|
11409
11928
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -11842,7 +12361,7 @@ class RevivableProjectTransport {
|
|
|
11842
12361
|
}
|
|
11843
12362
|
}
|
|
11844
12363
|
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/client.js
|
|
11845
|
-
import { promises as
|
|
12364
|
+
import { promises as fs3 } from "node:fs";
|
|
11846
12365
|
import { debuglog } from "node:util";
|
|
11847
12366
|
|
|
11848
12367
|
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/auth.js
|
|
@@ -11910,7 +12429,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
|
|
|
11910
12429
|
}
|
|
11911
12430
|
|
|
11912
12431
|
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/connection-file.js
|
|
11913
|
-
import { promises as
|
|
12432
|
+
import { promises as fs2 } from "node:fs";
|
|
11914
12433
|
|
|
11915
12434
|
// ../../node_modules/.bun/@cortexkit+subc-client@0.5.0/node_modules/@cortexkit/subc-client/dist/envelope.js
|
|
11916
12435
|
var PROTOCOL_VERSION = 2;
|
|
@@ -12078,7 +12597,7 @@ function validate(info) {
|
|
|
12078
12597
|
async function verifyOwnerOnly(path2) {
|
|
12079
12598
|
if (process.platform === "win32")
|
|
12080
12599
|
return;
|
|
12081
|
-
const stat2 = await
|
|
12600
|
+
const stat2 = await fs2.stat(path2);
|
|
12082
12601
|
const mode = stat2.mode & 511;
|
|
12083
12602
|
if ((mode & 63) !== 0) {
|
|
12084
12603
|
throw new ConnectionFileError(`connection file ${path2} has insecure permissions 0o${mode.toString(8)}; expected owner-only 0600`);
|
|
@@ -12086,7 +12605,7 @@ async function verifyOwnerOnly(path2) {
|
|
|
12086
12605
|
}
|
|
12087
12606
|
async function readConnectionFile(path2) {
|
|
12088
12607
|
await verifyOwnerOnly(path2);
|
|
12089
|
-
const raw = await
|
|
12608
|
+
const raw = await fs2.readFile(path2, "utf8");
|
|
12090
12609
|
let parsed;
|
|
12091
12610
|
try {
|
|
12092
12611
|
parsed = JSON.parse(raw);
|
|
@@ -13149,7 +13668,7 @@ function isRetryableRouteOpenCode(code) {
|
|
|
13149
13668
|
}
|
|
13150
13669
|
async function connectionFileExists(path2) {
|
|
13151
13670
|
try {
|
|
13152
|
-
await
|
|
13671
|
+
await fs3.access(path2);
|
|
13153
13672
|
return true;
|
|
13154
13673
|
} catch {
|
|
13155
13674
|
return false;
|
|
@@ -31268,7 +31787,7 @@ import {
|
|
|
31268
31787
|
readFileSync as readFileSync11,
|
|
31269
31788
|
renameSync as renameSync6,
|
|
31270
31789
|
rmSync as rmSync5,
|
|
31271
|
-
statSync as
|
|
31790
|
+
statSync as statSync7,
|
|
31272
31791
|
writeFileSync as writeFileSync7
|
|
31273
31792
|
} from "node:fs";
|
|
31274
31793
|
import { dirname as dirname8 } from "node:path";
|
|
@@ -31282,7 +31801,7 @@ var import_comment_json3 = __toESM(require_src2(), 1);
|
|
|
31282
31801
|
|
|
31283
31802
|
// src/hooks/auto-update-checker/checker.ts
|
|
31284
31803
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
31285
|
-
import { existsSync as existsSync10, readFileSync as readFileSync9, statSync as
|
|
31804
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, statSync as statSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
31286
31805
|
import { homedir as homedir13 } from "node:os";
|
|
31287
31806
|
import { dirname as dirname6, isAbsolute as isAbsolute7, join as join13, resolve as resolve8 } from "node:path";
|
|
31288
31807
|
import { fileURLToPath } from "node:url";
|
|
@@ -31409,7 +31928,7 @@ function getLocalDevPath(directory) {
|
|
|
31409
31928
|
}
|
|
31410
31929
|
function findPackageJsonUp(startPath) {
|
|
31411
31930
|
try {
|
|
31412
|
-
const stat2 =
|
|
31931
|
+
const stat2 = statSync6(startPath);
|
|
31413
31932
|
let dir = stat2.isDirectory() ? startPath : dirname6(startPath);
|
|
31414
31933
|
for (let i = 0;i < 10; i++) {
|
|
31415
31934
|
const pkgPath = join13(dir, "package.json");
|
|
@@ -31564,8 +32083,8 @@ function restoreAutoUpdateSnapshot(snapshot) {
|
|
|
31564
32083
|
rmSync4(snapshot.tempDir, { recursive: true, force: true });
|
|
31565
32084
|
}
|
|
31566
32085
|
}
|
|
31567
|
-
function stripPackageNameFromPath(
|
|
31568
|
-
let current =
|
|
32086
|
+
function stripPackageNameFromPath(pathValue2, packageName) {
|
|
32087
|
+
let current = pathValue2;
|
|
31569
32088
|
for (const segment of [...packageName.split("/")].reverse()) {
|
|
31570
32089
|
if (basename3(current) !== segment)
|
|
31571
32090
|
return null;
|
|
@@ -31890,7 +32409,7 @@ function isOrphanedCheckLock(lockPath) {
|
|
|
31890
32409
|
}
|
|
31891
32410
|
}
|
|
31892
32411
|
function readCheckLockState(lockPath) {
|
|
31893
|
-
const fallbackStartedMs =
|
|
32412
|
+
const fallbackStartedMs = statSync7(lockPath).mtimeMs;
|
|
31894
32413
|
try {
|
|
31895
32414
|
const parsed = JSON.parse(readFileSync11(lockPath, "utf-8"));
|
|
31896
32415
|
const pid = Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0 ? parsed.pid ?? null : null;
|
|
@@ -32024,7 +32543,7 @@ import {
|
|
|
32024
32543
|
readFileSync as readFileSync14,
|
|
32025
32544
|
renameSync as renameSync7,
|
|
32026
32545
|
rmSync as rmSync6,
|
|
32027
|
-
statSync as
|
|
32546
|
+
statSync as statSync9,
|
|
32028
32547
|
writeFileSync as writeFileSync9
|
|
32029
32548
|
} from "node:fs";
|
|
32030
32549
|
import { join as join17 } from "node:path";
|
|
@@ -32035,7 +32554,7 @@ import {
|
|
|
32035
32554
|
mkdirSync as mkdirSync7,
|
|
32036
32555
|
openSync as openSync6,
|
|
32037
32556
|
readFileSync as readFileSync12,
|
|
32038
|
-
statSync as
|
|
32557
|
+
statSync as statSync8,
|
|
32039
32558
|
unlinkSync as unlinkSync6,
|
|
32040
32559
|
writeFileSync as writeFileSync8
|
|
32041
32560
|
} from "node:fs";
|
|
@@ -32068,7 +32587,7 @@ function lspBinDir(npmPackage) {
|
|
|
32068
32587
|
function isInstalled(npmPackage, binary) {
|
|
32069
32588
|
for (const candidate of lspBinaryCandidates(binary)) {
|
|
32070
32589
|
try {
|
|
32071
|
-
if (
|
|
32590
|
+
if (statSync8(join15(lspBinDir(npmPackage), candidate)).isFile())
|
|
32072
32591
|
return true;
|
|
32073
32592
|
} catch {}
|
|
32074
32593
|
}
|
|
@@ -32096,7 +32615,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
|
|
|
32096
32615
|
function readInstalledMetaIn(installDir) {
|
|
32097
32616
|
const path3 = join15(installDir, INSTALLED_META_FILE);
|
|
32098
32617
|
try {
|
|
32099
|
-
if (!
|
|
32618
|
+
if (!statSync8(path3).isFile())
|
|
32100
32619
|
return null;
|
|
32101
32620
|
const raw = readFileSync12(path3, "utf8");
|
|
32102
32621
|
const parsed = JSON.parse(raw);
|
|
@@ -32153,7 +32672,7 @@ ${new Date().toISOString()}
|
|
|
32153
32672
|
const parsed = Number.parseInt(firstLine, 10);
|
|
32154
32673
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
32155
32674
|
owningPid = parsed;
|
|
32156
|
-
lockMtimeMs =
|
|
32675
|
+
lockMtimeMs = statSync8(lock).mtimeMs;
|
|
32157
32676
|
} catch {
|
|
32158
32677
|
return tryClaim();
|
|
32159
32678
|
}
|
|
@@ -32707,7 +33226,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
|
|
|
32707
33226
|
return null;
|
|
32708
33227
|
});
|
|
32709
33228
|
if (currentHash && currentHash !== installedMeta.sha256) {
|
|
32710
|
-
error2(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch — refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-install from scratch.`);
|
|
33229
|
+
error2(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch — refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`npx @cortexkit/aft doctor --clear\` to re-install from scratch.`);
|
|
32711
33230
|
return {
|
|
32712
33231
|
started: false,
|
|
32713
33232
|
reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} — see plugin log`
|
|
@@ -32758,7 +33277,7 @@ function hashInstalledBinary(spec) {
|
|
|
32758
33277
|
let pathToHash = null;
|
|
32759
33278
|
for (const p of candidates) {
|
|
32760
33279
|
try {
|
|
32761
|
-
if (
|
|
33280
|
+
if (statSync9(p).isFile()) {
|
|
32762
33281
|
pathToHash = p;
|
|
32763
33282
|
break;
|
|
32764
33283
|
}
|
|
@@ -32784,7 +33303,7 @@ function installedBinaryPath(spec) {
|
|
|
32784
33303
|
] : [lspBinaryPath(spec.npm, spec.binary)];
|
|
32785
33304
|
for (const candidate of candidates) {
|
|
32786
33305
|
try {
|
|
32787
|
-
if (
|
|
33306
|
+
if (statSync9(candidate).isFile())
|
|
32788
33307
|
return candidate;
|
|
32789
33308
|
} catch {}
|
|
32790
33309
|
}
|
|
@@ -32896,7 +33415,7 @@ import {
|
|
|
32896
33415
|
realpathSync as realpathSync3,
|
|
32897
33416
|
renameSync as renameSync8,
|
|
32898
33417
|
rmSync as rmSync7,
|
|
32899
|
-
statSync as
|
|
33418
|
+
statSync as statSync10,
|
|
32900
33419
|
unlinkSync as unlinkSync7,
|
|
32901
33420
|
writeFileSync as writeFileSync10
|
|
32902
33421
|
} from "node:fs";
|
|
@@ -33009,7 +33528,7 @@ function ghBinaryPath(spec, platform3) {
|
|
|
33009
33528
|
function isGithubInstalled(spec, platform3) {
|
|
33010
33529
|
for (const candidate of ghBinaryCandidates(spec, platform3)) {
|
|
33011
33530
|
try {
|
|
33012
|
-
if (
|
|
33531
|
+
if (statSync10(join18(ghBinDir(spec), candidate)).isFile())
|
|
33013
33532
|
return true;
|
|
33014
33533
|
} catch {}
|
|
33015
33534
|
}
|
|
@@ -33023,7 +33542,7 @@ function ghBinaryCandidates(spec, platform3) {
|
|
|
33023
33542
|
function readGithubInstalledMetaIn(installDir) {
|
|
33024
33543
|
try {
|
|
33025
33544
|
const path3 = join18(installDir, INSTALLED_META_FILE2);
|
|
33026
|
-
if (!
|
|
33545
|
+
if (!statSync10(path3).isFile())
|
|
33027
33546
|
return null;
|
|
33028
33547
|
const parsed = JSON.parse(readFileSync15(path3, "utf8"));
|
|
33029
33548
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
@@ -33401,7 +33920,7 @@ function validateCachedGithubInstall(spec, platform3) {
|
|
|
33401
33920
|
const meta3 = readGithubInstalledMetaIn(packageDir);
|
|
33402
33921
|
const binaryPath = ghBinaryCandidates(spec, platform3).map((candidate) => join18(ghBinDir(spec), candidate)).find((candidate) => {
|
|
33403
33922
|
try {
|
|
33404
|
-
return
|
|
33923
|
+
return statSync10(candidate).isFile();
|
|
33405
33924
|
} catch {
|
|
33406
33925
|
return false;
|
|
33407
33926
|
}
|
|
@@ -33500,7 +34019,7 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
33500
34019
|
const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
|
|
33501
34020
|
if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
|
|
33502
34021
|
if (previousArchiveSha256 !== archiveSha256) {
|
|
33503
|
-
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.`);
|
|
34022
|
+
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 \`npx @cortexkit/aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
|
|
33504
34023
|
try {
|
|
33505
34024
|
unlinkSync7(archivePath);
|
|
33506
34025
|
} catch {}
|
|
@@ -33732,12 +34251,43 @@ function normalizeToolArgSchemas(toolDefinition) {
|
|
|
33732
34251
|
}
|
|
33733
34252
|
return toolDefinition;
|
|
33734
34253
|
}
|
|
33735
|
-
function
|
|
33736
|
-
|
|
33737
|
-
|
|
34254
|
+
function bareToolName2(toolName) {
|
|
34255
|
+
return toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
|
|
34256
|
+
}
|
|
34257
|
+
function prepareOpenCodeArguments(toolName, rawArguments) {
|
|
34258
|
+
const bare = bareToolName2(toolName);
|
|
34259
|
+
if (bare === "edit") {
|
|
34260
|
+
return prepareCanonicalEditArguments(toolName, rawArguments);
|
|
34261
|
+
}
|
|
34262
|
+
return prepareCanonicalPathArguments(toolName, rawArguments);
|
|
34263
|
+
}
|
|
34264
|
+
var DISPLAY_FILE_PATH_TOOLS = new Set(["read", "write", "edit"]);
|
|
34265
|
+
function preserveDisplayFilePathAlias(toolName, rawArguments, prepared) {
|
|
34266
|
+
if (!DISPLAY_FILE_PATH_TOOLS.has(toolName))
|
|
34267
|
+
return;
|
|
34268
|
+
if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments))
|
|
34269
|
+
return;
|
|
34270
|
+
const raw = rawArguments;
|
|
34271
|
+
if (typeof prepared.path === "string" && !Object.hasOwn(raw, "filePath")) {
|
|
34272
|
+
raw.filePath = prepared.path;
|
|
34273
|
+
}
|
|
34274
|
+
}
|
|
34275
|
+
function prepareToolMap(tools) {
|
|
34276
|
+
for (const [toolName, def] of Object.entries(tools)) {
|
|
34277
|
+
const execute = def.execute;
|
|
34278
|
+
def.execute = async (args, context) => {
|
|
34279
|
+
const prepared = prepareOpenCodeArguments(toolName, args);
|
|
34280
|
+
preserveDisplayFilePathAlias(toolName, args, prepared);
|
|
34281
|
+
return execute(prepared, context);
|
|
34282
|
+
};
|
|
33738
34283
|
}
|
|
33739
34284
|
return tools;
|
|
33740
34285
|
}
|
|
34286
|
+
function normalizeToolMap(tools) {
|
|
34287
|
+
for (const def of Object.values(tools))
|
|
34288
|
+
normalizeToolArgSchemas(def);
|
|
34289
|
+
return prepareToolMap(tools);
|
|
34290
|
+
}
|
|
33741
34291
|
// src/shared/ignored-message.ts
|
|
33742
34292
|
async function sendIgnoredMessage2(client, sessionID, text) {
|
|
33743
34293
|
const typedClient = client;
|
|
@@ -34591,7 +35141,7 @@ import { tool as tool3 } from "@opencode-ai/plugin";
|
|
|
34591
35141
|
|
|
34592
35142
|
// src/tools/permissions.ts
|
|
34593
35143
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
34594
|
-
import * as
|
|
35144
|
+
import * as fs4 from "node:fs";
|
|
34595
35145
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
34596
35146
|
import * as path3 from "node:path";
|
|
34597
35147
|
var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.15.5 or newer for permission asks; please upgrade OpenCode";
|
|
@@ -34627,11 +35177,22 @@ function resolveAbsolutePath(context, target) {
|
|
|
34627
35177
|
const expanded = expandTilde2(target);
|
|
34628
35178
|
return path3.isAbsolute(expanded) ? expanded : path3.resolve(projectRootFor(context), expanded);
|
|
34629
35179
|
}
|
|
35180
|
+
function permissionPath(context, target) {
|
|
35181
|
+
const projectRoot = path3.resolve(projectRootFor(context));
|
|
35182
|
+
const absolutePath = path3.resolve(resolveAbsolutePath(context, target));
|
|
35183
|
+
if (projectRoot === path3.parse(projectRoot).root)
|
|
35184
|
+
return absolutePath;
|
|
35185
|
+
const relativePath = path3.relative(projectRoot, absolutePath);
|
|
35186
|
+
if (relativePath !== "" && relativePath !== ".." && !relativePath.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relativePath)) {
|
|
35187
|
+
return relativePath;
|
|
35188
|
+
}
|
|
35189
|
+
return relativePath === "" ? "." : absolutePath;
|
|
35190
|
+
}
|
|
34630
35191
|
function resolveRelativePattern(context, target) {
|
|
34631
|
-
return
|
|
35192
|
+
return permissionPath(context, target);
|
|
34632
35193
|
}
|
|
34633
35194
|
function resolveRelativePatternFromAbsolute(context, absolutePath) {
|
|
34634
|
-
return
|
|
35195
|
+
return permissionPath(context, absolutePath);
|
|
34635
35196
|
}
|
|
34636
35197
|
function resolveRelativePatterns(context, targets) {
|
|
34637
35198
|
const seen = new Set;
|
|
@@ -34703,7 +35264,7 @@ function windowsPath(p) {
|
|
|
34703
35264
|
function normalizePath(p) {
|
|
34704
35265
|
const resolved = path3.resolve(windowsPath(p));
|
|
34705
35266
|
try {
|
|
34706
|
-
return
|
|
35267
|
+
return fs4.realpathSync.native(resolved);
|
|
34707
35268
|
} catch {
|
|
34708
35269
|
return normalizeNearestExistingParent(resolved);
|
|
34709
35270
|
}
|
|
@@ -34713,7 +35274,7 @@ function normalizeNearestExistingParent(resolved) {
|
|
|
34713
35274
|
let current = resolved;
|
|
34714
35275
|
while (true) {
|
|
34715
35276
|
try {
|
|
34716
|
-
const realParent =
|
|
35277
|
+
const realParent = fs4.realpathSync.native(current);
|
|
34717
35278
|
return missingTail.length === 0 ? realParent : path3.join(realParent, ...missingTail.reverse());
|
|
34718
35279
|
} catch {
|
|
34719
35280
|
const parent = path3.dirname(current);
|
|
@@ -34787,7 +35348,7 @@ function gitRootForNearestExistingParent(resolved) {
|
|
|
34787
35348
|
const nearest = normalizeNearestExistingParent(resolved);
|
|
34788
35349
|
let cwd = nearest;
|
|
34789
35350
|
try {
|
|
34790
|
-
if (
|
|
35351
|
+
if (fs4.statSync(nearest).isFile())
|
|
34791
35352
|
cwd = path3.dirname(nearest);
|
|
34792
35353
|
} catch {}
|
|
34793
35354
|
try {
|
|
@@ -35039,8 +35600,45 @@ function astTools(ctx) {
|
|
|
35039
35600
|
};
|
|
35040
35601
|
}
|
|
35041
35602
|
|
|
35042
|
-
// src/tools/
|
|
35603
|
+
// src/tools/conflicts.ts
|
|
35043
35604
|
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
35605
|
+
var z4 = tool4.schema;
|
|
35606
|
+
function conflictTools(ctx) {
|
|
35607
|
+
return {
|
|
35608
|
+
aft_conflicts: {
|
|
35609
|
+
description: "Show all git merge conflicts across the repository — returns line-numbered conflict regions with context for every conflicted file in a single call. Conflicts are discovered from the git repository's top level. By default it inspects the session's project repository; pass `path` to inspect a different repository or git worktree (e.g. where a rebase/merge is running).",
|
|
35610
|
+
args: {
|
|
35611
|
+
path: z4.string().describe("Optional path inside the git repository or worktree to inspect (absolute or relative to project root). Conflicts are discovered from that repository's top level. Defaults to the session project root.").optional()
|
|
35612
|
+
},
|
|
35613
|
+
execute: async (args, context) => {
|
|
35614
|
+
const rawArgs = {};
|
|
35615
|
+
if (!isEmptyParam(args?.path)) {
|
|
35616
|
+
const expanded = expandTilde2(String(args.path));
|
|
35617
|
+
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
35618
|
+
const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
|
|
35619
|
+
const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
|
|
35620
|
+
kind: "directory"
|
|
35621
|
+
});
|
|
35622
|
+
if (denied)
|
|
35623
|
+
return permissionDeniedResponse(denied);
|
|
35624
|
+
rawArgs.path = resolved;
|
|
35625
|
+
}
|
|
35626
|
+
const response = await callToolCall(ctx, context, "conflicts", rawArgs);
|
|
35627
|
+
if (response.success === false) {
|
|
35628
|
+
throw new Error(response.message || "git_conflicts failed");
|
|
35629
|
+
}
|
|
35630
|
+
return response.text;
|
|
35631
|
+
}
|
|
35632
|
+
}
|
|
35633
|
+
};
|
|
35634
|
+
}
|
|
35635
|
+
|
|
35636
|
+
// src/tools/hoisted.ts
|
|
35637
|
+
import * as path4 from "node:path";
|
|
35638
|
+
import { tool as tool8 } from "@opencode-ai/plugin";
|
|
35639
|
+
|
|
35640
|
+
// src/tools/bash.ts
|
|
35641
|
+
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
35044
35642
|
|
|
35045
35643
|
// src/shared/subagent-detect.ts
|
|
35046
35644
|
var CACHE_MAX_ENTRIES2 = 200;
|
|
@@ -35093,7 +35691,7 @@ function setCache2(sessionId, isSubagent) {
|
|
|
35093
35691
|
}
|
|
35094
35692
|
|
|
35095
35693
|
// src/tools/bash.ts
|
|
35096
|
-
var
|
|
35694
|
+
var z5 = tool5.schema;
|
|
35097
35695
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
35098
35696
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
35099
35697
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
@@ -35193,22 +35791,22 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
|
35193
35791
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
35194
35792
|
const initialBashCfg = resolveBashConfig(ctx.config);
|
|
35195
35793
|
const backgroundFlagArg = initialBashCfg.background ? {
|
|
35196
|
-
background:
|
|
35794
|
+
background: z5.boolean().optional().describe("When true, spawn the command in the background and return a taskId for bash_status/bash_kill instead of waiting for completion. Defaults to false.")
|
|
35197
35795
|
} : {};
|
|
35198
35796
|
const ptyArgs = initialBashCfg.background ? {
|
|
35199
|
-
pty:
|
|
35797
|
+
pty: z5.boolean().optional().describe('When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Implies background: true automatically. Unavailable in subagent sessions. Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.'),
|
|
35200
35798
|
ptyRows: optionalInt(1, 60).describe("PTY terminal height in rows — ignored when pty is false. Defaults to 24 when pty: true. Minimum 1, maximum 60."),
|
|
35201
35799
|
ptyCols: optionalInt(1, 140).describe("PTY terminal width in columns — ignored when pty is false. Defaults to 80 when pty: true. Minimum 1, maximum 140.")
|
|
35202
35800
|
} : {};
|
|
35203
35801
|
const args = {
|
|
35204
|
-
command:
|
|
35205
|
-
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe(initialBashCfg.background ? "Hard kill cap in milliseconds (positive integer).
|
|
35206
|
-
workdir:
|
|
35207
|
-
description:
|
|
35208
|
-
wait:
|
|
35209
|
-
sandbox:
|
|
35802
|
+
command: z5.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
|
|
35803
|
+
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe(initialBashCfg.background ? "Hard kill cap in milliseconds (positive integer). In the default foreground mode when wait is false, a command that exceeds the configured wait window is promoted to background and gets a completion reminder when it exits; wait:true disables promotion and remains inline until completion or timeout." : "Hard kill cap in milliseconds (positive integer). When omitted, the foreground command can run up to 30 minutes and returns inline when it finishes."),
|
|
35804
|
+
workdir: z5.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
|
|
35805
|
+
description: z5.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
|
|
35806
|
+
wait: z5.boolean().optional().describe("When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout; if you send a new message, the wait detaches to background. Use only when you know the result is required before doing anything else."),
|
|
35807
|
+
sandbox: z5.literal("host").optional().describe("Request one-command approval to run unsandboxed on the host; use only when native sandboxing blocks required work, and note that it is a no-op when sandboxing is disabled."),
|
|
35210
35808
|
...backgroundFlagArg,
|
|
35211
|
-
compressed:
|
|
35809
|
+
compressed: z5.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
|
|
35212
35810
|
...ptyArgs
|
|
35213
35811
|
};
|
|
35214
35812
|
return {
|
|
@@ -35307,8 +35905,8 @@ function createBashStatusTool(ctx) {
|
|
|
35307
35905
|
return {
|
|
35308
35906
|
description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Never waits. One look to check on a task is fine — never loop it to wait for completion. To wait, use bash_watch.",
|
|
35309
35907
|
args: {
|
|
35310
|
-
taskId:
|
|
35311
|
-
outputMode:
|
|
35908
|
+
taskId: z5.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded."),
|
|
35909
|
+
outputMode: z5.enum(["screen", "raw", "both"]).optional().describe("PTY output rendering mode. Defaults to screen for PTY tasks and preserves existing behavior for piped tasks when omitted.")
|
|
35312
35910
|
},
|
|
35313
35911
|
execute: async (args, context) => {
|
|
35314
35912
|
const taskId = args.taskId;
|
|
@@ -35322,7 +35920,7 @@ function createBashKillTool(ctx) {
|
|
|
35322
35920
|
return {
|
|
35323
35921
|
description: "Terminate a running background bash task spawned with bash({ background: true }). Returns confirmation of kill or an error if the task already finished.",
|
|
35324
35922
|
args: {
|
|
35325
|
-
taskId:
|
|
35923
|
+
taskId: z5.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded.")
|
|
35326
35924
|
},
|
|
35327
35925
|
execute: async (args, context) => {
|
|
35328
35926
|
const data = await callBashBridge(ctx, context, "bash_kill", {
|
|
@@ -35414,44 +36012,6 @@ function shortenCommand(command) {
|
|
|
35414
36012
|
return collapsed.length <= 80 ? collapsed : `${collapsed.slice(0, 77)}...`;
|
|
35415
36013
|
}
|
|
35416
36014
|
|
|
35417
|
-
// src/tools/conflicts.ts
|
|
35418
|
-
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
35419
|
-
var z5 = tool5.schema;
|
|
35420
|
-
function conflictTools(ctx) {
|
|
35421
|
-
return {
|
|
35422
|
-
aft_conflicts: {
|
|
35423
|
-
description: "Show all git merge conflicts across the repository — returns line-numbered conflict regions with context for every conflicted file in a single call. Conflicts are discovered from the git repository's top level. By default it inspects the session's project repository; pass `path` to inspect a different repository or git worktree (e.g. where a rebase/merge is running).",
|
|
35424
|
-
args: {
|
|
35425
|
-
path: z5.string().describe("Optional path inside the git repository or worktree to inspect (absolute or relative to project root). Conflicts are discovered from that repository's top level. Defaults to the session project root.").optional()
|
|
35426
|
-
},
|
|
35427
|
-
execute: async (args, context) => {
|
|
35428
|
-
const rawArgs = {};
|
|
35429
|
-
if (!isEmptyParam(args?.path)) {
|
|
35430
|
-
const expanded = expandTilde2(String(args.path));
|
|
35431
|
-
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
35432
|
-
const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
|
|
35433
|
-
const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
|
|
35434
|
-
kind: "directory"
|
|
35435
|
-
});
|
|
35436
|
-
if (denied)
|
|
35437
|
-
return permissionDeniedResponse(denied);
|
|
35438
|
-
rawArgs.path = resolved;
|
|
35439
|
-
}
|
|
35440
|
-
const response = await callToolCall(ctx, context, "conflicts", rawArgs);
|
|
35441
|
-
if (response.success === false) {
|
|
35442
|
-
throw new Error(response.message || "git_conflicts failed");
|
|
35443
|
-
}
|
|
35444
|
-
return response.text;
|
|
35445
|
-
}
|
|
35446
|
-
}
|
|
35447
|
-
};
|
|
35448
|
-
}
|
|
35449
|
-
|
|
35450
|
-
// src/tools/hoisted.ts
|
|
35451
|
-
import * as fs4 from "node:fs";
|
|
35452
|
-
import * as path4 from "node:path";
|
|
35453
|
-
import { tool as tool8 } from "@opencode-ai/plugin";
|
|
35454
|
-
|
|
35455
36015
|
// src/tools/bash_watch.ts
|
|
35456
36016
|
import { tool as tool6 } from "@opencode-ai/plugin";
|
|
35457
36017
|
var z6 = tool6.schema;
|
|
@@ -35569,7 +36129,7 @@ Waited ${waited.elapsed_ms}ms; matched ${JSON.stringify(waited.match ?? "")}${st
|
|
|
35569
36129
|
Waited ${waited.elapsed_ms}ms; timeout reached without match.`;
|
|
35570
36130
|
} else if (waited.reason === "unavailable") {
|
|
35571
36131
|
text += `
|
|
35572
|
-
Waited ${waited.elapsed_ms}ms; the bridge
|
|
36132
|
+
Waited ${waited.elapsed_ms}ms; the bridge was busy, so task state is unknown. Do not poll; let the task's completion notification wake the session, or use one bash_status snapshot on the next normal tool call.`;
|
|
35573
36133
|
} else {
|
|
35574
36134
|
const stat2 = String(data.status ?? "unknown");
|
|
35575
36135
|
const e = typeof data.exit_code === "number" ? `, exit ${data.exit_code}` : "";
|
|
@@ -35831,6 +36391,12 @@ function relativeToWorktree(fp, worktree) {
|
|
|
35831
36391
|
function readAttachments(data) {
|
|
35832
36392
|
return Array.isArray(data.attachments) ? data.attachments : [];
|
|
35833
36393
|
}
|
|
36394
|
+
function persistFilePathAlias(args, context) {
|
|
36395
|
+
if (typeof args.path === "string" && !Object.hasOwn(args, "filePath")) {
|
|
36396
|
+
args.filePath = args.path;
|
|
36397
|
+
}
|
|
36398
|
+
context.metadata({ metadata: {} });
|
|
36399
|
+
}
|
|
35834
36400
|
function buildUnifiedDiff(fp, before, after) {
|
|
35835
36401
|
const beforeLines = before.split(`
|
|
35836
36402
|
`);
|
|
@@ -35994,19 +36560,6 @@ function inferBeforeStart(ops, from, beforeLen) {
|
|
|
35994
36560
|
return beforeLen;
|
|
35995
36561
|
}
|
|
35996
36562
|
var z8 = tool8.schema;
|
|
35997
|
-
function diagnosticsOnEditDefault(ctx) {
|
|
35998
|
-
return ctx.config.lsp?.diagnostics_on_edit ?? false;
|
|
35999
|
-
}
|
|
36000
|
-
async function readCurrentFileForPreview(filePath) {
|
|
36001
|
-
try {
|
|
36002
|
-
return await fs4.promises.readFile(filePath, "utf-8");
|
|
36003
|
-
} catch (error53) {
|
|
36004
|
-
if (error53 && typeof error53 === "object" && "code" in error53 && error53.code === "ENOENT") {
|
|
36005
|
-
return "";
|
|
36006
|
-
}
|
|
36007
|
-
throw error53;
|
|
36008
|
-
}
|
|
36009
|
-
}
|
|
36010
36563
|
var READ_DESCRIPTION = `Read file contents or list directory entries.
|
|
36011
36564
|
|
|
36012
36565
|
Use either startLine/endLine OR offset/limit to read a section of a file.
|
|
@@ -36020,96 +36573,99 @@ Behavior:
|
|
|
36020
36573
|
- Directories return sorted entries with trailing / for subdirectories
|
|
36021
36574
|
|
|
36022
36575
|
Examples:
|
|
36023
|
-
Read full file: { "
|
|
36024
|
-
Read lines 50-100: { "
|
|
36025
|
-
Read 30 lines from line 200: { "
|
|
36026
|
-
List directory: { "
|
|
36576
|
+
Read full file: { "path": "src/app.ts" }
|
|
36577
|
+
Read lines 50-100: { "path": "src/app.ts", "startLine": 50, "endLine": 100 }
|
|
36578
|
+
Read 30 lines from line 200: { "path": "src/app.ts", "offset": 200, "limit": 30 }
|
|
36579
|
+
List directory: { "path": "src/" }
|
|
36027
36580
|
`;
|
|
36028
36581
|
function createReadTool(ctx) {
|
|
36029
|
-
return {
|
|
36030
|
-
|
|
36031
|
-
|
|
36032
|
-
|
|
36033
|
-
|
|
36034
|
-
|
|
36035
|
-
|
|
36036
|
-
|
|
36037
|
-
|
|
36038
|
-
|
|
36039
|
-
|
|
36040
|
-
|
|
36041
|
-
|
|
36042
|
-
|
|
36043
|
-
|
|
36044
|
-
|
|
36045
|
-
|
|
36046
|
-
|
|
36047
|
-
|
|
36048
|
-
|
|
36049
|
-
|
|
36050
|
-
|
|
36051
|
-
|
|
36052
|
-
|
|
36053
|
-
|
|
36054
|
-
|
|
36055
|
-
|
|
36056
|
-
|
|
36057
|
-
|
|
36058
|
-
|
|
36059
|
-
|
|
36060
|
-
|
|
36061
|
-
|
|
36062
|
-
|
|
36063
|
-
|
|
36064
|
-
|
|
36065
|
-
|
|
36066
|
-
|
|
36067
|
-
|
|
36068
|
-
|
|
36069
|
-
if (
|
|
36070
|
-
|
|
36071
|
-
|
|
36072
|
-
|
|
36073
|
-
|
|
36074
|
-
|
|
36075
|
-
rawArgs
|
|
36076
|
-
|
|
36077
|
-
|
|
36078
|
-
|
|
36079
|
-
|
|
36080
|
-
|
|
36081
|
-
|
|
36082
|
-
|
|
36083
|
-
|
|
36084
|
-
|
|
36085
|
-
|
|
36086
|
-
|
|
36087
|
-
|
|
36088
|
-
const
|
|
36089
|
-
|
|
36090
|
-
mime
|
|
36091
|
-
|
|
36092
|
-
|
|
36093
|
-
|
|
36094
|
-
|
|
36095
|
-
|
|
36096
|
-
|
|
36097
|
-
|
|
36098
|
-
|
|
36099
|
-
|
|
36100
|
-
metadata: {
|
|
36101
|
-
preview: output,
|
|
36102
|
-
filepath: filePath,
|
|
36582
|
+
return prepareToolMap({
|
|
36583
|
+
read: {
|
|
36584
|
+
description: READ_DESCRIPTION,
|
|
36585
|
+
args: {
|
|
36586
|
+
filePath: z8.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
36587
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
|
|
36588
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to stop reading at (inclusive)"),
|
|
36589
|
+
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines to return (default: 2000)"),
|
|
36590
|
+
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
|
|
36591
|
+
},
|
|
36592
|
+
execute: async (args, context) => {
|
|
36593
|
+
const file2 = args.path;
|
|
36594
|
+
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36595
|
+
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36596
|
+
persistFilePathAlias(args, context);
|
|
36597
|
+
{
|
|
36598
|
+
const denial = await assertExternalDirectoryPermission(ctx, context, filePath, {
|
|
36599
|
+
serverValidatedRead: true
|
|
36600
|
+
});
|
|
36601
|
+
if (denial)
|
|
36602
|
+
return permissionDeniedResponse(denial);
|
|
36603
|
+
}
|
|
36604
|
+
try {
|
|
36605
|
+
await runAsk(context.ask({
|
|
36606
|
+
permission: "read",
|
|
36607
|
+
patterns: [filePath],
|
|
36608
|
+
always: ["*"],
|
|
36609
|
+
metadata: {}
|
|
36610
|
+
}));
|
|
36611
|
+
} catch (error53) {
|
|
36612
|
+
if (error53 instanceof Error && error53.message)
|
|
36613
|
+
return permissionDeniedResponse(error53.message);
|
|
36614
|
+
return permissionDeniedResponse("Permission denied.");
|
|
36615
|
+
}
|
|
36616
|
+
const rawStartLine = coerceOptionalInt(args.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
|
|
36617
|
+
const rawEndLine = coerceOptionalInt(args.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
|
|
36618
|
+
const rawLimit = coerceOptionalInt(args.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
|
|
36619
|
+
const rawOffset = coerceOptionalInt(args.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
|
|
36620
|
+
let startLine = rawStartLine;
|
|
36621
|
+
let endLine = rawEndLine;
|
|
36622
|
+
if (startLine === undefined && rawOffset !== undefined) {
|
|
36623
|
+
startLine = rawOffset;
|
|
36624
|
+
if (rawLimit !== undefined) {
|
|
36625
|
+
endLine = rawOffset + rawLimit - 1;
|
|
36626
|
+
}
|
|
36627
|
+
}
|
|
36628
|
+
const rawArgs = { filePath: file2 };
|
|
36629
|
+
if (startLine !== undefined)
|
|
36630
|
+
rawArgs.startLine = startLine;
|
|
36631
|
+
if (endLine !== undefined)
|
|
36632
|
+
rawArgs.endLine = endLine;
|
|
36633
|
+
if (rawLimit !== undefined && rawOffset === undefined)
|
|
36634
|
+
rawArgs.limit = rawLimit;
|
|
36635
|
+
const response = await callToolCall(ctx, context, "read", rawArgs);
|
|
36636
|
+
if (response.success === false) {
|
|
36637
|
+
throw new Error(response.message || "read failed");
|
|
36638
|
+
}
|
|
36639
|
+
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
36640
|
+
const output = response.text;
|
|
36641
|
+
const attachments = readAttachments(response);
|
|
36642
|
+
if (attachments.length > 0) {
|
|
36643
|
+
const toolAttachments = attachments.filter((attachment) => typeof attachment.mime === "string" && typeof attachment.data === "string").map((attachment) => ({
|
|
36644
|
+
type: "file",
|
|
36645
|
+
mime: attachment.mime,
|
|
36646
|
+
url: `data:${attachment.mime};base64,${attachment.data}`
|
|
36647
|
+
}));
|
|
36648
|
+
if (toolAttachments.length > 0) {
|
|
36649
|
+
const first = attachments[0];
|
|
36650
|
+
const firstMime = typeof first.mime === "string" ? first.mime : "";
|
|
36651
|
+
return {
|
|
36652
|
+
output,
|
|
36103
36653
|
title: dp,
|
|
36104
|
-
|
|
36105
|
-
|
|
36106
|
-
|
|
36107
|
-
|
|
36654
|
+
attachments: toolAttachments,
|
|
36655
|
+
metadata: {
|
|
36656
|
+
preview: output,
|
|
36657
|
+
filepath: filePath,
|
|
36658
|
+
title: dp,
|
|
36659
|
+
isImage: first.kind === "image" || firstMime.startsWith("image/"),
|
|
36660
|
+
isPdf: first.kind === "pdf" || firstMime === "application/pdf"
|
|
36661
|
+
}
|
|
36662
|
+
};
|
|
36663
|
+
}
|
|
36108
36664
|
}
|
|
36665
|
+
return { output, title: dp, metadata: { title: dp } };
|
|
36109
36666
|
}
|
|
36110
|
-
return { output, title: dp, metadata: { title: dp } };
|
|
36111
36667
|
}
|
|
36112
|
-
};
|
|
36668
|
+
}).read;
|
|
36113
36669
|
}
|
|
36114
36670
|
function getWriteDescription(ctx, editToolName) {
|
|
36115
36671
|
const backupText = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config." : "Existing files are backed up before overwriting (undo via aft_safety).";
|
|
@@ -36123,11 +36679,13 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36123
36679
|
content: z8.string().describe("The full content to write to the file")
|
|
36124
36680
|
},
|
|
36125
36681
|
execute: async (args, context) => {
|
|
36126
|
-
const
|
|
36682
|
+
const argsRecord = args;
|
|
36683
|
+
const file2 = args.path;
|
|
36127
36684
|
const content = args.content;
|
|
36128
36685
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36129
36686
|
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36130
|
-
|
|
36687
|
+
persistFilePathAlias(argsRecord, context);
|
|
36688
|
+
const permissionPattern = permissionPath(context, filePath);
|
|
36131
36689
|
{
|
|
36132
36690
|
const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
36133
36691
|
if (denial2)
|
|
@@ -36136,9 +36694,9 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36136
36694
|
const rawArgs = { filePath: file2, content };
|
|
36137
36695
|
const preview2 = await callToolCall(ctx, context, "write", rawArgs, { preview: true });
|
|
36138
36696
|
if (preview2.success === false) {
|
|
36139
|
-
throw
|
|
36697
|
+
throw toolErrorFromResponse("write", preview2);
|
|
36140
36698
|
}
|
|
36141
|
-
const denial = await askEditPermission(context, [
|
|
36699
|
+
const denial = await askEditPermission(context, [permissionPattern], {
|
|
36142
36700
|
filepath: filePath,
|
|
36143
36701
|
diff: typeof preview2.preview_diff === "string" ? preview2.preview_diff : ""
|
|
36144
36702
|
});
|
|
@@ -36146,7 +36704,7 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36146
36704
|
return permissionDeniedResponse(denial);
|
|
36147
36705
|
const data = await callToolCall(ctx, context, "write", rawArgs);
|
|
36148
36706
|
if (data.success === false) {
|
|
36149
|
-
throw
|
|
36707
|
+
throw toolErrorFromResponse("write", data);
|
|
36150
36708
|
}
|
|
36151
36709
|
const output = data.text;
|
|
36152
36710
|
const diff = data.diff;
|
|
@@ -36156,20 +36714,20 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36156
36714
|
const dp = relativeToWorktree(filePath, projectRoot);
|
|
36157
36715
|
const beforeContent = diff.before ?? "";
|
|
36158
36716
|
const afterContent = diff.after ?? content;
|
|
36717
|
+
const patch = truncated ? typeof preview2.preview_diff === "string" ? preview2.preview_diff : "" : buildUnifiedDiff(filePath, beforeContent, afterContent);
|
|
36159
36718
|
return {
|
|
36160
36719
|
output,
|
|
36161
36720
|
title: dp,
|
|
36162
36721
|
metadata: {
|
|
36163
|
-
diff:
|
|
36164
|
-
...
|
|
36722
|
+
diff: patch,
|
|
36723
|
+
...patch ? {
|
|
36165
36724
|
filediff: {
|
|
36166
36725
|
file: filePath,
|
|
36167
|
-
|
|
36168
|
-
|
|
36169
|
-
|
|
36170
|
-
deletions: diff?.deletions ?? 0
|
|
36726
|
+
patch,
|
|
36727
|
+
additions: diff.additions ?? 0,
|
|
36728
|
+
deletions: diff.deletions ?? 0
|
|
36171
36729
|
}
|
|
36172
|
-
},
|
|
36730
|
+
} : {},
|
|
36173
36731
|
diagnostics: {}
|
|
36174
36732
|
}
|
|
36175
36733
|
};
|
|
@@ -36182,40 +36740,33 @@ function getEditDescription(ctx, writeToolName) {
|
|
|
36182
36740
|
|
|
36183
36741
|
**Modes** (determined by which parameters you provide):
|
|
36184
36742
|
|
|
36185
|
-
|
|
36743
|
+
Provide exactly one mode per call: appendContent, edits[], or symbol plus content. Mixing modes or providing none is rejected — there is no implicit "write" fallback. To edit multiple files, make parallel \`edit\` calls in one response.
|
|
36186
36744
|
|
|
36187
|
-
1. **Append** — pass \`
|
|
36188
|
-
Appends text to the end of a file, creating
|
|
36189
|
-
Example: \`{ "
|
|
36745
|
+
1. **Append** — pass \`path\` + \`appendContent\`
|
|
36746
|
+
Appends text to the end of a file, creating it if it does not exist.
|
|
36747
|
+
Example: \`{ "path": "notes.txt", "appendContent": "new line\\n" }\`
|
|
36190
36748
|
|
|
36191
|
-
2. **Batch edits** — pass \`
|
|
36749
|
+
2. **Batch edits** — pass \`path\` + \`edits\` array
|
|
36192
36750
|
Multiple edits in one file atomically. Each edit is either:
|
|
36193
36751
|
- \`{ "oldString": "old", "newString": "new" }\` — find/replace
|
|
36194
36752
|
- \`{ "oldString": "old", "newString": "new", "replaceAll": true }\` — replace every match
|
|
36195
36753
|
- \`{ "startLine": 5, "endLine": 7, "content": "new lines" }\` — replace line range (1-based, both inclusive)
|
|
36196
36754
|
Set content to empty string to delete lines.
|
|
36197
36755
|
|
|
36198
|
-
3. **Symbol replace** — pass \`
|
|
36199
|
-
Replaces an entire named symbol (function, class, type)
|
|
36756
|
+
3. **Symbol replace** — pass \`path\` + \`symbol\` + \`content\`
|
|
36757
|
+
Replaces an entire named symbol (function, class, type).
|
|
36200
36758
|
Includes decorators, attributes, and doc comments in the replacement range.
|
|
36201
|
-
|
|
36202
|
-
Example: \`{ "filePath": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
|
|
36759
|
+
Example: \`{ "path": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
|
|
36203
36760
|
|
|
36204
|
-
4. **Find and replace** —
|
|
36761
|
+
4. **Find and replace** — put \`oldString\` and optional \`newString\` in an item of \`edits[]\`
|
|
36205
36762
|
Finds the exact text in \`oldString\` and replaces it with \`newString\`.
|
|
36206
36763
|
Supports fuzzy matching (handles whitespace differences automatically).
|
|
36207
|
-
If multiple matches exist, specify
|
|
36208
|
-
Example: \`{ "filePath": "src/app.ts", "oldString": "const x = 1", "newString": "const x = 2" }\`
|
|
36764
|
+
If multiple matches exist, specify \`occurrence\` or set \`replaceAll: true\` in that item.
|
|
36209
36765
|
|
|
36210
|
-
5. **Replace all occurrences** — add \`replaceAll: true\`
|
|
36211
|
-
Replaces every occurrence of \`oldString\` in the file.
|
|
36212
|
-
Example: \`{ "filePath": "src/app.ts", "oldString": "oldName", "newString": "newName", "replaceAll": true }\`
|
|
36766
|
+
5. **Replace all occurrences** — add \`replaceAll: true\` to a find/replace item.
|
|
36213
36767
|
|
|
36214
|
-
6. **Select specific occurrence** — add \`occurrence: N\` (
|
|
36215
|
-
When multiple matches exist, select the Nth one (
|
|
36216
|
-
Example: \`{ "filePath": "src/app.ts", "oldString": "TODO", "newString": "DONE", "occurrence": 0 }\`
|
|
36217
|
-
|
|
36218
|
-
Note: Modes 5 and 6 are options on mode 4 (find/replace) — they require \`oldString\`.
|
|
36768
|
+
6. **Select specific occurrence** — add \`occurrence: N\` to a find/replace item (1-based).
|
|
36769
|
+
When multiple matches exist, select the Nth one (1 = first, 2 = second, etc.).
|
|
36219
36770
|
|
|
36220
36771
|
**Behavior:**
|
|
36221
36772
|
${backupBehavior}
|
|
@@ -36228,66 +36779,47 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
36228
36779
|
return {
|
|
36229
36780
|
description: getEditDescription(ctx, writeToolName),
|
|
36230
36781
|
args: {
|
|
36231
|
-
filePath: z8.string().
|
|
36232
|
-
oldString: z8.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
|
|
36233
|
-
newString: z8.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
|
|
36234
|
-
replaceAll: z8.boolean().optional().describe("Replace all occurrences"),
|
|
36235
|
-
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER).describe("0-indexed occurrence to replace when multiple matches exist"),
|
|
36782
|
+
filePath: z8.string().describe("Path to the file to edit (absolute or relative to project root)"),
|
|
36236
36783
|
symbol: z8.string().optional().describe("Named symbol to replace (function, class, type)"),
|
|
36237
36784
|
content: z8.string().optional().describe("Replacement content for symbol mode. For whole-file writes, use the `write` tool."),
|
|
36238
|
-
appendContent: z8.string().optional().describe("Text to append to the end of
|
|
36785
|
+
appendContent: z8.string().optional().describe("Text to append to the end of path; creates the file if needed"),
|
|
36239
36786
|
edits: z8.array(z8.object({
|
|
36240
36787
|
oldString: z8.string().optional().describe("Text to find for a batch find/replace edit"),
|
|
36241
36788
|
newString: z8.string().optional().describe("Replacement text for a batch find/replace edit"),
|
|
36242
36789
|
replaceAll: z8.boolean().optional().describe("Replace every occurrence for this batch item"),
|
|
36243
|
-
occurrence: optionalInt(
|
|
36790
|
+
occurrence: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based occurrence for this batch item (1 = first match)"),
|
|
36244
36791
|
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based start line for a batch line-range edit"),
|
|
36245
36792
|
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based end line for a batch line-range edit"),
|
|
36246
36793
|
content: z8.string().optional().describe("Replacement text for a batch line-range edit")
|
|
36247
|
-
})).optional().describe("Batch edits — array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects")
|
|
36794
|
+
})).min(1).optional().describe("Batch edits — non-empty array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects")
|
|
36248
36795
|
},
|
|
36249
36796
|
execute: async (args, context) => {
|
|
36250
36797
|
const argsRecord = args;
|
|
36251
36798
|
if (argsRecord.startLine !== undefined || argsRecord.endLine !== undefined) {
|
|
36252
|
-
throw new Error("edit: 'startLine'/'endLine' are not top-level parameters. " + "For line-range edits, nest them inside the `edits` array: " + '`edits: [{ startLine: N, endLine: M, content: "..." }]`. ' + "For find/replace, use `
|
|
36799
|
+
throw new Error("edit: 'startLine'/'endLine' are not top-level parameters. " + "For line-range edits, nest them inside the `edits` array: " + '`edits: [{ startLine: N, endLine: M, content: "..." }]`. ' + "For find/replace, use an item in `edits[]` instead.");
|
|
36253
36800
|
}
|
|
36254
|
-
const file2 = args.
|
|
36801
|
+
const file2 = args.path;
|
|
36255
36802
|
if (!file2)
|
|
36256
|
-
throw new Error("'
|
|
36803
|
+
throw new Error("'path' parameter is required");
|
|
36257
36804
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36258
36805
|
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36259
|
-
|
|
36806
|
+
persistFilePathAlias(argsRecord, context);
|
|
36807
|
+
const permissionPattern = permissionPath(context, filePath);
|
|
36260
36808
|
{
|
|
36261
36809
|
const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
36262
36810
|
if (denial2)
|
|
36263
36811
|
return permissionDeniedResponse(denial2);
|
|
36264
36812
|
}
|
|
36265
|
-
const
|
|
36266
|
-
const
|
|
36267
|
-
for (const key of ["appendContent", "symbol", "content", "oldString", "newString"]) {
|
|
36813
|
+
const rawArgs = { path: file2 };
|
|
36814
|
+
for (const key of ["appendContent", "symbol", "content", "edits"]) {
|
|
36268
36815
|
if (argsRecord[key] !== undefined)
|
|
36269
36816
|
rawArgs[key] = argsRecord[key];
|
|
36270
36817
|
}
|
|
36271
|
-
if (Array.isArray(argsRecord.edits)) {
|
|
36272
|
-
rawArgs.edits = argsRecord.edits.map((item) => {
|
|
36273
|
-
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
36274
|
-
return item;
|
|
36275
|
-
const batchItem = item;
|
|
36276
|
-
return batchItem.replaceAll === undefined ? batchItem : { ...batchItem, replaceAll: coerceBoolean(batchItem.replaceAll) };
|
|
36277
|
-
});
|
|
36278
|
-
} else if (argsRecord.edits !== undefined) {
|
|
36279
|
-
rawArgs.edits = argsRecord.edits;
|
|
36280
|
-
}
|
|
36281
|
-
if (argsRecord.replaceAll !== undefined) {
|
|
36282
|
-
rawArgs.replaceAll = coerceBoolean(argsRecord.replaceAll);
|
|
36283
|
-
}
|
|
36284
|
-
if (occurrence !== undefined)
|
|
36285
|
-
rawArgs.occurrence = occurrence;
|
|
36286
36818
|
const preview2 = await callToolCall(ctx, context, "edit", rawArgs, { preview: true });
|
|
36287
36819
|
if (preview2.success === false) {
|
|
36288
|
-
throw
|
|
36820
|
+
throw toolErrorFromResponse("edit", preview2);
|
|
36289
36821
|
}
|
|
36290
|
-
const denial = await askEditPermission(context, [
|
|
36822
|
+
const denial = await askEditPermission(context, [permissionPattern], {
|
|
36291
36823
|
filepath: filePath,
|
|
36292
36824
|
diff: typeof preview2.preview_diff === "string" ? preview2.preview_diff : ""
|
|
36293
36825
|
});
|
|
@@ -36295,7 +36827,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
36295
36827
|
return permissionDeniedResponse(denial);
|
|
36296
36828
|
const data = await callToolCall(ctx, context, "edit", rawArgs);
|
|
36297
36829
|
if (data.success === false) {
|
|
36298
|
-
throw
|
|
36830
|
+
throw toolErrorFromResponse("edit", data);
|
|
36299
36831
|
}
|
|
36300
36832
|
const output = data.text;
|
|
36301
36833
|
const diff = data.diff;
|
|
@@ -36304,17 +36836,17 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
36304
36836
|
const truncated = diff.truncated === true;
|
|
36305
36837
|
const beforeContent = diff.before ?? "";
|
|
36306
36838
|
const afterContent = diff.after ?? "";
|
|
36839
|
+
const patch = truncated ? typeof preview2.preview_diff === "string" ? preview2.preview_diff : "" : buildUnifiedDiff(filePath, beforeContent, afterContent);
|
|
36307
36840
|
const uiMeta = {
|
|
36308
|
-
diff:
|
|
36309
|
-
...
|
|
36841
|
+
diff: patch,
|
|
36842
|
+
...patch ? {
|
|
36310
36843
|
filediff: {
|
|
36311
36844
|
file: filePath,
|
|
36312
|
-
|
|
36313
|
-
after: afterContent,
|
|
36845
|
+
patch,
|
|
36314
36846
|
additions: diff.additions ?? 0,
|
|
36315
36847
|
deletions: diff.deletions ?? 0
|
|
36316
36848
|
}
|
|
36317
|
-
},
|
|
36849
|
+
} : {},
|
|
36318
36850
|
diagnostics: {}
|
|
36319
36851
|
};
|
|
36320
36852
|
return { output, title: relativeToWorktree(filePath, projectRoot), metadata: uiMeta };
|
|
@@ -36401,9 +36933,11 @@ function createApplyPatchTool(ctx) {
|
|
|
36401
36933
|
return permissionDeniedResponse(denial2);
|
|
36402
36934
|
}
|
|
36403
36935
|
const affectedRelPaths = stringArray(preview2.affected_rel_paths);
|
|
36404
|
-
const
|
|
36936
|
+
const affectedPaths = stringArray(preview2.affected_paths);
|
|
36937
|
+
const permissionPatterns = (affectedPaths.length > 0 ? affectedPaths : affectedRelPaths).map((filePath) => permissionPath(context, filePath));
|
|
36938
|
+
const denial = await askEditPermission(context, permissionPatterns, {
|
|
36405
36939
|
diff: typeof preview2.preview_diff === "string" ? preview2.preview_diff : "",
|
|
36406
|
-
filepath: typeof preview2.filepath === "string" ? preview2.filepath : affectedRelPaths[0]
|
|
36940
|
+
filepath: typeof preview2.filepath === "string" ? preview2.filepath : affectedPaths[0] ?? affectedRelPaths[0]
|
|
36407
36941
|
});
|
|
36408
36942
|
if (denial)
|
|
36409
36943
|
return permissionDeniedResponse(denial);
|
|
@@ -36494,12 +37028,12 @@ function createMoveTool(ctx) {
|
|
|
36494
37028
|
return {
|
|
36495
37029
|
description: moveDescription(ctx),
|
|
36496
37030
|
args: {
|
|
36497
|
-
|
|
37031
|
+
path: z8.string().describe("Source file path to move (absolute or relative to project root)"),
|
|
36498
37032
|
destination: z8.string().describe("Destination file path (absolute or relative to project root)")
|
|
36499
37033
|
},
|
|
36500
37034
|
execute: async (args, context) => {
|
|
36501
37035
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36502
|
-
const filePath = resolvePathFromProjectRoot(projectRoot, args.
|
|
37036
|
+
const filePath = resolvePathFromProjectRoot(projectRoot, args.path);
|
|
36503
37037
|
const destPath = resolvePathFromProjectRoot(projectRoot, args.destination);
|
|
36504
37038
|
{
|
|
36505
37039
|
const sourceDenial = await assertExternalDirectoryPermission(ctx, context, filePath, {
|
|
@@ -36520,7 +37054,7 @@ function createMoveTool(ctx) {
|
|
|
36520
37054
|
metadata: { action: "move" }
|
|
36521
37055
|
}));
|
|
36522
37056
|
const result = await callToolCall(ctx, context, "move", {
|
|
36523
|
-
filePath: args.
|
|
37057
|
+
filePath: args.path,
|
|
36524
37058
|
destination: args.destination
|
|
36525
37059
|
});
|
|
36526
37060
|
if (result.success === false) {
|
|
@@ -36549,51 +37083,14 @@ function hoistedTools(ctx) {
|
|
|
36549
37083
|
tools.bash_kill = createBashKillTool(ctx);
|
|
36550
37084
|
}
|
|
36551
37085
|
}
|
|
36552
|
-
return tools;
|
|
37086
|
+
return prepareToolMap(tools);
|
|
36553
37087
|
}
|
|
36554
37088
|
function aftPrefixedTools(ctx) {
|
|
36555
37089
|
const aftEditTool = createEditTool(ctx, "aft_write");
|
|
36556
37090
|
const tools = {
|
|
36557
37091
|
aft_read: createReadTool(ctx),
|
|
36558
37092
|
aft_write: createWriteTool(ctx, "aft_edit"),
|
|
36559
|
-
aft_edit:
|
|
36560
|
-
...aftEditTool,
|
|
36561
|
-
execute: async (args, context) => {
|
|
36562
|
-
const argRecord = args;
|
|
36563
|
-
const normalizedArgs = argRecord.mode !== undefined && argRecord.filePath === undefined && typeof argRecord.file === "string" ? { ...argRecord, filePath: argRecord.file } : { ...argRecord };
|
|
36564
|
-
if (normalizedArgs.mode === "write" && typeof normalizedArgs.filePath === "string" && typeof normalizedArgs.content === "string") {
|
|
36565
|
-
const file2 = normalizedArgs.filePath;
|
|
36566
|
-
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36567
|
-
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36568
|
-
const relPath = path4.relative(projectRoot, filePath);
|
|
36569
|
-
{
|
|
36570
|
-
const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
36571
|
-
if (denial2)
|
|
36572
|
-
return permissionDeniedResponse(denial2);
|
|
36573
|
-
}
|
|
36574
|
-
const currentContent = await readCurrentFileForPreview(filePath);
|
|
36575
|
-
const previewDiff = buildUnifiedDiff(filePath, currentContent, normalizedArgs.content);
|
|
36576
|
-
const denial = await askEditPermission(context, [relPath], {
|
|
36577
|
-
filepath: filePath,
|
|
36578
|
-
diff: previewDiff
|
|
36579
|
-
});
|
|
36580
|
-
if (denial)
|
|
36581
|
-
return permissionDeniedResponse(denial);
|
|
36582
|
-
const writeParams = {
|
|
36583
|
-
file: filePath,
|
|
36584
|
-
content: normalizedArgs.content,
|
|
36585
|
-
create_dirs: normalizedArgs.create_dirs !== false,
|
|
36586
|
-
diagnostics: normalizedArgs.diagnostics ?? diagnosticsOnEditDefault(ctx)
|
|
36587
|
-
};
|
|
36588
|
-
const response = await callBridge(ctx, context, "write", writeParams);
|
|
36589
|
-
if (response.success === false) {
|
|
36590
|
-
throw new Error(response.message ?? "write failed");
|
|
36591
|
-
}
|
|
36592
|
-
return JSON.stringify(response);
|
|
36593
|
-
}
|
|
36594
|
-
return aftEditTool.execute(normalizedArgs, context);
|
|
36595
|
-
}
|
|
36596
|
-
},
|
|
37093
|
+
aft_edit: aftEditTool,
|
|
36597
37094
|
aft_apply_patch: createApplyPatchTool(ctx),
|
|
36598
37095
|
aft_delete: createDeleteTool(ctx),
|
|
36599
37096
|
aft_move: createMoveTool(ctx)
|
|
@@ -36608,7 +37105,7 @@ function aftPrefixedTools(ctx) {
|
|
|
36608
37105
|
tools.bash_kill = createBashKillTool(ctx);
|
|
36609
37106
|
}
|
|
36610
37107
|
}
|
|
36611
|
-
return tools;
|
|
37108
|
+
return prepareToolMap(tools);
|
|
36612
37109
|
}
|
|
36613
37110
|
|
|
36614
37111
|
// src/tools/imports.ts
|
|
@@ -36616,17 +37113,17 @@ import { tool as tool9 } from "@opencode-ai/plugin";
|
|
|
36616
37113
|
var z9 = tool9.schema;
|
|
36617
37114
|
function importTools(ctx) {
|
|
36618
37115
|
const organizeRecovery = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config; review broad cleanup changes before proceeding." : "Use aft_safety checkpoint/undo for recovery before broad cleanup.";
|
|
36619
|
-
return {
|
|
37116
|
+
return prepareToolMap({
|
|
36620
37117
|
aft_import: {
|
|
36621
37118
|
description: `Language-aware import management. Supports TS, JS, TSX, Python, Rust, Go, Solidity, Java, C#, PHP, Kotlin, Scala, Swift, Ruby, Lua, C, C++, Perl, and Vue.
|
|
36622
37119
|
|
|
36623
37120
|
` + `Ops:
|
|
36624
37121
|
` + `- 'add': Add an import. Auto-detects group (stdlib/external/internal), deduplicates. Requires 'module'. Optional 'names', 'defaultImport', 'typeOnly'.
|
|
36625
37122
|
` + `- '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.
|
|
36626
|
-
` + `- 'organize': Re-sort and re-group all imports by language convention, deduplicate. Requires only '
|
|
37123
|
+
` + `- 'organize': Re-sort and re-group all imports by language convention, deduplicate. Requires only 'path'. ${organizeRecovery}`,
|
|
36627
37124
|
args: {
|
|
36628
37125
|
op: z9.enum(["add", "remove", "organize"]).describe("Import operation"),
|
|
36629
|
-
|
|
37126
|
+
path: z9.string().describe("Path to the file (absolute or relative to project root)"),
|
|
36630
37127
|
module: z9.string().optional().describe("Module path (required for add, remove — e.g. 'react', './utils', 'std::fmt')"),
|
|
36631
37128
|
names: z9.array(z9.string()).optional().describe("Named imports to add. Each entry uses the language's native named-import text, " + "including per-name aliasing where the language uses `as` (e.g. ['useState', 'debounce as db'], " + "Solidity ['ERC20', 'IERC20 as IToken'])."),
|
|
36632
37129
|
defaultImport: z9.string().optional().describe("Default import name, ES only (e.g. 'React')"),
|
|
@@ -36643,7 +37140,7 @@ function importTools(ctx) {
|
|
|
36643
37140
|
if ((op === "add" || op === "remove") && isEmptyParam(args.module)) {
|
|
36644
37141
|
throw new Error(`'module' is required for '${op}' op`);
|
|
36645
37142
|
}
|
|
36646
|
-
const filePath = await resolvePathArg(ctx, context, args.
|
|
37143
|
+
const filePath = await resolvePathArg(ctx, context, args.path);
|
|
36647
37144
|
{
|
|
36648
37145
|
const denial = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
36649
37146
|
if (denial)
|
|
@@ -36680,7 +37177,7 @@ function importTools(ctx) {
|
|
|
36680
37177
|
return response.text;
|
|
36681
37178
|
}
|
|
36682
37179
|
}
|
|
36683
|
-
};
|
|
37180
|
+
});
|
|
36684
37181
|
}
|
|
36685
37182
|
|
|
36686
37183
|
// src/tools/inspect.ts
|
|
@@ -36787,7 +37284,7 @@ import { tool as tool11 } from "@opencode-ai/plugin";
|
|
|
36787
37284
|
var z11 = tool11.schema;
|
|
36788
37285
|
var CALLGRAPH_SOFT_CODES = new Set(["symbol_not_found", "callgraph_building"]);
|
|
36789
37286
|
function navigationTools(ctx) {
|
|
36790
|
-
return {
|
|
37287
|
+
return prepareToolMap({
|
|
36791
37288
|
aft_callgraph: {
|
|
36792
37289
|
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.
|
|
36793
37290
|
|
|
@@ -36796,27 +37293,27 @@ function navigationTools(ctx) {
|
|
|
36796
37293
|
` + `- 'impact': What breaks if a symbol changes — affected callers with signatures and entry-point status (blast radius). Use before a risky edit.
|
|
36797
37294
|
` + `- 'call_tree': What a function calls (forward traversal). Use to understand a function's dependencies before modifying it.
|
|
36798
37295
|
` + `- 'trace_to': How execution reaches a function from entry points (routes, exports, main). Use to understand context around deeply-nested code.
|
|
36799
|
-
` + `- 'trace_to_symbol': Shortest call path from one symbol to another. Requires 'toSymbol'. If multiple targets match, the error returns candidate files; retry with '
|
|
37296
|
+
` + `- 'trace_to_symbol': Shortest call path from one symbol to another. Requires 'toSymbol'. If multiple targets match, the error returns candidate files; retry with 'toPath' to disambiguate.
|
|
36800
37297
|
` + `- 'trace_data': Follow a value through variable assignments and function parameters across files. Requires 'symbol' (scope to trace from) and 'expression'.
|
|
36801
37298
|
|
|
36802
|
-
` + `All ops require both '
|
|
37299
|
+
` + `All ops require both 'path' and 'symbol'. 'expression' is additionally required for trace_data; 'toSymbol' for trace_to_symbol.
|
|
36803
37300
|
|
|
36804
37301
|
` + `Markers: ~ = edge resolved by name only (may point at the wrong same-named symbol); [unresolved] = callee not resolved to a definition, so the location shown is the call site. Unmarked edges are resolved exactly. By default, unresolved external/stdlib leaf calls in call_tree are collapsed into one summary per parent; pass includeUnresolved=true to show every unresolved edge individually.
|
|
36805
37302
|
`,
|
|
36806
37303
|
args: {
|
|
36807
37304
|
op: z11.enum(["call_tree", "callers", "trace_to", "trace_to_symbol", "impact", "trace_data"]).describe("Navigation operation"),
|
|
36808
|
-
|
|
37305
|
+
path: z11.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
|
|
36809
37306
|
symbol: z11.string().describe("Name of the symbol to analyze"),
|
|
36810
37307
|
depth: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max traversal depth (default: call_tree=5, callers=1, trace_to=10, trace_to_symbol=10 capped at 16, impact=5, trace_data=5)"),
|
|
36811
37308
|
expression: z11.string().optional().describe("Expression to track through data flow (required for trace_data op)"),
|
|
36812
37309
|
toSymbol: z11.string().optional().describe("Target symbol name for trace_to_symbol; the returned path ends at this symbol"),
|
|
36813
|
-
|
|
37310
|
+
toPath: z11.string().optional().describe("Optional target file for trace_to_symbol; required when toSymbol exists in multiple files"),
|
|
36814
37311
|
includeTests: z11.boolean().optional().describe("Include test files in callers/paths. Defaults to false; tests are hidden."),
|
|
36815
37312
|
includeUnresolved: z11.boolean().optional().describe("Show every unresolved external/stdlib call individually. Defaults to false; unresolved leaf calls are collapsed into one summary per parent.")
|
|
36816
37313
|
},
|
|
36817
37314
|
execute: async (args, context) => {
|
|
36818
|
-
if (isEmptyParam(args.
|
|
36819
|
-
throw new Error("'
|
|
37315
|
+
if (isEmptyParam(args.path)) {
|
|
37316
|
+
throw new Error("'path' is required");
|
|
36820
37317
|
}
|
|
36821
37318
|
if (isEmptyParam(args.symbol)) {
|
|
36822
37319
|
throw new Error("'symbol' is required");
|
|
@@ -36827,8 +37324,8 @@ function navigationTools(ctx) {
|
|
|
36827
37324
|
if (args.op === "trace_to_symbol" && isEmptyParam(args.toSymbol)) {
|
|
36828
37325
|
throw new Error("'toSymbol' is required for 'trace_to_symbol' op");
|
|
36829
37326
|
}
|
|
36830
|
-
const filePath = await resolvePathArg(ctx, context, args.
|
|
36831
|
-
const toFile = !isEmptyParam(args.
|
|
37327
|
+
const filePath = await resolvePathArg(ctx, context, args.path);
|
|
37328
|
+
const toFile = !isEmptyParam(args.toPath) ? await resolvePathArg(ctx, context, args.toPath) : undefined;
|
|
36832
37329
|
const checked = new Set;
|
|
36833
37330
|
for (const target of [filePath, ...toFile !== undefined ? [toFile] : []]) {
|
|
36834
37331
|
if (checked.has(target))
|
|
@@ -36840,7 +37337,7 @@ function navigationTools(ctx) {
|
|
|
36840
37337
|
}
|
|
36841
37338
|
const rawArgs = {
|
|
36842
37339
|
op: args.op,
|
|
36843
|
-
filePath: args.
|
|
37340
|
+
filePath: args.path,
|
|
36844
37341
|
symbol: args.symbol
|
|
36845
37342
|
};
|
|
36846
37343
|
const depth = coerceOptionalInt(args.depth, "depth", 1, Number.MAX_SAFE_INTEGER);
|
|
@@ -36850,8 +37347,8 @@ function navigationTools(ctx) {
|
|
|
36850
37347
|
rawArgs.expression = args.expression;
|
|
36851
37348
|
if (!isEmptyParam(args.toSymbol))
|
|
36852
37349
|
rawArgs.toSymbol = args.toSymbol;
|
|
36853
|
-
if (!isEmptyParam(args.
|
|
36854
|
-
rawArgs.toFile = args.
|
|
37350
|
+
if (!isEmptyParam(args.toPath))
|
|
37351
|
+
rawArgs.toFile = args.toPath;
|
|
36855
37352
|
if (!isEmptyParam(args.includeTests))
|
|
36856
37353
|
rawArgs.includeTests = coerceBoolean(args.includeTests);
|
|
36857
37354
|
if (!isEmptyParam(args.includeUnresolved))
|
|
@@ -36867,23 +37364,26 @@ function navigationTools(ctx) {
|
|
|
36867
37364
|
return response.text;
|
|
36868
37365
|
}
|
|
36869
37366
|
}
|
|
36870
|
-
};
|
|
37367
|
+
});
|
|
36871
37368
|
}
|
|
36872
37369
|
|
|
36873
37370
|
// src/tools/reading.ts
|
|
36874
37371
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
36875
37372
|
var z12 = tool12.schema;
|
|
36876
37373
|
function buildZoomTitle(args) {
|
|
36877
|
-
|
|
36878
|
-
|
|
36879
|
-
|
|
36880
|
-
|
|
37374
|
+
const targets = args.targets;
|
|
37375
|
+
if (!isEmptyParam(targets)) {
|
|
37376
|
+
if (Array.isArray(targets)) {
|
|
37377
|
+
if (targets.length === 1) {
|
|
37378
|
+
return `${targets[0].path}#${targets[0].symbol}`;
|
|
36881
37379
|
}
|
|
36882
|
-
return `${
|
|
37380
|
+
return `${targets.length} targets across files`;
|
|
37381
|
+
}
|
|
37382
|
+
if (targets && typeof targets === "object") {
|
|
37383
|
+
return `${targets.path}#${targets.symbol}`;
|
|
36883
37384
|
}
|
|
36884
|
-
return `${args.targets.filePath}#${args.targets.symbol}`;
|
|
36885
37385
|
}
|
|
36886
|
-
const path5 = args.
|
|
37386
|
+
const path5 = args.path ?? args.url ?? "";
|
|
36887
37387
|
if (typeof args.symbols === "string")
|
|
36888
37388
|
return path5 ? `${path5}#${args.symbols}` : args.symbols;
|
|
36889
37389
|
if (Array.isArray(args.symbols) && args.symbols.length > 0) {
|
|
@@ -36894,7 +37394,7 @@ function buildZoomTitle(args) {
|
|
|
36894
37394
|
return path5 || "(no target)";
|
|
36895
37395
|
}
|
|
36896
37396
|
function readingTools(ctx) {
|
|
36897
|
-
return {
|
|
37397
|
+
return prepareToolMap({
|
|
36898
37398
|
aft_outline: {
|
|
36899
37399
|
description: "Structural outline of source code, documentation files, or remote URLs. For code, returns symbols (functions, classes, types) with line ranges. For Markdown and HTML, returns heading hierarchy. Use this to explore structure before reading specific sections with aft_zoom. Set `files: true` with a directory target for a flat indexed file tree with language, symbol count, and byte metadata.\n\n" + "For understanding a specific feature, prefer aft_search + aft_zoom on named symbols; use aft_outline on a whole directory only for high-level structure mapping. aft_zoom with `callgraph:true` gives one-level forward calls-out; use aft_callgraph only for reverse callers or multi-level traces.\n\n" + "Pass a single `target`:\n" + ` • file path → outline that file (with signatures)
|
|
36900
37400
|
` + ` • directory path → outline all source files under it (recursively, up to 200 files)
|
|
@@ -36943,21 +37443,21 @@ function readingTools(ctx) {
|
|
|
36943
37443
|
}
|
|
36944
37444
|
},
|
|
36945
37445
|
aft_zoom: {
|
|
36946
|
-
description: "Inspect code symbols or documentation sections. For code, returns the full source of a symbol. Pass `callgraph: true` to also include call-graph annotations (calls-out / called-by within the same file). For Markdown and HTML, returns the section content under the given heading.\n\nUse exactly ONE mode: `{
|
|
37446
|
+
description: "Inspect code symbols or documentation sections. For code, returns the full source of a symbol. Pass `callgraph: true` to also include call-graph annotations (calls-out / called-by within the same file). For Markdown and HTML, returns the section content under the given heading.\n\nUse exactly ONE mode: `{ path, symbols }`, `{ url, symbols }`, or `{ targets }`. `symbols` can be a string or array (one or many lookups in the same file/URL). Use `targets` for cross-file batches: `{ path, symbol }` or an array of them.",
|
|
36947
37447
|
args: {
|
|
36948
|
-
|
|
37448
|
+
path: z12.string().optional().describe("Path to file (absolute or relative to project root)"),
|
|
36949
37449
|
url: z12.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
|
|
36950
37450
|
symbols: z12.union([z12.string(), z12.array(z12.string())]).optional().describe("Symbol name for code, or heading text for Markdown/HTML. Pass a string for one lookup or an array for batched lookups in the same file/URL."),
|
|
36951
37451
|
targets: z12.union([
|
|
36952
37452
|
z12.object({
|
|
36953
|
-
|
|
37453
|
+
path: z12.string().describe("Path to file (absolute or relative to project root)"),
|
|
36954
37454
|
symbol: z12.string().describe("Symbol name in that file")
|
|
36955
37455
|
}),
|
|
36956
37456
|
z12.array(z12.object({
|
|
36957
|
-
|
|
37457
|
+
path: z12.string().describe("Path to file (absolute or relative to project root)"),
|
|
36958
37458
|
symbol: z12.string().describe("Symbol name in that file")
|
|
36959
37459
|
}))
|
|
36960
|
-
]).optional().describe("Cross-file batch: `{
|
|
37460
|
+
]).optional().describe("Cross-file batch: `{ path, symbol }` or an array of them. Mutually exclusive with path/url/symbols."),
|
|
36961
37461
|
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)"),
|
|
36962
37462
|
callgraph: z12.boolean().optional().describe("Include call-graph annotations (calls-out / called-by within the same file). Default false; off keeps zoom output minimal.")
|
|
36963
37463
|
},
|
|
@@ -36968,7 +37468,7 @@ function readingTools(ctx) {
|
|
|
36968
37468
|
const entryEmpty = (entry) => {
|
|
36969
37469
|
if (!entry || typeof entry !== "object")
|
|
36970
37470
|
return true;
|
|
36971
|
-
const fp = entry.
|
|
37471
|
+
const fp = entry.path;
|
|
36972
37472
|
const sym = entry.symbol;
|
|
36973
37473
|
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
36974
37474
|
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
@@ -36978,7 +37478,7 @@ function readingTools(ctx) {
|
|
|
36978
37478
|
return !t.every(entryEmpty);
|
|
36979
37479
|
return !entryEmpty(t);
|
|
36980
37480
|
};
|
|
36981
|
-
const hasFilePath = !isEmptyParam(args.
|
|
37481
|
+
const hasFilePath = !isEmptyParam(args.path);
|
|
36982
37482
|
const hasUrl = !isEmptyParam(args.url);
|
|
36983
37483
|
const hasTargets = hasTargetsProvided(args.targets);
|
|
36984
37484
|
const hasSymbols = !isEmptyParam(args.symbols);
|
|
@@ -36987,7 +37487,7 @@ function readingTools(ctx) {
|
|
|
36987
37487
|
const zoomTitle = buildZoomTitle(args);
|
|
36988
37488
|
const zoomDisplay = { title: zoomTitle };
|
|
36989
37489
|
if (hasFilePath)
|
|
36990
|
-
zoomDisplay.
|
|
37490
|
+
zoomDisplay.path = args.path;
|
|
36991
37491
|
if (hasUrl)
|
|
36992
37492
|
zoomDisplay.url = args.url;
|
|
36993
37493
|
if (hasSymbols) {
|
|
@@ -37006,25 +37506,31 @@ function readingTools(ctx) {
|
|
|
37006
37506
|
});
|
|
37007
37507
|
if (hasTargets) {
|
|
37008
37508
|
if (hasFilePath || hasUrl || hasSymbols) {
|
|
37009
|
-
throw new Error("'targets' is mutually exclusive with '
|
|
37509
|
+
throw new Error("'targets' is mutually exclusive with 'path', 'url', and 'symbols'");
|
|
37010
37510
|
}
|
|
37011
37511
|
const targets = Array.isArray(args.targets) ? args.targets : [args.targets];
|
|
37012
37512
|
if (targets.length === 0) {
|
|
37013
37513
|
throw new Error("'targets' must be a non-empty object or array");
|
|
37014
37514
|
}
|
|
37015
37515
|
for (const [i, entry] of targets.entries()) {
|
|
37016
|
-
|
|
37017
|
-
|
|
37516
|
+
const targetPath = entry?.path;
|
|
37517
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
37518
|
+
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
37018
37519
|
}
|
|
37019
37520
|
if (typeof entry.symbol !== "string" || entry.symbol.length === 0) {
|
|
37020
37521
|
throw new Error(`targets[${i}].symbol must be a non-empty string`);
|
|
37021
37522
|
}
|
|
37022
37523
|
}
|
|
37023
|
-
const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(ctx, context, t.
|
|
37524
|
+
const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(ctx, context, t.path)));
|
|
37024
37525
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, resolvedTargets);
|
|
37025
37526
|
if (permissionDenied)
|
|
37026
37527
|
return permissionDeniedResponse(permissionDenied);
|
|
37027
|
-
const rawArgs2 = {
|
|
37528
|
+
const rawArgs2 = {
|
|
37529
|
+
targets: targets.map((target) => ({
|
|
37530
|
+
filePath: target.path,
|
|
37531
|
+
symbol: target.symbol
|
|
37532
|
+
}))
|
|
37533
|
+
};
|
|
37028
37534
|
if (contextLines !== undefined)
|
|
37029
37535
|
rawArgs2.contextLines = contextLines;
|
|
37030
37536
|
if (wantCallgraph)
|
|
@@ -37036,18 +37542,18 @@ function readingTools(ctx) {
|
|
|
37036
37542
|
return withMeta(response2.text);
|
|
37037
37543
|
}
|
|
37038
37544
|
if (!hasFilePath && !hasUrl) {
|
|
37039
|
-
throw new Error("Provide exactly one of '
|
|
37545
|
+
throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
|
|
37040
37546
|
}
|
|
37041
37547
|
if (hasFilePath && hasUrl) {
|
|
37042
|
-
throw new Error("Provide exactly ONE of '
|
|
37548
|
+
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
37043
37549
|
}
|
|
37044
37550
|
if (!hasUrl) {
|
|
37045
|
-
const file2 = await resolvePathArg(ctx, context, args.
|
|
37551
|
+
const file2 = await resolvePathArg(ctx, context, args.path);
|
|
37046
37552
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, file2);
|
|
37047
37553
|
if (permissionDenied)
|
|
37048
37554
|
return permissionDeniedResponse(permissionDenied);
|
|
37049
37555
|
}
|
|
37050
|
-
const rawArgs = hasUrl ? { url: args.url } : { filePath: args.
|
|
37556
|
+
const rawArgs = hasUrl ? { url: args.url } : { filePath: args.path };
|
|
37051
37557
|
if (hasSymbols)
|
|
37052
37558
|
rawArgs.symbols = args.symbols;
|
|
37053
37559
|
if (contextLines !== undefined)
|
|
@@ -37061,7 +37567,7 @@ function readingTools(ctx) {
|
|
|
37061
37567
|
return withMeta(response.text);
|
|
37062
37568
|
}
|
|
37063
37569
|
}
|
|
37064
|
-
};
|
|
37570
|
+
});
|
|
37065
37571
|
}
|
|
37066
37572
|
async function permissionKindForPath(resolvedPath) {
|
|
37067
37573
|
try {
|
|
@@ -37151,7 +37657,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
|
|
|
37151
37657
|
// src/tools/refactoring.ts
|
|
37152
37658
|
var z13 = tool13.schema;
|
|
37153
37659
|
function refactoringTools(ctx) {
|
|
37154
|
-
return {
|
|
37660
|
+
return prepareToolMap({
|
|
37155
37661
|
aft_refactor: {
|
|
37156
37662
|
description: `Workspace-wide refactoring that updates imports and references across files.
|
|
37157
37663
|
|
|
@@ -37161,7 +37667,7 @@ function refactoringTools(ctx) {
|
|
|
37161
37667
|
` + "- 'inline': replace a function call with the function's body.",
|
|
37162
37668
|
args: {
|
|
37163
37669
|
op: z13.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
|
|
37164
|
-
|
|
37670
|
+
path: z13.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
37165
37671
|
symbol: z13.string().optional().describe("Symbol name — required for 'move' and 'inline' ops"),
|
|
37166
37672
|
destination: z13.string().optional().describe("Target file path — required for 'move' op"),
|
|
37167
37673
|
scope: z13.string().optional().describe("Disambiguation scope for 'move' op — when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
|
|
@@ -37192,7 +37698,7 @@ function refactoringTools(ctx) {
|
|
|
37192
37698
|
if (op === "inline" && callSiteLine === undefined) {
|
|
37193
37699
|
throw new Error("'callSiteLine' is required for 'inline' op");
|
|
37194
37700
|
}
|
|
37195
|
-
const filePath = await resolvePathArg(ctx, context, args.
|
|
37701
|
+
const filePath = await resolvePathArg(ctx, context, args.path);
|
|
37196
37702
|
const destination = op === "move" ? await resolvePathArg(ctx, context, args.destination) : undefined;
|
|
37197
37703
|
const patterns = op === "move" ? resolveRelativePatterns(context, [
|
|
37198
37704
|
workspacePattern(context),
|
|
@@ -37246,7 +37752,7 @@ function refactoringTools(ctx) {
|
|
|
37246
37752
|
return response.text;
|
|
37247
37753
|
}
|
|
37248
37754
|
}
|
|
37249
|
-
};
|
|
37755
|
+
});
|
|
37250
37756
|
}
|
|
37251
37757
|
|
|
37252
37758
|
// src/tools/safety.ts
|
|
@@ -37273,15 +37779,15 @@ function relativePatternsFromPaths(context, paths) {
|
|
|
37273
37779
|
return patterns;
|
|
37274
37780
|
}
|
|
37275
37781
|
function safetyTools(ctx) {
|
|
37276
|
-
return {
|
|
37782
|
+
return prepareToolMap({
|
|
37277
37783
|
aft_safety: {
|
|
37278
37784
|
description: `File safety and recovery operations.
|
|
37279
37785
|
|
|
37280
37786
|
` + `Per-file undo stack is capped at 20 entries (oldest evicted).
|
|
37281
37787
|
|
|
37282
37788
|
` + `Ops:
|
|
37283
|
-
` + `- 'undo': Undo the entire last tool call when '
|
|
37284
|
-
` + `- 'history': List all edit snapshots for a file. Requires '
|
|
37789
|
+
` + `- 'undo': Undo the entire last tool call when 'path' is omitted (typical), or undo the last edit to one file when 'path' is provided. Note: pops from the undo stack (irreversible, no redo). Use 'history' to inspect per-file history before undoing.
|
|
37790
|
+
` + `- 'history': List all edit snapshots for a file. Requires 'path'.
|
|
37285
37791
|
` + `- 'checkpoint': Save a named snapshot of tracked files. Requires 'name'. Optional 'files' to snapshot specific files only.
|
|
37286
37792
|
` + `- 'restore': Restore files to a previously saved checkpoint. Requires 'name'.
|
|
37287
37793
|
` + `- 'list': List all available named checkpoints. No extra params needed.
|
|
@@ -37291,22 +37797,22 @@ function safetyTools(ctx) {
|
|
|
37291
37797
|
` + "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.",
|
|
37292
37798
|
args: {
|
|
37293
37799
|
op: z14.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
|
|
37294
|
-
|
|
37800
|
+
path: z14.string().optional().describe("File path (required for history, optional for undo). Absolute or relative to project root"),
|
|
37295
37801
|
name: z14.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
|
|
37296
37802
|
files: z14.array(z14.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
|
|
37297
37803
|
},
|
|
37298
37804
|
execute: async (args, context) => {
|
|
37299
37805
|
const op = args.op;
|
|
37300
|
-
if (op === "history" && typeof args.
|
|
37301
|
-
throw new Error(`'
|
|
37806
|
+
if (op === "history" && typeof args.path !== "string") {
|
|
37807
|
+
throw new Error(`'path' is required for '${op}' op`);
|
|
37302
37808
|
}
|
|
37303
37809
|
if ((op === "checkpoint" || op === "restore") && typeof args.name !== "string") {
|
|
37304
37810
|
throw new Error(`'name' is required for '${op}' op`);
|
|
37305
37811
|
}
|
|
37306
37812
|
if (op === "undo") {
|
|
37307
37813
|
const previewParams = {};
|
|
37308
|
-
if (typeof args.
|
|
37309
|
-
previewParams.file = args.
|
|
37814
|
+
if (typeof args.path === "string")
|
|
37815
|
+
previewParams.file = args.path;
|
|
37310
37816
|
const preview2 = await callBridge(ctx, context, "undo_preview", previewParams);
|
|
37311
37817
|
if (preview2.success === false) {
|
|
37312
37818
|
throw new Error(bridgeErrorMessage(preview2, "undo preview failed"));
|
|
@@ -37317,14 +37823,14 @@ function safetyTools(ctx) {
|
|
|
37317
37823
|
if (denial)
|
|
37318
37824
|
return permissionDeniedResponse(denial);
|
|
37319
37825
|
}
|
|
37320
|
-
const filePath = typeof args.
|
|
37826
|
+
const filePath = typeof args.path === "string" ? resolveAbsolutePath(context, args.path) : undefined;
|
|
37321
37827
|
const permissionError = await askEditPermission(context, relativePatternsFromPaths(context, previewPaths), filePath ? { filepath: filePath } : { operation: "undo", paths: previewPaths });
|
|
37322
37828
|
if (permissionError)
|
|
37323
37829
|
return permissionDeniedResponse(permissionError);
|
|
37324
37830
|
}
|
|
37325
37831
|
if (op === "checkpoint") {
|
|
37326
37832
|
const coercedFiles = coerceStringArray(args.files);
|
|
37327
|
-
const checkpointFiles = coercedFiles.length > 0 ? coercedFiles : typeof args.
|
|
37833
|
+
const checkpointFiles = coercedFiles.length > 0 ? coercedFiles : typeof args.path === "string" ? [args.path] : undefined;
|
|
37328
37834
|
if (Array.isArray(checkpointFiles)) {
|
|
37329
37835
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
37330
37836
|
const uniqueParents = new Set;
|
|
@@ -37365,7 +37871,7 @@ function safetyTools(ctx) {
|
|
|
37365
37871
|
if (args.name !== undefined)
|
|
37366
37872
|
rawArgs.name = args.name;
|
|
37367
37873
|
const payloadFiles = coerceStringArray(args.files).map(expandTilde2);
|
|
37368
|
-
const filePathArg = typeof args.
|
|
37874
|
+
const filePathArg = typeof args.path === "string" ? expandTilde2(args.path) : undefined;
|
|
37369
37875
|
if (filePathArg !== undefined)
|
|
37370
37876
|
rawArgs.filePath = filePathArg;
|
|
37371
37877
|
if (payloadFiles.length > 0)
|
|
@@ -37377,7 +37883,7 @@ function safetyTools(ctx) {
|
|
|
37377
37883
|
return response.text;
|
|
37378
37884
|
}
|
|
37379
37885
|
}
|
|
37380
|
-
};
|
|
37886
|
+
});
|
|
37381
37887
|
}
|
|
37382
37888
|
|
|
37383
37889
|
// src/tools/search.ts
|
|
@@ -37562,15 +38068,12 @@ function arg3(schema) {
|
|
|
37562
38068
|
function semanticTools(ctx) {
|
|
37563
38069
|
const searchTool = {
|
|
37564
38070
|
description: [
|
|
37565
|
-
"Search code with one tool: concepts, identifiers, error strings, regex, literals, and filenames are auto-routed to the right engine and returned ranked. For conceptual 'how does X work' queries, phrase a full natural-language sentence — the semantic lane is NL-aware and matches intent against docstrings and comments ('how does the ORM build and execute a query', 'where is rate limiting handled'), not just keywords. Exact names, strings, and regex stay terse ('^export', 'Cargo.lock')."
|
|
37566
|
-
"",
|
|
37567
|
-
"Set hint to 'regex', 'literal', or 'semantic' to force a lane."
|
|
38071
|
+
"Search code with one tool: concepts, identifiers, error strings, regex, literals, and filenames are auto-routed to the right engine and returned ranked. For conceptual 'how does X work' queries, phrase a full natural-language sentence — the semantic lane is NL-aware and matches intent against docstrings and comments ('how does the ORM build and execute a query', 'where is rate limiting handled'), not just keywords. Exact names, strings, and regex stay terse ('^export', 'Cargo.lock')."
|
|
37568
38072
|
].join(`
|
|
37569
38073
|
`),
|
|
37570
38074
|
args: {
|
|
37571
38075
|
query: arg3(z15.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
|
|
37572
38076
|
topK: arg3(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)")),
|
|
37573
|
-
hint: arg3(z15.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'.")),
|
|
37574
38077
|
includeTests: arg3(z15.boolean().optional().describe("Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false.")),
|
|
37575
38078
|
path: arg3(z15.string().optional().describe("Search a different project root (absolute or ~ path). Requires that project to have been indexed by AFT."))
|
|
37576
38079
|
},
|
|
@@ -37579,24 +38082,19 @@ function semanticTools(ctx) {
|
|
|
37579
38082
|
throw new Error("semantic_search: invalid params: `query` must be a non-empty string");
|
|
37580
38083
|
}
|
|
37581
38084
|
const query = args.query;
|
|
37582
|
-
const hint = typeof args.hint === "string" ? args.hint : undefined;
|
|
37583
38085
|
const pathArg = typeof args.path === "string" && args.path.trim() ? args.path.trim() : undefined;
|
|
37584
|
-
|
|
37585
|
-
|
|
37586
|
-
|
|
37587
|
-
return permissionDeniedResponse(denied);
|
|
37588
|
-
}
|
|
38086
|
+
const denied = await askSearchPermission(context, query);
|
|
38087
|
+
if (denied)
|
|
38088
|
+
return permissionDeniedResponse(denied);
|
|
37589
38089
|
if (pathArg) {
|
|
37590
|
-
const
|
|
37591
|
-
if (
|
|
37592
|
-
return permissionDeniedResponse(
|
|
38090
|
+
const denied2 = await assertAftSearchExternalPermission(ctx, context, pathArg);
|
|
38091
|
+
if (denied2)
|
|
38092
|
+
return permissionDeniedResponse(denied2);
|
|
37593
38093
|
}
|
|
37594
38094
|
const rawArgs = { query };
|
|
37595
38095
|
const topK = coerceOptionalInt(args.topK, "topK", 1, 100);
|
|
37596
38096
|
if (topK !== undefined)
|
|
37597
38097
|
rawArgs.topK = topK;
|
|
37598
|
-
if (hint)
|
|
37599
|
-
rawArgs.hint = hint;
|
|
37600
38098
|
if (typeof args.includeTests === "boolean")
|
|
37601
38099
|
rawArgs.includeTests = args.includeTests;
|
|
37602
38100
|
if (pathArg)
|
|
@@ -37614,6 +38112,37 @@ function semanticTools(ctx) {
|
|
|
37614
38112
|
};
|
|
37615
38113
|
}
|
|
37616
38114
|
|
|
38115
|
+
// src/tool-registration.ts
|
|
38116
|
+
var ALL_ONLY_TOOLS = ["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"];
|
|
38117
|
+
function buildOpenCodeToolMap(ctx, config2, onUnknownDisabled) {
|
|
38118
|
+
const surface = config2.tool_surface ?? "recommended";
|
|
38119
|
+
const allTools = normalizeToolMap({
|
|
38120
|
+
...surface !== "minimal" && (config2.hoist_builtin_tools !== false ? hoistedTools(ctx) : aftPrefixedTools(ctx)),
|
|
38121
|
+
...readingTools(ctx),
|
|
38122
|
+
...config2.backup?.enabled === false ? {} : safetyTools(ctx),
|
|
38123
|
+
...surface !== "minimal" && importTools(ctx),
|
|
38124
|
+
...navigationTools(ctx),
|
|
38125
|
+
...surface !== "minimal" && astTools(ctx),
|
|
38126
|
+
...surface !== "minimal" && config2.semantic_search === true && semanticTools(ctx),
|
|
38127
|
+
...inspectToolSurfaceEnabled(config2) && inspectTools(ctx),
|
|
38128
|
+
...surface !== "minimal" && config2.search_index === true && searchTools(ctx),
|
|
38129
|
+
...refactoringTools(ctx),
|
|
38130
|
+
...surface !== "minimal" && conflictTools(ctx)
|
|
38131
|
+
});
|
|
38132
|
+
if (surface !== "all") {
|
|
38133
|
+
for (const name of ALL_ONLY_TOOLS)
|
|
38134
|
+
delete allTools[name];
|
|
38135
|
+
}
|
|
38136
|
+
for (const name of config2.disabled_tools ?? []) {
|
|
38137
|
+
if (name in allTools) {
|
|
38138
|
+
delete allTools[name];
|
|
38139
|
+
} else {
|
|
38140
|
+
onUnknownDisabled?.(name, Object.keys(allTools));
|
|
38141
|
+
}
|
|
38142
|
+
}
|
|
38143
|
+
return allTools;
|
|
38144
|
+
}
|
|
38145
|
+
|
|
37617
38146
|
// src/workflow-hints.ts
|
|
37618
38147
|
var HEADING = "## IMPORTANT NOTICE about your tools";
|
|
37619
38148
|
function buildWorkflowHints(opts) {
|
|
@@ -37644,18 +38173,18 @@ function buildWorkflowHints(opts) {
|
|
|
37644
38173
|
}
|
|
37645
38174
|
if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
|
|
37646
38175
|
const searchName = hasSearch ? "aft_search" : grepName;
|
|
37647
|
-
const locate = hasSearch ?
|
|
38176
|
+
const locate = hasSearch ? "`aft_search` is the primary code-search tool: one call auto-routes concepts, identifiers, regex, error strings, and literals." : `\`${grepName}\` (the tool — indexed and ranked) locates code.`;
|
|
37648
38177
|
const readName = opts.hoistBuiltins ? "read" : "aft_read";
|
|
37649
38178
|
sections.push([
|
|
37650
38179
|
`**Code exploration**: ${locate} Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s). DO NOT run \`grep\`/\`rg\`/\`find\`/\`sed\`/\`cat\` through \`bash\` to locate or read code — the bash path is unindexed, unranked, serial, and routinely surfaces the wrong hit. Keep \`bash\` for shell facts (git state, file metadata, processes). Reflex translations:`,
|
|
37651
38180
|
`- \`grep -rn "handleAuth" src/\` in bash → \`${searchName}({ query: "handleAuth" })\``,
|
|
37652
38181
|
`- \`find . -name "*.ts" | xargs grep watcher\` in bash → \`${searchName}({ query: "watcher invalidation" })\` (concepts work too)`,
|
|
37653
|
-
`- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({
|
|
38182
|
+
`- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({ path: "app.ts", startLine: 100, endLine: 160 })\``
|
|
37654
38183
|
].join(`
|
|
37655
38184
|
`));
|
|
37656
38185
|
}
|
|
37657
38186
|
if (hasInspect) {
|
|
37658
|
-
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
|
|
38187
|
+
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/pending_categories as stale or incomplete cache state. AFT schedules a Tier-2 refresh after its next idle or inspect-triggered background run; use one later normal aft_inspect after that refresh, not a polling loop.");
|
|
37659
38188
|
sections.push("**AFT status bar**: tool results may end with a one-line health bar `[AFT E<errors> W<warnings> | D<dead-code> U<unused-exports> C<clone/dup-groups> | T<todos>]` — an IDE-style glance that appears when a count changes. `E`/`W` are live LSP diagnostics for files touched this session (your universal compile-error signal across every language with an LSP). A `~` before `D` means the dead-code/unused/dup counts predate your latest edit — run `aft_inspect` for current numbers and detail. When `E>0`, you likely just introduced errors; investigate before moving on.");
|
|
37660
38189
|
}
|
|
37661
38190
|
if (hasNavigate) {
|
|
@@ -37700,6 +38229,18 @@ function buildHintsFromConfig(config2, disabledTools) {
|
|
|
37700
38229
|
disabledTools
|
|
37701
38230
|
});
|
|
37702
38231
|
}
|
|
38232
|
+
function appendHintsToSystem(system, hintsBlock) {
|
|
38233
|
+
if (!hintsBlock)
|
|
38234
|
+
return;
|
|
38235
|
+
if (system.length > 0) {
|
|
38236
|
+
const last = system.length - 1;
|
|
38237
|
+
system[last] = `${system[last]}
|
|
38238
|
+
|
|
38239
|
+
${hintsBlock}`;
|
|
38240
|
+
} else {
|
|
38241
|
+
system.push(hintsBlock);
|
|
38242
|
+
}
|
|
38243
|
+
}
|
|
37703
38244
|
|
|
37704
38245
|
// src/index.ts
|
|
37705
38246
|
setActiveLogger(bridgeLogger);
|
|
@@ -37728,11 +38269,11 @@ var PLUGIN_VERSION = (() => {
|
|
|
37728
38269
|
return "0.0.0";
|
|
37729
38270
|
}
|
|
37730
38271
|
})();
|
|
37731
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
38272
|
+
var ANNOUNCEMENT_VERSION = "0.49.1";
|
|
37732
38273
|
var ANNOUNCEMENT_FEATURES = [
|
|
37733
|
-
"
|
|
37734
|
-
"
|
|
37735
|
-
"
|
|
38274
|
+
"Windows: language servers spawn again under extended-length paths, config edits reach running servers, and dead-code/diagnostics results no longer silently drop — one canonical path form across the LSP and inspect surface.",
|
|
38275
|
+
"Faster answers under load: completed bash commands respond immediately instead of waiting behind background maintenance (thanks @hheei), and chained commands like `cargo test && ...` keep every command's output.",
|
|
38276
|
+
"Permission rules with absolute paths outside the project root now match (thanks @iceteaSA), and slow embedding providers get a clear timeout message naming the setting that raises the budget."
|
|
37736
38277
|
];
|
|
37737
38278
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
37738
38279
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -38168,38 +38709,12 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38168
38709
|
} else {
|
|
38169
38710
|
cleanupWarnings(notifyOpts).catch(() => {});
|
|
38170
38711
|
}
|
|
38171
|
-
const
|
|
38172
|
-
|
|
38173
|
-
const allTools = normalizeToolMap({
|
|
38174
|
-
...surface !== "minimal" && (aftConfig.hoist_builtin_tools !== false ? hoistedTools(ctx) : aftPrefixedTools(ctx)),
|
|
38175
|
-
...readingTools(ctx),
|
|
38176
|
-
...aftConfig.backup?.enabled === false ? {} : safetyTools(ctx),
|
|
38177
|
-
...surface !== "minimal" && importTools(ctx),
|
|
38178
|
-
...navigationTools(ctx),
|
|
38179
|
-
...surface !== "minimal" && astTools(ctx),
|
|
38180
|
-
...surface !== "minimal" && aftConfig.semantic_search === true && semanticTools(ctx),
|
|
38181
|
-
...inspectToolSurfaceEnabled(aftConfig) && inspectTools(ctx),
|
|
38182
|
-
...surface !== "minimal" && aftConfig.search_index === true && searchTools(ctx),
|
|
38183
|
-
...refactoringTools(ctx),
|
|
38184
|
-
...surface !== "minimal" && conflictTools(ctx)
|
|
38712
|
+
const allTools = buildOpenCodeToolMap(ctx, aftConfig, (name, available) => {
|
|
38713
|
+
warn2(`disabled_tools: "${name}" not found — available: ${available.join(", ")}`);
|
|
38185
38714
|
});
|
|
38186
|
-
|
|
38187
|
-
|
|
38188
|
-
|
|
38189
|
-
delete allTools[name];
|
|
38190
|
-
}
|
|
38191
|
-
}
|
|
38192
|
-
}
|
|
38193
|
-
const disabled = new Set(aftConfig.disabled_tools ?? []);
|
|
38194
|
-
if (disabled.size > 0) {
|
|
38195
|
-
for (const name of disabled) {
|
|
38196
|
-
if (name in allTools) {
|
|
38197
|
-
delete allTools[name];
|
|
38198
|
-
} else {
|
|
38199
|
-
warn2(`disabled_tools: "${name}" not found — available: ${Object.keys(allTools).join(", ")}`);
|
|
38200
|
-
}
|
|
38201
|
-
}
|
|
38202
|
-
log2(`Disabled ${disabled.size} tool(s): ${[...disabled].join(", ")}`);
|
|
38715
|
+
const disabled = aftConfig.disabled_tools ?? [];
|
|
38716
|
+
if (disabled.length > 0) {
|
|
38717
|
+
log2(`Disabled ${disabled.length} tool(s): ${disabled.join(", ")}`);
|
|
38203
38718
|
}
|
|
38204
38719
|
instrumentToolMap(allTools);
|
|
38205
38720
|
const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
|
|
@@ -38259,9 +38774,9 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38259
38774
|
return {
|
|
38260
38775
|
tool: allTools,
|
|
38261
38776
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
38262
|
-
if (hintsBlock)
|
|
38263
|
-
|
|
38264
|
-
|
|
38777
|
+
if (!hintsBlock)
|
|
38778
|
+
return;
|
|
38779
|
+
appendHintsToSystem(output.system, hintsBlock);
|
|
38265
38780
|
},
|
|
38266
38781
|
event: async (eventInput) => {
|
|
38267
38782
|
await autoUpdateEventHook(eventInput);
|
|
@@ -38314,7 +38829,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38314
38829
|
const sessionDir = getSessionDirectoryCached(sid) ?? await getSessionDirectory(input.client, sid, input.directory) ?? input.directory;
|
|
38315
38830
|
signalBashWaitDetachForProject(pool, sessionDir, sid);
|
|
38316
38831
|
},
|
|
38317
|
-
"tool.execute.before": async (toolInput) => {
|
|
38832
|
+
"tool.execute.before": async (toolInput, output) => {
|
|
38833
|
+
output.args = prepareOpenCodeArguments(toolInput.tool, output.args);
|
|
38318
38834
|
if (toolInput.sessionID)
|
|
38319
38835
|
inspectTier2Idle.clear(toolInput.sessionID);
|
|
38320
38836
|
},
|