@cortexkit/aft-opencode 0.48.1 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +786 -383
- 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/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/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -9783,6 +9783,23 @@ async function renameIfPresent(from, to) {
|
|
|
9783
9783
|
throw error2;
|
|
9784
9784
|
}
|
|
9785
9785
|
}
|
|
9786
|
+
// ../aft-bridge/dist/error-contract.js
|
|
9787
|
+
class AftToolError extends Error {
|
|
9788
|
+
code;
|
|
9789
|
+
response;
|
|
9790
|
+
constructor(message, code, response) {
|
|
9791
|
+
const cause = { code, message, response };
|
|
9792
|
+
super(message, { cause });
|
|
9793
|
+
this.name = "AftToolError";
|
|
9794
|
+
this.code = code;
|
|
9795
|
+
this.response = response;
|
|
9796
|
+
}
|
|
9797
|
+
}
|
|
9798
|
+
function toolErrorFromResponse(command, response) {
|
|
9799
|
+
const code = typeof response.code === "string" && response.code.length > 0 ? response.code : "unknown_error";
|
|
9800
|
+
const message = typeof response.message === "string" && response.message.length > 0 ? response.message : `${command} failed`;
|
|
9801
|
+
return new AftToolError(message, code, response);
|
|
9802
|
+
}
|
|
9786
9803
|
// ../aft-bridge/dist/jsonc.js
|
|
9787
9804
|
function stripJsoncSymbols(value) {
|
|
9788
9805
|
if (Array.isArray(value)) {
|
|
@@ -11404,6 +11421,420 @@ function isProcessAlive(pid) {
|
|
|
11404
11421
|
return true;
|
|
11405
11422
|
}
|
|
11406
11423
|
}
|
|
11424
|
+
// ../aft-bridge/dist/path-aliases.js
|
|
11425
|
+
class InvalidRequestError extends AftToolError {
|
|
11426
|
+
constructor(message) {
|
|
11427
|
+
super(message, "invalid_request", {
|
|
11428
|
+
success: false,
|
|
11429
|
+
code: "invalid_request",
|
|
11430
|
+
message
|
|
11431
|
+
});
|
|
11432
|
+
this.name = "InvalidRequestError";
|
|
11433
|
+
}
|
|
11434
|
+
}
|
|
11435
|
+
function isWellFormedUnicodeString(value) {
|
|
11436
|
+
for (let index = 0;index < value.length; index++) {
|
|
11437
|
+
const codeUnit = value.charCodeAt(index);
|
|
11438
|
+
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
11439
|
+
const next = value.charCodeAt(index + 1);
|
|
11440
|
+
if (next < 56320 || next > 57343 || Number.isNaN(next))
|
|
11441
|
+
return false;
|
|
11442
|
+
index++;
|
|
11443
|
+
} else if (codeUnit >= 56320 && codeUnit <= 57343) {
|
|
11444
|
+
return false;
|
|
11445
|
+
}
|
|
11446
|
+
}
|
|
11447
|
+
return true;
|
|
11448
|
+
}
|
|
11449
|
+
function hasOwn(record, key) {
|
|
11450
|
+
return Object.hasOwn(record, key);
|
|
11451
|
+
}
|
|
11452
|
+
function invalidPathValue(property) {
|
|
11453
|
+
throw new InvalidRequestError(`'${property}' must be a non-empty well-formed Unicode string`);
|
|
11454
|
+
}
|
|
11455
|
+
function pathValue(record, property) {
|
|
11456
|
+
const value = record[property];
|
|
11457
|
+
if (typeof value !== "string" || value.length === 0 || !isWellFormedUnicodeString(value)) {
|
|
11458
|
+
invalidPathValue(property);
|
|
11459
|
+
}
|
|
11460
|
+
return value;
|
|
11461
|
+
}
|
|
11462
|
+
function normalizeAliasPair(record, canonical, legacy, required) {
|
|
11463
|
+
const hasCanonical = hasOwn(record, canonical);
|
|
11464
|
+
const hasLegacy = hasOwn(record, legacy);
|
|
11465
|
+
if (!hasCanonical && !hasLegacy) {
|
|
11466
|
+
if (required) {
|
|
11467
|
+
throw new InvalidRequestError(`'${canonical}' is required`);
|
|
11468
|
+
}
|
|
11469
|
+
return;
|
|
11470
|
+
}
|
|
11471
|
+
if (hasCanonical && hasLegacy) {
|
|
11472
|
+
let canonicalValue;
|
|
11473
|
+
let legacyValue;
|
|
11474
|
+
try {
|
|
11475
|
+
canonicalValue = pathValue(record, canonical);
|
|
11476
|
+
legacyValue = pathValue(record, legacy);
|
|
11477
|
+
} catch {
|
|
11478
|
+
throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must both be non-empty well-formed Unicode strings`);
|
|
11479
|
+
}
|
|
11480
|
+
if (canonicalValue !== legacyValue) {
|
|
11481
|
+
throw new InvalidRequestError(`Invalid request: '${canonical}' and '${legacy}' must contain equal decoded strings`);
|
|
11482
|
+
}
|
|
11483
|
+
delete record[legacy];
|
|
11484
|
+
return;
|
|
11485
|
+
}
|
|
11486
|
+
if (hasCanonical) {
|
|
11487
|
+
pathValue(record, canonical);
|
|
11488
|
+
return;
|
|
11489
|
+
}
|
|
11490
|
+
record[canonical] = pathValue(record, legacy);
|
|
11491
|
+
delete record[legacy];
|
|
11492
|
+
}
|
|
11493
|
+
function validateOptionalCanonicalPath(record, property) {
|
|
11494
|
+
if (hasOwn(record, property))
|
|
11495
|
+
pathValue(record, property);
|
|
11496
|
+
}
|
|
11497
|
+
function normalizeZoomTargets(record) {
|
|
11498
|
+
if (!hasOwn(record, "targets"))
|
|
11499
|
+
return;
|
|
11500
|
+
const targets = record.targets;
|
|
11501
|
+
const normalizeTarget = (target, index) => {
|
|
11502
|
+
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
11503
|
+
throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
|
|
11504
|
+
}
|
|
11505
|
+
const source = target;
|
|
11506
|
+
const emptyTarget = source.symbol === "" && (hasOwn(source, "path") && source.path === "" || hasOwn(source, "filePath") && source.filePath === "");
|
|
11507
|
+
if (emptyTarget)
|
|
11508
|
+
return { ...source };
|
|
11509
|
+
const normalized = { ...source };
|
|
11510
|
+
try {
|
|
11511
|
+
normalizeAliasPair(normalized, "path", "filePath", true);
|
|
11512
|
+
} catch (error2) {
|
|
11513
|
+
if (error2 instanceof InvalidRequestError) {
|
|
11514
|
+
throw new InvalidRequestError(error2.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
11515
|
+
}
|
|
11516
|
+
throw error2;
|
|
11517
|
+
}
|
|
11518
|
+
return normalized;
|
|
11519
|
+
};
|
|
11520
|
+
if (Array.isArray(targets)) {
|
|
11521
|
+
if (targets.length === 0)
|
|
11522
|
+
return;
|
|
11523
|
+
record.targets = targets.map(normalizeTarget);
|
|
11524
|
+
return;
|
|
11525
|
+
}
|
|
11526
|
+
if (targets && typeof targets === "object") {
|
|
11527
|
+
record.targets = normalizeTarget(targets, 0);
|
|
11528
|
+
}
|
|
11529
|
+
}
|
|
11530
|
+
function bareToolName(toolName) {
|
|
11531
|
+
const bare = toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
|
|
11532
|
+
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") {
|
|
11533
|
+
return bare;
|
|
11534
|
+
}
|
|
11535
|
+
return;
|
|
11536
|
+
}
|
|
11537
|
+
function prepareCanonicalPathArguments(toolName, rawArguments) {
|
|
11538
|
+
if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
|
|
11539
|
+
throw new InvalidRequestError("tool arguments must be an object");
|
|
11540
|
+
}
|
|
11541
|
+
const tool = bareToolName(toolName);
|
|
11542
|
+
const record = { ...rawArguments };
|
|
11543
|
+
if (!tool)
|
|
11544
|
+
return record;
|
|
11545
|
+
switch (tool) {
|
|
11546
|
+
case "read":
|
|
11547
|
+
case "write":
|
|
11548
|
+
case "edit":
|
|
11549
|
+
case "move":
|
|
11550
|
+
case "import":
|
|
11551
|
+
case "refactor":
|
|
11552
|
+
normalizeAliasPair(record, "path", "filePath", true);
|
|
11553
|
+
break;
|
|
11554
|
+
case "zoom":
|
|
11555
|
+
normalizeAliasPair(record, "path", "filePath", false);
|
|
11556
|
+
normalizeZoomTargets(record);
|
|
11557
|
+
break;
|
|
11558
|
+
case "callgraph":
|
|
11559
|
+
normalizeAliasPair(record, "path", "filePath", true);
|
|
11560
|
+
normalizeAliasPair(record, "toPath", "toFile", false);
|
|
11561
|
+
break;
|
|
11562
|
+
case "safety":
|
|
11563
|
+
normalizeAliasPair(record, "path", "filePath", false);
|
|
11564
|
+
break;
|
|
11565
|
+
case "grep":
|
|
11566
|
+
case "search":
|
|
11567
|
+
case "conflicts":
|
|
11568
|
+
validateOptionalCanonicalPath(record, "path");
|
|
11569
|
+
break;
|
|
11570
|
+
}
|
|
11571
|
+
return record;
|
|
11572
|
+
}
|
|
11573
|
+
var EDIT_ROOT_COMPATIBILITY_KEYS = new Set([
|
|
11574
|
+
"oldString",
|
|
11575
|
+
"newString",
|
|
11576
|
+
"replaceAll",
|
|
11577
|
+
"occurrence"
|
|
11578
|
+
]);
|
|
11579
|
+
var EDIT_ROOT_CANONICAL_KEYS = new Set(["path", "appendContent", "edits", "symbol", "content"]);
|
|
11580
|
+
var EDIT_ITEM_KEYS = new Set([
|
|
11581
|
+
"oldString",
|
|
11582
|
+
"newString",
|
|
11583
|
+
"replaceAll",
|
|
11584
|
+
"occurrence",
|
|
11585
|
+
"startLine",
|
|
11586
|
+
"endLine",
|
|
11587
|
+
"content"
|
|
11588
|
+
]);
|
|
11589
|
+
var ASCII_WHITESPACE = /^[\t\n\v\f\r ]+$/;
|
|
11590
|
+
var ASCII_TRIM = /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g;
|
|
11591
|
+
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
|
11592
|
+
function prepareCanonicalEditArguments(toolName, rawArguments) {
|
|
11593
|
+
if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments)) {
|
|
11594
|
+
throw new InvalidRequestError("tool arguments must be an object");
|
|
11595
|
+
}
|
|
11596
|
+
const raw = rawArguments;
|
|
11597
|
+
const record = copyOwnProperties(raw);
|
|
11598
|
+
normalizeEditPathAlias(record);
|
|
11599
|
+
const suppliedLineFields = ["startLine", "endLine"].filter((key) => hasOwn(record, key));
|
|
11600
|
+
if (suppliedLineFields.length > 0) {
|
|
11601
|
+
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 }].");
|
|
11602
|
+
}
|
|
11603
|
+
const isOpenCodeRetiredBoundary = toolName === "aft_edit";
|
|
11604
|
+
const retiredFields = ["file", "mode"].filter((key) => hasOwn(record, key));
|
|
11605
|
+
if (retiredFields.length > 0 && isOpenCodeRetiredBoundary) {
|
|
11606
|
+
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`.");
|
|
11607
|
+
}
|
|
11608
|
+
const unknownRootKeys = Object.getOwnPropertyNames(record).filter((key) => !EDIT_ROOT_CANONICAL_KEYS.has(key) && !EDIT_ROOT_COMPATIBILITY_KEYS.has(key) && key !== "filePath").sort();
|
|
11609
|
+
if (unknownRootKeys.length > 0) {
|
|
11610
|
+
throw new InvalidRequestError(formatUnknownKeys(unknownRootKeys));
|
|
11611
|
+
}
|
|
11612
|
+
const modes = editModesPresent(record);
|
|
11613
|
+
if (modes.length > 1) {
|
|
11614
|
+
throw new InvalidRequestError(`edit: conflicting modes: ${modes.join(", ")}`);
|
|
11615
|
+
}
|
|
11616
|
+
if (modes.length === 0) {
|
|
11617
|
+
throw new InvalidRequestError("edit: exactly one of `appendContent`, `edits`, or `symbol` plus `content` is required");
|
|
11618
|
+
}
|
|
11619
|
+
const mode = modes[0];
|
|
11620
|
+
if (mode === "appendContent") {
|
|
11621
|
+
if (typeof record.appendContent !== "string") {
|
|
11622
|
+
throw new InvalidRequestError("edit: 'appendContent' must be a string");
|
|
11623
|
+
}
|
|
11624
|
+
} else if (mode === "edits") {
|
|
11625
|
+
const parsedEdits = parseEditArray(record.edits);
|
|
11626
|
+
record.edits = parsedEdits.map((item, index) => normalizeEditItem(item, index));
|
|
11627
|
+
} else if (mode === "symbol/content") {
|
|
11628
|
+
if (!hasOwn(record, "symbol") || typeof record.symbol !== "string") {
|
|
11629
|
+
throw new InvalidRequestError("edit: 'symbol' must be a string when symbol mode is selected");
|
|
11630
|
+
}
|
|
11631
|
+
if (!hasOwn(record, "content") || typeof record.content !== "string") {
|
|
11632
|
+
throw new InvalidRequestError("edit: symbol mode requires both 'symbol' and 'content' string properties");
|
|
11633
|
+
}
|
|
11634
|
+
} else {
|
|
11635
|
+
const item = {};
|
|
11636
|
+
for (const key of EDIT_ROOT_COMPATIBILITY_KEYS) {
|
|
11637
|
+
if (hasOwn(record, key))
|
|
11638
|
+
item[key] = record[key];
|
|
11639
|
+
}
|
|
11640
|
+
record.edits = [normalizeEditItem(item, 0)];
|
|
11641
|
+
for (const key of EDIT_ROOT_COMPATIBILITY_KEYS)
|
|
11642
|
+
delete record[key];
|
|
11643
|
+
}
|
|
11644
|
+
validateEditPath(record);
|
|
11645
|
+
return record;
|
|
11646
|
+
}
|
|
11647
|
+
function normalizeEditPathAlias(record) {
|
|
11648
|
+
const hasCanonical = hasOwn(record, "path");
|
|
11649
|
+
const hasLegacy = hasOwn(record, "filePath");
|
|
11650
|
+
if (!hasCanonical && !hasLegacy)
|
|
11651
|
+
return;
|
|
11652
|
+
if (hasCanonical && hasLegacy) {
|
|
11653
|
+
let canonical;
|
|
11654
|
+
let legacy;
|
|
11655
|
+
try {
|
|
11656
|
+
canonical = pathValue(record, "path");
|
|
11657
|
+
legacy = pathValue(record, "filePath");
|
|
11658
|
+
} catch {
|
|
11659
|
+
throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must both be non-empty well-formed Unicode strings");
|
|
11660
|
+
}
|
|
11661
|
+
if (canonical !== legacy) {
|
|
11662
|
+
throw new InvalidRequestError("Invalid request: 'path' and 'filePath' must contain equal decoded strings");
|
|
11663
|
+
}
|
|
11664
|
+
delete record.filePath;
|
|
11665
|
+
return;
|
|
11666
|
+
}
|
|
11667
|
+
if (!hasCanonical) {
|
|
11668
|
+
record.path = pathValue(record, "filePath");
|
|
11669
|
+
delete record.filePath;
|
|
11670
|
+
}
|
|
11671
|
+
}
|
|
11672
|
+
function validateEditPath(record) {
|
|
11673
|
+
if (!hasOwn(record, "path")) {
|
|
11674
|
+
throw new InvalidRequestError("'path' is required");
|
|
11675
|
+
}
|
|
11676
|
+
pathValue(record, "path");
|
|
11677
|
+
}
|
|
11678
|
+
function formatUnknownKeys(keys) {
|
|
11679
|
+
return `Unrecognized keys: ${keys.map((key) => `"${key}"`).join(", ")}`;
|
|
11680
|
+
}
|
|
11681
|
+
function editModesPresent(record) {
|
|
11682
|
+
const modes = [];
|
|
11683
|
+
if (hasOwn(record, "appendContent"))
|
|
11684
|
+
modes.push("appendContent");
|
|
11685
|
+
if (hasOwn(record, "edits"))
|
|
11686
|
+
modes.push("edits");
|
|
11687
|
+
if (hasOwn(record, "symbol") || hasOwn(record, "content"))
|
|
11688
|
+
modes.push("symbol/content");
|
|
11689
|
+
if (["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(record, key))) {
|
|
11690
|
+
modes.push("oldString/newString");
|
|
11691
|
+
}
|
|
11692
|
+
return modes;
|
|
11693
|
+
}
|
|
11694
|
+
function parseEditArray(value) {
|
|
11695
|
+
if (typeof value === "string") {
|
|
11696
|
+
let parsed;
|
|
11697
|
+
try {
|
|
11698
|
+
parsed = JSON.parse(value);
|
|
11699
|
+
} catch {
|
|
11700
|
+
throw new InvalidRequestError("edit: 'edits' must contain valid JSON representing an array");
|
|
11701
|
+
}
|
|
11702
|
+
if (!Array.isArray(parsed)) {
|
|
11703
|
+
throw new InvalidRequestError("edit: 'edits' JSON must have an array root");
|
|
11704
|
+
}
|
|
11705
|
+
if (parsed.length === 0) {
|
|
11706
|
+
throw new InvalidRequestError("edit: 'edits' array must not be empty");
|
|
11707
|
+
}
|
|
11708
|
+
return parsed;
|
|
11709
|
+
}
|
|
11710
|
+
if (!Array.isArray(value)) {
|
|
11711
|
+
throw new InvalidRequestError("edit: 'edits' must be a non-empty array");
|
|
11712
|
+
}
|
|
11713
|
+
if (value.length === 0) {
|
|
11714
|
+
throw new InvalidRequestError("edit: 'edits' array must not be empty");
|
|
11715
|
+
}
|
|
11716
|
+
return value;
|
|
11717
|
+
}
|
|
11718
|
+
function normalizeEditItem(value, index) {
|
|
11719
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11720
|
+
throw new InvalidRequestError(`edit: edits[${index}] must be an object`);
|
|
11721
|
+
}
|
|
11722
|
+
const source = value;
|
|
11723
|
+
const item = copyOwnProperties(source);
|
|
11724
|
+
normalizeItemAlias(item, "oldString", "oldText");
|
|
11725
|
+
normalizeItemAlias(item, "newString", "newText");
|
|
11726
|
+
const hasFindField = ["oldString", "newString", "replaceAll", "occurrence"].some((key) => hasOwn(item, key));
|
|
11727
|
+
const hasRangeField = ["startLine", "endLine", "content"].some((key) => hasOwn(item, key));
|
|
11728
|
+
if (hasFindField && hasRangeField) {
|
|
11729
|
+
throw new InvalidRequestError(`edit: edits[${index}] mixes find/replace and line-range fields`);
|
|
11730
|
+
}
|
|
11731
|
+
if (hasFindField) {
|
|
11732
|
+
if (!hasOwn(item, "oldString") || typeof item.oldString !== "string") {
|
|
11733
|
+
throw new InvalidRequestError(`edit: edits[${index}] requires string 'oldString'`);
|
|
11734
|
+
}
|
|
11735
|
+
if (hasOwn(item, "newString") && typeof item.newString !== "string") {
|
|
11736
|
+
throw new InvalidRequestError(`edit: edits[${index}].newString must be a string`);
|
|
11737
|
+
}
|
|
11738
|
+
coerceEditScalars(item, index);
|
|
11739
|
+
validateEditItemKeys(item, index);
|
|
11740
|
+
return item;
|
|
11741
|
+
}
|
|
11742
|
+
if (hasRangeField) {
|
|
11743
|
+
for (const key of ["startLine", "endLine"]) {
|
|
11744
|
+
const value2 = item[key];
|
|
11745
|
+
if (typeof value2 === "string" && /^[0-9]+$/.test(value2.trim())) {
|
|
11746
|
+
item[key] = Number(value2.trim());
|
|
11747
|
+
}
|
|
11748
|
+
if (!hasOwn(item, key) || !isPositiveSafeInteger(item[key])) {
|
|
11749
|
+
throw new InvalidRequestError(`edit: edits[${index}].${key} must be a positive integer`);
|
|
11750
|
+
}
|
|
11751
|
+
}
|
|
11752
|
+
if (item.startLine > item.endLine) {
|
|
11753
|
+
throw new InvalidRequestError(`edit: edits[${index}] requires startLine <= endLine`);
|
|
11754
|
+
}
|
|
11755
|
+
if (!hasOwn(item, "content") || typeof item.content !== "string") {
|
|
11756
|
+
throw new InvalidRequestError(`edit: edits[${index}] requires string 'content'`);
|
|
11757
|
+
}
|
|
11758
|
+
validateEditItemKeys(item, index);
|
|
11759
|
+
return item;
|
|
11760
|
+
}
|
|
11761
|
+
throw new InvalidRequestError(`edit: edits[${index}] must be a find/replace or line-range item`);
|
|
11762
|
+
}
|
|
11763
|
+
function normalizeItemAlias(item, canonical, legacy) {
|
|
11764
|
+
if (hasOwn(item, legacy)) {
|
|
11765
|
+
if (!hasOwn(item, canonical))
|
|
11766
|
+
item[canonical] = item[legacy];
|
|
11767
|
+
delete item[legacy];
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
function validateEditItemKeys(item, index) {
|
|
11771
|
+
const unknown = Object.getOwnPropertyNames(item).filter((key) => !EDIT_ITEM_KEYS.has(key)).sort();
|
|
11772
|
+
if (unknown.length > 0) {
|
|
11773
|
+
throw new InvalidRequestError(`edit: edits[${index}] contains ${formatUnknownKeys(unknown)}`);
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
function coerceEditScalars(item, index) {
|
|
11777
|
+
if (hasOwn(item, "replaceAll") && hasOwn(item, "occurrence")) {
|
|
11778
|
+
throw new InvalidRequestError(`edit: edits[${index}] cannot contain both 'replaceAll' and 'occurrence'`);
|
|
11779
|
+
}
|
|
11780
|
+
if (hasOwn(item, "replaceAll"))
|
|
11781
|
+
item.replaceAll = coerceEditBoolean(item.replaceAll, index);
|
|
11782
|
+
if (hasOwn(item, "occurrence")) {
|
|
11783
|
+
const occurrence = coerceEditOccurrence(item.occurrence, index);
|
|
11784
|
+
if (occurrence === undefined)
|
|
11785
|
+
delete item.occurrence;
|
|
11786
|
+
else
|
|
11787
|
+
item.occurrence = occurrence;
|
|
11788
|
+
}
|
|
11789
|
+
}
|
|
11790
|
+
function coerceEditBoolean(value, index) {
|
|
11791
|
+
if (typeof value === "boolean")
|
|
11792
|
+
return value;
|
|
11793
|
+
if (typeof value === "number" && Number.isFinite(value) && (value === 0 || value === 1)) {
|
|
11794
|
+
return value === 1;
|
|
11795
|
+
}
|
|
11796
|
+
if (typeof value === "string") {
|
|
11797
|
+
if (value === "1")
|
|
11798
|
+
return true;
|
|
11799
|
+
if (value === "0")
|
|
11800
|
+
return false;
|
|
11801
|
+
if (/^(?:true|false)$/i.test(value))
|
|
11802
|
+
return value.toLowerCase() === "true";
|
|
11803
|
+
}
|
|
11804
|
+
throw new InvalidRequestError(`edit: edits[${index}].replaceAll must be a boolean, true/false string, or 0/1`);
|
|
11805
|
+
}
|
|
11806
|
+
function coerceEditOccurrence(value, index) {
|
|
11807
|
+
if (value === null)
|
|
11808
|
+
return;
|
|
11809
|
+
if (typeof value === "string") {
|
|
11810
|
+
const trimmed = value.replace(ASCII_TRIM, "");
|
|
11811
|
+
if (trimmed.length === 0 || ASCII_WHITESPACE.test(trimmed))
|
|
11812
|
+
return;
|
|
11813
|
+
if (!/^[+]?[0-9]+$/.test(trimmed)) {
|
|
11814
|
+
throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
|
|
11815
|
+
}
|
|
11816
|
+
try {
|
|
11817
|
+
const parsed = BigInt(trimmed);
|
|
11818
|
+
if (parsed < 1n || parsed > BigInt(MAX_SAFE_INTEGER))
|
|
11819
|
+
throw new Error("out of range");
|
|
11820
|
+
return Number(parsed);
|
|
11821
|
+
} catch {
|
|
11822
|
+
throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
|
|
11823
|
+
}
|
|
11824
|
+
}
|
|
11825
|
+
if (typeof value === "number" && isPositiveSafeInteger(value))
|
|
11826
|
+
return value;
|
|
11827
|
+
throw new InvalidRequestError(`edit: edits[${index}].occurrence must be a positive integer`);
|
|
11828
|
+
}
|
|
11829
|
+
function isPositiveSafeInteger(value) {
|
|
11830
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
11831
|
+
}
|
|
11832
|
+
function copyOwnProperties(source) {
|
|
11833
|
+
const copy = Object.create(null);
|
|
11834
|
+
for (const key of Object.getOwnPropertyNames(source))
|
|
11835
|
+
copy[key] = source[key];
|
|
11836
|
+
return copy;
|
|
11837
|
+
}
|
|
11407
11838
|
// ../aft-bridge/dist/project-identity.js
|
|
11408
11839
|
import { createHash as createHash4 } from "node:crypto";
|
|
11409
11840
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -31564,8 +31995,8 @@ function restoreAutoUpdateSnapshot(snapshot) {
|
|
|
31564
31995
|
rmSync4(snapshot.tempDir, { recursive: true, force: true });
|
|
31565
31996
|
}
|
|
31566
31997
|
}
|
|
31567
|
-
function stripPackageNameFromPath(
|
|
31568
|
-
let current =
|
|
31998
|
+
function stripPackageNameFromPath(pathValue2, packageName) {
|
|
31999
|
+
let current = pathValue2;
|
|
31569
32000
|
for (const segment of [...packageName.split("/")].reverse()) {
|
|
31570
32001
|
if (basename3(current) !== segment)
|
|
31571
32002
|
return null;
|
|
@@ -32707,7 +33138,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
|
|
|
32707
33138
|
return null;
|
|
32708
33139
|
});
|
|
32709
33140
|
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.`);
|
|
33141
|
+
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
33142
|
return {
|
|
32712
33143
|
started: false,
|
|
32713
33144
|
reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} — see plugin log`
|
|
@@ -33500,7 +33931,7 @@ async function downloadAndInstall(spec, tag, assets, platform3, arch, fetchImpl,
|
|
|
33500
33931
|
const previousArchiveSha256 = previousMeta?.archiveSha256 ?? (previousMeta?.binarySha256 ? undefined : previousMeta?.sha256);
|
|
33501
33932
|
if (previousMeta && previousMeta.version === tag && previousArchiveSha256) {
|
|
33502
33933
|
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.`);
|
|
33934
|
+
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
33935
|
try {
|
|
33505
33936
|
unlinkSync7(archivePath);
|
|
33506
33937
|
} catch {}
|
|
@@ -33732,12 +34163,43 @@ function normalizeToolArgSchemas(toolDefinition) {
|
|
|
33732
34163
|
}
|
|
33733
34164
|
return toolDefinition;
|
|
33734
34165
|
}
|
|
33735
|
-
function
|
|
33736
|
-
|
|
33737
|
-
|
|
34166
|
+
function bareToolName2(toolName) {
|
|
34167
|
+
return toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
|
|
34168
|
+
}
|
|
34169
|
+
function prepareOpenCodeArguments(toolName, rawArguments) {
|
|
34170
|
+
const bare = bareToolName2(toolName);
|
|
34171
|
+
if (bare === "edit") {
|
|
34172
|
+
return prepareCanonicalEditArguments(toolName, rawArguments);
|
|
34173
|
+
}
|
|
34174
|
+
return prepareCanonicalPathArguments(toolName, rawArguments);
|
|
34175
|
+
}
|
|
34176
|
+
var DISPLAY_FILE_PATH_TOOLS = new Set(["read", "write", "edit"]);
|
|
34177
|
+
function preserveDisplayFilePathAlias(toolName, rawArguments, prepared) {
|
|
34178
|
+
if (!DISPLAY_FILE_PATH_TOOLS.has(toolName))
|
|
34179
|
+
return;
|
|
34180
|
+
if (!rawArguments || typeof rawArguments !== "object" || Array.isArray(rawArguments))
|
|
34181
|
+
return;
|
|
34182
|
+
const raw = rawArguments;
|
|
34183
|
+
if (typeof prepared.path === "string" && !Object.hasOwn(raw, "filePath")) {
|
|
34184
|
+
raw.filePath = prepared.path;
|
|
34185
|
+
}
|
|
34186
|
+
}
|
|
34187
|
+
function prepareToolMap(tools) {
|
|
34188
|
+
for (const [toolName, def] of Object.entries(tools)) {
|
|
34189
|
+
const execute = def.execute;
|
|
34190
|
+
def.execute = async (args, context) => {
|
|
34191
|
+
const prepared = prepareOpenCodeArguments(toolName, args);
|
|
34192
|
+
preserveDisplayFilePathAlias(toolName, args, prepared);
|
|
34193
|
+
return execute(prepared, context);
|
|
34194
|
+
};
|
|
33738
34195
|
}
|
|
33739
34196
|
return tools;
|
|
33740
34197
|
}
|
|
34198
|
+
function normalizeToolMap(tools) {
|
|
34199
|
+
for (const def of Object.values(tools))
|
|
34200
|
+
normalizeToolArgSchemas(def);
|
|
34201
|
+
return prepareToolMap(tools);
|
|
34202
|
+
}
|
|
33741
34203
|
// src/shared/ignored-message.ts
|
|
33742
34204
|
async function sendIgnoredMessage2(client, sessionID, text) {
|
|
33743
34205
|
const typedClient = client;
|
|
@@ -35039,8 +35501,45 @@ function astTools(ctx) {
|
|
|
35039
35501
|
};
|
|
35040
35502
|
}
|
|
35041
35503
|
|
|
35042
|
-
// src/tools/
|
|
35504
|
+
// src/tools/conflicts.ts
|
|
35043
35505
|
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
35506
|
+
var z4 = tool4.schema;
|
|
35507
|
+
function conflictTools(ctx) {
|
|
35508
|
+
return {
|
|
35509
|
+
aft_conflicts: {
|
|
35510
|
+
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).",
|
|
35511
|
+
args: {
|
|
35512
|
+
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()
|
|
35513
|
+
},
|
|
35514
|
+
execute: async (args, context) => {
|
|
35515
|
+
const rawArgs = {};
|
|
35516
|
+
if (!isEmptyParam(args?.path)) {
|
|
35517
|
+
const expanded = expandTilde2(String(args.path));
|
|
35518
|
+
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
35519
|
+
const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
|
|
35520
|
+
const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
|
|
35521
|
+
kind: "directory"
|
|
35522
|
+
});
|
|
35523
|
+
if (denied)
|
|
35524
|
+
return permissionDeniedResponse(denied);
|
|
35525
|
+
rawArgs.path = resolved;
|
|
35526
|
+
}
|
|
35527
|
+
const response = await callToolCall(ctx, context, "conflicts", rawArgs);
|
|
35528
|
+
if (response.success === false) {
|
|
35529
|
+
throw new Error(response.message || "git_conflicts failed");
|
|
35530
|
+
}
|
|
35531
|
+
return response.text;
|
|
35532
|
+
}
|
|
35533
|
+
}
|
|
35534
|
+
};
|
|
35535
|
+
}
|
|
35536
|
+
|
|
35537
|
+
// src/tools/hoisted.ts
|
|
35538
|
+
import * as path4 from "node:path";
|
|
35539
|
+
import { tool as tool8 } from "@opencode-ai/plugin";
|
|
35540
|
+
|
|
35541
|
+
// src/tools/bash.ts
|
|
35542
|
+
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
35044
35543
|
|
|
35045
35544
|
// src/shared/subagent-detect.ts
|
|
35046
35545
|
var CACHE_MAX_ENTRIES2 = 200;
|
|
@@ -35093,7 +35592,7 @@ function setCache2(sessionId, isSubagent) {
|
|
|
35093
35592
|
}
|
|
35094
35593
|
|
|
35095
35594
|
// src/tools/bash.ts
|
|
35096
|
-
var
|
|
35595
|
+
var z5 = tool5.schema;
|
|
35097
35596
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
35098
35597
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
35099
35598
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
@@ -35193,22 +35692,22 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
|
35193
35692
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
35194
35693
|
const initialBashCfg = resolveBashConfig(ctx.config);
|
|
35195
35694
|
const backgroundFlagArg = initialBashCfg.background ? {
|
|
35196
|
-
background:
|
|
35695
|
+
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
35696
|
} : {};
|
|
35198
35697
|
const ptyArgs = initialBashCfg.background ? {
|
|
35199
|
-
pty:
|
|
35698
|
+
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
35699
|
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
35700
|
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
35701
|
} : {};
|
|
35203
35702
|
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:
|
|
35703
|
+
command: z5.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
|
|
35704
|
+
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."),
|
|
35705
|
+
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."),
|
|
35706
|
+
description: z5.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
|
|
35707
|
+
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."),
|
|
35708
|
+
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
35709
|
...backgroundFlagArg,
|
|
35211
|
-
compressed:
|
|
35710
|
+
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
35711
|
...ptyArgs
|
|
35213
35712
|
};
|
|
35214
35713
|
return {
|
|
@@ -35307,8 +35806,8 @@ function createBashStatusTool(ctx) {
|
|
|
35307
35806
|
return {
|
|
35308
35807
|
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
35808
|
args: {
|
|
35310
|
-
taskId:
|
|
35311
|
-
outputMode:
|
|
35809
|
+
taskId: z5.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded."),
|
|
35810
|
+
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
35811
|
},
|
|
35313
35812
|
execute: async (args, context) => {
|
|
35314
35813
|
const taskId = args.taskId;
|
|
@@ -35322,7 +35821,7 @@ function createBashKillTool(ctx) {
|
|
|
35322
35821
|
return {
|
|
35323
35822
|
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
35823
|
args: {
|
|
35325
|
-
taskId:
|
|
35824
|
+
taskId: z5.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded.")
|
|
35326
35825
|
},
|
|
35327
35826
|
execute: async (args, context) => {
|
|
35328
35827
|
const data = await callBashBridge(ctx, context, "bash_kill", {
|
|
@@ -35414,44 +35913,6 @@ function shortenCommand(command) {
|
|
|
35414
35913
|
return collapsed.length <= 80 ? collapsed : `${collapsed.slice(0, 77)}...`;
|
|
35415
35914
|
}
|
|
35416
35915
|
|
|
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
35916
|
// src/tools/bash_watch.ts
|
|
35456
35917
|
import { tool as tool6 } from "@opencode-ai/plugin";
|
|
35457
35918
|
var z6 = tool6.schema;
|
|
@@ -35569,7 +36030,7 @@ Waited ${waited.elapsed_ms}ms; matched ${JSON.stringify(waited.match ?? "")}${st
|
|
|
35569
36030
|
Waited ${waited.elapsed_ms}ms; timeout reached without match.`;
|
|
35570
36031
|
} else if (waited.reason === "unavailable") {
|
|
35571
36032
|
text += `
|
|
35572
|
-
Waited ${waited.elapsed_ms}ms; the bridge
|
|
36033
|
+
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
36034
|
} else {
|
|
35574
36035
|
const stat2 = String(data.status ?? "unknown");
|
|
35575
36036
|
const e = typeof data.exit_code === "number" ? `, exit ${data.exit_code}` : "";
|
|
@@ -35831,6 +36292,12 @@ function relativeToWorktree(fp, worktree) {
|
|
|
35831
36292
|
function readAttachments(data) {
|
|
35832
36293
|
return Array.isArray(data.attachments) ? data.attachments : [];
|
|
35833
36294
|
}
|
|
36295
|
+
function persistFilePathAlias(args, context) {
|
|
36296
|
+
if (typeof args.path === "string" && !Object.hasOwn(args, "filePath")) {
|
|
36297
|
+
args.filePath = args.path;
|
|
36298
|
+
}
|
|
36299
|
+
context.metadata({ metadata: {} });
|
|
36300
|
+
}
|
|
35834
36301
|
function buildUnifiedDiff(fp, before, after) {
|
|
35835
36302
|
const beforeLines = before.split(`
|
|
35836
36303
|
`);
|
|
@@ -35994,19 +36461,6 @@ function inferBeforeStart(ops, from, beforeLen) {
|
|
|
35994
36461
|
return beforeLen;
|
|
35995
36462
|
}
|
|
35996
36463
|
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
36464
|
var READ_DESCRIPTION = `Read file contents or list directory entries.
|
|
36011
36465
|
|
|
36012
36466
|
Use either startLine/endLine OR offset/limit to read a section of a file.
|
|
@@ -36020,96 +36474,99 @@ Behavior:
|
|
|
36020
36474
|
- Directories return sorted entries with trailing / for subdirectories
|
|
36021
36475
|
|
|
36022
36476
|
Examples:
|
|
36023
|
-
Read full file: { "
|
|
36024
|
-
Read lines 50-100: { "
|
|
36025
|
-
Read 30 lines from line 200: { "
|
|
36026
|
-
List directory: { "
|
|
36477
|
+
Read full file: { "path": "src/app.ts" }
|
|
36478
|
+
Read lines 50-100: { "path": "src/app.ts", "startLine": 50, "endLine": 100 }
|
|
36479
|
+
Read 30 lines from line 200: { "path": "src/app.ts", "offset": 200, "limit": 30 }
|
|
36480
|
+
List directory: { "path": "src/" }
|
|
36027
36481
|
`;
|
|
36028
36482
|
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,
|
|
36483
|
+
return prepareToolMap({
|
|
36484
|
+
read: {
|
|
36485
|
+
description: READ_DESCRIPTION,
|
|
36486
|
+
args: {
|
|
36487
|
+
filePath: z8.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
36488
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
|
|
36489
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to stop reading at (inclusive)"),
|
|
36490
|
+
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines to return (default: 2000)"),
|
|
36491
|
+
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
|
|
36492
|
+
},
|
|
36493
|
+
execute: async (args, context) => {
|
|
36494
|
+
const file2 = args.path;
|
|
36495
|
+
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36496
|
+
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36497
|
+
persistFilePathAlias(args, context);
|
|
36498
|
+
{
|
|
36499
|
+
const denial = await assertExternalDirectoryPermission(ctx, context, filePath, {
|
|
36500
|
+
serverValidatedRead: true
|
|
36501
|
+
});
|
|
36502
|
+
if (denial)
|
|
36503
|
+
return permissionDeniedResponse(denial);
|
|
36504
|
+
}
|
|
36505
|
+
try {
|
|
36506
|
+
await runAsk(context.ask({
|
|
36507
|
+
permission: "read",
|
|
36508
|
+
patterns: [filePath],
|
|
36509
|
+
always: ["*"],
|
|
36510
|
+
metadata: {}
|
|
36511
|
+
}));
|
|
36512
|
+
} catch (error53) {
|
|
36513
|
+
if (error53 instanceof Error && error53.message)
|
|
36514
|
+
return permissionDeniedResponse(error53.message);
|
|
36515
|
+
return permissionDeniedResponse("Permission denied.");
|
|
36516
|
+
}
|
|
36517
|
+
const rawStartLine = coerceOptionalInt(args.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
|
|
36518
|
+
const rawEndLine = coerceOptionalInt(args.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
|
|
36519
|
+
const rawLimit = coerceOptionalInt(args.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
|
|
36520
|
+
const rawOffset = coerceOptionalInt(args.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
|
|
36521
|
+
let startLine = rawStartLine;
|
|
36522
|
+
let endLine = rawEndLine;
|
|
36523
|
+
if (startLine === undefined && rawOffset !== undefined) {
|
|
36524
|
+
startLine = rawOffset;
|
|
36525
|
+
if (rawLimit !== undefined) {
|
|
36526
|
+
endLine = rawOffset + rawLimit - 1;
|
|
36527
|
+
}
|
|
36528
|
+
}
|
|
36529
|
+
const rawArgs = { filePath: file2 };
|
|
36530
|
+
if (startLine !== undefined)
|
|
36531
|
+
rawArgs.startLine = startLine;
|
|
36532
|
+
if (endLine !== undefined)
|
|
36533
|
+
rawArgs.endLine = endLine;
|
|
36534
|
+
if (rawLimit !== undefined && rawOffset === undefined)
|
|
36535
|
+
rawArgs.limit = rawLimit;
|
|
36536
|
+
const response = await callToolCall(ctx, context, "read", rawArgs);
|
|
36537
|
+
if (response.success === false) {
|
|
36538
|
+
throw new Error(response.message || "read failed");
|
|
36539
|
+
}
|
|
36540
|
+
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
36541
|
+
const output = response.text;
|
|
36542
|
+
const attachments = readAttachments(response);
|
|
36543
|
+
if (attachments.length > 0) {
|
|
36544
|
+
const toolAttachments = attachments.filter((attachment) => typeof attachment.mime === "string" && typeof attachment.data === "string").map((attachment) => ({
|
|
36545
|
+
type: "file",
|
|
36546
|
+
mime: attachment.mime,
|
|
36547
|
+
url: `data:${attachment.mime};base64,${attachment.data}`
|
|
36548
|
+
}));
|
|
36549
|
+
if (toolAttachments.length > 0) {
|
|
36550
|
+
const first = attachments[0];
|
|
36551
|
+
const firstMime = typeof first.mime === "string" ? first.mime : "";
|
|
36552
|
+
return {
|
|
36553
|
+
output,
|
|
36103
36554
|
title: dp,
|
|
36104
|
-
|
|
36105
|
-
|
|
36106
|
-
|
|
36107
|
-
|
|
36555
|
+
attachments: toolAttachments,
|
|
36556
|
+
metadata: {
|
|
36557
|
+
preview: output,
|
|
36558
|
+
filepath: filePath,
|
|
36559
|
+
title: dp,
|
|
36560
|
+
isImage: first.kind === "image" || firstMime.startsWith("image/"),
|
|
36561
|
+
isPdf: first.kind === "pdf" || firstMime === "application/pdf"
|
|
36562
|
+
}
|
|
36563
|
+
};
|
|
36564
|
+
}
|
|
36108
36565
|
}
|
|
36566
|
+
return { output, title: dp, metadata: { title: dp } };
|
|
36109
36567
|
}
|
|
36110
|
-
return { output, title: dp, metadata: { title: dp } };
|
|
36111
36568
|
}
|
|
36112
|
-
};
|
|
36569
|
+
}).read;
|
|
36113
36570
|
}
|
|
36114
36571
|
function getWriteDescription(ctx, editToolName) {
|
|
36115
36572
|
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,10 +36580,12 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36123
36580
|
content: z8.string().describe("The full content to write to the file")
|
|
36124
36581
|
},
|
|
36125
36582
|
execute: async (args, context) => {
|
|
36126
|
-
const
|
|
36583
|
+
const argsRecord = args;
|
|
36584
|
+
const file2 = args.path;
|
|
36127
36585
|
const content = args.content;
|
|
36128
36586
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36129
36587
|
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36588
|
+
persistFilePathAlias(argsRecord, context);
|
|
36130
36589
|
const relPath = path4.relative(projectRoot, filePath);
|
|
36131
36590
|
{
|
|
36132
36591
|
const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
@@ -36136,7 +36595,7 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36136
36595
|
const rawArgs = { filePath: file2, content };
|
|
36137
36596
|
const preview2 = await callToolCall(ctx, context, "write", rawArgs, { preview: true });
|
|
36138
36597
|
if (preview2.success === false) {
|
|
36139
|
-
throw
|
|
36598
|
+
throw toolErrorFromResponse("write", preview2);
|
|
36140
36599
|
}
|
|
36141
36600
|
const denial = await askEditPermission(context, [relPath], {
|
|
36142
36601
|
filepath: filePath,
|
|
@@ -36146,7 +36605,7 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36146
36605
|
return permissionDeniedResponse(denial);
|
|
36147
36606
|
const data = await callToolCall(ctx, context, "write", rawArgs);
|
|
36148
36607
|
if (data.success === false) {
|
|
36149
|
-
throw
|
|
36608
|
+
throw toolErrorFromResponse("write", data);
|
|
36150
36609
|
}
|
|
36151
36610
|
const output = data.text;
|
|
36152
36611
|
const diff = data.diff;
|
|
@@ -36156,20 +36615,20 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
36156
36615
|
const dp = relativeToWorktree(filePath, projectRoot);
|
|
36157
36616
|
const beforeContent = diff.before ?? "";
|
|
36158
36617
|
const afterContent = diff.after ?? content;
|
|
36618
|
+
const patch = truncated ? typeof preview2.preview_diff === "string" ? preview2.preview_diff : "" : buildUnifiedDiff(filePath, beforeContent, afterContent);
|
|
36159
36619
|
return {
|
|
36160
36620
|
output,
|
|
36161
36621
|
title: dp,
|
|
36162
36622
|
metadata: {
|
|
36163
|
-
diff:
|
|
36164
|
-
...
|
|
36623
|
+
diff: patch,
|
|
36624
|
+
...patch ? {
|
|
36165
36625
|
filediff: {
|
|
36166
36626
|
file: filePath,
|
|
36167
|
-
|
|
36168
|
-
|
|
36169
|
-
|
|
36170
|
-
deletions: diff?.deletions ?? 0
|
|
36627
|
+
patch,
|
|
36628
|
+
additions: diff.additions ?? 0,
|
|
36629
|
+
deletions: diff.deletions ?? 0
|
|
36171
36630
|
}
|
|
36172
|
-
},
|
|
36631
|
+
} : {},
|
|
36173
36632
|
diagnostics: {}
|
|
36174
36633
|
}
|
|
36175
36634
|
};
|
|
@@ -36182,40 +36641,33 @@ function getEditDescription(ctx, writeToolName) {
|
|
|
36182
36641
|
|
|
36183
36642
|
**Modes** (determined by which parameters you provide):
|
|
36184
36643
|
|
|
36185
|
-
|
|
36644
|
+
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
36645
|
|
|
36187
|
-
1. **Append** — pass \`
|
|
36188
|
-
Appends text to the end of a file, creating
|
|
36189
|
-
Example: \`{ "
|
|
36646
|
+
1. **Append** — pass \`path\` + \`appendContent\`
|
|
36647
|
+
Appends text to the end of a file, creating it if it does not exist.
|
|
36648
|
+
Example: \`{ "path": "notes.txt", "appendContent": "new line\\n" }\`
|
|
36190
36649
|
|
|
36191
|
-
2. **Batch edits** — pass \`
|
|
36650
|
+
2. **Batch edits** — pass \`path\` + \`edits\` array
|
|
36192
36651
|
Multiple edits in one file atomically. Each edit is either:
|
|
36193
36652
|
- \`{ "oldString": "old", "newString": "new" }\` — find/replace
|
|
36194
36653
|
- \`{ "oldString": "old", "newString": "new", "replaceAll": true }\` — replace every match
|
|
36195
36654
|
- \`{ "startLine": 5, "endLine": 7, "content": "new lines" }\` — replace line range (1-based, both inclusive)
|
|
36196
36655
|
Set content to empty string to delete lines.
|
|
36197
36656
|
|
|
36198
|
-
3. **Symbol replace** — pass \`
|
|
36199
|
-
Replaces an entire named symbol (function, class, type)
|
|
36657
|
+
3. **Symbol replace** — pass \`path\` + \`symbol\` + \`content\`
|
|
36658
|
+
Replaces an entire named symbol (function, class, type).
|
|
36200
36659
|
Includes decorators, attributes, and doc comments in the replacement range.
|
|
36201
|
-
|
|
36202
|
-
Example: \`{ "filePath": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
|
|
36660
|
+
Example: \`{ "path": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
|
|
36203
36661
|
|
|
36204
|
-
4. **Find and replace** —
|
|
36662
|
+
4. **Find and replace** — put \`oldString\` and optional \`newString\` in an item of \`edits[]\`
|
|
36205
36663
|
Finds the exact text in \`oldString\` and replaces it with \`newString\`.
|
|
36206
36664
|
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" }\`
|
|
36665
|
+
If multiple matches exist, specify \`occurrence\` or set \`replaceAll: true\` in that item.
|
|
36209
36666
|
|
|
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 }\`
|
|
36667
|
+
5. **Replace all occurrences** — add \`replaceAll: true\` to a find/replace item.
|
|
36213
36668
|
|
|
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\`.
|
|
36669
|
+
6. **Select specific occurrence** — add \`occurrence: N\` to a find/replace item (1-based).
|
|
36670
|
+
When multiple matches exist, select the Nth one (1 = first, 2 = second, etc.).
|
|
36219
36671
|
|
|
36220
36672
|
**Behavior:**
|
|
36221
36673
|
${backupBehavior}
|
|
@@ -36228,64 +36680,45 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
36228
36680
|
return {
|
|
36229
36681
|
description: getEditDescription(ctx, writeToolName),
|
|
36230
36682
|
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"),
|
|
36683
|
+
filePath: z8.string().describe("Path to the file to edit (absolute or relative to project root)"),
|
|
36236
36684
|
symbol: z8.string().optional().describe("Named symbol to replace (function, class, type)"),
|
|
36237
36685
|
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
|
|
36686
|
+
appendContent: z8.string().optional().describe("Text to append to the end of path; creates the file if needed"),
|
|
36239
36687
|
edits: z8.array(z8.object({
|
|
36240
36688
|
oldString: z8.string().optional().describe("Text to find for a batch find/replace edit"),
|
|
36241
36689
|
newString: z8.string().optional().describe("Replacement text for a batch find/replace edit"),
|
|
36242
36690
|
replaceAll: z8.boolean().optional().describe("Replace every occurrence for this batch item"),
|
|
36243
|
-
occurrence: optionalInt(
|
|
36691
|
+
occurrence: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based occurrence for this batch item (1 = first match)"),
|
|
36244
36692
|
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based start line for a batch line-range edit"),
|
|
36245
36693
|
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based end line for a batch line-range edit"),
|
|
36246
36694
|
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")
|
|
36695
|
+
})).min(1).optional().describe("Batch edits — non-empty array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects")
|
|
36248
36696
|
},
|
|
36249
36697
|
execute: async (args, context) => {
|
|
36250
36698
|
const argsRecord = args;
|
|
36251
36699
|
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 `
|
|
36700
|
+
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
36701
|
}
|
|
36254
|
-
const file2 = args.
|
|
36702
|
+
const file2 = args.path;
|
|
36255
36703
|
if (!file2)
|
|
36256
|
-
throw new Error("'
|
|
36704
|
+
throw new Error("'path' parameter is required");
|
|
36257
36705
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36258
36706
|
const filePath = resolvePathFromProjectRoot(projectRoot, file2);
|
|
36707
|
+
persistFilePathAlias(argsRecord, context);
|
|
36259
36708
|
const relPath = path4.relative(projectRoot, filePath);
|
|
36260
36709
|
{
|
|
36261
36710
|
const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
36262
36711
|
if (denial2)
|
|
36263
36712
|
return permissionDeniedResponse(denial2);
|
|
36264
36713
|
}
|
|
36265
|
-
const
|
|
36266
|
-
const
|
|
36267
|
-
for (const key of ["appendContent", "symbol", "content", "oldString", "newString"]) {
|
|
36714
|
+
const rawArgs = { path: file2 };
|
|
36715
|
+
for (const key of ["appendContent", "symbol", "content", "edits"]) {
|
|
36268
36716
|
if (argsRecord[key] !== undefined)
|
|
36269
36717
|
rawArgs[key] = argsRecord[key];
|
|
36270
36718
|
}
|
|
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
36719
|
const preview2 = await callToolCall(ctx, context, "edit", rawArgs, { preview: true });
|
|
36287
36720
|
if (preview2.success === false) {
|
|
36288
|
-
throw
|
|
36721
|
+
throw toolErrorFromResponse("edit", preview2);
|
|
36289
36722
|
}
|
|
36290
36723
|
const denial = await askEditPermission(context, [relPath], {
|
|
36291
36724
|
filepath: filePath,
|
|
@@ -36295,7 +36728,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
36295
36728
|
return permissionDeniedResponse(denial);
|
|
36296
36729
|
const data = await callToolCall(ctx, context, "edit", rawArgs);
|
|
36297
36730
|
if (data.success === false) {
|
|
36298
|
-
throw
|
|
36731
|
+
throw toolErrorFromResponse("edit", data);
|
|
36299
36732
|
}
|
|
36300
36733
|
const output = data.text;
|
|
36301
36734
|
const diff = data.diff;
|
|
@@ -36304,17 +36737,17 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
36304
36737
|
const truncated = diff.truncated === true;
|
|
36305
36738
|
const beforeContent = diff.before ?? "";
|
|
36306
36739
|
const afterContent = diff.after ?? "";
|
|
36740
|
+
const patch = truncated ? typeof preview2.preview_diff === "string" ? preview2.preview_diff : "" : buildUnifiedDiff(filePath, beforeContent, afterContent);
|
|
36307
36741
|
const uiMeta = {
|
|
36308
|
-
diff:
|
|
36309
|
-
...
|
|
36742
|
+
diff: patch,
|
|
36743
|
+
...patch ? {
|
|
36310
36744
|
filediff: {
|
|
36311
36745
|
file: filePath,
|
|
36312
|
-
|
|
36313
|
-
after: afterContent,
|
|
36746
|
+
patch,
|
|
36314
36747
|
additions: diff.additions ?? 0,
|
|
36315
36748
|
deletions: diff.deletions ?? 0
|
|
36316
36749
|
}
|
|
36317
|
-
},
|
|
36750
|
+
} : {},
|
|
36318
36751
|
diagnostics: {}
|
|
36319
36752
|
};
|
|
36320
36753
|
return { output, title: relativeToWorktree(filePath, projectRoot), metadata: uiMeta };
|
|
@@ -36494,12 +36927,12 @@ function createMoveTool(ctx) {
|
|
|
36494
36927
|
return {
|
|
36495
36928
|
description: moveDescription(ctx),
|
|
36496
36929
|
args: {
|
|
36497
|
-
|
|
36930
|
+
path: z8.string().describe("Source file path to move (absolute or relative to project root)"),
|
|
36498
36931
|
destination: z8.string().describe("Destination file path (absolute or relative to project root)")
|
|
36499
36932
|
},
|
|
36500
36933
|
execute: async (args, context) => {
|
|
36501
36934
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36502
|
-
const filePath = resolvePathFromProjectRoot(projectRoot, args.
|
|
36935
|
+
const filePath = resolvePathFromProjectRoot(projectRoot, args.path);
|
|
36503
36936
|
const destPath = resolvePathFromProjectRoot(projectRoot, args.destination);
|
|
36504
36937
|
{
|
|
36505
36938
|
const sourceDenial = await assertExternalDirectoryPermission(ctx, context, filePath, {
|
|
@@ -36520,7 +36953,7 @@ function createMoveTool(ctx) {
|
|
|
36520
36953
|
metadata: { action: "move" }
|
|
36521
36954
|
}));
|
|
36522
36955
|
const result = await callToolCall(ctx, context, "move", {
|
|
36523
|
-
filePath: args.
|
|
36956
|
+
filePath: args.path,
|
|
36524
36957
|
destination: args.destination
|
|
36525
36958
|
});
|
|
36526
36959
|
if (result.success === false) {
|
|
@@ -36549,51 +36982,14 @@ function hoistedTools(ctx) {
|
|
|
36549
36982
|
tools.bash_kill = createBashKillTool(ctx);
|
|
36550
36983
|
}
|
|
36551
36984
|
}
|
|
36552
|
-
return tools;
|
|
36985
|
+
return prepareToolMap(tools);
|
|
36553
36986
|
}
|
|
36554
36987
|
function aftPrefixedTools(ctx) {
|
|
36555
36988
|
const aftEditTool = createEditTool(ctx, "aft_write");
|
|
36556
36989
|
const tools = {
|
|
36557
36990
|
aft_read: createReadTool(ctx),
|
|
36558
36991
|
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
|
-
},
|
|
36992
|
+
aft_edit: aftEditTool,
|
|
36597
36993
|
aft_apply_patch: createApplyPatchTool(ctx),
|
|
36598
36994
|
aft_delete: createDeleteTool(ctx),
|
|
36599
36995
|
aft_move: createMoveTool(ctx)
|
|
@@ -36608,7 +37004,7 @@ function aftPrefixedTools(ctx) {
|
|
|
36608
37004
|
tools.bash_kill = createBashKillTool(ctx);
|
|
36609
37005
|
}
|
|
36610
37006
|
}
|
|
36611
|
-
return tools;
|
|
37007
|
+
return prepareToolMap(tools);
|
|
36612
37008
|
}
|
|
36613
37009
|
|
|
36614
37010
|
// src/tools/imports.ts
|
|
@@ -36616,17 +37012,17 @@ import { tool as tool9 } from "@opencode-ai/plugin";
|
|
|
36616
37012
|
var z9 = tool9.schema;
|
|
36617
37013
|
function importTools(ctx) {
|
|
36618
37014
|
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 {
|
|
37015
|
+
return prepareToolMap({
|
|
36620
37016
|
aft_import: {
|
|
36621
37017
|
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
37018
|
|
|
36623
37019
|
` + `Ops:
|
|
36624
37020
|
` + `- 'add': Add an import. Auto-detects group (stdlib/external/internal), deduplicates. Requires 'module'. Optional 'names', 'defaultImport', 'typeOnly'.
|
|
36625
37021
|
` + `- '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 '
|
|
37022
|
+
` + `- 'organize': Re-sort and re-group all imports by language convention, deduplicate. Requires only 'path'. ${organizeRecovery}`,
|
|
36627
37023
|
args: {
|
|
36628
37024
|
op: z9.enum(["add", "remove", "organize"]).describe("Import operation"),
|
|
36629
|
-
|
|
37025
|
+
path: z9.string().describe("Path to the file (absolute or relative to project root)"),
|
|
36630
37026
|
module: z9.string().optional().describe("Module path (required for add, remove — e.g. 'react', './utils', 'std::fmt')"),
|
|
36631
37027
|
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
37028
|
defaultImport: z9.string().optional().describe("Default import name, ES only (e.g. 'React')"),
|
|
@@ -36643,7 +37039,7 @@ function importTools(ctx) {
|
|
|
36643
37039
|
if ((op === "add" || op === "remove") && isEmptyParam(args.module)) {
|
|
36644
37040
|
throw new Error(`'module' is required for '${op}' op`);
|
|
36645
37041
|
}
|
|
36646
|
-
const filePath = await resolvePathArg(ctx, context, args.
|
|
37042
|
+
const filePath = await resolvePathArg(ctx, context, args.path);
|
|
36647
37043
|
{
|
|
36648
37044
|
const denial = await assertExternalDirectoryPermission(ctx, context, filePath);
|
|
36649
37045
|
if (denial)
|
|
@@ -36680,7 +37076,7 @@ function importTools(ctx) {
|
|
|
36680
37076
|
return response.text;
|
|
36681
37077
|
}
|
|
36682
37078
|
}
|
|
36683
|
-
};
|
|
37079
|
+
});
|
|
36684
37080
|
}
|
|
36685
37081
|
|
|
36686
37082
|
// src/tools/inspect.ts
|
|
@@ -36787,7 +37183,7 @@ import { tool as tool11 } from "@opencode-ai/plugin";
|
|
|
36787
37183
|
var z11 = tool11.schema;
|
|
36788
37184
|
var CALLGRAPH_SOFT_CODES = new Set(["symbol_not_found", "callgraph_building"]);
|
|
36789
37185
|
function navigationTools(ctx) {
|
|
36790
|
-
return {
|
|
37186
|
+
return prepareToolMap({
|
|
36791
37187
|
aft_callgraph: {
|
|
36792
37188
|
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
37189
|
|
|
@@ -36796,27 +37192,27 @@ function navigationTools(ctx) {
|
|
|
36796
37192
|
` + `- 'impact': What breaks if a symbol changes — affected callers with signatures and entry-point status (blast radius). Use before a risky edit.
|
|
36797
37193
|
` + `- 'call_tree': What a function calls (forward traversal). Use to understand a function's dependencies before modifying it.
|
|
36798
37194
|
` + `- '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 '
|
|
37195
|
+
` + `- '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
37196
|
` + `- 'trace_data': Follow a value through variable assignments and function parameters across files. Requires 'symbol' (scope to trace from) and 'expression'.
|
|
36801
37197
|
|
|
36802
|
-
` + `All ops require both '
|
|
37198
|
+
` + `All ops require both 'path' and 'symbol'. 'expression' is additionally required for trace_data; 'toSymbol' for trace_to_symbol.
|
|
36803
37199
|
|
|
36804
37200
|
` + `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
37201
|
`,
|
|
36806
37202
|
args: {
|
|
36807
37203
|
op: z11.enum(["call_tree", "callers", "trace_to", "trace_to_symbol", "impact", "trace_data"]).describe("Navigation operation"),
|
|
36808
|
-
|
|
37204
|
+
path: z11.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
|
|
36809
37205
|
symbol: z11.string().describe("Name of the symbol to analyze"),
|
|
36810
37206
|
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
37207
|
expression: z11.string().optional().describe("Expression to track through data flow (required for trace_data op)"),
|
|
36812
37208
|
toSymbol: z11.string().optional().describe("Target symbol name for trace_to_symbol; the returned path ends at this symbol"),
|
|
36813
|
-
|
|
37209
|
+
toPath: z11.string().optional().describe("Optional target file for trace_to_symbol; required when toSymbol exists in multiple files"),
|
|
36814
37210
|
includeTests: z11.boolean().optional().describe("Include test files in callers/paths. Defaults to false; tests are hidden."),
|
|
36815
37211
|
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
37212
|
},
|
|
36817
37213
|
execute: async (args, context) => {
|
|
36818
|
-
if (isEmptyParam(args.
|
|
36819
|
-
throw new Error("'
|
|
37214
|
+
if (isEmptyParam(args.path)) {
|
|
37215
|
+
throw new Error("'path' is required");
|
|
36820
37216
|
}
|
|
36821
37217
|
if (isEmptyParam(args.symbol)) {
|
|
36822
37218
|
throw new Error("'symbol' is required");
|
|
@@ -36827,8 +37223,8 @@ function navigationTools(ctx) {
|
|
|
36827
37223
|
if (args.op === "trace_to_symbol" && isEmptyParam(args.toSymbol)) {
|
|
36828
37224
|
throw new Error("'toSymbol' is required for 'trace_to_symbol' op");
|
|
36829
37225
|
}
|
|
36830
|
-
const filePath = await resolvePathArg(ctx, context, args.
|
|
36831
|
-
const toFile = !isEmptyParam(args.
|
|
37226
|
+
const filePath = await resolvePathArg(ctx, context, args.path);
|
|
37227
|
+
const toFile = !isEmptyParam(args.toPath) ? await resolvePathArg(ctx, context, args.toPath) : undefined;
|
|
36832
37228
|
const checked = new Set;
|
|
36833
37229
|
for (const target of [filePath, ...toFile !== undefined ? [toFile] : []]) {
|
|
36834
37230
|
if (checked.has(target))
|
|
@@ -36840,7 +37236,7 @@ function navigationTools(ctx) {
|
|
|
36840
37236
|
}
|
|
36841
37237
|
const rawArgs = {
|
|
36842
37238
|
op: args.op,
|
|
36843
|
-
filePath: args.
|
|
37239
|
+
filePath: args.path,
|
|
36844
37240
|
symbol: args.symbol
|
|
36845
37241
|
};
|
|
36846
37242
|
const depth = coerceOptionalInt(args.depth, "depth", 1, Number.MAX_SAFE_INTEGER);
|
|
@@ -36850,8 +37246,8 @@ function navigationTools(ctx) {
|
|
|
36850
37246
|
rawArgs.expression = args.expression;
|
|
36851
37247
|
if (!isEmptyParam(args.toSymbol))
|
|
36852
37248
|
rawArgs.toSymbol = args.toSymbol;
|
|
36853
|
-
if (!isEmptyParam(args.
|
|
36854
|
-
rawArgs.toFile = args.
|
|
37249
|
+
if (!isEmptyParam(args.toPath))
|
|
37250
|
+
rawArgs.toFile = args.toPath;
|
|
36855
37251
|
if (!isEmptyParam(args.includeTests))
|
|
36856
37252
|
rawArgs.includeTests = coerceBoolean(args.includeTests);
|
|
36857
37253
|
if (!isEmptyParam(args.includeUnresolved))
|
|
@@ -36867,23 +37263,26 @@ function navigationTools(ctx) {
|
|
|
36867
37263
|
return response.text;
|
|
36868
37264
|
}
|
|
36869
37265
|
}
|
|
36870
|
-
};
|
|
37266
|
+
});
|
|
36871
37267
|
}
|
|
36872
37268
|
|
|
36873
37269
|
// src/tools/reading.ts
|
|
36874
37270
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
36875
37271
|
var z12 = tool12.schema;
|
|
36876
37272
|
function buildZoomTitle(args) {
|
|
36877
|
-
|
|
36878
|
-
|
|
36879
|
-
|
|
36880
|
-
|
|
37273
|
+
const targets = args.targets;
|
|
37274
|
+
if (!isEmptyParam(targets)) {
|
|
37275
|
+
if (Array.isArray(targets)) {
|
|
37276
|
+
if (targets.length === 1) {
|
|
37277
|
+
return `${targets[0].path}#${targets[0].symbol}`;
|
|
36881
37278
|
}
|
|
36882
|
-
return `${
|
|
37279
|
+
return `${targets.length} targets across files`;
|
|
37280
|
+
}
|
|
37281
|
+
if (targets && typeof targets === "object") {
|
|
37282
|
+
return `${targets.path}#${targets.symbol}`;
|
|
36883
37283
|
}
|
|
36884
|
-
return `${args.targets.filePath}#${args.targets.symbol}`;
|
|
36885
37284
|
}
|
|
36886
|
-
const path5 = args.
|
|
37285
|
+
const path5 = args.path ?? args.url ?? "";
|
|
36887
37286
|
if (typeof args.symbols === "string")
|
|
36888
37287
|
return path5 ? `${path5}#${args.symbols}` : args.symbols;
|
|
36889
37288
|
if (Array.isArray(args.symbols) && args.symbols.length > 0) {
|
|
@@ -36894,7 +37293,7 @@ function buildZoomTitle(args) {
|
|
|
36894
37293
|
return path5 || "(no target)";
|
|
36895
37294
|
}
|
|
36896
37295
|
function readingTools(ctx) {
|
|
36897
|
-
return {
|
|
37296
|
+
return prepareToolMap({
|
|
36898
37297
|
aft_outline: {
|
|
36899
37298
|
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
37299
|
` + ` • directory path → outline all source files under it (recursively, up to 200 files)
|
|
@@ -36943,21 +37342,21 @@ function readingTools(ctx) {
|
|
|
36943
37342
|
}
|
|
36944
37343
|
},
|
|
36945
37344
|
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: `{
|
|
37345
|
+
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
37346
|
args: {
|
|
36948
|
-
|
|
37347
|
+
path: z12.string().optional().describe("Path to file (absolute or relative to project root)"),
|
|
36949
37348
|
url: z12.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
|
|
36950
37349
|
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
37350
|
targets: z12.union([
|
|
36952
37351
|
z12.object({
|
|
36953
|
-
|
|
37352
|
+
path: z12.string().describe("Path to file (absolute or relative to project root)"),
|
|
36954
37353
|
symbol: z12.string().describe("Symbol name in that file")
|
|
36955
37354
|
}),
|
|
36956
37355
|
z12.array(z12.object({
|
|
36957
|
-
|
|
37356
|
+
path: z12.string().describe("Path to file (absolute or relative to project root)"),
|
|
36958
37357
|
symbol: z12.string().describe("Symbol name in that file")
|
|
36959
37358
|
}))
|
|
36960
|
-
]).optional().describe("Cross-file batch: `{
|
|
37359
|
+
]).optional().describe("Cross-file batch: `{ path, symbol }` or an array of them. Mutually exclusive with path/url/symbols."),
|
|
36961
37360
|
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)"),
|
|
36962
37361
|
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
37362
|
},
|
|
@@ -36968,7 +37367,7 @@ function readingTools(ctx) {
|
|
|
36968
37367
|
const entryEmpty = (entry) => {
|
|
36969
37368
|
if (!entry || typeof entry !== "object")
|
|
36970
37369
|
return true;
|
|
36971
|
-
const fp = entry.
|
|
37370
|
+
const fp = entry.path;
|
|
36972
37371
|
const sym = entry.symbol;
|
|
36973
37372
|
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
36974
37373
|
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
@@ -36978,7 +37377,7 @@ function readingTools(ctx) {
|
|
|
36978
37377
|
return !t.every(entryEmpty);
|
|
36979
37378
|
return !entryEmpty(t);
|
|
36980
37379
|
};
|
|
36981
|
-
const hasFilePath = !isEmptyParam(args.
|
|
37380
|
+
const hasFilePath = !isEmptyParam(args.path);
|
|
36982
37381
|
const hasUrl = !isEmptyParam(args.url);
|
|
36983
37382
|
const hasTargets = hasTargetsProvided(args.targets);
|
|
36984
37383
|
const hasSymbols = !isEmptyParam(args.symbols);
|
|
@@ -36987,7 +37386,7 @@ function readingTools(ctx) {
|
|
|
36987
37386
|
const zoomTitle = buildZoomTitle(args);
|
|
36988
37387
|
const zoomDisplay = { title: zoomTitle };
|
|
36989
37388
|
if (hasFilePath)
|
|
36990
|
-
zoomDisplay.
|
|
37389
|
+
zoomDisplay.path = args.path;
|
|
36991
37390
|
if (hasUrl)
|
|
36992
37391
|
zoomDisplay.url = args.url;
|
|
36993
37392
|
if (hasSymbols) {
|
|
@@ -37006,25 +37405,31 @@ function readingTools(ctx) {
|
|
|
37006
37405
|
});
|
|
37007
37406
|
if (hasTargets) {
|
|
37008
37407
|
if (hasFilePath || hasUrl || hasSymbols) {
|
|
37009
|
-
throw new Error("'targets' is mutually exclusive with '
|
|
37408
|
+
throw new Error("'targets' is mutually exclusive with 'path', 'url', and 'symbols'");
|
|
37010
37409
|
}
|
|
37011
37410
|
const targets = Array.isArray(args.targets) ? args.targets : [args.targets];
|
|
37012
37411
|
if (targets.length === 0) {
|
|
37013
37412
|
throw new Error("'targets' must be a non-empty object or array");
|
|
37014
37413
|
}
|
|
37015
37414
|
for (const [i, entry] of targets.entries()) {
|
|
37016
|
-
|
|
37017
|
-
|
|
37415
|
+
const targetPath = entry?.path;
|
|
37416
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
37417
|
+
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
37018
37418
|
}
|
|
37019
37419
|
if (typeof entry.symbol !== "string" || entry.symbol.length === 0) {
|
|
37020
37420
|
throw new Error(`targets[${i}].symbol must be a non-empty string`);
|
|
37021
37421
|
}
|
|
37022
37422
|
}
|
|
37023
|
-
const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(ctx, context, t.
|
|
37423
|
+
const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(ctx, context, t.path)));
|
|
37024
37424
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, resolvedTargets);
|
|
37025
37425
|
if (permissionDenied)
|
|
37026
37426
|
return permissionDeniedResponse(permissionDenied);
|
|
37027
|
-
const rawArgs2 = {
|
|
37427
|
+
const rawArgs2 = {
|
|
37428
|
+
targets: targets.map((target) => ({
|
|
37429
|
+
filePath: target.path,
|
|
37430
|
+
symbol: target.symbol
|
|
37431
|
+
}))
|
|
37432
|
+
};
|
|
37028
37433
|
if (contextLines !== undefined)
|
|
37029
37434
|
rawArgs2.contextLines = contextLines;
|
|
37030
37435
|
if (wantCallgraph)
|
|
@@ -37036,18 +37441,18 @@ function readingTools(ctx) {
|
|
|
37036
37441
|
return withMeta(response2.text);
|
|
37037
37442
|
}
|
|
37038
37443
|
if (!hasFilePath && !hasUrl) {
|
|
37039
|
-
throw new Error("Provide exactly one of '
|
|
37444
|
+
throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
|
|
37040
37445
|
}
|
|
37041
37446
|
if (hasFilePath && hasUrl) {
|
|
37042
|
-
throw new Error("Provide exactly ONE of '
|
|
37447
|
+
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
37043
37448
|
}
|
|
37044
37449
|
if (!hasUrl) {
|
|
37045
|
-
const file2 = await resolvePathArg(ctx, context, args.
|
|
37450
|
+
const file2 = await resolvePathArg(ctx, context, args.path);
|
|
37046
37451
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, file2);
|
|
37047
37452
|
if (permissionDenied)
|
|
37048
37453
|
return permissionDeniedResponse(permissionDenied);
|
|
37049
37454
|
}
|
|
37050
|
-
const rawArgs = hasUrl ? { url: args.url } : { filePath: args.
|
|
37455
|
+
const rawArgs = hasUrl ? { url: args.url } : { filePath: args.path };
|
|
37051
37456
|
if (hasSymbols)
|
|
37052
37457
|
rawArgs.symbols = args.symbols;
|
|
37053
37458
|
if (contextLines !== undefined)
|
|
@@ -37061,7 +37466,7 @@ function readingTools(ctx) {
|
|
|
37061
37466
|
return withMeta(response.text);
|
|
37062
37467
|
}
|
|
37063
37468
|
}
|
|
37064
|
-
};
|
|
37469
|
+
});
|
|
37065
37470
|
}
|
|
37066
37471
|
async function permissionKindForPath(resolvedPath) {
|
|
37067
37472
|
try {
|
|
@@ -37151,7 +37556,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
|
|
|
37151
37556
|
// src/tools/refactoring.ts
|
|
37152
37557
|
var z13 = tool13.schema;
|
|
37153
37558
|
function refactoringTools(ctx) {
|
|
37154
|
-
return {
|
|
37559
|
+
return prepareToolMap({
|
|
37155
37560
|
aft_refactor: {
|
|
37156
37561
|
description: `Workspace-wide refactoring that updates imports and references across files.
|
|
37157
37562
|
|
|
@@ -37161,7 +37566,7 @@ function refactoringTools(ctx) {
|
|
|
37161
37566
|
` + "- 'inline': replace a function call with the function's body.",
|
|
37162
37567
|
args: {
|
|
37163
37568
|
op: z13.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
|
|
37164
|
-
|
|
37569
|
+
path: z13.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
37165
37570
|
symbol: z13.string().optional().describe("Symbol name — required for 'move' and 'inline' ops"),
|
|
37166
37571
|
destination: z13.string().optional().describe("Target file path — required for 'move' op"),
|
|
37167
37572
|
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 +37597,7 @@ function refactoringTools(ctx) {
|
|
|
37192
37597
|
if (op === "inline" && callSiteLine === undefined) {
|
|
37193
37598
|
throw new Error("'callSiteLine' is required for 'inline' op");
|
|
37194
37599
|
}
|
|
37195
|
-
const filePath = await resolvePathArg(ctx, context, args.
|
|
37600
|
+
const filePath = await resolvePathArg(ctx, context, args.path);
|
|
37196
37601
|
const destination = op === "move" ? await resolvePathArg(ctx, context, args.destination) : undefined;
|
|
37197
37602
|
const patterns = op === "move" ? resolveRelativePatterns(context, [
|
|
37198
37603
|
workspacePattern(context),
|
|
@@ -37246,7 +37651,7 @@ function refactoringTools(ctx) {
|
|
|
37246
37651
|
return response.text;
|
|
37247
37652
|
}
|
|
37248
37653
|
}
|
|
37249
|
-
};
|
|
37654
|
+
});
|
|
37250
37655
|
}
|
|
37251
37656
|
|
|
37252
37657
|
// src/tools/safety.ts
|
|
@@ -37273,15 +37678,15 @@ function relativePatternsFromPaths(context, paths) {
|
|
|
37273
37678
|
return patterns;
|
|
37274
37679
|
}
|
|
37275
37680
|
function safetyTools(ctx) {
|
|
37276
|
-
return {
|
|
37681
|
+
return prepareToolMap({
|
|
37277
37682
|
aft_safety: {
|
|
37278
37683
|
description: `File safety and recovery operations.
|
|
37279
37684
|
|
|
37280
37685
|
` + `Per-file undo stack is capped at 20 entries (oldest evicted).
|
|
37281
37686
|
|
|
37282
37687
|
` + `Ops:
|
|
37283
|
-
` + `- 'undo': Undo the entire last tool call when '
|
|
37284
|
-
` + `- 'history': List all edit snapshots for a file. Requires '
|
|
37688
|
+
` + `- '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.
|
|
37689
|
+
` + `- 'history': List all edit snapshots for a file. Requires 'path'.
|
|
37285
37690
|
` + `- 'checkpoint': Save a named snapshot of tracked files. Requires 'name'. Optional 'files' to snapshot specific files only.
|
|
37286
37691
|
` + `- 'restore': Restore files to a previously saved checkpoint. Requires 'name'.
|
|
37287
37692
|
` + `- 'list': List all available named checkpoints. No extra params needed.
|
|
@@ -37291,22 +37696,22 @@ function safetyTools(ctx) {
|
|
|
37291
37696
|
` + "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.",
|
|
37292
37697
|
args: {
|
|
37293
37698
|
op: z14.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
|
|
37294
|
-
|
|
37699
|
+
path: z14.string().optional().describe("File path (required for history, optional for undo). Absolute or relative to project root"),
|
|
37295
37700
|
name: z14.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
|
|
37296
37701
|
files: z14.array(z14.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
|
|
37297
37702
|
},
|
|
37298
37703
|
execute: async (args, context) => {
|
|
37299
37704
|
const op = args.op;
|
|
37300
|
-
if (op === "history" && typeof args.
|
|
37301
|
-
throw new Error(`'
|
|
37705
|
+
if (op === "history" && typeof args.path !== "string") {
|
|
37706
|
+
throw new Error(`'path' is required for '${op}' op`);
|
|
37302
37707
|
}
|
|
37303
37708
|
if ((op === "checkpoint" || op === "restore") && typeof args.name !== "string") {
|
|
37304
37709
|
throw new Error(`'name' is required for '${op}' op`);
|
|
37305
37710
|
}
|
|
37306
37711
|
if (op === "undo") {
|
|
37307
37712
|
const previewParams = {};
|
|
37308
|
-
if (typeof args.
|
|
37309
|
-
previewParams.file = args.
|
|
37713
|
+
if (typeof args.path === "string")
|
|
37714
|
+
previewParams.file = args.path;
|
|
37310
37715
|
const preview2 = await callBridge(ctx, context, "undo_preview", previewParams);
|
|
37311
37716
|
if (preview2.success === false) {
|
|
37312
37717
|
throw new Error(bridgeErrorMessage(preview2, "undo preview failed"));
|
|
@@ -37317,14 +37722,14 @@ function safetyTools(ctx) {
|
|
|
37317
37722
|
if (denial)
|
|
37318
37723
|
return permissionDeniedResponse(denial);
|
|
37319
37724
|
}
|
|
37320
|
-
const filePath = typeof args.
|
|
37725
|
+
const filePath = typeof args.path === "string" ? resolveAbsolutePath(context, args.path) : undefined;
|
|
37321
37726
|
const permissionError = await askEditPermission(context, relativePatternsFromPaths(context, previewPaths), filePath ? { filepath: filePath } : { operation: "undo", paths: previewPaths });
|
|
37322
37727
|
if (permissionError)
|
|
37323
37728
|
return permissionDeniedResponse(permissionError);
|
|
37324
37729
|
}
|
|
37325
37730
|
if (op === "checkpoint") {
|
|
37326
37731
|
const coercedFiles = coerceStringArray(args.files);
|
|
37327
|
-
const checkpointFiles = coercedFiles.length > 0 ? coercedFiles : typeof args.
|
|
37732
|
+
const checkpointFiles = coercedFiles.length > 0 ? coercedFiles : typeof args.path === "string" ? [args.path] : undefined;
|
|
37328
37733
|
if (Array.isArray(checkpointFiles)) {
|
|
37329
37734
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
37330
37735
|
const uniqueParents = new Set;
|
|
@@ -37365,7 +37770,7 @@ function safetyTools(ctx) {
|
|
|
37365
37770
|
if (args.name !== undefined)
|
|
37366
37771
|
rawArgs.name = args.name;
|
|
37367
37772
|
const payloadFiles = coerceStringArray(args.files).map(expandTilde2);
|
|
37368
|
-
const filePathArg = typeof args.
|
|
37773
|
+
const filePathArg = typeof args.path === "string" ? expandTilde2(args.path) : undefined;
|
|
37369
37774
|
if (filePathArg !== undefined)
|
|
37370
37775
|
rawArgs.filePath = filePathArg;
|
|
37371
37776
|
if (payloadFiles.length > 0)
|
|
@@ -37377,11 +37782,11 @@ function safetyTools(ctx) {
|
|
|
37377
37782
|
return response.text;
|
|
37378
37783
|
}
|
|
37379
37784
|
}
|
|
37380
|
-
};
|
|
37785
|
+
});
|
|
37381
37786
|
}
|
|
37382
37787
|
|
|
37383
37788
|
// src/tools/search.ts
|
|
37384
|
-
import * as
|
|
37789
|
+
import * as fs4 from "node:fs";
|
|
37385
37790
|
import * as path6 from "node:path";
|
|
37386
37791
|
function arg2(schema) {
|
|
37387
37792
|
return schema;
|
|
@@ -37390,7 +37795,7 @@ function absoluteSearchPath(projectRoot, target) {
|
|
|
37390
37795
|
return resolvePathFromProjectRoot(projectRoot, expandTilde2(target));
|
|
37391
37796
|
}
|
|
37392
37797
|
function searchPathExists(projectRoot, target) {
|
|
37393
|
-
return
|
|
37798
|
+
return fs4.existsSync(absoluteSearchPath(projectRoot, target));
|
|
37394
37799
|
}
|
|
37395
37800
|
function splitSearchPathArg(projectRoot, raw) {
|
|
37396
37801
|
if (searchPathExists(projectRoot, raw) || !/\s/.test(raw)) {
|
|
@@ -37433,7 +37838,7 @@ ${note}` : note;
|
|
|
37433
37838
|
}
|
|
37434
37839
|
function searchPathKind(projectRoot, target, defaultKind) {
|
|
37435
37840
|
try {
|
|
37436
|
-
const stat2 =
|
|
37841
|
+
const stat2 = fs4.lstatSync(absoluteSearchPath(projectRoot, target));
|
|
37437
37842
|
if (defaultKind === "file") {
|
|
37438
37843
|
return stat2.isDirectory() ? "directory" : "file";
|
|
37439
37844
|
}
|
|
@@ -37562,15 +37967,12 @@ function arg3(schema) {
|
|
|
37562
37967
|
function semanticTools(ctx) {
|
|
37563
37968
|
const searchTool = {
|
|
37564
37969
|
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."
|
|
37970
|
+
"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
37971
|
].join(`
|
|
37569
37972
|
`),
|
|
37570
37973
|
args: {
|
|
37571
37974
|
query: arg3(z15.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
|
|
37572
37975
|
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
37976
|
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
37977
|
path: arg3(z15.string().optional().describe("Search a different project root (absolute or ~ path). Requires that project to have been indexed by AFT."))
|
|
37576
37978
|
},
|
|
@@ -37579,24 +37981,19 @@ function semanticTools(ctx) {
|
|
|
37579
37981
|
throw new Error("semantic_search: invalid params: `query` must be a non-empty string");
|
|
37580
37982
|
}
|
|
37581
37983
|
const query = args.query;
|
|
37582
|
-
const hint = typeof args.hint === "string" ? args.hint : undefined;
|
|
37583
37984
|
const pathArg = typeof args.path === "string" && args.path.trim() ? args.path.trim() : undefined;
|
|
37584
|
-
|
|
37585
|
-
|
|
37586
|
-
|
|
37587
|
-
return permissionDeniedResponse(denied);
|
|
37588
|
-
}
|
|
37985
|
+
const denied = await askSearchPermission(context, query);
|
|
37986
|
+
if (denied)
|
|
37987
|
+
return permissionDeniedResponse(denied);
|
|
37589
37988
|
if (pathArg) {
|
|
37590
|
-
const
|
|
37591
|
-
if (
|
|
37592
|
-
return permissionDeniedResponse(
|
|
37989
|
+
const denied2 = await assertAftSearchExternalPermission(ctx, context, pathArg);
|
|
37990
|
+
if (denied2)
|
|
37991
|
+
return permissionDeniedResponse(denied2);
|
|
37593
37992
|
}
|
|
37594
37993
|
const rawArgs = { query };
|
|
37595
37994
|
const topK = coerceOptionalInt(args.topK, "topK", 1, 100);
|
|
37596
37995
|
if (topK !== undefined)
|
|
37597
37996
|
rawArgs.topK = topK;
|
|
37598
|
-
if (hint)
|
|
37599
|
-
rawArgs.hint = hint;
|
|
37600
37997
|
if (typeof args.includeTests === "boolean")
|
|
37601
37998
|
rawArgs.includeTests = args.includeTests;
|
|
37602
37999
|
if (pathArg)
|
|
@@ -37614,6 +38011,37 @@ function semanticTools(ctx) {
|
|
|
37614
38011
|
};
|
|
37615
38012
|
}
|
|
37616
38013
|
|
|
38014
|
+
// src/tool-registration.ts
|
|
38015
|
+
var ALL_ONLY_TOOLS = ["aft_callgraph", "aft_delete", "aft_move", "aft_refactor"];
|
|
38016
|
+
function buildOpenCodeToolMap(ctx, config2, onUnknownDisabled) {
|
|
38017
|
+
const surface = config2.tool_surface ?? "recommended";
|
|
38018
|
+
const allTools = normalizeToolMap({
|
|
38019
|
+
...surface !== "minimal" && (config2.hoist_builtin_tools !== false ? hoistedTools(ctx) : aftPrefixedTools(ctx)),
|
|
38020
|
+
...readingTools(ctx),
|
|
38021
|
+
...config2.backup?.enabled === false ? {} : safetyTools(ctx),
|
|
38022
|
+
...surface !== "minimal" && importTools(ctx),
|
|
38023
|
+
...navigationTools(ctx),
|
|
38024
|
+
...surface !== "minimal" && astTools(ctx),
|
|
38025
|
+
...surface !== "minimal" && config2.semantic_search === true && semanticTools(ctx),
|
|
38026
|
+
...inspectToolSurfaceEnabled(config2) && inspectTools(ctx),
|
|
38027
|
+
...surface !== "minimal" && config2.search_index === true && searchTools(ctx),
|
|
38028
|
+
...refactoringTools(ctx),
|
|
38029
|
+
...surface !== "minimal" && conflictTools(ctx)
|
|
38030
|
+
});
|
|
38031
|
+
if (surface !== "all") {
|
|
38032
|
+
for (const name of ALL_ONLY_TOOLS)
|
|
38033
|
+
delete allTools[name];
|
|
38034
|
+
}
|
|
38035
|
+
for (const name of config2.disabled_tools ?? []) {
|
|
38036
|
+
if (name in allTools) {
|
|
38037
|
+
delete allTools[name];
|
|
38038
|
+
} else {
|
|
38039
|
+
onUnknownDisabled?.(name, Object.keys(allTools));
|
|
38040
|
+
}
|
|
38041
|
+
}
|
|
38042
|
+
return allTools;
|
|
38043
|
+
}
|
|
38044
|
+
|
|
37617
38045
|
// src/workflow-hints.ts
|
|
37618
38046
|
var HEADING = "## IMPORTANT NOTICE about your tools";
|
|
37619
38047
|
function buildWorkflowHints(opts) {
|
|
@@ -37644,18 +38072,18 @@ function buildWorkflowHints(opts) {
|
|
|
37644
38072
|
}
|
|
37645
38073
|
if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
|
|
37646
38074
|
const searchName = hasSearch ? "aft_search" : grepName;
|
|
37647
|
-
const locate = hasSearch ?
|
|
38075
|
+
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
38076
|
const readName = opts.hoistBuiltins ? "read" : "aft_read";
|
|
37649
38077
|
sections.push([
|
|
37650
38078
|
`**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
38079
|
`- \`grep -rn "handleAuth" src/\` in bash → \`${searchName}({ query: "handleAuth" })\``,
|
|
37652
38080
|
`- \`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}({
|
|
38081
|
+
`- \`sed -n '100,160p' app.ts\` / \`cat app.ts\` in bash → \`${readName}({ path: "app.ts", startLine: 100, endLine: 160 })\``
|
|
37654
38082
|
].join(`
|
|
37655
38083
|
`));
|
|
37656
38084
|
}
|
|
37657
38085
|
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
|
|
38086
|
+
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
38087
|
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
38088
|
}
|
|
37661
38089
|
if (hasNavigate) {
|
|
@@ -37728,11 +38156,11 @@ var PLUGIN_VERSION = (() => {
|
|
|
37728
38156
|
return "0.0.0";
|
|
37729
38157
|
}
|
|
37730
38158
|
})();
|
|
37731
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
38159
|
+
var ANNOUNCEMENT_VERSION = "0.49.0";
|
|
37732
38160
|
var ANNOUNCEMENT_FEATURES = [
|
|
37733
|
-
"
|
|
37734
|
-
"
|
|
37735
|
-
"
|
|
38161
|
+
"One unified tool surface: every tool now takes `path` (old spellings keep working), `edit` has one calling shape, and `occurrence` is 1-based. Fewer ways for models to get a call wrong; prompt caches re-warm once after this upgrade.",
|
|
38162
|
+
"aft_search now routes queries itself — the hint parameter is gone, and searches that find no exact match are re-ranked by their terms instead of returning nothing.",
|
|
38163
|
+
"Corporate and custom certificate authorities now work: HTTPS requests verify through the OS trust store (Keychain, Windows certificate store, SSL_CERT_FILE on Linux)."
|
|
37736
38164
|
];
|
|
37737
38165
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
37738
38166
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -38168,38 +38596,12 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38168
38596
|
} else {
|
|
38169
38597
|
cleanupWarnings(notifyOpts).catch(() => {});
|
|
38170
38598
|
}
|
|
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)
|
|
38599
|
+
const allTools = buildOpenCodeToolMap(ctx, aftConfig, (name, available) => {
|
|
38600
|
+
warn2(`disabled_tools: "${name}" not found — available: ${available.join(", ")}`);
|
|
38185
38601
|
});
|
|
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(", ")}`);
|
|
38602
|
+
const disabled = aftConfig.disabled_tools ?? [];
|
|
38603
|
+
if (disabled.length > 0) {
|
|
38604
|
+
log2(`Disabled ${disabled.length} tool(s): ${disabled.join(", ")}`);
|
|
38203
38605
|
}
|
|
38204
38606
|
instrumentToolMap(allTools);
|
|
38205
38607
|
const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
|
|
@@ -38314,7 +38716,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38314
38716
|
const sessionDir = getSessionDirectoryCached(sid) ?? await getSessionDirectory(input.client, sid, input.directory) ?? input.directory;
|
|
38315
38717
|
signalBashWaitDetachForProject(pool, sessionDir, sid);
|
|
38316
38718
|
},
|
|
38317
|
-
"tool.execute.before": async (toolInput) => {
|
|
38719
|
+
"tool.execute.before": async (toolInput, output) => {
|
|
38720
|
+
output.args = prepareOpenCodeArguments(toolInput.tool, output.args);
|
|
38318
38721
|
if (toolInput.sessionID)
|
|
38319
38722
|
inspectTier2Idle.clear(toolInput.sessionID);
|
|
38320
38723
|
},
|