@cortexkit/aft-opencode 0.36.0 → 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 +468 -259
- 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/reading.d.ts.map +1 -1
- package/dist/tui.js +78 -92
- package/package.json +8 -8
- 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
|
@@ -12781,6 +12781,11 @@ function formatEditSummary(data) {
|
|
|
12781
12781
|
const n = data.files_modified;
|
|
12782
12782
|
return `Applied edits to ${n} file${n === 1 ? "" : "s"}.`;
|
|
12783
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
|
+
}
|
|
12784
12789
|
const additions = data.diff?.additions ?? 0;
|
|
12785
12790
|
const deletions = data.diff?.deletions ?? 0;
|
|
12786
12791
|
const counts = `+${additions}/-${deletions}`;
|
|
@@ -13994,7 +13999,23 @@ function isProcessAlive(pid) {
|
|
|
13994
13999
|
}
|
|
13995
14000
|
}
|
|
13996
14001
|
// ../aft-bridge/dist/pipe-strip.js
|
|
13997
|
-
var NOISE_FILTERS = new Set([
|
|
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
|
+
]);
|
|
13998
14019
|
var GREP_GUARD_FLAGS = new Set([
|
|
13999
14020
|
"c",
|
|
14000
14021
|
"count",
|
|
@@ -14008,6 +14029,8 @@ var GREP_GUARD_FLAGS = new Set([
|
|
|
14008
14029
|
function maybeStripCompressorPipe(command, compressionEnabled) {
|
|
14009
14030
|
if (!compressionEnabled)
|
|
14010
14031
|
return { command, stripped: false };
|
|
14032
|
+
if (containsUnsplittableConstruct(command))
|
|
14033
|
+
return { command, stripped: false };
|
|
14011
14034
|
const chain = splitTopLevelAndChain(command);
|
|
14012
14035
|
if (chain === null)
|
|
14013
14036
|
return { command, stripped: false };
|
|
@@ -14021,7 +14044,7 @@ function maybeStripCompressorPipe(command, compressionEnabled) {
|
|
|
14021
14044
|
return { command, stripped: false };
|
|
14022
14045
|
const filterStages = stages.slice(1).map((stage) => stage.trim());
|
|
14023
14046
|
for (const stage of filterStages) {
|
|
14024
|
-
if (!
|
|
14047
|
+
if (!filterStageIsSafeToDrop(stage))
|
|
14025
14048
|
return { command, stripped: false };
|
|
14026
14049
|
}
|
|
14027
14050
|
const filters = filterStages.join(" | ");
|
|
@@ -14067,6 +14090,14 @@ function splitTopLevelAndChain(command) {
|
|
|
14067
14090
|
return null;
|
|
14068
14091
|
if (char === ";")
|
|
14069
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
|
+
}
|
|
14070
14101
|
}
|
|
14071
14102
|
segments.push(command.slice(start));
|
|
14072
14103
|
return segments;
|
|
@@ -14109,45 +14140,262 @@ function isCompressorHandledRunner(stage) {
|
|
|
14109
14140
|
const tokens = tokenizeStage(stage);
|
|
14110
14141
|
if (tokens.length === 0)
|
|
14111
14142
|
return false;
|
|
14112
|
-
const [first, second, third] = tokens;
|
|
14113
|
-
if (!first)
|
|
14114
|
-
return false;
|
|
14115
14143
|
if (tokens.some((token) => token === "&&" || token === "||" || token.includes(";"))) {
|
|
14116
14144
|
return false;
|
|
14117
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;
|
|
14118
14152
|
if (first === "bun")
|
|
14119
14153
|
return second === "test" || second === "run" && startsWithTest(third);
|
|
14120
|
-
if (first === "
|
|
14121
|
-
return ["test", "build", "check", "clippy"].includes(second ?? "");
|
|
14122
|
-
if (first === "go")
|
|
14123
|
-
return second === "test" || second === "build";
|
|
14124
|
-
if (["npm", "pnpm"].includes(first)) {
|
|
14154
|
+
if (first === "npm" || first === "pnpm") {
|
|
14125
14155
|
return second === "test" || second === "run" && startsWithTest(third);
|
|
14126
14156
|
}
|
|
14127
|
-
if (first === "yarn")
|
|
14128
|
-
return second === "
|
|
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
|
+
}
|
|
14129
14165
|
if (first === "playwright")
|
|
14130
14166
|
return second === "test";
|
|
14131
|
-
if (first === "
|
|
14132
|
-
return ["
|
|
14133
|
-
|
|
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;
|
|
14134
14224
|
}
|
|
14135
14225
|
function startsWithTest(token) {
|
|
14136
14226
|
return token?.startsWith("test") === true;
|
|
14137
14227
|
}
|
|
14138
|
-
|
|
14139
|
-
|
|
14140
|
-
|
|
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];
|
|
14141
14303
|
if (!head)
|
|
14142
14304
|
return false;
|
|
14143
14305
|
if (head === "wc")
|
|
14144
14306
|
return false;
|
|
14145
14307
|
if (!NOISE_FILTERS.has(head))
|
|
14146
14308
|
return false;
|
|
14147
|
-
if ((
|
|
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(".")))
|
|
14148
14347
|
return false;
|
|
14149
14348
|
return true;
|
|
14150
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
|
+
}
|
|
14151
14399
|
function hasIntentChangingGrepFlag(args) {
|
|
14152
14400
|
for (const arg of args) {
|
|
14153
14401
|
if (arg === "--")
|
|
@@ -32515,38 +32763,6 @@ function discoverRelevantGithubServers(projectRoot) {
|
|
|
32515
32763
|
return relevant;
|
|
32516
32764
|
}
|
|
32517
32765
|
|
|
32518
|
-
// src/metadata-store.ts
|
|
32519
|
-
var pendingStore = new Map;
|
|
32520
|
-
var STALE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
32521
|
-
function makeKey(sessionID, callID) {
|
|
32522
|
-
return `${sessionID}:${callID}`;
|
|
32523
|
-
}
|
|
32524
|
-
function cleanupStaleEntries() {
|
|
32525
|
-
const now = Date.now();
|
|
32526
|
-
for (const [key, entry] of pendingStore) {
|
|
32527
|
-
if (now - entry.storedAt > STALE_TIMEOUT_MS) {
|
|
32528
|
-
pendingStore.delete(key);
|
|
32529
|
-
}
|
|
32530
|
-
}
|
|
32531
|
-
}
|
|
32532
|
-
function storeToolMetadata(sessionID, callID, data) {
|
|
32533
|
-
cleanupStaleEntries();
|
|
32534
|
-
pendingStore.set(makeKey(sessionID, callID), {
|
|
32535
|
-
...data,
|
|
32536
|
-
storedAt: Date.now()
|
|
32537
|
-
});
|
|
32538
|
-
}
|
|
32539
|
-
function consumeToolMetadata(sessionID, callID) {
|
|
32540
|
-
const key = makeKey(sessionID, callID);
|
|
32541
|
-
const stored = pendingStore.get(key);
|
|
32542
|
-
if (stored) {
|
|
32543
|
-
pendingStore.delete(key);
|
|
32544
|
-
const { storedAt: _, ...data } = stored;
|
|
32545
|
-
return data;
|
|
32546
|
-
}
|
|
32547
|
-
return;
|
|
32548
|
-
}
|
|
32549
|
-
|
|
32550
32766
|
// src/normalize-schemas.ts
|
|
32551
32767
|
import { tool } from "@opencode-ai/plugin";
|
|
32552
32768
|
function stripRootJsonSchemaFields(jsonSchema) {
|
|
@@ -32718,10 +32934,12 @@ class AftRpcServer {
|
|
|
32718
32934
|
const dir = dirname9(this.portFilePath);
|
|
32719
32935
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
32720
32936
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
32721
|
-
writeFileSync10(tmpPath, JSON.stringify({
|
|
32722
|
-
|
|
32723
|
-
|
|
32724
|
-
|
|
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 });
|
|
32725
32943
|
renameSync8(tmpPath, this.portFilePath);
|
|
32726
32944
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
32727
32945
|
} catch (err) {
|
|
@@ -33193,7 +33411,7 @@ function registerShutdownCleanup(fn) {
|
|
|
33193
33411
|
|
|
33194
33412
|
// src/status-bar-inject.ts
|
|
33195
33413
|
var emitStateBySession = new Map;
|
|
33196
|
-
function statusBarSuffixForSession(sessionID, counts) {
|
|
33414
|
+
function statusBarSuffixForSession(sessionID, counts, force = false) {
|
|
33197
33415
|
if (!counts)
|
|
33198
33416
|
return "";
|
|
33199
33417
|
let state = emitStateBySession.get(sessionID);
|
|
@@ -33201,12 +33419,67 @@ function statusBarSuffixForSession(sessionID, counts) {
|
|
|
33201
33419
|
state = createStatusBarEmitState();
|
|
33202
33420
|
emitStateBySession.set(sessionID, state);
|
|
33203
33421
|
}
|
|
33422
|
+
if (force) {
|
|
33423
|
+
state.last = counts;
|
|
33424
|
+
state.callsSinceEmit = 0;
|
|
33425
|
+
return statusBarLine(counts);
|
|
33426
|
+
}
|
|
33204
33427
|
return shouldEmitStatusBar(state, counts) ? statusBarLine(counts) : "";
|
|
33205
33428
|
}
|
|
33206
33429
|
function clearStatusBarSession(sessionID) {
|
|
33207
33430
|
emitStateBySession.delete(sessionID);
|
|
33208
33431
|
}
|
|
33209
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
|
+
|
|
33210
33483
|
// src/tools/ast.ts
|
|
33211
33484
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
33212
33485
|
|
|
@@ -33360,7 +33633,13 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
33360
33633
|
configureWarningClient: ctx.client,
|
|
33361
33634
|
...options
|
|
33362
33635
|
};
|
|
33363
|
-
|
|
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
|
+
}
|
|
33364
33643
|
ingestBgCompletions(runtime.sessionID, response.bg_completions);
|
|
33365
33644
|
return response;
|
|
33366
33645
|
}
|
|
@@ -34291,22 +34570,17 @@ function createBashTool(ctx) {
|
|
|
34291
34570
|
throw new Error(data.message || "bash failed");
|
|
34292
34571
|
}
|
|
34293
34572
|
if (data.status === "running" && typeof data.task_id === "string") {
|
|
34294
|
-
const callID2 = getCallID(context);
|
|
34295
34573
|
const taskId = data.task_id;
|
|
34574
|
+
const uiTitle = description ?? shortenCommand(command);
|
|
34296
34575
|
if (effectiveBackground) {
|
|
34297
34576
|
trackBgTask(context.sessionID, taskId);
|
|
34298
34577
|
let startedLine = formatBackgroundLaunch(taskId, requestedPty);
|
|
34299
34578
|
if (isSubagent && allowSubagentBg)
|
|
34300
34579
|
startedLine += subagentGuidance(taskId);
|
|
34580
|
+
startedLine = appendPipeStripNote(startedLine, pipeStrip.note);
|
|
34301
34581
|
const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
|
|
34302
34582
|
metadata?.(metadataPayload2);
|
|
34303
|
-
|
|
34304
|
-
storeToolMetadata(context.sessionID, callID2, {
|
|
34305
|
-
title: description ?? shortenCommand(command),
|
|
34306
|
-
metadata: metadataPayload2
|
|
34307
|
-
});
|
|
34308
|
-
}
|
|
34309
|
-
return startedLine;
|
|
34583
|
+
return { output: startedLine, title: uiTitle, metadata: metadataPayload2 };
|
|
34310
34584
|
}
|
|
34311
34585
|
const foregroundWaitMs = resolveForegroundWaitMs(bashCfg.foreground_wait_window_ms);
|
|
34312
34586
|
const waitTimeoutMs = subagentForcedForeground ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
|
|
@@ -34320,13 +34594,7 @@ function createBashTool(ctx) {
|
|
|
34320
34594
|
const rendered2 = appendPipeStripNote(formatForegroundResult(status), pipeStrip.note);
|
|
34321
34595
|
const metadataPayload2 = foregroundMetadata(description, status, rendered2);
|
|
34322
34596
|
metadata?.(metadataPayload2);
|
|
34323
|
-
|
|
34324
|
-
storeToolMetadata(context.sessionID, callID2, {
|
|
34325
|
-
title: description ?? shortenCommand(command),
|
|
34326
|
-
metadata: metadataPayload2
|
|
34327
|
-
});
|
|
34328
|
-
}
|
|
34329
|
-
return rendered2;
|
|
34597
|
+
return { output: rendered2, title: uiTitle, metadata: metadataPayload2 };
|
|
34330
34598
|
}
|
|
34331
34599
|
if (Date.now() - startedAt >= waitTimeoutMs) {
|
|
34332
34600
|
if (subagentForcedForeground) {
|
|
@@ -34343,15 +34611,10 @@ function createBashTool(ctx) {
|
|
|
34343
34611
|
let message = formatPromotionMessage(taskId, effectiveTimeout, foregroundWaitMs);
|
|
34344
34612
|
if (isSubagent && allowSubagentBg)
|
|
34345
34613
|
message += subagentGuidance(taskId);
|
|
34614
|
+
message = appendPipeStripNote(message, pipeStrip.note);
|
|
34346
34615
|
const metadataPayload2 = { description, output: message, status: "running", taskId };
|
|
34347
34616
|
metadata?.(metadataPayload2);
|
|
34348
|
-
|
|
34349
|
-
storeToolMetadata(context.sessionID, callID2, {
|
|
34350
|
-
title: description ?? shortenCommand(command),
|
|
34351
|
-
metadata: metadataPayload2
|
|
34352
|
-
});
|
|
34353
|
-
}
|
|
34354
|
-
return message;
|
|
34617
|
+
return { output: message, title: uiTitle, metadata: metadataPayload2 };
|
|
34355
34618
|
}
|
|
34356
34619
|
await sleep(FOREGROUND_POLL_INTERVAL_MS);
|
|
34357
34620
|
}
|
|
@@ -34362,7 +34625,6 @@ function createBashTool(ctx) {
|
|
|
34362
34625
|
const truncated = data.truncated;
|
|
34363
34626
|
const outputPath = data.output_path;
|
|
34364
34627
|
const timedOut = data.timed_out === true;
|
|
34365
|
-
const callID = getCallID(context);
|
|
34366
34628
|
const metadataPayload = {
|
|
34367
34629
|
description,
|
|
34368
34630
|
output: metadataOutput,
|
|
@@ -34371,12 +34633,6 @@ function createBashTool(ctx) {
|
|
|
34371
34633
|
...outputPath ? { outputPath } : {}
|
|
34372
34634
|
};
|
|
34373
34635
|
metadata?.(metadataPayload);
|
|
34374
|
-
if (callID) {
|
|
34375
|
-
storeToolMetadata(context.sessionID, callID, {
|
|
34376
|
-
title: description ?? shortenCommand(command),
|
|
34377
|
-
metadata: metadataPayload
|
|
34378
|
-
});
|
|
34379
|
-
}
|
|
34380
34636
|
let rendered = output;
|
|
34381
34637
|
if (truncated && outputPath) {
|
|
34382
34638
|
rendered += `
|
|
@@ -34391,7 +34647,11 @@ function createBashTool(ctx) {
|
|
|
34391
34647
|
[exit code: ${exit}]`;
|
|
34392
34648
|
}
|
|
34393
34649
|
rendered = appendPipeStripNote(rendered, pipeStrip.note);
|
|
34394
|
-
return
|
|
34650
|
+
return {
|
|
34651
|
+
output: rendered,
|
|
34652
|
+
title: description ?? shortenCommand(command),
|
|
34653
|
+
metadata: metadataPayload
|
|
34654
|
+
};
|
|
34395
34655
|
}
|
|
34396
34656
|
};
|
|
34397
34657
|
}
|
|
@@ -34874,10 +35134,6 @@ function createBashWriteTool(ctx) {
|
|
|
34874
35134
|
}
|
|
34875
35135
|
|
|
34876
35136
|
// src/tools/hoisted.ts
|
|
34877
|
-
function getCallID2(ctx) {
|
|
34878
|
-
const c = ctx;
|
|
34879
|
-
return c.callID ?? c.callId ?? c.call_id;
|
|
34880
|
-
}
|
|
34881
35137
|
function relativeToWorktree(fp, worktree) {
|
|
34882
35138
|
return path4.relative(worktree, fp);
|
|
34883
35139
|
}
|
|
@@ -35163,19 +35419,16 @@ function createReadTool(ctx) {
|
|
|
35163
35419
|
} catch {}
|
|
35164
35420
|
const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
|
|
35165
35421
|
const msg = `${label} read successfully`;
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
});
|
|
35177
|
-
}
|
|
35178
|
-
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
|
+
};
|
|
35179
35432
|
}
|
|
35180
35433
|
let startLine = args.startLine;
|
|
35181
35434
|
let endLine = args.endLine;
|
|
@@ -35196,32 +35449,24 @@ function createReadTool(ctx) {
|
|
|
35196
35449
|
if (data.success === false) {
|
|
35197
35450
|
throw new Error(data.message || "read failed");
|
|
35198
35451
|
}
|
|
35199
|
-
const
|
|
35452
|
+
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
35200
35453
|
if (data.entries) {
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35454
|
+
return {
|
|
35455
|
+
output: data.entries.join(`
|
|
35456
|
+
`),
|
|
35457
|
+
title: dp,
|
|
35458
|
+
metadata: { title: dp }
|
|
35459
|
+
};
|
|
35207
35460
|
}
|
|
35208
35461
|
if (data.binary) {
|
|
35209
|
-
|
|
35210
|
-
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
35211
|
-
storeToolMetadata(context.sessionID, readCallID, { title: dp, metadata: { title: dp } });
|
|
35212
|
-
}
|
|
35213
|
-
return data.message;
|
|
35214
|
-
}
|
|
35215
|
-
if (readCallID) {
|
|
35216
|
-
const dp = relativeToWorktree(filePath, projectRoot) || file2;
|
|
35217
|
-
storeToolMetadata(context.sessionID, readCallID, { title: dp, metadata: { title: dp } });
|
|
35462
|
+
return { output: data.message, title: dp, metadata: { title: dp } };
|
|
35218
35463
|
}
|
|
35219
35464
|
let output = data.content;
|
|
35220
35465
|
const agentSpecifiedRange = args.startLine !== undefined || args.endLine !== undefined || args.offset !== undefined || args.limit !== undefined;
|
|
35221
35466
|
const footer = formatReadFooter(agentSpecifiedRange, data);
|
|
35222
35467
|
if (footer)
|
|
35223
35468
|
output += footer;
|
|
35224
|
-
return output;
|
|
35469
|
+
return { output, title: dp, metadata: { title: dp } };
|
|
35225
35470
|
}
|
|
35226
35471
|
};
|
|
35227
35472
|
}
|
|
@@ -35311,27 +35556,24 @@ Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagn
|
|
|
35311
35556
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
35312
35557
|
}
|
|
35313
35558
|
const diff = data.diff;
|
|
35314
|
-
const
|
|
35315
|
-
|
|
35316
|
-
|
|
35317
|
-
|
|
35318
|
-
|
|
35319
|
-
|
|
35320
|
-
|
|
35321
|
-
|
|
35322
|
-
|
|
35323
|
-
|
|
35324
|
-
|
|
35325
|
-
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
|
|
35329
|
-
|
|
35330
|
-
|
|
35331
|
-
|
|
35332
|
-
});
|
|
35333
|
-
}
|
|
35334
|
-
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
|
+
};
|
|
35335
35577
|
}
|
|
35336
35578
|
};
|
|
35337
35579
|
}
|
|
@@ -35340,45 +35582,39 @@ function getEditDescription(writeToolName) {
|
|
|
35340
35582
|
|
|
35341
35583
|
**Modes** (determined by which parameters you provide):
|
|
35342
35584
|
|
|
35343
|
-
Mode priority:
|
|
35344
|
-
|
|
35345
|
-
1. **Multi-file transaction** — pass \`operations\` array
|
|
35346
|
-
Edits across multiple files with checkpoint-based rollback on failure.
|
|
35347
|
-
Each operation: \`{ "file": "path", "command": "edit_match" | "write", ... }\`.
|
|
35348
|
-
For \`edit_match\`: include \`match\`, \`replacement\`. For \`write\`: include \`content\`.
|
|
35349
|
-
Example: \`{ "operations": [{ "file": "a.ts", "command": "edit_match", "match": "old", "replacement": "new" }, { "file": "b.ts", "command": "write", "content": "..." }] }\`
|
|
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.
|
|
35350
35586
|
|
|
35351
|
-
|
|
35587
|
+
1. **Append** — pass \`filePath\` + \`appendContent\`
|
|
35352
35588
|
Appends text to the end of a file, creating the file if it does not exist.
|
|
35353
35589
|
Example: \`{ "filePath": "notes.txt", "appendContent": "new line\\n" }\`
|
|
35354
35590
|
|
|
35355
|
-
|
|
35591
|
+
2. **Batch edits** — pass \`filePath\` + \`edits\` array
|
|
35356
35592
|
Multiple edits in one file atomically. Each edit is either:
|
|
35357
35593
|
- \`{ "oldString": "old", "newString": "new" }\` — find/replace
|
|
35358
35594
|
- \`{ "startLine": 5, "endLine": 7, "content": "new lines" }\` — replace line range (1-based, both inclusive)
|
|
35359
35595
|
Set content to empty string to delete lines.
|
|
35360
35596
|
|
|
35361
|
-
|
|
35597
|
+
3. **Symbol replace** — pass \`filePath\` + \`symbol\` + \`content\`
|
|
35362
35598
|
Replaces an entire named symbol (function, class, type) with new content.
|
|
35363
35599
|
Includes decorators, attributes, and doc comments in the replacement range.
|
|
35364
35600
|
**Important:** You must NOT provide \`oldString\` when using symbol mode — if present, the tool silently falls back to find/replace mode.
|
|
35365
35601
|
Example: \`{ "filePath": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
|
|
35366
35602
|
|
|
35367
|
-
|
|
35603
|
+
4. **Find and replace** — pass \`filePath\` + \`oldString\` + \`newString\`
|
|
35368
35604
|
Finds the exact text in \`oldString\` and replaces it with \`newString\`.
|
|
35369
35605
|
Supports fuzzy matching (handles whitespace differences automatically).
|
|
35370
35606
|
If multiple matches exist, specify which one with \`occurrence\` or use \`replaceAll: true\`.
|
|
35371
35607
|
Example: \`{ "filePath": "src/app.ts", "oldString": "const x = 1", "newString": "const x = 2" }\`
|
|
35372
35608
|
|
|
35373
|
-
|
|
35609
|
+
5. **Replace all occurrences** — add \`replaceAll: true\`
|
|
35374
35610
|
Replaces every occurrence of \`oldString\` in the file.
|
|
35375
35611
|
Example: \`{ "filePath": "src/app.ts", "oldString": "oldName", "newString": "newName", "replaceAll": true }\`
|
|
35376
35612
|
|
|
35377
|
-
|
|
35613
|
+
6. **Select specific occurrence** — add \`occurrence: N\` (0-indexed)
|
|
35378
35614
|
When multiple matches exist, select the Nth one (0 = first, 1 = second, etc.).
|
|
35379
35615
|
Example: \`{ "filePath": "src/app.ts", "oldString": "TODO", "newString": "DONE", "occurrence": 0 }\`
|
|
35380
35616
|
|
|
35381
|
-
Note: Modes
|
|
35617
|
+
Note: Modes 5 and 6 are options on mode 4 (find/replace) — they require \`oldString\`.
|
|
35382
35618
|
|
|
35383
35619
|
**Behavior:**
|
|
35384
35620
|
- Backs up files before editing (recoverable via aft_safety undo)
|
|
@@ -35392,16 +35628,15 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
35392
35628
|
return {
|
|
35393
35629
|
description: getEditDescription(writeToolName),
|
|
35394
35630
|
args: {
|
|
35395
|
-
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)"),
|
|
35396
35632
|
oldString: z8.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
|
|
35397
35633
|
newString: z8.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
|
|
35398
35634
|
replaceAll: z8.boolean().optional().describe("Replace all occurrences"),
|
|
35399
35635
|
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER).describe("0-indexed occurrence to replace when multiple matches exist"),
|
|
35400
35636
|
symbol: z8.string().optional().describe("Named symbol to replace (function, class, type)"),
|
|
35401
|
-
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."),
|
|
35402
35638
|
appendContent: z8.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
35403
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 }"),
|
|
35404
|
-
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)"),
|
|
35405
35640
|
diagnostics: z8.boolean().optional().describe(DIAGNOSTICS_PARAM_DESCRIPTION)
|
|
35406
35641
|
},
|
|
35407
35642
|
execute: async (args, context) => {
|
|
@@ -35409,38 +35644,6 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
35409
35644
|
if (argsRecord.startLine !== undefined || argsRecord.endLine !== undefined) {
|
|
35410
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.");
|
|
35411
35646
|
}
|
|
35412
|
-
if (Array.isArray(args.operations)) {
|
|
35413
|
-
const ops = args.operations;
|
|
35414
|
-
const files = ops.map((op) => op.file).filter(Boolean);
|
|
35415
|
-
const projectRoot2 = await resolveProjectRoot(ctx, context);
|
|
35416
|
-
{
|
|
35417
|
-
const asked = new Set;
|
|
35418
|
-
for (const file3 of files) {
|
|
35419
|
-
const absPath = resolvePathFromProjectRoot(projectRoot2, file3);
|
|
35420
|
-
if (asked.has(absPath))
|
|
35421
|
-
continue;
|
|
35422
|
-
asked.add(absPath);
|
|
35423
|
-
const denial = await assertExternalDirectoryPermission(context, absPath);
|
|
35424
|
-
if (denial)
|
|
35425
|
-
return permissionDeniedResponse(denial);
|
|
35426
|
-
}
|
|
35427
|
-
}
|
|
35428
|
-
await runAsk(context.ask({
|
|
35429
|
-
permission: "edit",
|
|
35430
|
-
patterns: files.map((f) => path4.relative(projectRoot2, resolvePathFromProjectRoot(projectRoot2, f))),
|
|
35431
|
-
always: ["*"],
|
|
35432
|
-
metadata: {}
|
|
35433
|
-
}));
|
|
35434
|
-
const resolvedOps = ops.map((op) => ({
|
|
35435
|
-
...op,
|
|
35436
|
-
file: resolvePathFromProjectRoot(projectRoot2, op.file)
|
|
35437
|
-
}));
|
|
35438
|
-
const response = await callBridge(ctx, context, "transaction", { operations: resolvedOps });
|
|
35439
|
-
if (response.success === false) {
|
|
35440
|
-
throw new Error(response.message ?? "transaction failed");
|
|
35441
|
-
}
|
|
35442
|
-
return formatEditSummary(response);
|
|
35443
|
-
}
|
|
35444
35647
|
const file2 = args.filePath;
|
|
35445
35648
|
if (!file2)
|
|
35446
35649
|
throw new Error("'filePath' parameter is required");
|
|
@@ -35497,34 +35700,33 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
35497
35700
|
if (args.occurrence !== undefined)
|
|
35498
35701
|
params.occurrence = args.occurrence;
|
|
35499
35702
|
} else {
|
|
35500
|
-
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.";
|
|
35501
35704
|
throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
|
|
35502
35705
|
}
|
|
35503
35706
|
params.diagnostics = args.diagnostics ?? diagnosticsOnEditDefault(ctx);
|
|
35504
35707
|
params.include_diff_content = true;
|
|
35505
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;
|
|
35506
35714
|
if (data.success && data.diff) {
|
|
35507
35715
|
const diff = data.diff;
|
|
35508
|
-
|
|
35509
|
-
|
|
35510
|
-
|
|
35511
|
-
|
|
35512
|
-
|
|
35513
|
-
|
|
35514
|
-
|
|
35515
|
-
|
|
35516
|
-
|
|
35517
|
-
|
|
35518
|
-
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
deletions: diff.deletions ?? 0
|
|
35523
|
-
},
|
|
35524
|
-
diagnostics: {}
|
|
35525
|
-
}
|
|
35526
|
-
});
|
|
35527
|
-
}
|
|
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
|
+
};
|
|
35528
35730
|
}
|
|
35529
35731
|
let result = formatEditSummary(data);
|
|
35530
35732
|
const globSkipNote = formatGlobSkipReasonsNote(data.format_skip_reasons);
|
|
@@ -35561,6 +35763,9 @@ Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagn
|
|
|
35561
35763
|
|
|
35562
35764
|
Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
|
|
35563
35765
|
}
|
|
35766
|
+
if (uiMeta) {
|
|
35767
|
+
return { output: result, title: uiTitle ?? "", metadata: uiMeta };
|
|
35768
|
+
}
|
|
35564
35769
|
return result;
|
|
35565
35770
|
}
|
|
35566
35771
|
};
|
|
@@ -35710,6 +35915,9 @@ function createApplyPatchTool(ctx) {
|
|
|
35710
35915
|
include_diff_content: true,
|
|
35711
35916
|
multi_file_write_paths: multiFileWritePaths
|
|
35712
35917
|
});
|
|
35918
|
+
if (writeResult.success === false) {
|
|
35919
|
+
throw new Error(writeResult.message ?? "write failed");
|
|
35920
|
+
}
|
|
35713
35921
|
const wrDiff = writeResult.diff;
|
|
35714
35922
|
perFileDiffs.push({
|
|
35715
35923
|
filePath,
|
|
@@ -35735,7 +35943,12 @@ function createApplyPatchTool(ctx) {
|
|
|
35735
35943
|
case "delete": {
|
|
35736
35944
|
try {
|
|
35737
35945
|
const before = await fs6.promises.readFile(filePath, "utf-8").catch(() => "");
|
|
35738
|
-
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
|
+
}
|
|
35739
35952
|
perFileDiffs.push({
|
|
35740
35953
|
filePath,
|
|
35741
35954
|
before,
|
|
@@ -35763,6 +35976,9 @@ function createApplyPatchTool(ctx) {
|
|
|
35763
35976
|
include_diff_content: true,
|
|
35764
35977
|
multi_file_write_paths: multiFileWritePaths
|
|
35765
35978
|
});
|
|
35979
|
+
if (writeResult.success === false) {
|
|
35980
|
+
throw new Error(writeResult.message ?? "write failed");
|
|
35981
|
+
}
|
|
35766
35982
|
const diags = writeResult.lsp_diagnostics;
|
|
35767
35983
|
if (diags && diags.length > 0) {
|
|
35768
35984
|
const errors3 = diags.filter((d) => d.severity === "error");
|
|
@@ -35842,10 +36058,11 @@ ${diagLines}`);
|
|
|
35842
36058
|
`));
|
|
35843
36059
|
}
|
|
35844
36060
|
}
|
|
35845
|
-
|
|
35846
|
-
if (callID) {
|
|
36061
|
+
{
|
|
35847
36062
|
const diffByPath = new Map(perFileDiffs.map((d) => [d.filePath, d]));
|
|
35848
|
-
const
|
|
36063
|
+
const failedPaths = new Set(failures);
|
|
36064
|
+
const appliedHunks = hunks.filter((h) => !failedPaths.has(h.path));
|
|
36065
|
+
const files = appliedHunks.map((h) => {
|
|
35849
36066
|
const filePath = resolvePathFromProjectRoot(projectRoot, h.path);
|
|
35850
36067
|
const rawMovePath = h.type === "update" ? h.move_path : undefined;
|
|
35851
36068
|
const movePath = rawMovePath ? resolvePathFromProjectRoot(projectRoot, rawMovePath) : undefined;
|
|
@@ -35871,20 +36088,21 @@ ${diagLines}`);
|
|
|
35871
36088
|
return `${prefix} ${f.relativePath}`;
|
|
35872
36089
|
}).join(`
|
|
35873
36090
|
`);
|
|
35874
|
-
const title =
|
|
36091
|
+
const title = failures.length > 0 ? `Partially applied (${files.length} of ${hunks.length}). Updated:
|
|
36092
|
+
${fileList}` : `Success. Updated the following files:
|
|
35875
36093
|
${fileList}`;
|
|
35876
36094
|
const diffText = files.map((f) => f.patch).filter(Boolean).join(`
|
|
35877
36095
|
`);
|
|
35878
|
-
|
|
36096
|
+
return {
|
|
36097
|
+
output: results.join(`
|
|
36098
|
+
`),
|
|
35879
36099
|
title,
|
|
35880
36100
|
metadata: {
|
|
35881
36101
|
diff: diffText,
|
|
35882
36102
|
files
|
|
35883
36103
|
}
|
|
35884
|
-
}
|
|
36104
|
+
};
|
|
35885
36105
|
}
|
|
35886
|
-
return results.join(`
|
|
35887
|
-
`);
|
|
35888
36106
|
}
|
|
35889
36107
|
};
|
|
35890
36108
|
}
|
|
@@ -36408,10 +36626,6 @@ function navigationTools(ctx) {
|
|
|
36408
36626
|
// src/tools/reading.ts
|
|
36409
36627
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
36410
36628
|
var z12 = tool12.schema;
|
|
36411
|
-
function getCallID3(ctx) {
|
|
36412
|
-
const c = ctx;
|
|
36413
|
-
return c.callID ?? c.callId ?? c.call_id;
|
|
36414
|
-
}
|
|
36415
36629
|
function buildZoomTitle(args) {
|
|
36416
36630
|
if (!isEmptyParam(args.targets)) {
|
|
36417
36631
|
if (Array.isArray(args.targets)) {
|
|
@@ -36574,25 +36788,26 @@ function readingTools(ctx) {
|
|
|
36574
36788
|
const hasTargets = hasTargetsProvided(args.targets);
|
|
36575
36789
|
const hasSymbols = !isEmptyParam(args.symbols);
|
|
36576
36790
|
const wantCallgraph = args.callgraph === true;
|
|
36577
|
-
const
|
|
36578
|
-
|
|
36579
|
-
|
|
36580
|
-
|
|
36581
|
-
|
|
36582
|
-
|
|
36583
|
-
|
|
36584
|
-
|
|
36585
|
-
|
|
36586
|
-
|
|
36587
|
-
|
|
36588
|
-
|
|
36589
|
-
|
|
36590
|
-
|
|
36591
|
-
|
|
36592
|
-
|
|
36593
|
-
|
|
36594
|
-
|
|
36595
|
-
|
|
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
|
+
});
|
|
36596
36811
|
if (hasTargets) {
|
|
36597
36812
|
if (hasFilePath || hasUrl || hasSymbols) {
|
|
36598
36813
|
throw new Error("'targets' is mutually exclusive with 'filePath', 'url', and 'symbols'");
|
|
@@ -36632,7 +36847,7 @@ function readingTools(ctx) {
|
|
|
36632
36847
|
name: t.symbol,
|
|
36633
36848
|
response: responses[i] ?? { success: false, message: "missing zoom response" }
|
|
36634
36849
|
}));
|
|
36635
|
-
return formatZoomMultiTargetResult(entries).text;
|
|
36850
|
+
return withMeta(formatZoomMultiTargetResult(entries).text);
|
|
36636
36851
|
}
|
|
36637
36852
|
if (!hasFilePath && !hasUrl) {
|
|
36638
36853
|
throw new Error("Provide exactly one of 'filePath', 'url', or 'targets'");
|
|
@@ -36665,9 +36880,9 @@ function readingTools(ctx) {
|
|
|
36665
36880
|
if (response.success === false) {
|
|
36666
36881
|
throw new Error(response.message || "zoom failed");
|
|
36667
36882
|
}
|
|
36668
|
-
return formatZoomText(targetLabel, response);
|
|
36883
|
+
return withMeta(formatZoomText(targetLabel, response));
|
|
36669
36884
|
}
|
|
36670
|
-
return formatZoomBatchResult(targetLabel, symbolsArray, results).text;
|
|
36885
|
+
return withMeta(formatZoomBatchResult(targetLabel, symbolsArray, results).text);
|
|
36671
36886
|
}
|
|
36672
36887
|
const params = { file: file2 };
|
|
36673
36888
|
if (args.contextLines !== undefined)
|
|
@@ -36678,7 +36893,7 @@ function readingTools(ctx) {
|
|
|
36678
36893
|
if (data.success === false) {
|
|
36679
36894
|
throw new Error(data.message || "zoom failed");
|
|
36680
36895
|
}
|
|
36681
|
-
return formatZoomText(targetLabel, data);
|
|
36896
|
+
return withMeta(formatZoomText(targetLabel, data));
|
|
36682
36897
|
}
|
|
36683
36898
|
}
|
|
36684
36899
|
};
|
|
@@ -37092,7 +37307,7 @@ function formatGrepOutput(response) {
|
|
|
37092
37307
|
const totalMatches = response.total_matches ?? matches.length;
|
|
37093
37308
|
const filesWithMatches = response.files_with_matches ?? new Set(matches.map((m) => m.file)).size;
|
|
37094
37309
|
if (matches.length === 0) {
|
|
37095
|
-
return `Found ${totalMatches} match
|
|
37310
|
+
return `Found ${totalMatches} match across ${filesWithMatches} file`;
|
|
37096
37311
|
}
|
|
37097
37312
|
const body = matches.map((match) => {
|
|
37098
37313
|
const file2 = match.file ?? "unknown";
|
|
@@ -37103,7 +37318,7 @@ function formatGrepOutput(response) {
|
|
|
37103
37318
|
`);
|
|
37104
37319
|
return `${body}
|
|
37105
37320
|
|
|
37106
|
-
Found ${totalMatches} match
|
|
37321
|
+
Found ${totalMatches} match across ${filesWithMatches} file`;
|
|
37107
37322
|
}
|
|
37108
37323
|
function normalizeGlob(pattern) {
|
|
37109
37324
|
if (!pattern.includes("/") && !pattern.startsWith("**/")) {
|
|
@@ -37818,10 +38033,10 @@ ${lines}
|
|
|
37818
38033
|
const sessionID = params.sessionID || "rpc";
|
|
37819
38034
|
const cachedDir = getSessionDirectoryCached(sessionID);
|
|
37820
38035
|
const candidateDirs = new Set;
|
|
38036
|
+
candidateDirs.add(input.directory);
|
|
37821
38037
|
if (typeof cachedDir === "string" && cachedDir.length > 0) {
|
|
37822
38038
|
candidateDirs.add(cachedDir);
|
|
37823
38039
|
}
|
|
37824
|
-
candidateDirs.add(input.directory);
|
|
37825
38040
|
let bridge = null;
|
|
37826
38041
|
for (const dir of candidateDirs) {
|
|
37827
38042
|
bridge = pool.getActiveBridgeForRoot(dir);
|
|
@@ -37944,6 +38159,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
37944
38159
|
}
|
|
37945
38160
|
log2(`Disabled ${disabled.size} tool(s): ${[...disabled].join(", ")}`);
|
|
37946
38161
|
}
|
|
38162
|
+
instrumentToolMap(allTools);
|
|
37947
38163
|
const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
|
|
37948
38164
|
enabled: true,
|
|
37949
38165
|
autoUpdate: aftConfig.auto_update ?? true,
|
|
@@ -38050,13 +38266,6 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38050
38266
|
"tool.execute.after": async (toolInput, output) => {
|
|
38051
38267
|
if (!output)
|
|
38052
38268
|
return;
|
|
38053
|
-
const stored = consumeToolMetadata(toolInput.sessionID, toolInput.callID);
|
|
38054
|
-
if (stored) {
|
|
38055
|
-
if (stored.title)
|
|
38056
|
-
output.title = stored.title;
|
|
38057
|
-
if (stored.metadata)
|
|
38058
|
-
output.metadata = { ...output.metadata, ...stored.metadata };
|
|
38059
|
-
}
|
|
38060
38269
|
if (toolInput.tool === "bash" && output.output) {
|
|
38061
38270
|
output.output = maybeAppendConflictsHint(output.output);
|
|
38062
38271
|
}
|
|
@@ -38064,7 +38273,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
38064
38273
|
await appendInTurnBgCompletions({ ctx, directory: sessionDir, sessionID: toolInput.sessionID }, output);
|
|
38065
38274
|
if (output.output !== undefined) {
|
|
38066
38275
|
const activeBridge = ctx.pool.getActiveBridgeForRoot(sessionDir);
|
|
38067
|
-
const suffix = statusBarSuffixForSession(toolInput.sessionID, activeBridge?.getStatusBar());
|
|
38276
|
+
const suffix = statusBarSuffixForSession(toolInput.sessionID, activeBridge?.getStatusBar(), toolInput.tool === "aft_inspect");
|
|
38068
38277
|
if (suffix)
|
|
38069
38278
|
output.output += suffix;
|
|
38070
38279
|
}
|