@cortexkit/aft-opencode 0.35.4 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +752 -279
- package/dist/shared/bash-hints.d.ts +1 -1
- package/dist/shared/bash-hints.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +2 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +23 -0
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/status-bar-inject.d.ts +1 -1
- package/dist/status-bar-inject.d.ts.map +1 -1
- package/dist/tool-perf.d.ts +17 -0
- package/dist/tool-perf.d.ts.map +1 -0
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/safety.d.ts.map +1 -1
- package/dist/tui.js +78 -92
- package/dist/workflow-hints.d.ts +2 -0
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/shared/bash-hints.ts +1 -1
- package/src/shared/rpc-client.ts +63 -26
- package/src/shared/rpc-server.ts +13 -4
- package/src/shared/rpc-utils.ts +62 -0
- package/dist/metadata-store.d.ts +0 -29
- package/dist/metadata-store.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -11597,9 +11597,6 @@ function error(message, meta) {
|
|
|
11597
11597
|
var CONFLICT_HINT = `
|
|
11598
11598
|
|
|
11599
11599
|
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
11600
|
-
var GREP_HINT = `
|
|
11601
|
-
|
|
11602
|
-
[Hint] Use the grep tool instead of bash for faster indexed search.`;
|
|
11603
11600
|
function maybeAppendConflictsHint(output) {
|
|
11604
11601
|
if (!output.includes("Automatic merge failed; fix conflicts"))
|
|
11605
11602
|
return output;
|
|
@@ -11607,12 +11604,12 @@ function maybeAppendConflictsHint(output) {
|
|
|
11607
11604
|
return output;
|
|
11608
11605
|
return output + CONFLICT_HINT;
|
|
11609
11606
|
}
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
return
|
|
11607
|
+
// ../aft-bridge/dist/bash-timeout.js
|
|
11608
|
+
function resolveBashKillTimeout(modelTimeout, foregroundWaitMs) {
|
|
11609
|
+
if (modelTimeout !== undefined && modelTimeout >= foregroundWaitMs) {
|
|
11610
|
+
return modelTimeout;
|
|
11611
|
+
}
|
|
11612
|
+
return;
|
|
11616
11613
|
}
|
|
11617
11614
|
// ../aft-bridge/dist/bridge.js
|
|
11618
11615
|
import { spawn } from "node:child_process";
|
|
@@ -12467,6 +12464,27 @@ class BinaryBridge {
|
|
|
12467
12464
|
}
|
|
12468
12465
|
}
|
|
12469
12466
|
}
|
|
12467
|
+
// ../aft-bridge/dist/coerce.js
|
|
12468
|
+
function coerceStringArray(value) {
|
|
12469
|
+
if (Array.isArray(value)) {
|
|
12470
|
+
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
12471
|
+
}
|
|
12472
|
+
if (typeof value === "string") {
|
|
12473
|
+
const trimmed = value.trim();
|
|
12474
|
+
if (trimmed.length === 0)
|
|
12475
|
+
return [];
|
|
12476
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
12477
|
+
try {
|
|
12478
|
+
const parsed = JSON.parse(trimmed);
|
|
12479
|
+
if (Array.isArray(parsed)) {
|
|
12480
|
+
return parsed.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
12481
|
+
}
|
|
12482
|
+
} catch {}
|
|
12483
|
+
}
|
|
12484
|
+
return [value];
|
|
12485
|
+
}
|
|
12486
|
+
return [];
|
|
12487
|
+
}
|
|
12470
12488
|
// ../aft-bridge/dist/downloader.js
|
|
12471
12489
|
import { spawnSync } from "node:child_process";
|
|
12472
12490
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -12763,6 +12781,11 @@ function formatEditSummary(data) {
|
|
|
12763
12781
|
const n = data.files_modified;
|
|
12764
12782
|
return `Applied edits to ${n} file${n === 1 ? "" : "s"}.`;
|
|
12765
12783
|
}
|
|
12784
|
+
if (typeof data.total_files === "number") {
|
|
12785
|
+
const files = data.total_files;
|
|
12786
|
+
const reps = data.total_replacements ?? 0;
|
|
12787
|
+
return `Edited ${files} file${files === 1 ? "" : "s"} (${reps} replacement${reps === 1 ? "" : "s"}).`;
|
|
12788
|
+
}
|
|
12766
12789
|
const additions = data.diff?.additions ?? 0;
|
|
12767
12790
|
const deletions = data.diff?.deletions ?? 0;
|
|
12768
12791
|
const counts = `+${additions}/-${deletions}`;
|
|
@@ -13975,6 +13998,464 @@ function isProcessAlive(pid) {
|
|
|
13975
13998
|
return true;
|
|
13976
13999
|
}
|
|
13977
14000
|
}
|
|
14001
|
+
// ../aft-bridge/dist/pipe-strip.js
|
|
14002
|
+
var NOISE_FILTERS = new Set([
|
|
14003
|
+
"grep",
|
|
14004
|
+
"rg",
|
|
14005
|
+
"head",
|
|
14006
|
+
"tail",
|
|
14007
|
+
"cat",
|
|
14008
|
+
"less",
|
|
14009
|
+
"more",
|
|
14010
|
+
"sed",
|
|
14011
|
+
"awk",
|
|
14012
|
+
"cut",
|
|
14013
|
+
"sort",
|
|
14014
|
+
"uniq",
|
|
14015
|
+
"tr",
|
|
14016
|
+
"column",
|
|
14017
|
+
"fold"
|
|
14018
|
+
]);
|
|
14019
|
+
var GREP_GUARD_FLAGS = new Set([
|
|
14020
|
+
"c",
|
|
14021
|
+
"count",
|
|
14022
|
+
"q",
|
|
14023
|
+
"quiet",
|
|
14024
|
+
"o",
|
|
14025
|
+
"only-matching",
|
|
14026
|
+
"l",
|
|
14027
|
+
"files-with-matches"
|
|
14028
|
+
]);
|
|
14029
|
+
function maybeStripCompressorPipe(command, compressionEnabled) {
|
|
14030
|
+
if (!compressionEnabled)
|
|
14031
|
+
return { command, stripped: false };
|
|
14032
|
+
if (containsUnsplittableConstruct(command))
|
|
14033
|
+
return { command, stripped: false };
|
|
14034
|
+
const chain = splitTopLevelAndChain(command);
|
|
14035
|
+
if (chain === null)
|
|
14036
|
+
return { command, stripped: false };
|
|
14037
|
+
const prefix = chain.slice(0, -1).map((segment) => segment.trim()).filter(Boolean);
|
|
14038
|
+
const pipeline3 = chain[chain.length - 1] ?? "";
|
|
14039
|
+
const stages = splitTopLevelPipeline(pipeline3);
|
|
14040
|
+
if (stages.length < 2)
|
|
14041
|
+
return { command, stripped: false };
|
|
14042
|
+
const firstStage = stages[0]?.trim() ?? "";
|
|
14043
|
+
if (!isCompressorHandledRunner(firstStage))
|
|
14044
|
+
return { command, stripped: false };
|
|
14045
|
+
const filterStages = stages.slice(1).map((stage) => stage.trim());
|
|
14046
|
+
for (const stage of filterStages) {
|
|
14047
|
+
if (!filterStageIsSafeToDrop(stage))
|
|
14048
|
+
return { command, stripped: false };
|
|
14049
|
+
}
|
|
14050
|
+
const filters = filterStages.join(" | ");
|
|
14051
|
+
const rebuilt = [...prefix, firstStage].join(" && ");
|
|
14052
|
+
return {
|
|
14053
|
+
command: rebuilt,
|
|
14054
|
+
stripped: true,
|
|
14055
|
+
note: `[AFT dropped \`| ${filters}\` (compressed:false to keep)]`
|
|
14056
|
+
};
|
|
14057
|
+
}
|
|
14058
|
+
function splitTopLevelAndChain(command) {
|
|
14059
|
+
const segments = [];
|
|
14060
|
+
let start = 0;
|
|
14061
|
+
let quote = null;
|
|
14062
|
+
let escaped = false;
|
|
14063
|
+
for (let index = 0;index < command.length; index++) {
|
|
14064
|
+
const char = command[index];
|
|
14065
|
+
const next = command[index + 1];
|
|
14066
|
+
if (escaped) {
|
|
14067
|
+
escaped = false;
|
|
14068
|
+
continue;
|
|
14069
|
+
}
|
|
14070
|
+
if (char === "\\" && quote !== "'") {
|
|
14071
|
+
escaped = true;
|
|
14072
|
+
continue;
|
|
14073
|
+
}
|
|
14074
|
+
if (quote) {
|
|
14075
|
+
if (char === quote)
|
|
14076
|
+
quote = null;
|
|
14077
|
+
continue;
|
|
14078
|
+
}
|
|
14079
|
+
if (char === "'" || char === '"') {
|
|
14080
|
+
quote = char;
|
|
14081
|
+
continue;
|
|
14082
|
+
}
|
|
14083
|
+
if (char === "&" && next === "&") {
|
|
14084
|
+
segments.push(command.slice(start, index));
|
|
14085
|
+
start = index + 2;
|
|
14086
|
+
index++;
|
|
14087
|
+
continue;
|
|
14088
|
+
}
|
|
14089
|
+
if (char === "|" && next === "|")
|
|
14090
|
+
return null;
|
|
14091
|
+
if (char === ";")
|
|
14092
|
+
return null;
|
|
14093
|
+
if (char === `
|
|
14094
|
+
` || char === "\r")
|
|
14095
|
+
return null;
|
|
14096
|
+
if (char === "&") {
|
|
14097
|
+
const prev = command[index - 1];
|
|
14098
|
+
if (prev !== ">" && next !== ">")
|
|
14099
|
+
return null;
|
|
14100
|
+
}
|
|
14101
|
+
}
|
|
14102
|
+
segments.push(command.slice(start));
|
|
14103
|
+
return segments;
|
|
14104
|
+
}
|
|
14105
|
+
function splitTopLevelPipeline(command) {
|
|
14106
|
+
const stages = [];
|
|
14107
|
+
let start = 0;
|
|
14108
|
+
let quote = null;
|
|
14109
|
+
let escaped = false;
|
|
14110
|
+
for (let index = 0;index < command.length; index++) {
|
|
14111
|
+
const char = command[index];
|
|
14112
|
+
const next = command[index + 1];
|
|
14113
|
+
const previous = command[index - 1];
|
|
14114
|
+
if (escaped) {
|
|
14115
|
+
escaped = false;
|
|
14116
|
+
continue;
|
|
14117
|
+
}
|
|
14118
|
+
if (char === "\\" && quote !== "'") {
|
|
14119
|
+
escaped = true;
|
|
14120
|
+
continue;
|
|
14121
|
+
}
|
|
14122
|
+
if (quote) {
|
|
14123
|
+
if (char === quote)
|
|
14124
|
+
quote = null;
|
|
14125
|
+
continue;
|
|
14126
|
+
}
|
|
14127
|
+
if (char === "'" || char === '"') {
|
|
14128
|
+
quote = char;
|
|
14129
|
+
continue;
|
|
14130
|
+
}
|
|
14131
|
+
if (char === "|" && previous !== "|" && next !== "|") {
|
|
14132
|
+
stages.push(command.slice(start, index));
|
|
14133
|
+
start = index + 1;
|
|
14134
|
+
}
|
|
14135
|
+
}
|
|
14136
|
+
stages.push(command.slice(start));
|
|
14137
|
+
return stages;
|
|
14138
|
+
}
|
|
14139
|
+
function isCompressorHandledRunner(stage) {
|
|
14140
|
+
const tokens = tokenizeStage(stage);
|
|
14141
|
+
if (tokens.length === 0)
|
|
14142
|
+
return false;
|
|
14143
|
+
if (tokens.some((token) => token === "&&" || token === "||" || token.includes(";"))) {
|
|
14144
|
+
return false;
|
|
14145
|
+
}
|
|
14146
|
+
const first = runnerName(tokens[0]);
|
|
14147
|
+
const second = tokens[1];
|
|
14148
|
+
const third = tokens[2];
|
|
14149
|
+
const rest = tokens.slice(1);
|
|
14150
|
+
if (!first)
|
|
14151
|
+
return false;
|
|
14152
|
+
if (first === "bun")
|
|
14153
|
+
return second === "test" || second === "run" && startsWithTest(third);
|
|
14154
|
+
if (first === "npm" || first === "pnpm") {
|
|
14155
|
+
return second === "test" || second === "run" && startsWithTest(third);
|
|
14156
|
+
}
|
|
14157
|
+
if (first === "yarn") {
|
|
14158
|
+
return startsWithTest(second) || second === "run" && startsWithTest(third);
|
|
14159
|
+
}
|
|
14160
|
+
if (first === "deno")
|
|
14161
|
+
return ["test", "lint", "check", "bench"].includes(second ?? "");
|
|
14162
|
+
if (first === "npx") {
|
|
14163
|
+
return ["tsc", "eslint", "vitest", "jest", "playwright", "biome"].includes(second ?? "");
|
|
14164
|
+
}
|
|
14165
|
+
if (first === "playwright")
|
|
14166
|
+
return second === "test";
|
|
14167
|
+
if (first === "cargo") {
|
|
14168
|
+
return ["test", "build", "check", "clippy", "nextest"].includes(second ?? "");
|
|
14169
|
+
}
|
|
14170
|
+
if (first === "go")
|
|
14171
|
+
return ["test", "build", "vet"].includes(second ?? "");
|
|
14172
|
+
if (first === "gradle" || first === "gradlew") {
|
|
14173
|
+
return hasBuildTask(rest, ["test", "check", "build", "assemble", "clean"]);
|
|
14174
|
+
}
|
|
14175
|
+
if (first === "mvn" || first === "mvnw") {
|
|
14176
|
+
return hasBuildTask(rest, ["test", "verify", "package", "install"]);
|
|
14177
|
+
}
|
|
14178
|
+
if (first === "dotnet")
|
|
14179
|
+
return ["test", "build"].includes(second ?? "");
|
|
14180
|
+
if (first === "rspec")
|
|
14181
|
+
return true;
|
|
14182
|
+
if (first === "rake")
|
|
14183
|
+
return second === "test" || second === "spec";
|
|
14184
|
+
if (first === "phpunit" || first === "pest")
|
|
14185
|
+
return true;
|
|
14186
|
+
if (first === "xcodebuild")
|
|
14187
|
+
return xcodebuildHasBuildAction(rest);
|
|
14188
|
+
if (first === "swift")
|
|
14189
|
+
return second === "test" || second === "build";
|
|
14190
|
+
if (first === "make" || first === "gmake") {
|
|
14191
|
+
return hasBuildTask(rest, ["test", "check", "lint", "clean"]);
|
|
14192
|
+
}
|
|
14193
|
+
return [
|
|
14194
|
+
"vitest",
|
|
14195
|
+
"jest",
|
|
14196
|
+
"pytest",
|
|
14197
|
+
"tsc",
|
|
14198
|
+
"eslint",
|
|
14199
|
+
"biome",
|
|
14200
|
+
"ruff",
|
|
14201
|
+
"mypy",
|
|
14202
|
+
"tox",
|
|
14203
|
+
"nox"
|
|
14204
|
+
].includes(first);
|
|
14205
|
+
}
|
|
14206
|
+
function runnerName(token) {
|
|
14207
|
+
if (!token)
|
|
14208
|
+
return "";
|
|
14209
|
+
const slash = token.lastIndexOf("/");
|
|
14210
|
+
return slash === -1 ? token : token.slice(slash + 1);
|
|
14211
|
+
}
|
|
14212
|
+
function hasBuildTask(args, tasks) {
|
|
14213
|
+
const isAllowedTask = (arg) => tasks.some((task) => arg === task || arg.endsWith(`:${task}`));
|
|
14214
|
+
const isFlagOrProperty = (arg) => arg.startsWith("-") || arg.includes("=");
|
|
14215
|
+
let sawAllowed = false;
|
|
14216
|
+
for (const arg of args) {
|
|
14217
|
+
if (isFlagOrProperty(arg))
|
|
14218
|
+
continue;
|
|
14219
|
+
if (!isAllowedTask(arg))
|
|
14220
|
+
return false;
|
|
14221
|
+
sawAllowed = true;
|
|
14222
|
+
}
|
|
14223
|
+
return sawAllowed;
|
|
14224
|
+
}
|
|
14225
|
+
function startsWithTest(token) {
|
|
14226
|
+
return token?.startsWith("test") === true;
|
|
14227
|
+
}
|
|
14228
|
+
var XCODEBUILD_VALUE_FLAGS = new Set([
|
|
14229
|
+
"-scheme",
|
|
14230
|
+
"-target",
|
|
14231
|
+
"-project",
|
|
14232
|
+
"-workspace",
|
|
14233
|
+
"-configuration",
|
|
14234
|
+
"-sdk",
|
|
14235
|
+
"-destination",
|
|
14236
|
+
"-arch",
|
|
14237
|
+
"-derivedDataPath",
|
|
14238
|
+
"-resultBundlePath",
|
|
14239
|
+
"-xcconfig",
|
|
14240
|
+
"-toolchain"
|
|
14241
|
+
]);
|
|
14242
|
+
var XCODEBUILD_BUILD_ACTIONS = new Set([
|
|
14243
|
+
"build",
|
|
14244
|
+
"test",
|
|
14245
|
+
"build-for-testing",
|
|
14246
|
+
"test-without-building",
|
|
14247
|
+
"analyze"
|
|
14248
|
+
]);
|
|
14249
|
+
function xcodebuildHasBuildAction(args) {
|
|
14250
|
+
for (let i = 0;i < args.length; i++) {
|
|
14251
|
+
const arg = args[i];
|
|
14252
|
+
if (arg.startsWith("-")) {
|
|
14253
|
+
if (XCODEBUILD_VALUE_FLAGS.has(arg))
|
|
14254
|
+
i++;
|
|
14255
|
+
continue;
|
|
14256
|
+
}
|
|
14257
|
+
if (XCODEBUILD_BUILD_ACTIONS.has(arg))
|
|
14258
|
+
return true;
|
|
14259
|
+
}
|
|
14260
|
+
return false;
|
|
14261
|
+
}
|
|
14262
|
+
function containsUnsplittableConstruct(command) {
|
|
14263
|
+
let quote = null;
|
|
14264
|
+
let escaped = false;
|
|
14265
|
+
for (let i = 0;i < command.length; i++) {
|
|
14266
|
+
const char = command[i];
|
|
14267
|
+
if (escaped) {
|
|
14268
|
+
escaped = false;
|
|
14269
|
+
continue;
|
|
14270
|
+
}
|
|
14271
|
+
if (char === "\\" && quote !== "'") {
|
|
14272
|
+
escaped = true;
|
|
14273
|
+
continue;
|
|
14274
|
+
}
|
|
14275
|
+
if (quote === "'") {
|
|
14276
|
+
if (char === "'")
|
|
14277
|
+
quote = null;
|
|
14278
|
+
continue;
|
|
14279
|
+
}
|
|
14280
|
+
if (quote === '"') {
|
|
14281
|
+
if (char === '"')
|
|
14282
|
+
quote = null;
|
|
14283
|
+
else if (char === "`")
|
|
14284
|
+
return true;
|
|
14285
|
+
else if (char === "$" && command[i + 1] === "(")
|
|
14286
|
+
return true;
|
|
14287
|
+
continue;
|
|
14288
|
+
}
|
|
14289
|
+
if (char === "'" || char === '"') {
|
|
14290
|
+
quote = char;
|
|
14291
|
+
continue;
|
|
14292
|
+
}
|
|
14293
|
+
if (char === "`")
|
|
14294
|
+
return true;
|
|
14295
|
+
if (char === "(" || char === ")")
|
|
14296
|
+
return true;
|
|
14297
|
+
}
|
|
14298
|
+
return false;
|
|
14299
|
+
}
|
|
14300
|
+
var READS_FILE_OPERAND = new Set(["cat", "tac", "nl", "less", "more"]);
|
|
14301
|
+
function filterStageIsSafeToDrop(stage) {
|
|
14302
|
+
const head = tokenizeStage(stage)[0];
|
|
14303
|
+
if (!head)
|
|
14304
|
+
return false;
|
|
14305
|
+
if (head === "wc")
|
|
14306
|
+
return false;
|
|
14307
|
+
if (!NOISE_FILTERS.has(head))
|
|
14308
|
+
return false;
|
|
14309
|
+
if (/[<>]/.test(stage))
|
|
14310
|
+
return false;
|
|
14311
|
+
if (hasUnquotedBackground(stage))
|
|
14312
|
+
return false;
|
|
14313
|
+
const args = tokenizeStage(stage).slice(1);
|
|
14314
|
+
const hasFlag = (...names) => args.some((a) => names.some((n) => a === n || a.startsWith(`${n}=`)));
|
|
14315
|
+
if (head === "grep" || head === "rg") {
|
|
14316
|
+
if (hasIntentChangingGrepFlag(args))
|
|
14317
|
+
return false;
|
|
14318
|
+
if (countBareOperands(args) > 1)
|
|
14319
|
+
return false;
|
|
14320
|
+
return true;
|
|
14321
|
+
}
|
|
14322
|
+
if (head === "head" || head === "tail") {
|
|
14323
|
+
if (bareOperands(args).some((op) => !/^\d+$/.test(op)))
|
|
14324
|
+
return false;
|
|
14325
|
+
return true;
|
|
14326
|
+
}
|
|
14327
|
+
if (READS_FILE_OPERAND.has(head)) {
|
|
14328
|
+
if (countBareOperands(args) > 0)
|
|
14329
|
+
return false;
|
|
14330
|
+
return true;
|
|
14331
|
+
}
|
|
14332
|
+
if (head === "sed") {
|
|
14333
|
+
if (hasFlag("-i", "--in-place"))
|
|
14334
|
+
return false;
|
|
14335
|
+
if (countBareOperands(args) > 1)
|
|
14336
|
+
return false;
|
|
14337
|
+
return true;
|
|
14338
|
+
}
|
|
14339
|
+
if (head === "awk") {
|
|
14340
|
+
if (countBareOperands(args) > 1)
|
|
14341
|
+
return false;
|
|
14342
|
+
return true;
|
|
14343
|
+
}
|
|
14344
|
+
if (head === "sort" && hasFlag("-o", "--output"))
|
|
14345
|
+
return false;
|
|
14346
|
+
if (bareOperands(args).some((op) => op.includes("/") || op.includes(".")))
|
|
14347
|
+
return false;
|
|
14348
|
+
return true;
|
|
14349
|
+
}
|
|
14350
|
+
function bareOperands(args) {
|
|
14351
|
+
const out = [];
|
|
14352
|
+
let afterDoubleDash = false;
|
|
14353
|
+
for (const arg of args) {
|
|
14354
|
+
if (!afterDoubleDash && arg === "--") {
|
|
14355
|
+
afterDoubleDash = true;
|
|
14356
|
+
continue;
|
|
14357
|
+
}
|
|
14358
|
+
if (!afterDoubleDash && arg.startsWith("-") && arg !== "-")
|
|
14359
|
+
continue;
|
|
14360
|
+
out.push(arg);
|
|
14361
|
+
}
|
|
14362
|
+
return out;
|
|
14363
|
+
}
|
|
14364
|
+
function countBareOperands(args) {
|
|
14365
|
+
return bareOperands(args).length;
|
|
14366
|
+
}
|
|
14367
|
+
function hasUnquotedBackground(stage) {
|
|
14368
|
+
let quote = null;
|
|
14369
|
+
let escaped = false;
|
|
14370
|
+
for (let i = 0;i < stage.length; i++) {
|
|
14371
|
+
const char = stage[i];
|
|
14372
|
+
if (escaped) {
|
|
14373
|
+
escaped = false;
|
|
14374
|
+
continue;
|
|
14375
|
+
}
|
|
14376
|
+
if (char === "\\" && quote !== "'") {
|
|
14377
|
+
escaped = true;
|
|
14378
|
+
continue;
|
|
14379
|
+
}
|
|
14380
|
+
if (quote) {
|
|
14381
|
+
if (char === quote)
|
|
14382
|
+
quote = null;
|
|
14383
|
+
continue;
|
|
14384
|
+
}
|
|
14385
|
+
if (char === "'" || char === '"') {
|
|
14386
|
+
quote = char;
|
|
14387
|
+
continue;
|
|
14388
|
+
}
|
|
14389
|
+
if (char === "&") {
|
|
14390
|
+
const prev = stage[i - 1];
|
|
14391
|
+
const next = stage[i + 1];
|
|
14392
|
+
if (prev === "&" || next === "&" || prev === ">" || next === ">")
|
|
14393
|
+
continue;
|
|
14394
|
+
return true;
|
|
14395
|
+
}
|
|
14396
|
+
}
|
|
14397
|
+
return false;
|
|
14398
|
+
}
|
|
14399
|
+
function hasIntentChangingGrepFlag(args) {
|
|
14400
|
+
for (const arg of args) {
|
|
14401
|
+
if (arg === "--")
|
|
14402
|
+
return false;
|
|
14403
|
+
if (!arg.startsWith("-") || arg === "-")
|
|
14404
|
+
continue;
|
|
14405
|
+
if (arg.startsWith("--")) {
|
|
14406
|
+
const flag = arg.slice(2).split("=", 1)[0];
|
|
14407
|
+
if (GREP_GUARD_FLAGS.has(flag))
|
|
14408
|
+
return true;
|
|
14409
|
+
continue;
|
|
14410
|
+
}
|
|
14411
|
+
for (const flag of arg.slice(1)) {
|
|
14412
|
+
if (GREP_GUARD_FLAGS.has(flag))
|
|
14413
|
+
return true;
|
|
14414
|
+
}
|
|
14415
|
+
}
|
|
14416
|
+
return false;
|
|
14417
|
+
}
|
|
14418
|
+
function tokenizeStage(stage) {
|
|
14419
|
+
const tokens = [];
|
|
14420
|
+
let current = "";
|
|
14421
|
+
let quote = null;
|
|
14422
|
+
let escaped = false;
|
|
14423
|
+
for (let index = 0;index < stage.length; index++) {
|
|
14424
|
+
const char = stage[index];
|
|
14425
|
+
if (escaped) {
|
|
14426
|
+
current += char;
|
|
14427
|
+
escaped = false;
|
|
14428
|
+
continue;
|
|
14429
|
+
}
|
|
14430
|
+
if (char === "\\" && quote !== "'") {
|
|
14431
|
+
escaped = true;
|
|
14432
|
+
continue;
|
|
14433
|
+
}
|
|
14434
|
+
if (quote) {
|
|
14435
|
+
if (char === quote) {
|
|
14436
|
+
quote = null;
|
|
14437
|
+
} else {
|
|
14438
|
+
current += char;
|
|
14439
|
+
}
|
|
14440
|
+
continue;
|
|
14441
|
+
}
|
|
14442
|
+
if (char === "'" || char === '"') {
|
|
14443
|
+
quote = char;
|
|
14444
|
+
continue;
|
|
14445
|
+
}
|
|
14446
|
+
if (/\s/.test(char)) {
|
|
14447
|
+
if (current.length > 0) {
|
|
14448
|
+
tokens.push(current);
|
|
14449
|
+
current = "";
|
|
14450
|
+
}
|
|
14451
|
+
continue;
|
|
14452
|
+
}
|
|
14453
|
+
current += char;
|
|
14454
|
+
}
|
|
14455
|
+
if (current.length > 0)
|
|
14456
|
+
tokens.push(current);
|
|
14457
|
+
return tokens;
|
|
14458
|
+
}
|
|
13978
14459
|
// ../aft-bridge/dist/pool.js
|
|
13979
14460
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
13980
14461
|
var DEFAULT_IDLE_TIMEOUT_MS = Infinity;
|
|
@@ -32282,38 +32763,6 @@ function discoverRelevantGithubServers(projectRoot) {
|
|
|
32282
32763
|
return relevant;
|
|
32283
32764
|
}
|
|
32284
32765
|
|
|
32285
|
-
// src/metadata-store.ts
|
|
32286
|
-
var pendingStore = new Map;
|
|
32287
|
-
var STALE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
32288
|
-
function makeKey(sessionID, callID) {
|
|
32289
|
-
return `${sessionID}:${callID}`;
|
|
32290
|
-
}
|
|
32291
|
-
function cleanupStaleEntries() {
|
|
32292
|
-
const now = Date.now();
|
|
32293
|
-
for (const [key, entry] of pendingStore) {
|
|
32294
|
-
if (now - entry.storedAt > STALE_TIMEOUT_MS) {
|
|
32295
|
-
pendingStore.delete(key);
|
|
32296
|
-
}
|
|
32297
|
-
}
|
|
32298
|
-
}
|
|
32299
|
-
function storeToolMetadata(sessionID, callID, data) {
|
|
32300
|
-
cleanupStaleEntries();
|
|
32301
|
-
pendingStore.set(makeKey(sessionID, callID), {
|
|
32302
|
-
...data,
|
|
32303
|
-
storedAt: Date.now()
|
|
32304
|
-
});
|
|
32305
|
-
}
|
|
32306
|
-
function consumeToolMetadata(sessionID, callID) {
|
|
32307
|
-
const key = makeKey(sessionID, callID);
|
|
32308
|
-
const stored = pendingStore.get(key);
|
|
32309
|
-
if (stored) {
|
|
32310
|
-
pendingStore.delete(key);
|
|
32311
|
-
const { storedAt: _, ...data } = stored;
|
|
32312
|
-
return data;
|
|
32313
|
-
}
|
|
32314
|
-
return;
|
|
32315
|
-
}
|
|
32316
|
-
|
|
32317
32766
|
// src/normalize-schemas.ts
|
|
32318
32767
|
import { tool } from "@opencode-ai/plugin";
|
|
32319
32768
|
function stripRootJsonSchemaFields(jsonSchema) {
|
|
@@ -32485,10 +32934,12 @@ class AftRpcServer {
|
|
|
32485
32934
|
const dir = dirname9(this.portFilePath);
|
|
32486
32935
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
32487
32936
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
32488
|
-
writeFileSync10(tmpPath, JSON.stringify({
|
|
32489
|
-
|
|
32490
|
-
|
|
32491
|
-
|
|
32937
|
+
writeFileSync10(tmpPath, JSON.stringify({
|
|
32938
|
+
port: this.port,
|
|
32939
|
+
token: this.token,
|
|
32940
|
+
pid: process.pid,
|
|
32941
|
+
started_at: Date.now()
|
|
32942
|
+
}), { encoding: "utf-8", mode: 384 });
|
|
32492
32943
|
renameSync8(tmpPath, this.portFilePath);
|
|
32493
32944
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
32494
32945
|
} catch (err) {
|
|
@@ -32960,7 +33411,7 @@ function registerShutdownCleanup(fn) {
|
|
|
32960
33411
|
|
|
32961
33412
|
// src/status-bar-inject.ts
|
|
32962
33413
|
var emitStateBySession = new Map;
|
|
32963
|
-
function statusBarSuffixForSession(sessionID, counts) {
|
|
33414
|
+
function statusBarSuffixForSession(sessionID, counts, force = false) {
|
|
32964
33415
|
if (!counts)
|
|
32965
33416
|
return "";
|
|
32966
33417
|
let state = emitStateBySession.get(sessionID);
|
|
@@ -32968,12 +33419,67 @@ function statusBarSuffixForSession(sessionID, counts) {
|
|
|
32968
33419
|
state = createStatusBarEmitState();
|
|
32969
33420
|
emitStateBySession.set(sessionID, state);
|
|
32970
33421
|
}
|
|
33422
|
+
if (force) {
|
|
33423
|
+
state.last = counts;
|
|
33424
|
+
state.callsSinceEmit = 0;
|
|
33425
|
+
return statusBarLine(counts);
|
|
33426
|
+
}
|
|
32971
33427
|
return shouldEmitStatusBar(state, counts) ? statusBarLine(counts) : "";
|
|
32972
33428
|
}
|
|
32973
33429
|
function clearStatusBarSession(sessionID) {
|
|
32974
33430
|
emitStateBySession.delete(sessionID);
|
|
32975
33431
|
}
|
|
32976
33432
|
|
|
33433
|
+
// src/tool-perf.ts
|
|
33434
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
33435
|
+
var perfStore = new AsyncLocalStorage;
|
|
33436
|
+
function markBridgeStart() {
|
|
33437
|
+
const slot = perfStore.getStore();
|
|
33438
|
+
if (slot && slot.bridgeStart === undefined) {
|
|
33439
|
+
slot.bridgeStart = performance.now();
|
|
33440
|
+
}
|
|
33441
|
+
}
|
|
33442
|
+
function markBridgeEnd() {
|
|
33443
|
+
const slot = perfStore.getStore();
|
|
33444
|
+
if (slot) {
|
|
33445
|
+
slot.bridgeEnd = performance.now();
|
|
33446
|
+
}
|
|
33447
|
+
}
|
|
33448
|
+
function emit(slot) {
|
|
33449
|
+
const t3 = performance.now();
|
|
33450
|
+
const total = Math.round(t3 - slot.t0);
|
|
33451
|
+
if (slot.bridgeStart !== undefined && slot.bridgeEnd !== undefined) {
|
|
33452
|
+
const pre = Math.round(slot.bridgeStart - slot.t0);
|
|
33453
|
+
const bridge = Math.round(slot.bridgeEnd - slot.bridgeStart);
|
|
33454
|
+
const post = Math.round(t3 - slot.bridgeEnd);
|
|
33455
|
+
sessionLog(slot.sessionID, `perf tool=${slot.tool} total=${total}ms pre=${pre}ms bridge=${bridge}ms post=${post}ms`);
|
|
33456
|
+
} else {
|
|
33457
|
+
sessionLog(slot.sessionID, `perf tool=${slot.tool} total=${total}ms (no bridge call)`);
|
|
33458
|
+
}
|
|
33459
|
+
}
|
|
33460
|
+
function instrumentToolMap(tools) {
|
|
33461
|
+
for (const [name, def] of Object.entries(tools)) {
|
|
33462
|
+
const original = def.execute;
|
|
33463
|
+
if (typeof original !== "function")
|
|
33464
|
+
continue;
|
|
33465
|
+
def.execute = (args, context) => {
|
|
33466
|
+
const slot = {
|
|
33467
|
+
tool: name,
|
|
33468
|
+
sessionID: context?.sessionID,
|
|
33469
|
+
t0: performance.now()
|
|
33470
|
+
};
|
|
33471
|
+
return perfStore.run(slot, async () => {
|
|
33472
|
+
try {
|
|
33473
|
+
return await original(args, context);
|
|
33474
|
+
} finally {
|
|
33475
|
+
emit(slot);
|
|
33476
|
+
}
|
|
33477
|
+
});
|
|
33478
|
+
};
|
|
33479
|
+
}
|
|
33480
|
+
return tools;
|
|
33481
|
+
}
|
|
33482
|
+
|
|
32977
33483
|
// src/tools/ast.ts
|
|
32978
33484
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
32979
33485
|
|
|
@@ -33127,7 +33633,13 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
33127
33633
|
configureWarningClient: ctx.client,
|
|
33128
33634
|
...options
|
|
33129
33635
|
};
|
|
33130
|
-
|
|
33636
|
+
markBridgeStart();
|
|
33637
|
+
let response;
|
|
33638
|
+
try {
|
|
33639
|
+
response = await bridgeFor(ctx, runtime).send(command, merged, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
33640
|
+
} finally {
|
|
33641
|
+
markBridgeEnd();
|
|
33642
|
+
}
|
|
33131
33643
|
ingestBgCompletions(runtime.sessionID, response.bg_completions);
|
|
33132
33644
|
return response;
|
|
33133
33645
|
}
|
|
@@ -33964,6 +34476,15 @@ var z5 = tool5.schema;
|
|
|
33964
34476
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
33965
34477
|
var FOREGROUND_POLL_INTERVAL_MS = 100;
|
|
33966
34478
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
34479
|
+
function resolveForegroundWaitMs(configured) {
|
|
34480
|
+
const override = process.env.AFT_TEST_FOREGROUND_WAIT_MS;
|
|
34481
|
+
if (override !== undefined) {
|
|
34482
|
+
const parsed = Number(override);
|
|
34483
|
+
if (Number.isFinite(parsed) && parsed >= 0)
|
|
34484
|
+
return parsed;
|
|
34485
|
+
}
|
|
34486
|
+
return configured;
|
|
34487
|
+
}
|
|
33967
34488
|
var BASH_DESCRIPTION = `Hoisted bash tool with output compression, command rewriting to AFT tools, optional background execution, and PTY mode for interactive programs. By default, output is compressed; pass compressed: false for raw output. Pass background: true to spawn in the background and get a taskId for bash_status/bash_kill. Pass pty: true for interactive REPLs and drive them with bash_status({ outputMode: "screen" }) plus bash_write (pty implies background automatically). Use bash_watch to block on or register for pattern matches and exit events.`;
|
|
33968
34489
|
async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
33969
34490
|
const first = await bridgeCall(ctx, runtime, "bash", params, options);
|
|
@@ -34006,7 +34527,10 @@ function createBashTool(ctx) {
|
|
|
34006
34527
|
let accumulatedOutput = "";
|
|
34007
34528
|
const description = args.description;
|
|
34008
34529
|
const metadata = context.metadata;
|
|
34009
|
-
const
|
|
34530
|
+
const rawCommand = args.command;
|
|
34531
|
+
const compressionEnabled = bashCfg.compress && args.compressed !== false;
|
|
34532
|
+
const pipeStrip = maybeStripCompressorPipe(rawCommand, compressionEnabled);
|
|
34533
|
+
const command = pipeStrip.command;
|
|
34010
34534
|
const cwd = args.workdir ?? context.directory;
|
|
34011
34535
|
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
34012
34536
|
const requestedPty = args.pty === true;
|
|
@@ -34017,13 +34541,15 @@ function createBashTool(ctx) {
|
|
|
34017
34541
|
const allowSubagentBg = bashCfg.subagent_background;
|
|
34018
34542
|
const subagentForcedForeground = isSubagent && !allowSubagentBg;
|
|
34019
34543
|
const effectiveBackground = subagentForcedForeground ? false : requestedBackground;
|
|
34544
|
+
const rawTimeout = args.timeout;
|
|
34545
|
+
const effectiveTimeout = effectiveBackground ? rawTimeout : resolveBashKillTimeout(rawTimeout, bashCfg.foreground_wait_window_ms);
|
|
34020
34546
|
if (subagentForcedForeground && requestedBackground) {
|
|
34021
34547
|
sessionLog(context.sessionID, "[bash] subagent + background:true → converting to foreground (subagent would lose task_id)");
|
|
34022
34548
|
}
|
|
34023
34549
|
const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
|
|
34024
34550
|
const data = await withPermissionLoop(ctx, context, {
|
|
34025
34551
|
command,
|
|
34026
|
-
timeout:
|
|
34552
|
+
timeout: effectiveTimeout,
|
|
34027
34553
|
workdir: args.workdir,
|
|
34028
34554
|
env: shellEnv?.env ?? {},
|
|
34029
34555
|
description,
|
|
@@ -34044,26 +34570,20 @@ function createBashTool(ctx) {
|
|
|
34044
34570
|
throw new Error(data.message || "bash failed");
|
|
34045
34571
|
}
|
|
34046
34572
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
34047
|
-
const callID2 = getCallID(context);
|
|
34048
34573
|
const taskId = data.task_id;
|
|
34574
|
+
const uiTitle = description ?? shortenCommand(command);
|
|
34049
34575
|
if (effectiveBackground) {
|
|
34050
34576
|
trackBgTask(context.sessionID, taskId);
|
|
34051
34577
|
let startedLine = formatBackgroundLaunch(taskId, requestedPty);
|
|
34052
34578
|
if (isSubagent && allowSubagentBg)
|
|
34053
34579
|
startedLine += subagentGuidance(taskId);
|
|
34580
|
+
startedLine = appendPipeStripNote(startedLine, pipeStrip.note);
|
|
34054
34581
|
const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
|
|
34055
34582
|
metadata?.(metadataPayload2);
|
|
34056
|
-
|
|
34057
|
-
storeToolMetadata(context.sessionID, callID2, {
|
|
34058
|
-
title: description ?? shortenCommand(command),
|
|
34059
|
-
metadata: metadataPayload2
|
|
34060
|
-
});
|
|
34061
|
-
}
|
|
34062
|
-
return startedLine;
|
|
34583
|
+
return { output: startedLine, title: uiTitle, metadata: metadataPayload2 };
|
|
34063
34584
|
}
|
|
34064
|
-
const
|
|
34065
|
-
const
|
|
34066
|
-
const waitTimeoutMs = subagentForcedForeground ? argTimeout ?? DEFAULT_HARD_TIMEOUT_MS : argTimeout !== undefined ? Math.min(argTimeout, foregroundWaitMs) : foregroundWaitMs;
|
|
34585
|
+
const foregroundWaitMs = resolveForegroundWaitMs(bashCfg.foreground_wait_window_ms);
|
|
34586
|
+
const waitTimeoutMs = subagentForcedForeground ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
|
|
34067
34587
|
const startedAt = Date.now();
|
|
34068
34588
|
while (true) {
|
|
34069
34589
|
const status = await callBashBridge(ctx, context, "bash_status", { task_id: taskId });
|
|
@@ -34071,16 +34591,10 @@ function createBashTool(ctx) {
|
|
|
34071
34591
|
throw new Error(status.message ?? "bash_status failed");
|
|
34072
34592
|
}
|
|
34073
34593
|
if (isTerminalStatus(status.status)) {
|
|
34074
|
-
const rendered2 = formatForegroundResult(status);
|
|
34594
|
+
const rendered2 = appendPipeStripNote(formatForegroundResult(status), pipeStrip.note);
|
|
34075
34595
|
const metadataPayload2 = foregroundMetadata(description, status, rendered2);
|
|
34076
34596
|
metadata?.(metadataPayload2);
|
|
34077
|
-
|
|
34078
|
-
storeToolMetadata(context.sessionID, callID2, {
|
|
34079
|
-
title: description ?? shortenCommand(command),
|
|
34080
|
-
metadata: metadataPayload2
|
|
34081
|
-
});
|
|
34082
|
-
}
|
|
34083
|
-
return rendered2;
|
|
34597
|
+
return { output: rendered2, title: uiTitle, metadata: metadataPayload2 };
|
|
34084
34598
|
}
|
|
34085
34599
|
if (Date.now() - startedAt >= waitTimeoutMs) {
|
|
34086
34600
|
if (subagentForcedForeground) {
|
|
@@ -34094,18 +34608,13 @@ function createBashTool(ctx) {
|
|
|
34094
34608
|
throw new Error(promoted.message ?? "bash_promote failed");
|
|
34095
34609
|
}
|
|
34096
34610
|
trackBgTask(context.sessionID, taskId);
|
|
34097
|
-
let message = formatPromotionMessage(taskId,
|
|
34611
|
+
let message = formatPromotionMessage(taskId, effectiveTimeout, foregroundWaitMs);
|
|
34098
34612
|
if (isSubagent && allowSubagentBg)
|
|
34099
34613
|
message += subagentGuidance(taskId);
|
|
34614
|
+
message = appendPipeStripNote(message, pipeStrip.note);
|
|
34100
34615
|
const metadataPayload2 = { description, output: message, status: "running", taskId };
|
|
34101
34616
|
metadata?.(metadataPayload2);
|
|
34102
|
-
|
|
34103
|
-
storeToolMetadata(context.sessionID, callID2, {
|
|
34104
|
-
title: description ?? shortenCommand(command),
|
|
34105
|
-
metadata: metadataPayload2
|
|
34106
|
-
});
|
|
34107
|
-
}
|
|
34108
|
-
return message;
|
|
34617
|
+
return { output: message, title: uiTitle, metadata: metadataPayload2 };
|
|
34109
34618
|
}
|
|
34110
34619
|
await sleep(FOREGROUND_POLL_INTERVAL_MS);
|
|
34111
34620
|
}
|
|
@@ -34116,7 +34625,6 @@ function createBashTool(ctx) {
|
|
|
34116
34625
|
const truncated = data.truncated;
|
|
34117
34626
|
const outputPath = data.output_path;
|
|
34118
34627
|
const timedOut = data.timed_out === true;
|
|
34119
|
-
const callID = getCallID(context);
|
|
34120
34628
|
const metadataPayload = {
|
|
34121
34629
|
description,
|
|
34122
34630
|
output: metadataOutput,
|
|
@@ -34125,12 +34633,6 @@ function createBashTool(ctx) {
|
|
|
34125
34633
|
...outputPath ? { outputPath } : {}
|
|
34126
34634
|
};
|
|
34127
34635
|
metadata?.(metadataPayload);
|
|
34128
|
-
if (callID) {
|
|
34129
|
-
storeToolMetadata(context.sessionID, callID, {
|
|
34130
|
-
title: description ?? shortenCommand(command),
|
|
34131
|
-
metadata: metadataPayload
|
|
34132
|
-
});
|
|
34133
|
-
}
|
|
34134
34636
|
let rendered = output;
|
|
34135
34637
|
if (truncated && outputPath) {
|
|
34136
34638
|
rendered += `
|
|
@@ -34144,10 +34646,20 @@ function createBashTool(ctx) {
|
|
|
34144
34646
|
rendered += `
|
|
34145
34647
|
[exit code: ${exit}]`;
|
|
34146
34648
|
}
|
|
34147
|
-
|
|
34649
|
+
rendered = appendPipeStripNote(rendered, pipeStrip.note);
|
|
34650
|
+
return {
|
|
34651
|
+
output: rendered,
|
|
34652
|
+
title: description ?? shortenCommand(command),
|
|
34653
|
+
metadata: metadataPayload
|
|
34654
|
+
};
|
|
34148
34655
|
}
|
|
34149
34656
|
};
|
|
34150
34657
|
}
|
|
34658
|
+
function appendPipeStripNote(output, note) {
|
|
34659
|
+
return note ? `${output}
|
|
34660
|
+
|
|
34661
|
+
${note}` : output;
|
|
34662
|
+
}
|
|
34151
34663
|
function createBashStatusTool(ctx) {
|
|
34152
34664
|
return {
|
|
34153
34665
|
description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Never waits. Use bash_watch to block on or register for pattern matches and exit events.",
|
|
@@ -34202,7 +34714,7 @@ async function formatBashStatusText(runtime, taskId, data, requestedOutputMode)
|
|
|
34202
34714
|
const preview = data.output_preview;
|
|
34203
34715
|
if (preview && status !== "running") {
|
|
34204
34716
|
text += `
|
|
34205
|
-
${preview
|
|
34717
|
+
${preview}`;
|
|
34206
34718
|
}
|
|
34207
34719
|
if (status === "running") {
|
|
34208
34720
|
text += `
|
|
@@ -34326,7 +34838,7 @@ var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 300000;
|
|
|
34326
34838
|
var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
|
|
34327
34839
|
function createBashWatchTool(ctx) {
|
|
34328
34840
|
return {
|
|
34329
|
-
description: "
|
|
34841
|
+
description: "Watch a background bash task. Two modes. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately — use this to be pinged when a specific line appears or the task exits, without freezing your turn. Sync (default) blocks until a pattern matches/the task exits/timeout, and is ONLY for short bounded waits (seconds, e.g. a dev server printing a readiness line). Do NOT sync-wait for a long task (build/test/install): blocking locks the user out until it ends — instead end your turn and let the automatic completion reminder arrive, or use async mode.",
|
|
34330
34842
|
args: {
|
|
34331
34843
|
taskId: z6.string().describe("Background task ID returned by bash({ background: true })."),
|
|
34332
34844
|
pattern: z6.union([z6.string(), z6.object({ regex: z6.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true. Sync substring watches keep only the overlap tail needed for boundary matches; sync regex watches use a 64 KB rolling output window."),
|
|
@@ -34407,7 +34919,7 @@ Waited ${waited.elapsed_ms}ms; task exited (${stat}${e}).`;
|
|
|
34407
34919
|
const preview2 = data.output_preview;
|
|
34408
34920
|
if (preview2 && status !== "running") {
|
|
34409
34921
|
text += `
|
|
34410
|
-
${preview2
|
|
34922
|
+
${preview2}`;
|
|
34411
34923
|
}
|
|
34412
34924
|
return text;
|
|
34413
34925
|
}
|
|
@@ -34622,10 +35134,6 @@ function createBashWriteTool(ctx) {
|
|
|
34622
35134
|
}
|
|
34623
35135
|
|
|
34624
35136
|
// src/tools/hoisted.ts
|
|
34625
|
-
function getCallID2(ctx) {
|
|
34626
|
-
const c = ctx;
|
|
34627
|
-
return c.callID ?? c.callId ?? c.call_id;
|
|
34628
|
-
}
|
|
34629
35137
|
function relativeToWorktree(fp, worktree) {
|
|
34630
35138
|
return path4.relative(worktree, fp);
|
|
34631
35139
|
}
|
|
@@ -34911,19 +35419,16 @@ function createReadTool(ctx) {
|
|
|
34911
35419
|
} catch {}
|
|
34912
35420
|
const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
|
|
34913
35421
|
const msg = `${label} read successfully`;
|
|
34914
|
-
|
|
34915
|
-
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
|
|
34919
|
-
|
|
34920
|
-
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
|
|
34924
|
-
});
|
|
34925
|
-
}
|
|
34926
|
-
return `${msg} (${ext.slice(1).toUpperCase()}, ${sizeStr}). File: ${filePath}`;
|
|
35422
|
+
return {
|
|
35423
|
+
output: `${msg} (${ext.slice(1).toUpperCase()}, ${sizeStr}). File: ${filePath}`,
|
|
35424
|
+
title: path4.relative(projectRoot, filePath),
|
|
35425
|
+
metadata: {
|
|
35426
|
+
preview: msg,
|
|
35427
|
+
filepath: filePath,
|
|
35428
|
+
isImage,
|
|
35429
|
+
isPdf: mime === "application/pdf"
|
|
35430
|
+
}
|
|
35431
|
+
};
|
|
34927
35432
|
}
|
|
34928
35433
|
let startLine = args.startLine;
|
|
34929
35434
|
let endLine = args.endLine;
|
|
@@ -34944,32 +35449,24 @@ function createReadTool(ctx) {
|
|
|
34944
35449
|
if (data.success === false) {
|
|
34945
35450
|
throw new Error(data.message || "read failed");
|
|
34946
35451
|
}
|
|
34947
|
-
const
|
|
35452
|
+
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
34948
35453
|
if (data.entries) {
|
|
34949
|
-
|
|
34950
|
-
|
|
34951
|
-
|
|
34952
|
-
|
|
34953
|
-
|
|
34954
|
-
|
|
35454
|
+
return {
|
|
35455
|
+
output: data.entries.join(`
|
|
35456
|
+
`),
|
|
35457
|
+
title: dp,
|
|
35458
|
+
metadata: { title: dp }
|
|
35459
|
+
};
|
|
34955
35460
|
}
|
|
34956
35461
|
if (data.binary) {
|
|
34957
|
-
|
|
34958
|
-
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
34959
|
-
storeToolMetadata(context.sessionID, readCallID, { title: dp, metadata: { title: dp } });
|
|
34960
|
-
}
|
|
34961
|
-
return data.message;
|
|
34962
|
-
}
|
|
34963
|
-
if (readCallID) {
|
|
34964
|
-
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
34965
|
-
storeToolMetadata(context.sessionID, readCallID, { title: dp, metadata: { title: dp } });
|
|
35462
|
+
return { output: data.message, title: dp, metadata: { title: dp } };
|
|
34966
35463
|
}
|
|
34967
35464
|
let output = data.content;
|
|
34968
35465
|
const agentSpecifiedRange = args.startLine !== undefined || args.endLine !== undefined || args.offset !== undefined || args.limit !== undefined;
|
|
34969
35466
|
const footer = formatReadFooter(agentSpecifiedRange, data);
|
|
34970
35467
|
if (footer)
|
|
34971
35468
|
output += footer;
|
|
34972
|
-
return output;
|
|
35469
|
+
return { output, title: dp, metadata: { title: dp } };
|
|
34973
35470
|
}
|
|
34974
35471
|
};
|
|
34975
35472
|
}
|
|
@@ -35059,27 +35556,24 @@ Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagn
|
|
|
35059
35556
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
35060
35557
|
}
|
|
35061
35558
|
const diff = data.diff;
|
|
35062
|
-
const
|
|
35063
|
-
|
|
35064
|
-
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35069
|
-
|
|
35070
|
-
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
});
|
|
35081
|
-
}
|
|
35082
|
-
return output;
|
|
35559
|
+
const dp = relativeToWorktree(filePath, projectRoot);
|
|
35560
|
+
const beforeContent = diff?.before ?? "";
|
|
35561
|
+
const afterContent = diff?.after ?? content;
|
|
35562
|
+
return {
|
|
35563
|
+
output,
|
|
35564
|
+
title: dp,
|
|
35565
|
+
metadata: {
|
|
35566
|
+
diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
|
|
35567
|
+
filediff: {
|
|
35568
|
+
file: filePath,
|
|
35569
|
+
before: beforeContent,
|
|
35570
|
+
after: afterContent,
|
|
35571
|
+
additions: diff?.additions ?? 0,
|
|
35572
|
+
deletions: diff?.deletions ?? 0
|
|
35573
|
+
},
|
|
35574
|
+
diagnostics: {}
|
|
35575
|
+
}
|
|
35576
|
+
};
|
|
35083
35577
|
}
|
|
35084
35578
|
};
|
|
35085
35579
|
}
|
|
@@ -35088,45 +35582,39 @@ function getEditDescription(writeToolName) {
|
|
|
35088
35582
|
|
|
35089
35583
|
**Modes** (determined by which parameters you provide):
|
|
35090
35584
|
|
|
35091
|
-
Mode priority:
|
|
35585
|
+
Mode priority: appendContent > edits > symbol (without oldString) > oldString (find/replace). If none match, the call is rejected — there is no implicit "write" fallback. To edit multiple files, make parallel \`edit\` calls in one response.
|
|
35092
35586
|
|
|
35093
|
-
1. **
|
|
35094
|
-
Edits across multiple files with checkpoint-based rollback on failure.
|
|
35095
|
-
Each operation: \`{ "file": "path", "command": "edit_match" | "write", ... }\`.
|
|
35096
|
-
For \`edit_match\`: include \`match\`, \`replacement\`. For \`write\`: include \`content\`.
|
|
35097
|
-
Example: \`{ "operations": [{ "file": "a.ts", "command": "edit_match", "match": "old", "replacement": "new" }, { "file": "b.ts", "command": "write", "content": "..." }] }\`
|
|
35098
|
-
|
|
35099
|
-
2. **Append** — pass \`filePath\` + \`appendContent\`
|
|
35587
|
+
1. **Append** — pass \`filePath\` + \`appendContent\`
|
|
35100
35588
|
Appends text to the end of a file, creating the file if it does not exist.
|
|
35101
35589
|
Example: \`{ "filePath": "notes.txt", "appendContent": "new line\\n" }\`
|
|
35102
35590
|
|
|
35103
|
-
|
|
35591
|
+
2. **Batch edits** — pass \`filePath\` + \`edits\` array
|
|
35104
35592
|
Multiple edits in one file atomically. Each edit is either:
|
|
35105
35593
|
- \`{ "oldString": "old", "newString": "new" }\` — find/replace
|
|
35106
35594
|
- \`{ "startLine": 5, "endLine": 7, "content": "new lines" }\` — replace line range (1-based, both inclusive)
|
|
35107
35595
|
Set content to empty string to delete lines.
|
|
35108
35596
|
|
|
35109
|
-
|
|
35597
|
+
3. **Symbol replace** — pass \`filePath\` + \`symbol\` + \`content\`
|
|
35110
35598
|
Replaces an entire named symbol (function, class, type) with new content.
|
|
35111
35599
|
Includes decorators, attributes, and doc comments in the replacement range.
|
|
35112
35600
|
**Important:** You must NOT provide \`oldString\` when using symbol mode — if present, the tool silently falls back to find/replace mode.
|
|
35113
35601
|
Example: \`{ "filePath": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
|
|
35114
35602
|
|
|
35115
|
-
|
|
35603
|
+
4. **Find and replace** — pass \`filePath\` + \`oldString\` + \`newString\`
|
|
35116
35604
|
Finds the exact text in \`oldString\` and replaces it with \`newString\`.
|
|
35117
35605
|
Supports fuzzy matching (handles whitespace differences automatically).
|
|
35118
35606
|
If multiple matches exist, specify which one with \`occurrence\` or use \`replaceAll: true\`.
|
|
35119
35607
|
Example: \`{ "filePath": "src/app.ts", "oldString": "const x = 1", "newString": "const x = 2" }\`
|
|
35120
35608
|
|
|
35121
|
-
|
|
35609
|
+
5. **Replace all occurrences** — add \`replaceAll: true\`
|
|
35122
35610
|
Replaces every occurrence of \`oldString\` in the file.
|
|
35123
35611
|
Example: \`{ "filePath": "src/app.ts", "oldString": "oldName", "newString": "newName", "replaceAll": true }\`
|
|
35124
35612
|
|
|
35125
|
-
|
|
35613
|
+
6. **Select specific occurrence** — add \`occurrence: N\` (0-indexed)
|
|
35126
35614
|
When multiple matches exist, select the Nth one (0 = first, 1 = second, etc.).
|
|
35127
35615
|
Example: \`{ "filePath": "src/app.ts", "oldString": "TODO", "newString": "DONE", "occurrence": 0 }\`
|
|
35128
35616
|
|
|
35129
|
-
Note: Modes
|
|
35617
|
+
Note: Modes 5 and 6 are options on mode 4 (find/replace) — they require \`oldString\`.
|
|
35130
35618
|
|
|
35131
35619
|
**Behavior:**
|
|
35132
35620
|
- Backs up files before editing (recoverable via aft_safety undo)
|
|
@@ -35140,16 +35628,15 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
35140
35628
|
return {
|
|
35141
35629
|
description: getEditDescription(writeToolName),
|
|
35142
35630
|
args: {
|
|
35143
|
-
filePath: z8.string().optional().describe("Path to the file to edit (absolute or relative to project root)
|
|
35631
|
+
filePath: z8.string().optional().describe("Path to the file to edit (absolute or relative to project root)"),
|
|
35144
35632
|
oldString: z8.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
|
|
35145
35633
|
newString: z8.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
|
|
35146
35634
|
replaceAll: z8.boolean().optional().describe("Replace all occurrences"),
|
|
35147
35635
|
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER).describe("0-indexed occurrence to replace when multiple matches exist"),
|
|
35148
35636
|
symbol: z8.string().optional().describe("Named symbol to replace (function, class, type)"),
|
|
35149
|
-
content: z8.string().optional().describe("Replacement content for symbol mode
|
|
35637
|
+
content: z8.string().optional().describe("Replacement content for symbol mode. For whole-file writes, use the `write` tool."),
|
|
35150
35638
|
appendContent: z8.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
35151
35639
|
edits: z8.array(z8.record(z8.string(), z8.unknown())).optional().describe("Batch edits — array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
|
|
35152
|
-
operations: z8.array(z8.record(z8.string(), z8.unknown())).optional().describe("Transaction — array of { file: string, command: 'edit_match' | 'write', match?: string, replacement?: string, content?: string } for multi-file edits with rollback. Note: uses 'file'/'match'/'replacement' (not filePath/oldString/newString)"),
|
|
35153
35640
|
diagnostics: z8.boolean().optional().describe(DIAGNOSTICS_PARAM_DESCRIPTION)
|
|
35154
35641
|
},
|
|
35155
35642
|
execute: async (args, context) => {
|
|
@@ -35157,38 +35644,6 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
35157
35644
|
if (argsRecord.startLine !== undefined || argsRecord.endLine !== undefined) {
|
|
35158
35645
|
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 `oldString`/`newString` instead.");
|
|
35159
35646
|
}
|
|
35160
|
-
if (Array.isArray(args.operations)) {
|
|
35161
|
-
const ops = args.operations;
|
|
35162
|
-
const files = ops.map((op) => op.file).filter(Boolean);
|
|
35163
|
-
const projectRoot2 = await resolveProjectRoot(ctx, context);
|
|
35164
|
-
{
|
|
35165
|
-
const asked = new Set;
|
|
35166
|
-
for (const file3 of files) {
|
|
35167
|
-
const absPath = resolvePathFromProjectRoot(projectRoot2, file3);
|
|
35168
|
-
if (asked.has(absPath))
|
|
35169
|
-
continue;
|
|
35170
|
-
asked.add(absPath);
|
|
35171
|
-
const denial = await assertExternalDirectoryPermission(context, absPath);
|
|
35172
|
-
if (denial)
|
|
35173
|
-
return permissionDeniedResponse(denial);
|
|
35174
|
-
}
|
|
35175
|
-
}
|
|
35176
|
-
await runAsk(context.ask({
|
|
35177
|
-
permission: "edit",
|
|
35178
|
-
patterns: files.map((f) => path4.relative(projectRoot2, resolvePathFromProjectRoot(projectRoot2, f))),
|
|
35179
|
-
always: ["*"],
|
|
35180
|
-
metadata: {}
|
|
35181
|
-
}));
|
|
35182
|
-
const resolvedOps = ops.map((op) => ({
|
|
35183
|
-
...op,
|
|
35184
|
-
file: resolvePathFromProjectRoot(projectRoot2, op.file)
|
|
35185
|
-
}));
|
|
35186
|
-
const response = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
|
|
35187
|
-
if (response.success === false) {
|
|
35188
|
-
throw new Error(response.message ?? "transaction failed");
|
|
35189
|
-
}
|
|
35190
|
-
return formatEditSummary(response);
|
|
35191
|
-
}
|
|
35192
35647
|
const file2 = args.filePath;
|
|
35193
35648
|
if (!file2)
|
|
35194
35649
|
throw new Error("'filePath' parameter is required");
|
|
@@ -35245,34 +35700,33 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
35245
35700
|
if (args.occurrence !== undefined)
|
|
35246
35701
|
params.occurrence = args.occurrence;
|
|
35247
35702
|
} else {
|
|
35248
|
-
const hint = typeof args.content === "string" ? ` To write the whole file, use the '${writeToolName}' tool. To edit existing content, provide 'oldString' (and optionally 'newString'), 'symbol' + 'content', or an 'edits' array.` : " Provide 'oldString' (+ optional 'newString'), 'symbol' + 'content',
|
|
35703
|
+
const hint = typeof args.content === "string" ? ` To write the whole file, use the '${writeToolName}' tool. To edit existing content, provide 'oldString' (and optionally 'newString'), 'symbol' + 'content', or an 'edits' array.` : " Provide 'oldString' (+ optional 'newString'), 'symbol' + 'content', or an 'edits' array.";
|
|
35249
35704
|
throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
|
|
35250
35705
|
}
|
|
35251
35706
|
params.diagnostics = args.diagnostics ?? diagnosticsOnEditDefault(ctx);
|
|
35252
35707
|
params.include_diff_content = true;
|
|
35253
35708
|
const data = await callBridge(ctx, context, command, params);
|
|
35709
|
+
if (data.success === false) {
|
|
35710
|
+
throw new Error(data.message || "edit failed");
|
|
35711
|
+
}
|
|
35712
|
+
let uiMeta;
|
|
35713
|
+
let uiTitle;
|
|
35254
35714
|
if (data.success && data.diff) {
|
|
35255
35715
|
const diff = data.diff;
|
|
35256
|
-
|
|
35257
|
-
|
|
35258
|
-
|
|
35259
|
-
|
|
35260
|
-
|
|
35261
|
-
|
|
35262
|
-
|
|
35263
|
-
|
|
35264
|
-
|
|
35265
|
-
|
|
35266
|
-
|
|
35267
|
-
|
|
35268
|
-
|
|
35269
|
-
|
|
35270
|
-
deletions: diff.deletions ?? 0
|
|
35271
|
-
},
|
|
35272
|
-
diagnostics: {}
|
|
35273
|
-
}
|
|
35274
|
-
});
|
|
35275
|
-
}
|
|
35716
|
+
uiTitle = relativeToWorktree(filePath, projectRoot);
|
|
35717
|
+
const beforeContent = diff.before ?? "";
|
|
35718
|
+
const afterContent = diff.after ?? "";
|
|
35719
|
+
uiMeta = {
|
|
35720
|
+
diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
|
|
35721
|
+
filediff: {
|
|
35722
|
+
file: filePath,
|
|
35723
|
+
before: beforeContent,
|
|
35724
|
+
after: afterContent,
|
|
35725
|
+
additions: diff.additions ?? 0,
|
|
35726
|
+
deletions: diff.deletions ?? 0
|
|
35727
|
+
},
|
|
35728
|
+
diagnostics: {}
|
|
35729
|
+
};
|
|
35276
35730
|
}
|
|
35277
35731
|
let result = formatEditSummary(data);
|
|
35278
35732
|
const globSkipNote = formatGlobSkipReasonsNote(data.format_skip_reasons);
|
|
@@ -35309,6 +35763,9 @@ Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagn
|
|
|
35309
35763
|
|
|
35310
35764
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
35311
35765
|
}
|
|
35766
|
+
if (uiMeta) {
|
|
35767
|
+
return { output: result, title: uiTitle ?? "", metadata: uiMeta };
|
|
35768
|
+
}
|
|
35312
35769
|
return result;
|
|
35313
35770
|
}
|
|
35314
35771
|
};
|
|
@@ -35458,6 +35915,9 @@ function createApplyPatchTool(ctx) {
|
|
|
35458
35915
|
include_diff_content: true,
|
|
35459
35916
|
multi_file_write_paths: multiFileWritePaths
|
|
35460
35917
|
});
|
|
35918
|
+
if (writeResult.success === false) {
|
|
35919
|
+
throw new Error(writeResult.message ?? "write failed");
|
|
35920
|
+
}
|
|
35461
35921
|
const wrDiff = writeResult.diff;
|
|
35462
35922
|
perFileDiffs.push({
|
|
35463
35923
|
filePath,
|
|
@@ -35483,7 +35943,12 @@ function createApplyPatchTool(ctx) {
|
|
|
35483
35943
|
case "delete": {
|
|
35484
35944
|
try {
|
|
35485
35945
|
const before = await fs6.promises.readFile(filePath, "utf-8").catch(() => "");
|
|
35486
|
-
await callBridge(ctx, context, "delete_file", {
|
|
35946
|
+
const deleteResult = await callBridge(ctx, context, "delete_file", {
|
|
35947
|
+
file: filePath
|
|
35948
|
+
});
|
|
35949
|
+
if (deleteResult.success === false) {
|
|
35950
|
+
throw new Error(deleteResult.message ?? "delete failed");
|
|
35951
|
+
}
|
|
35487
35952
|
perFileDiffs.push({
|
|
35488
35953
|
filePath,
|
|
35489
35954
|
before,
|
|
@@ -35511,6 +35976,9 @@ function createApplyPatchTool(ctx) {
|
|
|
35511
35976
|
include_diff_content: true,
|
|
35512
35977
|
multi_file_write_paths: multiFileWritePaths
|
|
35513
35978
|
});
|
|
35979
|
+
if (writeResult.success === false) {
|
|
35980
|
+
throw new Error(writeResult.message ?? "write failed");
|
|
35981
|
+
}
|
|
35514
35982
|
const diags = writeResult.lsp_diagnostics;
|
|
35515
35983
|
if (diags && diags.length > 0) {
|
|
35516
35984
|
const errors3 = diags.filter((d) => d.severity === "error");
|
|
@@ -35590,10 +36058,11 @@ ${diagLines}`);
|
|
|
35590
36058
|
`));
|
|
35591
36059
|
}
|
|
35592
36060
|
}
|
|
35593
|
-
|
|
35594
|
-
if (callID) {
|
|
36061
|
+
{
|
|
35595
36062
|
const diffByPath = new Map(perFileDiffs.map((d) => [d.filePath, d]));
|
|
35596
|
-
const
|
|
36063
|
+
const failedPaths = new Set(failures);
|
|
36064
|
+
const appliedHunks = hunks.filter((h) => !failedPaths.has(h.path));
|
|
36065
|
+
const files = appliedHunks.map((h) => {
|
|
35597
36066
|
const filePath = resolvePathFromProjectRoot(projectRoot, h.path);
|
|
35598
36067
|
const rawMovePath = h.type === "update" ? h.move_path : undefined;
|
|
35599
36068
|
const movePath = rawMovePath ? resolvePathFromProjectRoot(projectRoot, rawMovePath) : undefined;
|
|
@@ -35619,20 +36088,21 @@ ${diagLines}`);
|
|
|
35619
36088
|
return `${prefix} ${f.relativePath}`;
|
|
35620
36089
|
}).join(`
|
|
35621
36090
|
`);
|
|
35622
|
-
const title =
|
|
36091
|
+
const title = failures.length > 0 ? `Partially applied (${files.length} of ${hunks.length}). Updated:
|
|
36092
|
+
${fileList}` : `Success. Updated the following files:
|
|
35623
36093
|
${fileList}`;
|
|
35624
36094
|
const diffText = files.map((f) => f.patch).filter(Boolean).join(`
|
|
35625
36095
|
`);
|
|
35626
|
-
|
|
36096
|
+
return {
|
|
36097
|
+
output: results.join(`
|
|
36098
|
+
`),
|
|
35627
36099
|
title,
|
|
35628
36100
|
metadata: {
|
|
35629
36101
|
diff: diffText,
|
|
35630
36102
|
files
|
|
35631
36103
|
}
|
|
35632
|
-
}
|
|
36104
|
+
};
|
|
35633
36105
|
}
|
|
35634
|
-
return results.join(`
|
|
35635
|
-
`);
|
|
35636
36106
|
}
|
|
35637
36107
|
};
|
|
35638
36108
|
}
|
|
@@ -35647,7 +36117,10 @@ function createDeleteTool(ctx) {
|
|
|
35647
36117
|
recursive: z8.boolean().optional().describe("Required to delete a directory and its contents. Defaults to false; passing a directory without this returns an error.")
|
|
35648
36118
|
},
|
|
35649
36119
|
execute: async (args, context) => {
|
|
35650
|
-
const inputs = args.files;
|
|
36120
|
+
const inputs = coerceStringArray(args.files);
|
|
36121
|
+
if (inputs.length === 0) {
|
|
36122
|
+
throw new Error("delete: `files` must be a non-empty array of paths");
|
|
36123
|
+
}
|
|
35651
36124
|
const recursive = args.recursive === true;
|
|
35652
36125
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
35653
36126
|
const absolutePaths = inputs.map((f) => resolvePathFromProjectRoot(projectRoot, f));
|
|
@@ -36073,6 +36546,7 @@ ${diagnostics}` : json2;
|
|
|
36073
36546
|
// src/tools/navigation.ts
|
|
36074
36547
|
import { tool as tool11 } from "@opencode-ai/plugin";
|
|
36075
36548
|
var z11 = tool11.schema;
|
|
36549
|
+
var CALLGRAPH_SOFT_CODES = new Set(["symbol_not_found", "callgraph_building"]);
|
|
36076
36550
|
function navigationTools(ctx) {
|
|
36077
36551
|
return {
|
|
36078
36552
|
aft_callgraph: {
|
|
@@ -36136,7 +36610,12 @@ function navigationTools(ctx) {
|
|
|
36136
36610
|
params.toFile = toFile;
|
|
36137
36611
|
const response = await callBridge(ctx, context, args.op, params);
|
|
36138
36612
|
if (response.success === false) {
|
|
36139
|
-
|
|
36613
|
+
const message = formatBridgeErrorMessage(args.op, response, params);
|
|
36614
|
+
const code = typeof response.code === "string" ? response.code : "";
|
|
36615
|
+
if (CALLGRAPH_SOFT_CODES.has(code)) {
|
|
36616
|
+
return message;
|
|
36617
|
+
}
|
|
36618
|
+
throw new Error(message);
|
|
36140
36619
|
}
|
|
36141
36620
|
return JSON.stringify(response);
|
|
36142
36621
|
}
|
|
@@ -36147,10 +36626,6 @@ function navigationTools(ctx) {
|
|
|
36147
36626
|
// src/tools/reading.ts
|
|
36148
36627
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
36149
36628
|
var z12 = tool12.schema;
|
|
36150
|
-
function getCallID3(ctx) {
|
|
36151
|
-
const c = ctx;
|
|
36152
|
-
return c.callID ?? c.callId ?? c.call_id;
|
|
36153
|
-
}
|
|
36154
36629
|
function buildZoomTitle(args) {
|
|
36155
36630
|
if (!isEmptyParam(args.targets)) {
|
|
36156
36631
|
if (Array.isArray(args.targets)) {
|
|
@@ -36313,25 +36788,26 @@ function readingTools(ctx) {
|
|
|
36313
36788
|
const hasTargets = hasTargetsProvided(args.targets);
|
|
36314
36789
|
const hasSymbols = !isEmptyParam(args.symbols);
|
|
36315
36790
|
const wantCallgraph = args.callgraph === true;
|
|
36316
|
-
const
|
|
36317
|
-
|
|
36318
|
-
|
|
36319
|
-
|
|
36320
|
-
|
|
36321
|
-
|
|
36322
|
-
|
|
36323
|
-
|
|
36324
|
-
|
|
36325
|
-
|
|
36326
|
-
|
|
36327
|
-
|
|
36328
|
-
|
|
36329
|
-
|
|
36330
|
-
|
|
36331
|
-
|
|
36332
|
-
|
|
36333
|
-
|
|
36334
|
-
|
|
36791
|
+
const zoomTitle = buildZoomTitle(args);
|
|
36792
|
+
const zoomDisplay = { title: zoomTitle };
|
|
36793
|
+
if (hasFilePath)
|
|
36794
|
+
zoomDisplay.filePath = args.filePath;
|
|
36795
|
+
if (hasUrl)
|
|
36796
|
+
zoomDisplay.url = args.url;
|
|
36797
|
+
if (hasSymbols) {
|
|
36798
|
+
zoomDisplay.symbols = typeof args.symbols === "string" ? args.symbols : JSON.stringify(args.symbols);
|
|
36799
|
+
}
|
|
36800
|
+
if (hasTargets)
|
|
36801
|
+
zoomDisplay.targets = JSON.stringify(args.targets);
|
|
36802
|
+
if (args.contextLines !== undefined)
|
|
36803
|
+
zoomDisplay.contextLines = args.contextLines;
|
|
36804
|
+
if (wantCallgraph)
|
|
36805
|
+
zoomDisplay.callgraph = true;
|
|
36806
|
+
const withMeta = (output) => ({
|
|
36807
|
+
output,
|
|
36808
|
+
title: zoomTitle,
|
|
36809
|
+
metadata: zoomDisplay
|
|
36810
|
+
});
|
|
36335
36811
|
if (hasTargets) {
|
|
36336
36812
|
if (hasFilePath || hasUrl || hasSymbols) {
|
|
36337
36813
|
throw new Error("'targets' is mutually exclusive with 'filePath', 'url', and 'symbols'");
|
|
@@ -36371,7 +36847,7 @@ function readingTools(ctx) {
|
|
|
36371
36847
|
name: t.symbol,
|
|
36372
36848
|
response: responses[i] ?? { success: false, message: "missing zoom response" }
|
|
36373
36849
|
}));
|
|
36374
|
-
return formatZoomMultiTargetResult(entries).text;
|
|
36850
|
+
return withMeta(formatZoomMultiTargetResult(entries).text);
|
|
36375
36851
|
}
|
|
36376
36852
|
if (!hasFilePath && !hasUrl) {
|
|
36377
36853
|
throw new Error("Provide exactly one of 'filePath', 'url', or 'targets'");
|
|
@@ -36404,9 +36880,9 @@ function readingTools(ctx) {
|
|
|
36404
36880
|
if (response.success === false) {
|
|
36405
36881
|
throw new Error(response.message || "zoom failed");
|
|
36406
36882
|
}
|
|
36407
|
-
return formatZoomText(targetLabel, response);
|
|
36883
|
+
return withMeta(formatZoomText(targetLabel, response));
|
|
36408
36884
|
}
|
|
36409
|
-
return formatZoomBatchResult(targetLabel, symbolsArray, results).text;
|
|
36885
|
+
return withMeta(formatZoomBatchResult(targetLabel, symbolsArray, results).text);
|
|
36410
36886
|
}
|
|
36411
36887
|
const params = { file: file2 };
|
|
36412
36888
|
if (args.contextLines !== undefined)
|
|
@@ -36417,7 +36893,7 @@ function readingTools(ctx) {
|
|
|
36417
36893
|
if (data.success === false) {
|
|
36418
36894
|
throw new Error(data.message || "zoom failed");
|
|
36419
36895
|
}
|
|
36420
|
-
return formatZoomText(targetLabel, data);
|
|
36896
|
+
return withMeta(formatZoomText(targetLabel, data));
|
|
36421
36897
|
}
|
|
36422
36898
|
}
|
|
36423
36899
|
};
|
|
@@ -36747,7 +37223,8 @@ function safetyTools(ctx) {
|
|
|
36747
37223
|
return permissionDeniedResponse(permissionError);
|
|
36748
37224
|
}
|
|
36749
37225
|
if (op === "checkpoint") {
|
|
36750
|
-
const
|
|
37226
|
+
const coercedFiles = coerceStringArray(args.files);
|
|
37227
|
+
const checkpointFiles = coercedFiles.length > 0 ? coercedFiles : typeof args.filePath === "string" ? [args.filePath] : undefined;
|
|
36751
37228
|
if (Array.isArray(checkpointFiles)) {
|
|
36752
37229
|
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
36753
37230
|
const uniqueParents = new Set;
|
|
@@ -36793,17 +37270,18 @@ function safetyTools(ctx) {
|
|
|
36793
37270
|
const params = {};
|
|
36794
37271
|
if (args.name !== undefined)
|
|
36795
37272
|
params.name = args.name;
|
|
37273
|
+
const payloadFiles = coerceStringArray(args.files);
|
|
36796
37274
|
if (op === "checkpoint") {
|
|
36797
|
-
if (
|
|
36798
|
-
params.files =
|
|
37275
|
+
if (payloadFiles.length > 0) {
|
|
37276
|
+
params.files = payloadFiles;
|
|
36799
37277
|
} else if (args.filePath !== undefined) {
|
|
36800
37278
|
params.files = [args.filePath];
|
|
36801
37279
|
}
|
|
36802
37280
|
} else {
|
|
36803
37281
|
if (args.filePath !== undefined)
|
|
36804
37282
|
params.file = args.filePath;
|
|
36805
|
-
if (
|
|
36806
|
-
params.files =
|
|
37283
|
+
if (payloadFiles.length > 0)
|
|
37284
|
+
params.files = payloadFiles;
|
|
36807
37285
|
}
|
|
36808
37286
|
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
36809
37287
|
if (response.success === false) {
|
|
@@ -36829,7 +37307,7 @@ function formatGrepOutput(response) {
|
|
|
36829
37307
|
const totalMatches = response.total_matches ?? matches.length;
|
|
36830
37308
|
const filesWithMatches = response.files_with_matches ?? new Set(matches.map((m) => m.file)).size;
|
|
36831
37309
|
if (matches.length === 0) {
|
|
36832
|
-
return `Found ${totalMatches} match
|
|
37310
|
+
return `Found ${totalMatches} match across ${filesWithMatches} file`;
|
|
36833
37311
|
}
|
|
36834
37312
|
const body = matches.map((match) => {
|
|
36835
37313
|
const file2 = match.file ?? "unknown";
|
|
@@ -36840,7 +37318,7 @@ function formatGrepOutput(response) {
|
|
|
36840
37318
|
`);
|
|
36841
37319
|
return `${body}
|
|
36842
37320
|
|
|
36843
|
-
Found ${totalMatches} match
|
|
37321
|
+
Found ${totalMatches} match across ${filesWithMatches} file`;
|
|
36844
37322
|
}
|
|
36845
37323
|
function normalizeGlob(pattern) {
|
|
36846
37324
|
if (!pattern.includes("/") && !pattern.startsWith("**/")) {
|
|
@@ -37229,16 +37707,15 @@ function buildWorkflowHints(opts) {
|
|
|
37229
37707
|
const hasInspect = opts.toolSurface !== "minimal" && !opts.disabledTools.has("aft_inspect");
|
|
37230
37708
|
const hasBash = !opts.disabledTools.has(bashName);
|
|
37231
37709
|
const hasBgBash = hasBash && opts.bashBackgroundEnabled && !opts.disabledTools.has(bashStatusName);
|
|
37710
|
+
if (hasBash && opts.bashCompressionEnabled) {
|
|
37711
|
+
sections.push("When AFT bash output compression is on, do NOT pipe test/build commands through grep/head/tail (e.g. `bun test | grep fail`) to summarize. The compressor already keeps failures and the summary; piping hides failures. Run the command bare.");
|
|
37712
|
+
}
|
|
37232
37713
|
if (hasOutline && hasZoom) {
|
|
37233
37714
|
sections.push(`**Web/URL access**: \`aft_outline({ target: url })\` first for structure, then \`aft_zoom({ url, symbols: "<heading>" })\` for the specific section.`);
|
|
37234
37715
|
}
|
|
37235
37716
|
if (hasOutline && hasZoom && (hasGrep || hasSearch)) {
|
|
37236
|
-
|
|
37237
|
-
|
|
37238
|
-
sections.push(`**Code exploration**: \`aft_search\` is the primary code-search tool. It auto-routes by query shape — exact identifiers, regex, error messages, natural language all use the same call. Very short queries fall back to literal scans; pass \`hint: "regex"\` / \`hint: "literal"\` / \`hint: "semantic"\` to override routing if needed. Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).${grepFallback}`);
|
|
37239
|
-
} else {
|
|
37240
|
-
sections.push(`**Code exploration**: \`${grepName}\` to locate → \`aft_outline\` for structure → \`aft_zoom\` for symbol(s).`);
|
|
37241
|
-
}
|
|
37717
|
+
const locate = hasSearch ? '`aft_search` is the primary code-search tool: one call auto-routes concepts, identifiers, regex, error strings, and literals (pass `hint: "regex"`/`"literal"`/`"semantic"` to force a lane).' : `\`${grepName}\` (the tool — indexed and ranked) locates code.`;
|
|
37718
|
+
sections.push(`**Code exploration**: fire independent lookups in ONE parallel tool-call wave — do NOT serialize them. ${locate} Then \`aft_outline\` for structure → \`aft_zoom\` for symbol(s). DO NOT run \`grep\`/\`rg\`/\`find\` through \`bash\` to locate code — the bash path is unindexed, unranked, serial, and routinely surfaces the wrong hit. Keep \`bash\` for shell facts (git state, file metadata, processes).`);
|
|
37242
37719
|
}
|
|
37243
37720
|
if (hasInspect) {
|
|
37244
37721
|
sections.push("**Codebase health & diagnostics**: AFT does not surface compile/type errors automatically after edits — pull them with `aft_inspect`. Run it after a batch of edits and before you run tests or commit, when starting in unfamiliar code, or before a refactor/review. One call summarizes diagnostics (compile/type errors), TODOs, metrics, dead code, unused exports, and duplicates; pass `sections` for focused drill-down and `scope` to actively pull diagnostics for a specific file or directory. Its diagnostics are a fast checkpoint, not the authority — a clean `tsc` / `cargo check` / `pyright` run is the real gate. Treat `stale_categories` as a genuine stale-cache signal while an async Tier 2 refresh catches up.");
|
|
@@ -37256,12 +37733,13 @@ function buildWorkflowHints(opts) {
|
|
|
37256
37733
|
`));
|
|
37257
37734
|
}
|
|
37258
37735
|
if (hasBash && hasBgBash) {
|
|
37259
|
-
sections.push(`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns immediately with a \`taskId
|
|
37736
|
+
sections.push(`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns immediately with a \`taskId\`, then **end your turn** — a completion reminder arrives automatically when it finishes. Do not poll \`${bashStatusName}({ taskId })\`, and do not sync-wait with \`bash_watch\` for a long task: blocking freezes your turn and locks the user out until it ends. For an early non-blocking ping on a specific output line, register an async watch \`bash_watch({ taskId, pattern, background: true })\`. \`bash_watch\` synchronous mode is only for short bounded waits (seconds, e.g. a dev server printing a readiness line), never for multi-minute jobs.`);
|
|
37260
37737
|
sections.push(`**PTY / interactive commands**: PTY mode is for interactive REPLs and terminal apps (python, node, bash itself, vim). Start with \`${bashName}({ command: "python", pty: true, background: true })\`, read the screen with \`${bashStatusName}({ taskId, outputMode: "screen" })\`, and send input with \`${bashWriteName}({ taskId, input: "..." })\`.`);
|
|
37261
37738
|
}
|
|
37262
37739
|
if (sections.length === 0) {
|
|
37263
37740
|
return null;
|
|
37264
37741
|
}
|
|
37742
|
+
sections.unshift("**Parallel tool calls**: when several read-only operations are independent, emit them in ONE response instead of serializing — file reads, structure and symbol lookups, code search, diagnostics, and git status/diff/log. Sequence only when a call depends on a prior result or when a command mutates state.");
|
|
37265
37743
|
return `${HEADING}
|
|
37266
37744
|
|
|
37267
37745
|
${sections.join(`
|
|
@@ -37274,6 +37752,7 @@ function buildHintsFromConfig(config2, disabledTools) {
|
|
|
37274
37752
|
hoistBuiltins: config2.hoist_builtin_tools !== false,
|
|
37275
37753
|
semanticEnabled: config2.semantic_search === true,
|
|
37276
37754
|
bashBackgroundEnabled: resolveBashConfig(config2).background,
|
|
37755
|
+
bashCompressionEnabled: resolveBashConfig(config2).compress,
|
|
37277
37756
|
disabledTools
|
|
37278
37757
|
});
|
|
37279
37758
|
}
|
|
@@ -37322,12 +37801,12 @@ var PLUGIN_VERSION = (() => {
|
|
|
37322
37801
|
return "0.0.0";
|
|
37323
37802
|
}
|
|
37324
37803
|
})();
|
|
37325
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
37804
|
+
var ANNOUNCEMENT_VERSION = "0.36.0";
|
|
37326
37805
|
var ANNOUNCEMENT_FEATURES = [
|
|
37327
|
-
"
|
|
37328
|
-
"
|
|
37329
|
-
"
|
|
37330
|
-
"
|
|
37806
|
+
"Persisted call graph: dead-code analysis runs on large repositories again (the file-count cap is gone), `aft_callgraph` queries resolve from disk, and method-call edges are more accurate across more languages.",
|
|
37807
|
+
"Bash output stops hiding failures: piping a test/build through `grep`/`head` with compression on now keeps the failures and summary instead of stripping them.",
|
|
37808
|
+
"Code search steering: guidance and hints now point to `aft_search` and parallel `aft_*` tools instead of `grep`/`find` in bash.",
|
|
37809
|
+
"Fixes: a small `timeout` no longer kills a foreground `bash` command (#102), and `aft_delete`/`aft_safety checkpoint` no longer crash on a mistyped `files` argument."
|
|
37331
37810
|
];
|
|
37332
37811
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
37333
37812
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -37554,10 +38033,10 @@ ${lines}
|
|
|
37554
38033
|
const sessionID = params.sessionID || "rpc";
|
|
37555
38034
|
const cachedDir = getSessionDirectoryCached(sessionID);
|
|
37556
38035
|
const candidateDirs = new Set;
|
|
38036
|
+
candidateDirs.add(input.directory);
|
|
37557
38037
|
if (typeof cachedDir === "string" && cachedDir.length > 0) {
|
|
37558
38038
|
candidateDirs.add(cachedDir);
|
|
37559
38039
|
}
|
|
37560
|
-
candidateDirs.add(input.directory);
|
|
37561
38040
|
let bridge = null;
|
|
37562
38041
|
for (const dir of candidateDirs) {
|
|
37563
38042
|
bridge = pool.getActiveBridgeForRoot(dir);
|
|
@@ -37680,6 +38159,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
37680
38159
|
}
|
|
37681
38160
|
log2(`Disabled ${disabled.size} tool(s): ${[...disabled].join(", ")}`);
|
|
37682
38161
|
}
|
|
38162
|
+
instrumentToolMap(allTools);
|
|
37683
38163
|
const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
|
|
37684
38164
|
enabled: true,
|
|
37685
38165
|
autoUpdate: aftConfig.auto_update ?? true,
|
|
@@ -37699,6 +38179,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
37699
38179
|
"bash_status"
|
|
37700
38180
|
];
|
|
37701
38181
|
const registeredTools = new Set(Object.keys(allTools));
|
|
38182
|
+
pool.setConfigureOverride("aft_search_registered", registeredTools.has("aft_search"));
|
|
37702
38183
|
const hintsAbsentTools = new Set;
|
|
37703
38184
|
for (const name of HINTS_TOOL_NAMES) {
|
|
37704
38185
|
if (!registeredTools.has(name))
|
|
@@ -37785,22 +38266,14 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
37785
38266
|
"tool.execute.after": async (toolInput, output) => {
|
|
37786
38267
|
if (!output)
|
|
37787
38268
|
return;
|
|
37788
|
-
const stored = consumeToolMetadata(toolInput.sessionID, toolInput.callID);
|
|
37789
|
-
if (stored) {
|
|
37790
|
-
if (stored.title)
|
|
37791
|
-
output.title = stored.title;
|
|
37792
|
-
if (stored.metadata)
|
|
37793
|
-
output.metadata = { ...output.metadata, ...stored.metadata };
|
|
37794
|
-
}
|
|
37795
38269
|
if (toolInput.tool === "bash" && output.output) {
|
|
37796
38270
|
output.output = maybeAppendConflictsHint(output.output);
|
|
37797
|
-
output.output = maybeAppendGrepHint(output.output);
|
|
37798
38271
|
}
|
|
37799
38272
|
const sessionDir = getSessionDirectoryCached(toolInput.sessionID) ?? input.directory;
|
|
37800
38273
|
await appendInTurnBgCompletions({ ctx, directory: sessionDir, sessionID: toolInput.sessionID }, output);
|
|
37801
38274
|
if (output.output !== undefined) {
|
|
37802
38275
|
const activeBridge = ctx.pool.getActiveBridgeForRoot(sessionDir);
|
|
37803
|
-
const suffix = statusBarSuffixForSession(toolInput.sessionID, activeBridge?.getStatusBar());
|
|
38276
|
+
const suffix = statusBarSuffixForSession(toolInput.sessionID, activeBridge?.getStatusBar(), toolInput.tool === "aft_inspect");
|
|
37804
38277
|
if (suffix)
|
|
37805
38278
|
output.output += suffix;
|
|
37806
38279
|
}
|