@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.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(["grep", "rg", "head", "tail", "cat", "less", "more"]);
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 (!isPlainNoiseFilter(stage))
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 === "cargo")
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 === "test";
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 === "npx")
14132
- return ["tsc", "eslint", "vitest", "jest"].includes(second ?? "");
14133
- return ["vitest", "jest", "pytest", "tsc", "eslint", "biome", "ruff", "mypy"].includes(first);
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
- function isPlainNoiseFilter(stage) {
14139
- const tokens = tokenizeStage(stage);
14140
- const head = tokens[0];
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 ((head === "grep" || head === "rg") && hasIntentChangingGrepFlag(tokens.slice(1)))
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({ port: this.port, token: this.token }), {
32722
- encoding: "utf-8",
32723
- mode: 384
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
- const response = await bridgeFor(ctx, runtime).send(command, merged, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
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
- if (callID2) {
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
- if (callID2) {
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
- if (callID2) {
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 rendered;
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
- const imgCallID = getCallID2(context);
35167
- if (imgCallID) {
35168
- storeToolMetadata(context.sessionID, imgCallID, {
35169
- title: path4.relative(projectRoot, filePath),
35170
- metadata: {
35171
- preview: msg,
35172
- filepath: filePath,
35173
- isImage,
35174
- isPdf: mime === "application/pdf"
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 readCallID = getCallID2(context);
35452
+ const dp = relativeToWorktree(filePath, projectRoot) || file2;
35200
35453
  if (data.entries) {
35201
- if (readCallID) {
35202
- const dp = relativeToWorktree(filePath, projectRoot) || file2;
35203
- storeToolMetadata(context.sessionID, readCallID, { title: dp, metadata: { title: dp } });
35204
- }
35205
- return data.entries.join(`
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
- if (readCallID) {
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 callID = getCallID2(context);
35315
- if (callID) {
35316
- const dp = relativeToWorktree(filePath, projectRoot);
35317
- const beforeContent = diff?.before ?? "";
35318
- const afterContent = diff?.after ?? content;
35319
- storeToolMetadata(context.sessionID, callID, {
35320
- title: dp,
35321
- metadata: {
35322
- diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
35323
- filediff: {
35324
- file: filePath,
35325
- before: beforeContent,
35326
- after: afterContent,
35327
- additions: diff?.additions ?? 0,
35328
- deletions: diff?.deletions ?? 0
35329
- },
35330
- diagnostics: {}
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: operations > appendContent > edits > symbol (without oldString) > oldString (find/replace). If none match, the call is rejected — there is no implicit "write" fallback.
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
- 2. **Append** — pass \`filePath\` + \`appendContent\`
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
- 3. **Batch edits** — pass \`filePath\` + \`edits\` array
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
- 4. **Symbol replace** — pass \`filePath\` + \`symbol\` + \`content\`
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
- 5. **Find and replace** — pass \`filePath\` + \`oldString\` + \`newString\`
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
- 6. **Replace all occurrences** — add \`replaceAll: true\`
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
- 7. **Select specific occurrence** — add \`occurrence: N\` (0-indexed)
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 6 and 7 are options on mode 5 (find/replace) — they require \`oldString\`.
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). Required for all modes except 'operations' multi-file transactions"),
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 or operations[].command='write'. For whole-file writes, use the `write` tool."),
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', 'edits' array, or 'operations' array.";
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
- const callID = getCallID2(context);
35509
- if (callID) {
35510
- const dp = relativeToWorktree(filePath, projectRoot);
35511
- const beforeContent = diff.before ?? "";
35512
- const afterContent = diff.after ?? "";
35513
- storeToolMetadata(context.sessionID, callID, {
35514
- title: dp,
35515
- metadata: {
35516
- diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
35517
- filediff: {
35518
- file: filePath,
35519
- before: beforeContent,
35520
- after: afterContent,
35521
- additions: diff.additions ?? 0,
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", { file: filePath });
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
- const callID = getCallID2(context);
35846
- if (callID) {
36061
+ {
35847
36062
  const diffByPath = new Map(perFileDiffs.map((d) => [d.filePath, d]));
35848
- const files = hunks.map((h) => {
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 = `Success. Updated the following files:
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
- storeToolMetadata(context.sessionID, callID, {
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 zoomCallID = getCallID3(context);
36578
- if (zoomCallID) {
36579
- const title = buildZoomTitle(args);
36580
- const display = { title };
36581
- if (hasFilePath)
36582
- display.filePath = args.filePath;
36583
- if (hasUrl)
36584
- display.url = args.url;
36585
- if (hasSymbols) {
36586
- display.symbols = typeof args.symbols === "string" ? args.symbols : JSON.stringify(args.symbols);
36587
- }
36588
- if (hasTargets)
36589
- display.targets = JSON.stringify(args.targets);
36590
- if (args.contextLines !== undefined)
36591
- display.contextLines = args.contextLines;
36592
- if (wantCallgraph)
36593
- display.callgraph = true;
36594
- storeToolMetadata(context.sessionID, zoomCallID, { title, metadata: display });
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(es) in ${filesWithMatches} file(s).`;
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(es) in ${filesWithMatches} file(s).`;
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
  }