@cortexkit/aft-opencode 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4583,45 +4583,45 @@ var require_esprima = __commonJS((exports, module) => {
4583
4583
  this.errors = [];
4584
4584
  this.tolerant = false;
4585
4585
  }
4586
- ErrorHandler2.prototype.recordError = function(error) {
4587
- this.errors.push(error);
4586
+ ErrorHandler2.prototype.recordError = function(error2) {
4587
+ this.errors.push(error2);
4588
4588
  };
4589
- ErrorHandler2.prototype.tolerate = function(error) {
4589
+ ErrorHandler2.prototype.tolerate = function(error2) {
4590
4590
  if (this.tolerant) {
4591
- this.recordError(error);
4591
+ this.recordError(error2);
4592
4592
  } else {
4593
- throw error;
4593
+ throw error2;
4594
4594
  }
4595
4595
  };
4596
4596
  ErrorHandler2.prototype.constructError = function(msg, column) {
4597
- var error = new Error(msg);
4597
+ var error2 = new Error(msg);
4598
4598
  try {
4599
- throw error;
4599
+ throw error2;
4600
4600
  } catch (base) {
4601
4601
  if (Object.create && Object.defineProperty) {
4602
- error = Object.create(base);
4603
- Object.defineProperty(error, "column", { value: column });
4602
+ error2 = Object.create(base);
4603
+ Object.defineProperty(error2, "column", { value: column });
4604
4604
  }
4605
4605
  }
4606
- return error;
4606
+ return error2;
4607
4607
  };
4608
4608
  ErrorHandler2.prototype.createError = function(index, line, col, description) {
4609
4609
  var msg = "Line " + line + ": " + description;
4610
- var error = this.constructError(msg, col);
4611
- error.index = index;
4612
- error.lineNumber = line;
4613
- error.description = description;
4614
- return error;
4610
+ var error2 = this.constructError(msg, col);
4611
+ error2.index = index;
4612
+ error2.lineNumber = line;
4613
+ error2.description = description;
4614
+ return error2;
4615
4615
  };
4616
4616
  ErrorHandler2.prototype.throwError = function(index, line, col, description) {
4617
4617
  throw this.createError(index, line, col, description);
4618
4618
  };
4619
4619
  ErrorHandler2.prototype.tolerateError = function(index, line, col, description) {
4620
- var error = this.createError(index, line, col, description);
4620
+ var error2 = this.createError(index, line, col, description);
4621
4621
  if (this.tolerant) {
4622
- this.recordError(error);
4622
+ this.recordError(error2);
4623
4623
  } else {
4624
- throw error;
4624
+ throw error2;
4625
4625
  }
4626
4626
  };
4627
4627
  return ErrorHandler2;
@@ -7291,19 +7291,19 @@ var require_parse = __commonJS((exports, module) => {
7291
7291
  var symbolFor = (prefix) => Symbol.for(last_prop !== UNDEFINED ? prefix + COLON + last_prop : prefix);
7292
7292
  var transform = (k, { value, context = {} }) => reviver ? reviver(k, value, context) : value;
7293
7293
  var unexpected = () => {
7294
- const error = new SyntaxError(`Unexpected token '${current.value.slice(0, 1)}', "${current_code}" is not valid JSON`);
7295
- Object.assign(error, current.loc.start);
7294
+ const error2 = new SyntaxError(`Unexpected token '${current.value.slice(0, 1)}', "${current_code}" is not valid JSON`);
7295
+ Object.assign(error2, current.loc.start);
7296
7296
  free();
7297
- throw error;
7297
+ throw error2;
7298
7298
  };
7299
7299
  var unexpected_end = () => {
7300
- const error = new SyntaxError("Unexpected end of JSON input");
7301
- Object.assign(error, last ? last.loc.end : {
7300
+ const error2 = new SyntaxError("Unexpected end of JSON input");
7301
+ Object.assign(error2, last ? last.loc.end : {
7302
7302
  line: 1,
7303
7303
  column: 0
7304
7304
  });
7305
7305
  free();
7306
- throw error;
7306
+ throw error2;
7307
7307
  };
7308
7308
  var next = () => {
7309
7309
  const new_token = tokens[++index];
@@ -7652,10 +7652,10 @@ var require_stringify = __commonJS((exports, module) => {
7652
7652
  replacer = null;
7653
7653
  indent = EMPTY;
7654
7654
  };
7655
- var join = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
7655
+ var join2 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
7656
7656
  var join_content = (inside, value, gap) => {
7657
7657
  const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
7658
- return join(comment, inside, gap);
7658
+ return join2(comment, inside, gap);
7659
7659
  };
7660
7660
  var stringify_string = (holder, key, value) => {
7661
7661
  const raw = get_raw_string_literal(holder, key);
@@ -7677,13 +7677,13 @@ var require_stringify = __commonJS((exports, module) => {
7677
7677
  if (i !== 0) {
7678
7678
  inside += COMMA;
7679
7679
  }
7680
- const before = join(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
7680
+ const before = join2(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
7681
7681
  inside += before || LF + deeper_gap;
7682
7682
  inside += stringify(i, value, deeper_gap) || STR_NULL;
7683
7683
  inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
7684
7684
  after_comma = process_comments(value, AFTER(i), deeper_gap);
7685
7685
  }
7686
- inside += join(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7686
+ inside += join2(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7687
7687
  return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
7688
7688
  };
7689
7689
  var object_stringify = (value, gap) => {
@@ -7704,13 +7704,13 @@ var require_stringify = __commonJS((exports, module) => {
7704
7704
  inside += COMMA;
7705
7705
  }
7706
7706
  first = false;
7707
- const before = join(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
7707
+ const before = join2(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
7708
7708
  inside += before || LF + deeper_gap;
7709
7709
  inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
7710
7710
  after_comma = process_comments(value, AFTER(key), deeper_gap);
7711
7711
  };
7712
7712
  keys.forEach(iteratee);
7713
- inside += join(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7713
+ inside += join2(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
7714
7714
  return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
7715
7715
  };
7716
7716
  function stringify(key, holder, gap) {
@@ -7803,14 +7803,297 @@ var require_src2 = __commonJS((exports, module) => {
7803
7803
  });
7804
7804
 
7805
7805
  // src/index.ts
7806
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
7806
+ import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
7808
  import { homedir as homedir10 } from "os";
7809
7809
  import { join as join19 } from "path";
7810
7810
 
7811
+ // src/logger.ts
7812
+ import * as fs from "fs";
7813
+ import * as os from "os";
7814
+ import * as path from "path";
7815
+ var TAG = "[aft-plugin]";
7816
+ var logFile = path.join(os.tmpdir(), "aft-plugin.log");
7817
+ var useStderr = process.env.AFT_LOG_STDERR === "1";
7818
+ var buffer = [];
7819
+ var flushTimer = null;
7820
+ var FLUSH_INTERVAL_MS = 500;
7821
+ var BUFFER_SIZE_LIMIT = 50;
7822
+ function flush() {
7823
+ if (buffer.length === 0)
7824
+ return;
7825
+ const data = buffer.join("");
7826
+ buffer = [];
7827
+ try {
7828
+ if (useStderr) {
7829
+ process.stderr.write(data);
7830
+ } else {
7831
+ fs.appendFileSync(logFile, data);
7832
+ }
7833
+ } catch {}
7834
+ }
7835
+ function scheduleFlush() {
7836
+ if (flushTimer)
7837
+ return;
7838
+ flushTimer = setTimeout(() => {
7839
+ flushTimer = null;
7840
+ flush();
7841
+ }, FLUSH_INTERVAL_MS);
7842
+ if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
7843
+ flushTimer.unref();
7844
+ }
7845
+ }
7846
+ function write(level, message, data, sessionId) {
7847
+ try {
7848
+ const timestamp = new Date().toISOString();
7849
+ const serialized = data === undefined ? "" : ` ${JSON.stringify(data)}`;
7850
+ const sessionPrefix = sessionId ? ` [${sessionId}]` : "";
7851
+ const line = `[${timestamp}] ${level} ${TAG}${sessionPrefix} ${message}${serialized}
7852
+ `;
7853
+ if (useStderr) {
7854
+ process.stderr.write(line);
7855
+ return;
7856
+ }
7857
+ buffer.push(line);
7858
+ if (buffer.length >= BUFFER_SIZE_LIMIT) {
7859
+ flush();
7860
+ } else {
7861
+ scheduleFlush();
7862
+ }
7863
+ } catch {}
7864
+ }
7865
+ function log(message, data) {
7866
+ write("INFO", message, data);
7867
+ }
7868
+ function warn(message, data) {
7869
+ write("WARN", message, data);
7870
+ }
7871
+ function error(message, data) {
7872
+ write("ERROR", message, data);
7873
+ }
7874
+ function sessionWarn(sessionId, message, data) {
7875
+ write("WARN", message, data, sessionId);
7876
+ }
7877
+ function getLogFilePath() {
7878
+ return logFile;
7879
+ }
7880
+
7881
+ // src/bg-notifications.ts
7882
+ var sessionBgStates = new Map;
7883
+ var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
7884
+ var DEBOUNCE_STEP_MS = 200;
7885
+ var DEBOUNCE_CAP_MS = 1000;
7886
+ var DEFAULT_SESSION_ID = "__default__";
7887
+ var LOG_PREFIX = "[aft-plugin] bg-notifications:";
7888
+ function trackBgTask(sessionID, taskId) {
7889
+ stateFor(sessionID).outstandingTaskIds.add(taskId);
7890
+ }
7891
+ function ingestBgCompletions(sessionID, completions) {
7892
+ if (!Array.isArray(completions) || completions.length === 0)
7893
+ return [];
7894
+ const state = stateFor(sessionID);
7895
+ const accepted = [];
7896
+ for (const completion of completions) {
7897
+ if (!isBgCompletion(completion))
7898
+ continue;
7899
+ if (!state.outstandingTaskIds.has(completion.task_id))
7900
+ continue;
7901
+ state.outstandingTaskIds.delete(completion.task_id);
7902
+ if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
7903
+ accepted.push(completion);
7904
+ }
7905
+ }
7906
+ state.pendingCompletions.push(...accepted);
7907
+ return accepted;
7908
+ }
7909
+ async function appendInTurnBgCompletions(drainContext, output) {
7910
+ if (!output)
7911
+ return;
7912
+ const state = stateFor(drainContext.sessionID);
7913
+ if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0)
7914
+ return;
7915
+ if (state.outstandingTaskIds.size > 0) {
7916
+ await drainCompletions(drainContext);
7917
+ }
7918
+ if (state.pendingCompletions.length === 0)
7919
+ return;
7920
+ const reminder = formatSystemReminder(state.pendingCompletions);
7921
+ output.output = appendReminder(output.output ?? "", reminder);
7922
+ state.pendingCompletions = [];
7923
+ }
7924
+ async function handleIdleBgCompletions(drainContext) {
7925
+ const state = stateFor(drainContext.sessionID);
7926
+ if (state.wakeFiredThisIdle)
7927
+ return;
7928
+ if (state.outstandingTaskIds.size > 0) {
7929
+ await drainCompletions(drainContext);
7930
+ }
7931
+ if (state.pendingCompletions.length === 0)
7932
+ return;
7933
+ scheduleWake(state, async (reminder) => {
7934
+ try {
7935
+ const client = drainContext.client;
7936
+ if (typeof client.session?.promptAsync !== "function") {
7937
+ throw new Error("client.session.promptAsync is unavailable");
7938
+ }
7939
+ await client.session.promptAsync({
7940
+ path: { id: drainContext.sessionID },
7941
+ body: {
7942
+ noReply: false,
7943
+ parts: [{ type: "text", text: reminder }]
7944
+ }
7945
+ });
7946
+ } catch (err) {
7947
+ warn(`${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
7948
+ }
7949
+ });
7950
+ }
7951
+ function resetBgWake(sessionID) {
7952
+ stateFor(sessionID).wakeFiredThisIdle = false;
7953
+ }
7954
+ function formatSystemReminder(completions) {
7955
+ const bullets = completions.map((completion) => `- ${formatCompletion(completion)}`).join(`
7956
+ `);
7957
+ return `<system-reminder>
7958
+ [BACKGROUND BASH COMPLETED]
7959
+ ${bullets}
7960
+
7961
+ Use bash_status({ task_id: "..." }) to retrieve full output.
7962
+ </system-reminder>`;
7963
+ }
7964
+ function extractSessionID(value) {
7965
+ if (!value || typeof value !== "object")
7966
+ return;
7967
+ const record = value;
7968
+ for (const key of ["sessionID", "sessionId", "id"]) {
7969
+ if (typeof record[key] === "string")
7970
+ return record[key];
7971
+ }
7972
+ const info = record.info;
7973
+ if (info && typeof info === "object") {
7974
+ const nested = info;
7975
+ for (const key of ["sessionID", "sessionId", "id"]) {
7976
+ if (typeof nested[key] === "string")
7977
+ return nested[key];
7978
+ }
7979
+ }
7980
+ return;
7981
+ }
7982
+ async function drainCompletions({ ctx, directory, sessionID }) {
7983
+ try {
7984
+ const bridge = ctx.pool.getAnyActiveBridge(directory) ?? ctx.pool.getBridge(directory);
7985
+ const response = await bridge.send("bash_drain_completions", { session_id: sessionID });
7986
+ if (response.success === false) {
7987
+ warn(`${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
7988
+ return;
7989
+ }
7990
+ ingestBgCompletions(sessionID, response.bg_completions);
7991
+ } catch (err) {
7992
+ warn(`${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
7993
+ }
7994
+ }
7995
+ function scheduleWake(state, sendWake) {
7996
+ const now = Date.now();
7997
+ if (state.debounceTimer && state.pendingCompletions.length <= state.scheduledCompletionCount) {
7998
+ return;
7999
+ }
8000
+ if (state.firstCompletionAt === null) {
8001
+ state.firstCompletionAt = now;
8002
+ state.scheduledFireAt = now + DEBOUNCE_STEP_MS;
8003
+ } else {
8004
+ const previousFireAt = state.scheduledFireAt ?? now;
8005
+ state.scheduledFireAt = Math.min(previousFireAt + DEBOUNCE_STEP_MS, state.firstCompletionAt + DEBOUNCE_CAP_MS);
8006
+ }
8007
+ state.scheduledCompletionCount = state.pendingCompletions.length;
8008
+ if (state.debounceTimer)
8009
+ clearTimeout(state.debounceTimer);
8010
+ const delay = Math.max(0, (state.scheduledFireAt ?? now) - now);
8011
+ state.debounceTimer = setTimeout(() => {
8012
+ const reminder = formatSystemReminder(state.pendingCompletions);
8013
+ state.pendingCompletions = [];
8014
+ state.debounceTimer = null;
8015
+ state.wakeFiredThisIdle = true;
8016
+ state.firstCompletionAt = null;
8017
+ state.scheduledFireAt = null;
8018
+ state.scheduledCompletionCount = 0;
8019
+ sendWake(reminder);
8020
+ }, delay);
8021
+ state.debounceTimer.unref?.();
8022
+ }
8023
+ function stateFor(sessionID) {
8024
+ const now = Date.now();
8025
+ cleanupIdleSessionStates(now);
8026
+ const key = sessionID || DEFAULT_SESSION_ID;
8027
+ let state = sessionBgStates.get(key);
8028
+ if (!state) {
8029
+ state = {
8030
+ outstandingTaskIds: new Set,
8031
+ pendingCompletions: [],
8032
+ debounceTimer: null,
8033
+ wakeFiredThisIdle: false,
8034
+ firstCompletionAt: null,
8035
+ scheduledFireAt: null,
8036
+ scheduledCompletionCount: 0,
8037
+ lastSeenAt: now
8038
+ };
8039
+ sessionBgStates.set(key, state);
8040
+ } else {
8041
+ state.lastSeenAt = now;
8042
+ }
8043
+ return state;
8044
+ }
8045
+ function cleanupIdleSessionStates(now) {
8046
+ const cutoff = now - SESSION_BG_STATE_IDLE_TTL_MS;
8047
+ for (const [sessionID, state] of sessionBgStates) {
8048
+ if (state.outstandingTaskIds.size > 0)
8049
+ continue;
8050
+ if (state.lastSeenAt >= cutoff)
8051
+ continue;
8052
+ if (state.debounceTimer)
8053
+ clearTimeout(state.debounceTimer);
8054
+ sessionBgStates.delete(sessionID);
8055
+ }
8056
+ }
8057
+ function isBgCompletion(value) {
8058
+ if (!value || typeof value !== "object" || Array.isArray(value))
8059
+ return false;
8060
+ const completion = value;
8061
+ return typeof completion.task_id === "string" && typeof completion.status === "string" && (typeof completion.exit_code === "number" || completion.exit_code === null) && typeof completion.command === "string";
8062
+ }
8063
+ function appendReminder(output, reminder) {
8064
+ return output.length > 0 ? `${output}
8065
+
8066
+ ${reminder}` : reminder;
8067
+ }
8068
+ function formatCompletion(completion) {
8069
+ const status = formatStatus(completion);
8070
+ const duration = formatDuration(completion);
8071
+ return `task ${completion.task_id} (${status}${duration ? `, ${duration}` : ""}): ${completion.command}`;
8072
+ }
8073
+ function formatStatus(completion) {
8074
+ if (completion.status === "timeout")
8075
+ return "timed out";
8076
+ if (completion.status === "killed")
8077
+ return "killed";
8078
+ if (completion.exit_code !== null)
8079
+ return `exit ${completion.exit_code}`;
8080
+ return completion.status;
8081
+ }
8082
+ function formatDuration(completion) {
8083
+ const raw = completion.duration_ms ?? completion.runtime_ms ?? completion.runtime;
8084
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0)
8085
+ return null;
8086
+ if (raw < 1000)
8087
+ return `${Math.round(raw)}ms`;
8088
+ const totalSeconds = Math.round(raw / 1000);
8089
+ const minutes = Math.floor(totalSeconds / 60);
8090
+ const seconds = totalSeconds % 60;
8091
+ return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
8092
+ }
8093
+
7811
8094
  // src/config.ts
7812
8095
  var import_comment_json = __toESM(require_src2(), 1);
7813
- import { existsSync, readFileSync } from "fs";
8096
+ import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
7814
8097
  import { homedir } from "os";
7815
8098
  import { join as join2 } from "path";
7816
8099
 
@@ -8579,10 +8862,10 @@ function mergeDefs(...defs) {
8579
8862
  function cloneDef(schema) {
8580
8863
  return mergeDefs(schema._zod.def);
8581
8864
  }
8582
- function getElementAtPath(obj, path) {
8583
- if (!path)
8865
+ function getElementAtPath(obj, path2) {
8866
+ if (!path2)
8584
8867
  return obj;
8585
- return path.reduce((acc, key) => acc?.[key], obj);
8868
+ return path2.reduce((acc, key) => acc?.[key], obj);
8586
8869
  }
8587
8870
  function promiseAllObject(promisesObj) {
8588
8871
  const keys = Object.keys(promisesObj);
@@ -8963,11 +9246,11 @@ function aborted(x, startIndex = 0) {
8963
9246
  }
8964
9247
  return false;
8965
9248
  }
8966
- function prefixIssues(path, issues) {
9249
+ function prefixIssues(path2, issues) {
8967
9250
  return issues.map((iss) => {
8968
9251
  var _a;
8969
9252
  (_a = iss).path ?? (_a.path = []);
8970
- iss.path.unshift(path);
9253
+ iss.path.unshift(path2);
8971
9254
  return iss;
8972
9255
  });
8973
9256
  }
@@ -9102,10 +9385,10 @@ var initializer = (inst, def) => {
9102
9385
  };
9103
9386
  var $ZodError = $constructor("$ZodError", initializer);
9104
9387
  var $ZodRealError = $constructor("$ZodError", initializer, { Parent: Error });
9105
- function flattenError(error, mapper = (issue2) => issue2.message) {
9388
+ function flattenError(error2, mapper = (issue2) => issue2.message) {
9106
9389
  const fieldErrors = {};
9107
9390
  const formErrors = [];
9108
- for (const sub of error.issues) {
9391
+ for (const sub of error2.issues) {
9109
9392
  if (sub.path.length > 0) {
9110
9393
  fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
9111
9394
  fieldErrors[sub.path[0]].push(mapper(sub));
@@ -9115,10 +9398,10 @@ function flattenError(error, mapper = (issue2) => issue2.message) {
9115
9398
  }
9116
9399
  return { formErrors, fieldErrors };
9117
9400
  }
9118
- function formatError(error, mapper = (issue2) => issue2.message) {
9401
+ function formatError(error2, mapper = (issue2) => issue2.message) {
9119
9402
  const fieldErrors = { _errors: [] };
9120
- const processError = (error2) => {
9121
- for (const issue2 of error2.issues) {
9403
+ const processError = (error3) => {
9404
+ for (const issue2 of error3.issues) {
9122
9405
  if (issue2.code === "invalid_union" && issue2.errors.length) {
9123
9406
  issue2.errors.map((issues) => processError({ issues }));
9124
9407
  } else if (issue2.code === "invalid_key") {
@@ -9145,14 +9428,14 @@ function formatError(error, mapper = (issue2) => issue2.message) {
9145
9428
  }
9146
9429
  }
9147
9430
  };
9148
- processError(error);
9431
+ processError(error2);
9149
9432
  return fieldErrors;
9150
9433
  }
9151
- function treeifyError(error, mapper = (issue2) => issue2.message) {
9434
+ function treeifyError(error2, mapper = (issue2) => issue2.message) {
9152
9435
  const result = { errors: [] };
9153
- const processError = (error2, path = []) => {
9436
+ const processError = (error3, path2 = []) => {
9154
9437
  var _a, _b;
9155
- for (const issue2 of error2.issues) {
9438
+ for (const issue2 of error3.issues) {
9156
9439
  if (issue2.code === "invalid_union" && issue2.errors.length) {
9157
9440
  issue2.errors.map((issues) => processError({ issues }, issue2.path));
9158
9441
  } else if (issue2.code === "invalid_key") {
@@ -9160,7 +9443,7 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
9160
9443
  } else if (issue2.code === "invalid_element") {
9161
9444
  processError({ issues: issue2.issues }, issue2.path);
9162
9445
  } else {
9163
- const fullpath = [...path, ...issue2.path];
9446
+ const fullpath = [...path2, ...issue2.path];
9164
9447
  if (fullpath.length === 0) {
9165
9448
  result.errors.push(mapper(issue2));
9166
9449
  continue;
@@ -9187,13 +9470,13 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
9187
9470
  }
9188
9471
  }
9189
9472
  };
9190
- processError(error);
9473
+ processError(error2);
9191
9474
  return result;
9192
9475
  }
9193
9476
  function toDotPath(_path) {
9194
9477
  const segs = [];
9195
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
9196
- for (const seg of path) {
9478
+ const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
9479
+ for (const seg of path2) {
9197
9480
  if (typeof seg === "number")
9198
9481
  segs.push(`[${seg}]`);
9199
9482
  else if (typeof seg === "symbol")
@@ -9208,9 +9491,9 @@ function toDotPath(_path) {
9208
9491
  }
9209
9492
  return segs.join("");
9210
9493
  }
9211
- function prettifyError(error) {
9494
+ function prettifyError(error2) {
9212
9495
  const lines = [];
9213
- const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
9496
+ const issues = [...error2.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
9214
9497
  for (const issue2 of issues) {
9215
9498
  lines.push(`\u2716 ${issue2.message}`);
9216
9499
  if (issue2.path?.length)
@@ -12073,7 +12356,7 @@ __export(exports_locales, {
12073
12356
  });
12074
12357
 
12075
12358
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ar.js
12076
- var error = () => {
12359
+ var error2 = () => {
12077
12360
  const Sizable = {
12078
12361
  string: { unit: "\u062D\u0631\u0641", verb: "\u0623\u0646 \u064A\u062D\u0648\u064A" },
12079
12362
  file: { unit: "\u0628\u0627\u064A\u062A", verb: "\u0623\u0646 \u064A\u062D\u0648\u064A" },
@@ -12175,11 +12458,11 @@ var error = () => {
12175
12458
  };
12176
12459
  function ar_default() {
12177
12460
  return {
12178
- localeError: error()
12461
+ localeError: error2()
12179
12462
  };
12180
12463
  }
12181
12464
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/az.js
12182
- var error2 = () => {
12465
+ var error3 = () => {
12183
12466
  const Sizable = {
12184
12467
  string: { unit: "simvol", verb: "olmal\u0131d\u0131r" },
12185
12468
  file: { unit: "bayt", verb: "olmal\u0131d\u0131r" },
@@ -12280,7 +12563,7 @@ var error2 = () => {
12280
12563
  };
12281
12564
  function az_default() {
12282
12565
  return {
12283
- localeError: error2()
12566
+ localeError: error3()
12284
12567
  };
12285
12568
  }
12286
12569
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/be.js
@@ -12299,7 +12582,7 @@ function getBelarusianPlural(count, one, few, many) {
12299
12582
  }
12300
12583
  return many;
12301
12584
  }
12302
- var error3 = () => {
12585
+ var error4 = () => {
12303
12586
  const Sizable = {
12304
12587
  string: {
12305
12588
  unit: {
@@ -12436,11 +12719,11 @@ var error3 = () => {
12436
12719
  };
12437
12720
  function be_default() {
12438
12721
  return {
12439
- localeError: error3()
12722
+ localeError: error4()
12440
12723
  };
12441
12724
  }
12442
12725
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/bg.js
12443
- var error4 = () => {
12726
+ var error5 = () => {
12444
12727
  const Sizable = {
12445
12728
  string: { unit: "\u0441\u0438\u043C\u0432\u043E\u043B\u0430", verb: "\u0434\u0430 \u0441\u044A\u0434\u044A\u0440\u0436\u0430" },
12446
12729
  file: { unit: "\u0431\u0430\u0439\u0442\u0430", verb: "\u0434\u0430 \u0441\u044A\u0434\u044A\u0440\u0436\u0430" },
@@ -12556,11 +12839,11 @@ var error4 = () => {
12556
12839
  };
12557
12840
  function bg_default() {
12558
12841
  return {
12559
- localeError: error4()
12842
+ localeError: error5()
12560
12843
  };
12561
12844
  }
12562
12845
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ca.js
12563
- var error5 = () => {
12846
+ var error6 = () => {
12564
12847
  const Sizable = {
12565
12848
  string: { unit: "car\xE0cters", verb: "contenir" },
12566
12849
  file: { unit: "bytes", verb: "contenir" },
@@ -12663,11 +12946,11 @@ var error5 = () => {
12663
12946
  };
12664
12947
  function ca_default() {
12665
12948
  return {
12666
- localeError: error5()
12949
+ localeError: error6()
12667
12950
  };
12668
12951
  }
12669
12952
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/cs.js
12670
- var error6 = () => {
12953
+ var error7 = () => {
12671
12954
  const Sizable = {
12672
12955
  string: { unit: "znak\u016F", verb: "m\xEDt" },
12673
12956
  file: { unit: "bajt\u016F", verb: "m\xEDt" },
@@ -12774,11 +13057,11 @@ var error6 = () => {
12774
13057
  };
12775
13058
  function cs_default() {
12776
13059
  return {
12777
- localeError: error6()
13060
+ localeError: error7()
12778
13061
  };
12779
13062
  }
12780
13063
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/da.js
12781
- var error7 = () => {
13064
+ var error8 = () => {
12782
13065
  const Sizable = {
12783
13066
  string: { unit: "tegn", verb: "havde" },
12784
13067
  file: { unit: "bytes", verb: "havde" },
@@ -12889,11 +13172,11 @@ var error7 = () => {
12889
13172
  };
12890
13173
  function da_default() {
12891
13174
  return {
12892
- localeError: error7()
13175
+ localeError: error8()
12893
13176
  };
12894
13177
  }
12895
13178
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/de.js
12896
- var error8 = () => {
13179
+ var error9 = () => {
12897
13180
  const Sizable = {
12898
13181
  string: { unit: "Zeichen", verb: "zu haben" },
12899
13182
  file: { unit: "Bytes", verb: "zu haben" },
@@ -12997,11 +13280,11 @@ var error8 = () => {
12997
13280
  };
12998
13281
  function de_default() {
12999
13282
  return {
13000
- localeError: error8()
13283
+ localeError: error9()
13001
13284
  };
13002
13285
  }
13003
13286
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/en.js
13004
- var error9 = () => {
13287
+ var error10 = () => {
13005
13288
  const Sizable = {
13006
13289
  string: { unit: "characters", verb: "to have" },
13007
13290
  file: { unit: "bytes", verb: "to have" },
@@ -13103,11 +13386,11 @@ var error9 = () => {
13103
13386
  };
13104
13387
  function en_default() {
13105
13388
  return {
13106
- localeError: error9()
13389
+ localeError: error10()
13107
13390
  };
13108
13391
  }
13109
13392
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/eo.js
13110
- var error10 = () => {
13393
+ var error11 = () => {
13111
13394
  const Sizable = {
13112
13395
  string: { unit: "karaktrojn", verb: "havi" },
13113
13396
  file: { unit: "bajtojn", verb: "havi" },
@@ -13212,11 +13495,11 @@ var error10 = () => {
13212
13495
  };
13213
13496
  function eo_default() {
13214
13497
  return {
13215
- localeError: error10()
13498
+ localeError: error11()
13216
13499
  };
13217
13500
  }
13218
13501
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/es.js
13219
- var error11 = () => {
13502
+ var error12 = () => {
13220
13503
  const Sizable = {
13221
13504
  string: { unit: "caracteres", verb: "tener" },
13222
13505
  file: { unit: "bytes", verb: "tener" },
@@ -13344,11 +13627,11 @@ var error11 = () => {
13344
13627
  };
13345
13628
  function es_default() {
13346
13629
  return {
13347
- localeError: error11()
13630
+ localeError: error12()
13348
13631
  };
13349
13632
  }
13350
13633
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fa.js
13351
- var error12 = () => {
13634
+ var error13 = () => {
13352
13635
  const Sizable = {
13353
13636
  string: { unit: "\u06A9\u0627\u0631\u0627\u06A9\u062A\u0631", verb: "\u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F" },
13354
13637
  file: { unit: "\u0628\u0627\u06CC\u062A", verb: "\u062F\u0627\u0634\u062A\u0647 \u0628\u0627\u0634\u062F" },
@@ -13458,11 +13741,11 @@ var error12 = () => {
13458
13741
  };
13459
13742
  function fa_default() {
13460
13743
  return {
13461
- localeError: error12()
13744
+ localeError: error13()
13462
13745
  };
13463
13746
  }
13464
13747
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fi.js
13465
- var error13 = () => {
13748
+ var error14 = () => {
13466
13749
  const Sizable = {
13467
13750
  string: { unit: "merkki\xE4", subject: "merkkijonon" },
13468
13751
  file: { unit: "tavua", subject: "tiedoston" },
@@ -13570,11 +13853,11 @@ var error13 = () => {
13570
13853
  };
13571
13854
  function fi_default() {
13572
13855
  return {
13573
- localeError: error13()
13856
+ localeError: error14()
13574
13857
  };
13575
13858
  }
13576
13859
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr.js
13577
- var error14 = () => {
13860
+ var error15 = () => {
13578
13861
  const Sizable = {
13579
13862
  string: { unit: "caract\xE8res", verb: "avoir" },
13580
13863
  file: { unit: "octets", verb: "avoir" },
@@ -13678,11 +13961,11 @@ var error14 = () => {
13678
13961
  };
13679
13962
  function fr_default() {
13680
13963
  return {
13681
- localeError: error14()
13964
+ localeError: error15()
13682
13965
  };
13683
13966
  }
13684
13967
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr-CA.js
13685
- var error15 = () => {
13968
+ var error16 = () => {
13686
13969
  const Sizable = {
13687
13970
  string: { unit: "caract\xE8res", verb: "avoir" },
13688
13971
  file: { unit: "octets", verb: "avoir" },
@@ -13785,11 +14068,11 @@ var error15 = () => {
13785
14068
  };
13786
14069
  function fr_CA_default() {
13787
14070
  return {
13788
- localeError: error15()
14071
+ localeError: error16()
13789
14072
  };
13790
14073
  }
13791
14074
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/he.js
13792
- var error16 = () => {
14075
+ var error17 = () => {
13793
14076
  const TypeNames = {
13794
14077
  string: { label: "\u05DE\u05D7\u05E8\u05D5\u05D6\u05EA", gender: "f" },
13795
14078
  number: { label: "\u05DE\u05E1\u05E4\u05E8", gender: "m" },
@@ -13978,11 +14261,11 @@ var error16 = () => {
13978
14261
  };
13979
14262
  function he_default() {
13980
14263
  return {
13981
- localeError: error16()
14264
+ localeError: error17()
13982
14265
  };
13983
14266
  }
13984
14267
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hu.js
13985
- var error17 = () => {
14268
+ var error18 = () => {
13986
14269
  const Sizable = {
13987
14270
  string: { unit: "karakter", verb: "legyen" },
13988
14271
  file: { unit: "byte", verb: "legyen" },
@@ -14086,7 +14369,7 @@ var error17 = () => {
14086
14369
  };
14087
14370
  function hu_default() {
14088
14371
  return {
14089
- localeError: error17()
14372
+ localeError: error18()
14090
14373
  };
14091
14374
  }
14092
14375
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hy.js
@@ -14100,7 +14383,7 @@ function withDefiniteArticle(word) {
14100
14383
  const lastChar = word[word.length - 1];
14101
14384
  return word + (vowels.includes(lastChar) ? "\u0576" : "\u0568");
14102
14385
  }
14103
- var error18 = () => {
14386
+ var error19 = () => {
14104
14387
  const Sizable = {
14105
14388
  string: {
14106
14389
  unit: {
@@ -14233,11 +14516,11 @@ var error18 = () => {
14233
14516
  };
14234
14517
  function hy_default() {
14235
14518
  return {
14236
- localeError: error18()
14519
+ localeError: error19()
14237
14520
  };
14238
14521
  }
14239
14522
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/id.js
14240
- var error19 = () => {
14523
+ var error20 = () => {
14241
14524
  const Sizable = {
14242
14525
  string: { unit: "karakter", verb: "memiliki" },
14243
14526
  file: { unit: "byte", verb: "memiliki" },
@@ -14339,11 +14622,11 @@ var error19 = () => {
14339
14622
  };
14340
14623
  function id_default() {
14341
14624
  return {
14342
- localeError: error19()
14625
+ localeError: error20()
14343
14626
  };
14344
14627
  }
14345
14628
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/is.js
14346
- var error20 = () => {
14629
+ var error21 = () => {
14347
14630
  const Sizable = {
14348
14631
  string: { unit: "stafi", verb: "a\xF0 hafa" },
14349
14632
  file: { unit: "b\xE6ti", verb: "a\xF0 hafa" },
@@ -14448,11 +14731,11 @@ var error20 = () => {
14448
14731
  };
14449
14732
  function is_default() {
14450
14733
  return {
14451
- localeError: error20()
14734
+ localeError: error21()
14452
14735
  };
14453
14736
  }
14454
14737
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/it.js
14455
- var error21 = () => {
14738
+ var error22 = () => {
14456
14739
  const Sizable = {
14457
14740
  string: { unit: "caratteri", verb: "avere" },
14458
14741
  file: { unit: "byte", verb: "avere" },
@@ -14556,11 +14839,11 @@ var error21 = () => {
14556
14839
  };
14557
14840
  function it_default() {
14558
14841
  return {
14559
- localeError: error21()
14842
+ localeError: error22()
14560
14843
  };
14561
14844
  }
14562
14845
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ja.js
14563
- var error22 = () => {
14846
+ var error23 = () => {
14564
14847
  const Sizable = {
14565
14848
  string: { unit: "\u6587\u5B57", verb: "\u3067\u3042\u308B" },
14566
14849
  file: { unit: "\u30D0\u30A4\u30C8", verb: "\u3067\u3042\u308B" },
@@ -14663,11 +14946,11 @@ var error22 = () => {
14663
14946
  };
14664
14947
  function ja_default() {
14665
14948
  return {
14666
- localeError: error22()
14949
+ localeError: error23()
14667
14950
  };
14668
14951
  }
14669
14952
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ka.js
14670
- var error23 = () => {
14953
+ var error24 = () => {
14671
14954
  const Sizable = {
14672
14955
  string: { unit: "\u10E1\u10D8\u10DB\u10D1\u10DD\u10DA\u10DD", verb: "\u10E3\u10DC\u10D3\u10D0 \u10E8\u10D4\u10D8\u10EA\u10D0\u10D5\u10D3\u10D4\u10E1" },
14673
14956
  file: { unit: "\u10D1\u10D0\u10D8\u10E2\u10D8", verb: "\u10E3\u10DC\u10D3\u10D0 \u10E8\u10D4\u10D8\u10EA\u10D0\u10D5\u10D3\u10D4\u10E1" },
@@ -14775,11 +15058,11 @@ var error23 = () => {
14775
15058
  };
14776
15059
  function ka_default() {
14777
15060
  return {
14778
- localeError: error23()
15061
+ localeError: error24()
14779
15062
  };
14780
15063
  }
14781
15064
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/km.js
14782
- var error24 = () => {
15065
+ var error25 = () => {
14783
15066
  const Sizable = {
14784
15067
  string: { unit: "\u178F\u17BD\u17A2\u1780\u17D2\u179F\u179A", verb: "\u1782\u17BD\u179A\u1798\u17B6\u1793" },
14785
15068
  file: { unit: "\u1794\u17C3", verb: "\u1782\u17BD\u179A\u1798\u17B6\u1793" },
@@ -14885,7 +15168,7 @@ var error24 = () => {
14885
15168
  };
14886
15169
  function km_default() {
14887
15170
  return {
14888
- localeError: error24()
15171
+ localeError: error25()
14889
15172
  };
14890
15173
  }
14891
15174
 
@@ -14894,7 +15177,7 @@ function kh_default() {
14894
15177
  return km_default();
14895
15178
  }
14896
15179
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ko.js
14897
- var error25 = () => {
15180
+ var error26 = () => {
14898
15181
  const Sizable = {
14899
15182
  string: { unit: "\uBB38\uC790", verb: "to have" },
14900
15183
  file: { unit: "\uBC14\uC774\uD2B8", verb: "to have" },
@@ -15001,7 +15284,7 @@ var error25 = () => {
15001
15284
  };
15002
15285
  function ko_default() {
15003
15286
  return {
15004
- localeError: error25()
15287
+ localeError: error26()
15005
15288
  };
15006
15289
  }
15007
15290
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/lt.js
@@ -15018,7 +15301,7 @@ function getUnitTypeFromNumber(number2) {
15018
15301
  return "one";
15019
15302
  return "few";
15020
15303
  }
15021
- var error26 = () => {
15304
+ var error27 = () => {
15022
15305
  const Sizable = {
15023
15306
  string: {
15024
15307
  unit: {
@@ -15204,11 +15487,11 @@ var error26 = () => {
15204
15487
  };
15205
15488
  function lt_default() {
15206
15489
  return {
15207
- localeError: error26()
15490
+ localeError: error27()
15208
15491
  };
15209
15492
  }
15210
15493
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/mk.js
15211
- var error27 = () => {
15494
+ var error28 = () => {
15212
15495
  const Sizable = {
15213
15496
  string: { unit: "\u0437\u043D\u0430\u0446\u0438", verb: "\u0434\u0430 \u0438\u043C\u0430\u0430\u0442" },
15214
15497
  file: { unit: "\u0431\u0430\u0458\u0442\u0438", verb: "\u0434\u0430 \u0438\u043C\u0430\u0430\u0442" },
@@ -15313,11 +15596,11 @@ var error27 = () => {
15313
15596
  };
15314
15597
  function mk_default() {
15315
15598
  return {
15316
- localeError: error27()
15599
+ localeError: error28()
15317
15600
  };
15318
15601
  }
15319
15602
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ms.js
15320
- var error28 = () => {
15603
+ var error29 = () => {
15321
15604
  const Sizable = {
15322
15605
  string: { unit: "aksara", verb: "mempunyai" },
15323
15606
  file: { unit: "bait", verb: "mempunyai" },
@@ -15420,11 +15703,11 @@ var error28 = () => {
15420
15703
  };
15421
15704
  function ms_default() {
15422
15705
  return {
15423
- localeError: error28()
15706
+ localeError: error29()
15424
15707
  };
15425
15708
  }
15426
15709
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/nl.js
15427
- var error29 = () => {
15710
+ var error30 = () => {
15428
15711
  const Sizable = {
15429
15712
  string: { unit: "tekens", verb: "heeft" },
15430
15713
  file: { unit: "bytes", verb: "heeft" },
@@ -15530,11 +15813,11 @@ var error29 = () => {
15530
15813
  };
15531
15814
  function nl_default() {
15532
15815
  return {
15533
- localeError: error29()
15816
+ localeError: error30()
15534
15817
  };
15535
15818
  }
15536
15819
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/no.js
15537
- var error30 = () => {
15820
+ var error31 = () => {
15538
15821
  const Sizable = {
15539
15822
  string: { unit: "tegn", verb: "\xE5 ha" },
15540
15823
  file: { unit: "bytes", verb: "\xE5 ha" },
@@ -15638,11 +15921,11 @@ var error30 = () => {
15638
15921
  };
15639
15922
  function no_default() {
15640
15923
  return {
15641
- localeError: error30()
15924
+ localeError: error31()
15642
15925
  };
15643
15926
  }
15644
15927
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ota.js
15645
- var error31 = () => {
15928
+ var error32 = () => {
15646
15929
  const Sizable = {
15647
15930
  string: { unit: "harf", verb: "olmal\u0131d\u0131r" },
15648
15931
  file: { unit: "bayt", verb: "olmal\u0131d\u0131r" },
@@ -15747,11 +16030,11 @@ var error31 = () => {
15747
16030
  };
15748
16031
  function ota_default() {
15749
16032
  return {
15750
- localeError: error31()
16033
+ localeError: error32()
15751
16034
  };
15752
16035
  }
15753
16036
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ps.js
15754
- var error32 = () => {
16037
+ var error33 = () => {
15755
16038
  const Sizable = {
15756
16039
  string: { unit: "\u062A\u0648\u06A9\u064A", verb: "\u0648\u0644\u0631\u064A" },
15757
16040
  file: { unit: "\u0628\u0627\u06CC\u067C\u0633", verb: "\u0648\u0644\u0631\u064A" },
@@ -15861,11 +16144,11 @@ var error32 = () => {
15861
16144
  };
15862
16145
  function ps_default() {
15863
16146
  return {
15864
- localeError: error32()
16147
+ localeError: error33()
15865
16148
  };
15866
16149
  }
15867
16150
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pl.js
15868
- var error33 = () => {
16151
+ var error34 = () => {
15869
16152
  const Sizable = {
15870
16153
  string: { unit: "znak\xF3w", verb: "mie\u0107" },
15871
16154
  file: { unit: "bajt\xF3w", verb: "mie\u0107" },
@@ -15970,11 +16253,11 @@ var error33 = () => {
15970
16253
  };
15971
16254
  function pl_default() {
15972
16255
  return {
15973
- localeError: error33()
16256
+ localeError: error34()
15974
16257
  };
15975
16258
  }
15976
16259
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pt.js
15977
- var error34 = () => {
16260
+ var error35 = () => {
15978
16261
  const Sizable = {
15979
16262
  string: { unit: "caracteres", verb: "ter" },
15980
16263
  file: { unit: "bytes", verb: "ter" },
@@ -16078,7 +16361,7 @@ var error34 = () => {
16078
16361
  };
16079
16362
  function pt_default() {
16080
16363
  return {
16081
- localeError: error34()
16364
+ localeError: error35()
16082
16365
  };
16083
16366
  }
16084
16367
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ru.js
@@ -16097,7 +16380,7 @@ function getRussianPlural(count, one, few, many) {
16097
16380
  }
16098
16381
  return many;
16099
16382
  }
16100
- var error35 = () => {
16383
+ var error36 = () => {
16101
16384
  const Sizable = {
16102
16385
  string: {
16103
16386
  unit: {
@@ -16234,11 +16517,11 @@ var error35 = () => {
16234
16517
  };
16235
16518
  function ru_default() {
16236
16519
  return {
16237
- localeError: error35()
16520
+ localeError: error36()
16238
16521
  };
16239
16522
  }
16240
16523
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sl.js
16241
- var error36 = () => {
16524
+ var error37 = () => {
16242
16525
  const Sizable = {
16243
16526
  string: { unit: "znakov", verb: "imeti" },
16244
16527
  file: { unit: "bajtov", verb: "imeti" },
@@ -16343,11 +16626,11 @@ var error36 = () => {
16343
16626
  };
16344
16627
  function sl_default() {
16345
16628
  return {
16346
- localeError: error36()
16629
+ localeError: error37()
16347
16630
  };
16348
16631
  }
16349
16632
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sv.js
16350
- var error37 = () => {
16633
+ var error38 = () => {
16351
16634
  const Sizable = {
16352
16635
  string: { unit: "tecken", verb: "att ha" },
16353
16636
  file: { unit: "bytes", verb: "att ha" },
@@ -16453,11 +16736,11 @@ var error37 = () => {
16453
16736
  };
16454
16737
  function sv_default() {
16455
16738
  return {
16456
- localeError: error37()
16739
+ localeError: error38()
16457
16740
  };
16458
16741
  }
16459
16742
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ta.js
16460
- var error38 = () => {
16743
+ var error39 = () => {
16461
16744
  const Sizable = {
16462
16745
  string: { unit: "\u0B8E\u0BB4\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1\u0B95\u0BCD\u0B95\u0BB3\u0BCD", verb: "\u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BBF\u0BB0\u0BC1\u0B95\u0BCD\u0B95 \u0BB5\u0BC7\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD" },
16463
16746
  file: { unit: "\u0BAA\u0BC8\u0B9F\u0BCD\u0B9F\u0BC1\u0B95\u0BB3\u0BCD", verb: "\u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BBF\u0BB0\u0BC1\u0B95\u0BCD\u0B95 \u0BB5\u0BC7\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD" },
@@ -16563,11 +16846,11 @@ var error38 = () => {
16563
16846
  };
16564
16847
  function ta_default() {
16565
16848
  return {
16566
- localeError: error38()
16849
+ localeError: error39()
16567
16850
  };
16568
16851
  }
16569
16852
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/th.js
16570
- var error39 = () => {
16853
+ var error40 = () => {
16571
16854
  const Sizable = {
16572
16855
  string: { unit: "\u0E15\u0E31\u0E27\u0E2D\u0E31\u0E01\u0E29\u0E23", verb: "\u0E04\u0E27\u0E23\u0E21\u0E35" },
16573
16856
  file: { unit: "\u0E44\u0E1A\u0E15\u0E4C", verb: "\u0E04\u0E27\u0E23\u0E21\u0E35" },
@@ -16673,11 +16956,11 @@ var error39 = () => {
16673
16956
  };
16674
16957
  function th_default() {
16675
16958
  return {
16676
- localeError: error39()
16959
+ localeError: error40()
16677
16960
  };
16678
16961
  }
16679
16962
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/tr.js
16680
- var error40 = () => {
16963
+ var error41 = () => {
16681
16964
  const Sizable = {
16682
16965
  string: { unit: "karakter", verb: "olmal\u0131" },
16683
16966
  file: { unit: "bayt", verb: "olmal\u0131" },
@@ -16778,11 +17061,11 @@ var error40 = () => {
16778
17061
  };
16779
17062
  function tr_default() {
16780
17063
  return {
16781
- localeError: error40()
17064
+ localeError: error41()
16782
17065
  };
16783
17066
  }
16784
17067
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uk.js
16785
- var error41 = () => {
17068
+ var error42 = () => {
16786
17069
  const Sizable = {
16787
17070
  string: { unit: "\u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432", verb: "\u043C\u0430\u0442\u0438\u043C\u0435" },
16788
17071
  file: { unit: "\u0431\u0430\u0439\u0442\u0456\u0432", verb: "\u043C\u0430\u0442\u0438\u043C\u0435" },
@@ -16886,7 +17169,7 @@ var error41 = () => {
16886
17169
  };
16887
17170
  function uk_default() {
16888
17171
  return {
16889
- localeError: error41()
17172
+ localeError: error42()
16890
17173
  };
16891
17174
  }
16892
17175
 
@@ -16895,7 +17178,7 @@ function ua_default() {
16895
17178
  return uk_default();
16896
17179
  }
16897
17180
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ur.js
16898
- var error42 = () => {
17181
+ var error43 = () => {
16899
17182
  const Sizable = {
16900
17183
  string: { unit: "\u062D\u0631\u0648\u0641", verb: "\u06C1\u0648\u0646\u0627" },
16901
17184
  file: { unit: "\u0628\u0627\u0626\u0679\u0633", verb: "\u06C1\u0648\u0646\u0627" },
@@ -17001,11 +17284,11 @@ var error42 = () => {
17001
17284
  };
17002
17285
  function ur_default() {
17003
17286
  return {
17004
- localeError: error42()
17287
+ localeError: error43()
17005
17288
  };
17006
17289
  }
17007
17290
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uz.js
17008
- var error43 = () => {
17291
+ var error44 = () => {
17009
17292
  const Sizable = {
17010
17293
  string: { unit: "belgi", verb: "bo\u2018lishi kerak" },
17011
17294
  file: { unit: "bayt", verb: "bo\u2018lishi kerak" },
@@ -17110,11 +17393,11 @@ var error43 = () => {
17110
17393
  };
17111
17394
  function uz_default() {
17112
17395
  return {
17113
- localeError: error43()
17396
+ localeError: error44()
17114
17397
  };
17115
17398
  }
17116
17399
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/vi.js
17117
- var error44 = () => {
17400
+ var error45 = () => {
17118
17401
  const Sizable = {
17119
17402
  string: { unit: "k\xFD t\u1EF1", verb: "c\xF3" },
17120
17403
  file: { unit: "byte", verb: "c\xF3" },
@@ -17218,11 +17501,11 @@ var error44 = () => {
17218
17501
  };
17219
17502
  function vi_default() {
17220
17503
  return {
17221
- localeError: error44()
17504
+ localeError: error45()
17222
17505
  };
17223
17506
  }
17224
17507
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-CN.js
17225
- var error45 = () => {
17508
+ var error46 = () => {
17226
17509
  const Sizable = {
17227
17510
  string: { unit: "\u5B57\u7B26", verb: "\u5305\u542B" },
17228
17511
  file: { unit: "\u5B57\u8282", verb: "\u5305\u542B" },
@@ -17327,11 +17610,11 @@ var error45 = () => {
17327
17610
  };
17328
17611
  function zh_CN_default() {
17329
17612
  return {
17330
- localeError: error45()
17613
+ localeError: error46()
17331
17614
  };
17332
17615
  }
17333
17616
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-TW.js
17334
- var error46 = () => {
17617
+ var error47 = () => {
17335
17618
  const Sizable = {
17336
17619
  string: { unit: "\u5B57\u5143", verb: "\u64C1\u6709" },
17337
17620
  file: { unit: "\u4F4D\u5143\u7D44", verb: "\u64C1\u6709" },
@@ -17434,11 +17717,11 @@ var error46 = () => {
17434
17717
  };
17435
17718
  function zh_TW_default() {
17436
17719
  return {
17437
- localeError: error46()
17720
+ localeError: error47()
17438
17721
  };
17439
17722
  }
17440
17723
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/yo.js
17441
- var error47 = () => {
17724
+ var error48 = () => {
17442
17725
  const Sizable = {
17443
17726
  string: { unit: "\xE0mi", verb: "n\xED" },
17444
17727
  file: { unit: "bytes", verb: "n\xED" },
@@ -17541,7 +17824,7 @@ var error47 = () => {
17541
17824
  };
17542
17825
  function yo_default() {
17543
17826
  return {
17544
- localeError: error47()
17827
+ localeError: error48()
17545
17828
  };
17546
17829
  }
17547
17830
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/registries.js
@@ -20940,13 +21223,13 @@ function resolveRef(ref, ctx) {
20940
21223
  if (!ref.startsWith("#")) {
20941
21224
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
20942
21225
  }
20943
- const path = ref.slice(1).split("/").filter(Boolean);
20944
- if (path.length === 0) {
21226
+ const path2 = ref.slice(1).split("/").filter(Boolean);
21227
+ if (path2.length === 0) {
20945
21228
  return ctx.rootSchema;
20946
21229
  }
20947
21230
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
20948
- if (path[0] === defsKey) {
20949
- const key = path[1];
21231
+ if (path2[0] === defsKey) {
21232
+ const key = path2[1];
20950
21233
  if (!key || !ctx.defs[key]) {
20951
21234
  throw new Error(`Reference not found: ${ref}`);
20952
21235
  }
@@ -21346,72 +21629,6 @@ function date4(params) {
21346
21629
 
21347
21630
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
21348
21631
  config(en_default());
21349
- // src/logger.ts
21350
- import * as fs from "fs";
21351
- import * as os from "os";
21352
- import * as path from "path";
21353
- var TAG = "[aft-plugin]";
21354
- var logFile = path.join(os.tmpdir(), "aft-plugin.log");
21355
- var useStderr = process.env.AFT_LOG_STDERR === "1";
21356
- var buffer = [];
21357
- var flushTimer = null;
21358
- var FLUSH_INTERVAL_MS = 500;
21359
- var BUFFER_SIZE_LIMIT = 50;
21360
- function flush() {
21361
- if (buffer.length === 0)
21362
- return;
21363
- const data = buffer.join("");
21364
- buffer = [];
21365
- try {
21366
- if (useStderr) {
21367
- process.stderr.write(data);
21368
- } else {
21369
- fs.appendFileSync(logFile, data);
21370
- }
21371
- } catch {}
21372
- }
21373
- function scheduleFlush() {
21374
- if (flushTimer)
21375
- return;
21376
- flushTimer = setTimeout(() => {
21377
- flushTimer = null;
21378
- flush();
21379
- }, FLUSH_INTERVAL_MS);
21380
- if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
21381
- flushTimer.unref();
21382
- }
21383
- }
21384
- function write(level, message, data) {
21385
- try {
21386
- const timestamp = new Date().toISOString();
21387
- const serialized = data === undefined ? "" : ` ${JSON.stringify(data)}`;
21388
- const line = `[${timestamp}] ${level} ${TAG} ${message}${serialized}
21389
- `;
21390
- if (useStderr) {
21391
- process.stderr.write(line);
21392
- return;
21393
- }
21394
- buffer.push(line);
21395
- if (buffer.length >= BUFFER_SIZE_LIMIT) {
21396
- flush();
21397
- } else {
21398
- scheduleFlush();
21399
- }
21400
- } catch {}
21401
- }
21402
- function log(message, data) {
21403
- write("INFO", message, data);
21404
- }
21405
- function warn(message, data) {
21406
- write("WARN", message, data);
21407
- }
21408
- function error48(message, data) {
21409
- write("ERROR", message, data);
21410
- }
21411
- function getLogFilePath() {
21412
- return logFile;
21413
- }
21414
-
21415
21632
  // src/config.ts
21416
21633
  var FormatterEnum = exports_external.enum([
21417
21634
  "biome",
@@ -21466,6 +21683,14 @@ var LspConfigSchema = exports_external.object({
21466
21683
  grace_days: exports_external.number().int().positive().optional(),
21467
21684
  versions: exports_external.record(exports_external.string().trim().min(1), exports_external.string().trim().min(1)).optional()
21468
21685
  });
21686
+ var ExperimentalConfigSchema = exports_external.object({
21687
+ bash: exports_external.object({
21688
+ rewrite: exports_external.boolean().optional(),
21689
+ compress: exports_external.boolean().optional(),
21690
+ background: exports_external.boolean().optional()
21691
+ }).optional(),
21692
+ lsp_ty: exports_external.boolean().optional()
21693
+ });
21469
21694
  var AftConfigSchema = exports_external.object({
21470
21695
  format_on_edit: exports_external.boolean().optional(),
21471
21696
  validate_on_edit: exports_external.enum(["syntax", "full"]).optional(),
@@ -21475,22 +21700,22 @@ var AftConfigSchema = exports_external.object({
21475
21700
  tool_surface: exports_external.enum(["minimal", "recommended", "all"]).optional(),
21476
21701
  disabled_tools: exports_external.array(exports_external.string()).optional(),
21477
21702
  restrict_to_project_root: exports_external.boolean().optional(),
21478
- experimental_search_index: exports_external.boolean().optional(),
21479
- experimental_semantic_search: exports_external.boolean().optional(),
21480
- experimental_lsp_ty: exports_external.boolean().optional(),
21703
+ search_index: exports_external.boolean().optional(),
21704
+ semantic_search: exports_external.boolean().optional(),
21705
+ experimental: ExperimentalConfigSchema.optional(),
21481
21706
  lsp: LspConfigSchema.optional(),
21482
21707
  url_fetch_allow_private: exports_external.boolean().optional(),
21483
21708
  semantic: SemanticConfigSchema.optional(),
21484
21709
  max_callgraph_files: exports_external.number().int().positive().optional(),
21485
21710
  auto_update: exports_external.boolean().optional()
21486
- });
21711
+ }).strict();
21487
21712
  function normalizeLspExtension(extension) {
21488
21713
  return extension.trim().replace(/^\.+/, "");
21489
21714
  }
21490
21715
  function resolveLspConfigForConfigure(config2) {
21491
21716
  const overrides = {};
21492
21717
  const disabled = new Set(config2.lsp?.disabled ?? []);
21493
- let experimentalTy = config2.experimental_lsp_ty;
21718
+ let experimentalTy = config2.experimental?.lsp_ty;
21494
21719
  switch (config2.lsp?.python ?? "auto") {
21495
21720
  case "ty":
21496
21721
  experimentalTy = true;
@@ -21531,6 +21756,130 @@ function resolveLspConfigForConfigure(config2) {
21531
21756
  }
21532
21757
  return overrides;
21533
21758
  }
21759
+ function resolveExperimentalConfigForConfigure(config2) {
21760
+ const overrides = {};
21761
+ if (config2.experimental?.bash?.rewrite !== undefined) {
21762
+ overrides.experimental_bash_rewrite = config2.experimental.bash.rewrite;
21763
+ }
21764
+ if (config2.experimental?.bash?.compress !== undefined) {
21765
+ overrides.experimental_bash_compress = config2.experimental.bash.compress;
21766
+ }
21767
+ if (config2.experimental?.bash?.background !== undefined) {
21768
+ overrides.experimental_bash_background = config2.experimental.bash.background;
21769
+ }
21770
+ if (config2.experimental?.lsp_ty !== undefined) {
21771
+ overrides.experimental_lsp_ty = config2.experimental.lsp_ty;
21772
+ }
21773
+ return overrides;
21774
+ }
21775
+ var CONFIG_MIGRATIONS = [
21776
+ { oldKey: "experimental_search_index", newPath: ["search_index"] },
21777
+ { oldKey: "experimental_semantic_search", newPath: ["semantic_search"] },
21778
+ { oldKey: "experimental_lsp_ty", newPath: ["experimental", "lsp_ty"] },
21779
+ { oldKey: "experimental_bash_rewrite", newPath: ["experimental", "bash", "rewrite"] },
21780
+ { oldKey: "experimental_bash_compress", newPath: ["experimental", "bash", "compress"] },
21781
+ { oldKey: "experimental_bash_background", newPath: ["experimental", "bash", "background"] }
21782
+ ];
21783
+ function isWritableMigrationError(errorValue) {
21784
+ const code = errorValue?.code;
21785
+ return code === "EROFS" || code === "EACCES" || code === "EPERM";
21786
+ }
21787
+ function extractCommentsForPreservation(content) {
21788
+ const comments = [];
21789
+ const linePattern = /\/\/[^\n]*/g;
21790
+ for (const match of content.match(linePattern) ?? []) {
21791
+ comments.push(match.trim());
21792
+ }
21793
+ const blockPattern = /\/\*[\s\S]*?\*\//g;
21794
+ for (const match of content.match(blockPattern) ?? []) {
21795
+ comments.push(match.replace(/\s+/g, " ").trim());
21796
+ }
21797
+ return comments;
21798
+ }
21799
+ function ensureRecordAtPath(root, path2) {
21800
+ let current = root;
21801
+ for (const segment of path2) {
21802
+ const existing = current[segment];
21803
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
21804
+ current[segment] = {};
21805
+ }
21806
+ current = current[segment];
21807
+ }
21808
+ return current;
21809
+ }
21810
+ function hasPath(root, path2) {
21811
+ let current = root;
21812
+ for (const segment of path2) {
21813
+ if (!current || typeof current !== "object" || Array.isArray(current))
21814
+ return false;
21815
+ const record2 = current;
21816
+ if (!Object.hasOwn(record2, segment))
21817
+ return false;
21818
+ current = record2[segment];
21819
+ }
21820
+ return true;
21821
+ }
21822
+ function setPath(root, path2, value) {
21823
+ const parent = ensureRecordAtPath(root, path2.slice(0, -1));
21824
+ parent[path2[path2.length - 1]] = value;
21825
+ }
21826
+ function migrateRawConfig(rawConfig, configPath, logger) {
21827
+ const oldKeys = [];
21828
+ for (const migration of CONFIG_MIGRATIONS) {
21829
+ if (!Object.hasOwn(rawConfig, migration.oldKey))
21830
+ continue;
21831
+ if (hasPath(rawConfig, migration.newPath)) {
21832
+ logger?.warn(`Config migration conflict at ${configPath}: ${migration.oldKey} ignored because ${migration.newPath.join(".")} is already set`);
21833
+ } else {
21834
+ setPath(rawConfig, migration.newPath, rawConfig[migration.oldKey]);
21835
+ }
21836
+ delete rawConfig[migration.oldKey];
21837
+ oldKeys.push(migration.oldKey);
21838
+ }
21839
+ return oldKeys;
21840
+ }
21841
+ function migrateAftConfigFile(configPath, logger = { log, warn }) {
21842
+ if (!existsSync(configPath)) {
21843
+ return { migrated: false, oldKeys: [] };
21844
+ }
21845
+ let tmpPath = null;
21846
+ let oldKeys = [];
21847
+ try {
21848
+ const content = readFileSync(configPath, "utf-8");
21849
+ const rawConfig = import_comment_json.parse(content);
21850
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
21851
+ return { migrated: false, oldKeys: [] };
21852
+ }
21853
+ oldKeys = migrateRawConfig(rawConfig, configPath, logger);
21854
+ if (oldKeys.length === 0) {
21855
+ return { migrated: false, oldKeys: [] };
21856
+ }
21857
+ const serialized = `${import_comment_json.stringify(rawConfig, null, 2)}
21858
+ `;
21859
+ const originalComments = extractCommentsForPreservation(content);
21860
+ const droppedComments = originalComments.filter((comment) => !serialized.includes(comment.trim()));
21861
+ const nextContent = droppedComments.length > 0 ? `${droppedComments.join(`
21862
+ `)}
21863
+ ${serialized}` : serialized;
21864
+ tmpPath = `${configPath}.tmp.${process.pid}`;
21865
+ writeFileSync(tmpPath, nextContent, "utf-8");
21866
+ renameSync(tmpPath, configPath);
21867
+ logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
21868
+ return { migrated: true, oldKeys };
21869
+ } catch (err) {
21870
+ if (tmpPath) {
21871
+ try {
21872
+ unlinkSync(tmpPath);
21873
+ } catch {}
21874
+ }
21875
+ if (isWritableMigrationError(err)) {
21876
+ const errorMsg = err instanceof Error ? err.message : String(err);
21877
+ logger.warn(`Config migration could not write ${configPath} (${errorMsg}); using migrated config in memory`);
21878
+ return { migrated: oldKeys.length > 0, oldKeys };
21879
+ }
21880
+ return { migrated: false, oldKeys: [] };
21881
+ }
21882
+ }
21534
21883
  function detectConfigFile(basePath) {
21535
21884
  const jsoncPath = `${basePath}.jsonc`;
21536
21885
  const jsonPath = `${basePath}.json`;
@@ -21575,6 +21924,7 @@ function loadConfigFromPath(configPath) {
21575
21924
  }
21576
21925
  const content = readFileSync(configPath, "utf-8");
21577
21926
  const rawConfig = import_comment_json.parse(content);
21927
+ migrateRawConfig(rawConfig, configPath, { log, warn });
21578
21928
  const result = AftConfigSchema.safeParse(rawConfig);
21579
21929
  if (result.success) {
21580
21930
  log(`Config loaded from ${configPath}`);
@@ -21585,7 +21935,7 @@ function loadConfigFromPath(configPath) {
21585
21935
  return parseConfigPartially(rawConfig);
21586
21936
  } catch (err) {
21587
21937
  const errorMsg = err instanceof Error ? err.message : String(err);
21588
- error48(`Error loading config from ${configPath}: ${errorMsg}`);
21938
+ error(`Error loading config from ${configPath}: ${errorMsg}`);
21589
21939
  return null;
21590
21940
  }
21591
21941
  }
@@ -21623,6 +21973,25 @@ function mergeLspConfig(baseLsp, overrideLsp) {
21623
21973
  }
21624
21974
  return Object.fromEntries(Object.entries(lsp).filter(([, value]) => value !== undefined));
21625
21975
  }
21976
+ function mergeExperimentalConfig(baseExperimental, overrideExperimental) {
21977
+ const bash = {
21978
+ ...baseExperimental?.bash,
21979
+ ...overrideExperimental?.bash
21980
+ };
21981
+ const experimental = {
21982
+ ...baseExperimental,
21983
+ ...overrideExperimental
21984
+ };
21985
+ if (Object.values(bash).some((value) => value !== undefined)) {
21986
+ experimental.bash = bash;
21987
+ } else {
21988
+ delete experimental.bash;
21989
+ }
21990
+ if (Object.values(experimental).every((value) => value === undefined)) {
21991
+ return;
21992
+ }
21993
+ return Object.fromEntries(Object.entries(experimental).filter(([, value]) => value !== undefined));
21994
+ }
21626
21995
  function getProjectLspStrippedKeys(lsp) {
21627
21996
  if (!lsp) {
21628
21997
  return [];
@@ -21645,9 +22014,9 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
21645
22014
  "hoist_builtin_tools",
21646
22015
  "format_on_edit",
21647
22016
  "validate_on_edit",
21648
- "experimental_search_index",
21649
- "experimental_semantic_search",
21650
- "experimental_lsp_ty"
22017
+ "search_index",
22018
+ "semantic_search",
22019
+ "experimental"
21651
22020
  ]);
21652
22021
  function pickProjectSafeFields(override) {
21653
22022
  const safe = {};
@@ -21676,6 +22045,7 @@ function mergeConfigs(base, override) {
21676
22045
  const checker = { ...base.checker, ...override.checker };
21677
22046
  const semantic = mergeSemanticConfig(base.semantic, override.semantic);
21678
22047
  const lsp = mergeLspConfig(base.lsp, override.lsp);
22048
+ const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
21679
22049
  const safeOverride = pickProjectSafeFields(override);
21680
22050
  return {
21681
22051
  ...base,
@@ -21683,6 +22053,7 @@ function mergeConfigs(base, override) {
21683
22053
  ...Object.keys(formatter).length > 0 ? { formatter } : {},
21684
22054
  ...Object.keys(checker).length > 0 ? { checker } : {},
21685
22055
  ...lsp ? { lsp } : {},
22056
+ experimental,
21686
22057
  semantic,
21687
22058
  ...disabledTools.length > 0 ? { disabled_tools: [...new Set(disabledTools)] } : {}
21688
22059
  };
@@ -21698,9 +22069,13 @@ function getOpenCodeConfigDir() {
21698
22069
  function loadAftConfig(projectDirectory) {
21699
22070
  const configDir = getOpenCodeConfigDir();
21700
22071
  const userBasePath = join2(configDir, "aft");
22072
+ migrateAftConfigFile(`${userBasePath}.jsonc`);
22073
+ migrateAftConfigFile(`${userBasePath}.json`);
21701
22074
  const userDetected = detectConfigFile(userBasePath);
21702
22075
  const userConfigPath = userDetected.format !== "none" ? userDetected.path : `${userBasePath}.json`;
21703
22076
  const projectBasePath = join2(projectDirectory, ".opencode", "aft");
22077
+ migrateAftConfigFile(`${projectBasePath}.jsonc`);
22078
+ migrateAftConfigFile(`${projectBasePath}.json`);
21704
22079
  const projectDetected = detectConfigFile(projectBasePath);
21705
22080
  const projectConfigPath = projectDetected.format !== "none" ? projectDetected.path : `${projectBasePath}.json`;
21706
22081
  let config2 = loadConfigFromPath(userConfigPath) ?? {};
@@ -21723,7 +22098,7 @@ function loadAftConfig(projectDirectory) {
21723
22098
  }
21724
22099
 
21725
22100
  // src/downloader.ts
21726
- import { chmodSync, existsSync as existsSync2, mkdirSync, unlinkSync } from "fs";
22101
+ import { chmodSync, existsSync as existsSync2, mkdirSync, unlinkSync as unlinkSync2 } from "fs";
21727
22102
  import { homedir as homedir2 } from "os";
21728
22103
  import { join as join3 } from "path";
21729
22104
 
@@ -21765,12 +22140,12 @@ async function downloadBinary(version2) {
21765
22140
  const platformKey = `${process.platform}-${process.arch}`;
21766
22141
  const assetName = PLATFORM_ASSET_MAP[platformKey];
21767
22142
  if (!assetName) {
21768
- error48(`Unsupported platform: ${platformKey}`);
22143
+ error(`Unsupported platform: ${platformKey}`);
21769
22144
  return null;
21770
22145
  }
21771
22146
  const tag = version2 ?? await fetchLatestTag();
21772
22147
  if (!tag) {
21773
- error48("Could not determine latest release version.");
22148
+ error("Could not determine latest release version.");
21774
22149
  return null;
21775
22150
  }
21776
22151
  const versionedCacheDir = join3(getCacheDir(), tag);
@@ -21811,22 +22186,22 @@ async function downloadBinary(version2) {
21811
22186
  }
21812
22187
  log(`Checksum verified (SHA-256: ${actualHash.slice(0, 16)}...)`);
21813
22188
  const tmpPath = `${binaryPath}.tmp`;
21814
- const { writeFileSync } = await import("fs");
21815
- writeFileSync(tmpPath, Buffer.from(arrayBuffer));
22189
+ const { writeFileSync: writeFileSync2 } = await import("fs");
22190
+ writeFileSync2(tmpPath, Buffer.from(arrayBuffer));
21816
22191
  if (process.platform !== "win32") {
21817
22192
  chmodSync(tmpPath, 493);
21818
22193
  }
21819
- const { renameSync } = await import("fs");
21820
- renameSync(tmpPath, binaryPath);
22194
+ const { renameSync: renameSync2 } = await import("fs");
22195
+ renameSync2(tmpPath, binaryPath);
21821
22196
  log(`AFT binary ready at ${binaryPath}`);
21822
22197
  return binaryPath;
21823
22198
  } catch (err) {
21824
22199
  const msg = err instanceof Error ? err.message : String(err);
21825
- error48(`Failed to download AFT binary: ${msg}`);
22200
+ error(`Failed to download AFT binary: ${msg}`);
21826
22201
  const tmpPath = `${binaryPath}.tmp`;
21827
22202
  if (existsSync2(tmpPath)) {
21828
22203
  try {
21829
- unlinkSync(tmpPath);
22204
+ unlinkSync2(tmpPath);
21830
22205
  } catch {}
21831
22206
  }
21832
22207
  return null;
@@ -21875,12 +22250,12 @@ async function fetchLatestTag() {
21875
22250
  // src/hooks/auto-update-checker/cache.ts
21876
22251
  var import_comment_json3 = __toESM(require_src2(), 1);
21877
22252
  import { spawn } from "child_process";
21878
- import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
22253
+ import { existsSync as existsSync4, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
21879
22254
  import { basename, dirname as dirname2, join as join6 } from "path";
21880
22255
 
21881
22256
  // src/hooks/auto-update-checker/checker.ts
21882
22257
  var import_comment_json2 = __toESM(require_src2(), 1);
21883
- import { existsSync as existsSync3, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
22258
+ import { existsSync as existsSync3, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
21884
22259
  import { homedir as homedir4 } from "os";
21885
22260
  import { dirname, isAbsolute, join as join5, resolve } from "path";
21886
22261
  import { fileURLToPath } from "url";
@@ -22151,7 +22526,7 @@ function removeFromBunLock(installDir, packageName) {
22151
22526
  modified = true;
22152
22527
  }
22153
22528
  if (modified) {
22154
- writeFileSync2(lockPath, JSON.stringify(lock, null, 2));
22529
+ writeFileSync3(lockPath, JSON.stringify(lock, null, 2));
22155
22530
  log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
22156
22531
  }
22157
22532
  return modified;
@@ -22173,7 +22548,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
22173
22548
  return true;
22174
22549
  dependencies[packageName] = version2;
22175
22550
  nextPackageJson.dependencies = dependencies;
22176
- writeFileSync2(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
22551
+ writeFileSync3(packageJsonPath, JSON.stringify(nextPackageJson, null, 2));
22177
22552
  log(`[auto-update-checker] Updated dependency in package.json: ${packageName} \u2192 ${version2}`);
22178
22553
  return true;
22179
22554
  } catch (err) {
@@ -22398,8 +22773,8 @@ import {
22398
22773
  openSync,
22399
22774
  readFileSync as readFileSync4,
22400
22775
  statSync as statSync2,
22401
- unlinkSync as unlinkSync2,
22402
- writeFileSync as writeFileSync3
22776
+ unlinkSync as unlinkSync3,
22777
+ writeFileSync as writeFileSync4
22403
22778
  } from "fs";
22404
22779
  import { homedir as homedir5 } from "os";
22405
22780
  import { join as join7 } from "path";
@@ -22450,7 +22825,7 @@ function writeInstalledMetaIn(installDir, version2, sha256) {
22450
22825
  installedAt: new Date().toISOString(),
22451
22826
  ...sha256 ? { sha256 } : {}
22452
22827
  };
22453
- writeFileSync3(join7(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
22828
+ writeFileSync4(join7(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
22454
22829
  } catch (err) {
22455
22830
  log(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
22456
22831
  }
@@ -22490,7 +22865,7 @@ function acquireInstallLock(lockKey) {
22490
22865
  try {
22491
22866
  const fd = openSync(lock, "wx");
22492
22867
  try {
22493
- writeFileSync3(fd, `${process.pid}
22868
+ writeFileSync4(fd, `${process.pid}
22494
22869
  ${new Date().toISOString()}
22495
22870
  `);
22496
22871
  } finally {
@@ -22528,7 +22903,7 @@ ${new Date().toISOString()}
22528
22903
  }
22529
22904
  log(`[lsp] reclaiming install lock for ${lockKey} (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
22530
22905
  try {
22531
- unlinkSync2(lock);
22906
+ unlinkSync3(lock);
22532
22907
  } catch {}
22533
22908
  return tryClaim();
22534
22909
  }
@@ -22565,7 +22940,7 @@ function releaseInstallLock(lockKey) {
22565
22940
  return;
22566
22941
  }
22567
22942
  try {
22568
- unlinkSync2(lock);
22943
+ unlinkSync3(lock);
22569
22944
  } catch (unlinkErr) {
22570
22945
  const code = unlinkErr.code;
22571
22946
  if (code !== "ENOENT") {
@@ -22609,7 +22984,7 @@ function writeVersionCheck(npmPackage, latest) {
22609
22984
  last_checked: new Date().toISOString(),
22610
22985
  latest_eligible: latest
22611
22986
  };
22612
- writeFileSync3(file2, JSON.stringify(record2, null, 2));
22987
+ writeFileSync4(file2, JSON.stringify(record2, null, 2));
22613
22988
  }
22614
22989
  function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
22615
22990
  if (!record2)
@@ -22968,7 +23343,7 @@ function runInstall(spec, version2, cwd, signal) {
22968
23343
  }
22969
23344
  });
22970
23345
  child.on("error", (err) => {
22971
- error48(`[lsp] install ${target} failed to spawn: ${err}`);
23346
+ error(`[lsp] install ${target} failed to spawn: ${err}`);
22972
23347
  finish(false);
22973
23348
  });
22974
23349
  child.on("exit", (code) => {
@@ -22976,7 +23351,7 @@ function runInstall(spec, version2, cwd, signal) {
22976
23351
  log(`[lsp] installed ${target}`);
22977
23352
  finish(true);
22978
23353
  } else {
22979
- error48(`[lsp] install ${target} exited with code ${code}; last stderr:
23354
+ error(`[lsp] install ${target} exited with code ${code}; last stderr:
22980
23355
  ${stderrBuf.trim()}`);
22981
23356
  finish(false);
22982
23357
  }
@@ -23005,7 +23380,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
23005
23380
  return null;
23006
23381
  });
23007
23382
  if (currentHash && currentHash !== installedMeta.sha256) {
23008
- error48(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch \u2014 refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-install from scratch.`);
23383
+ error(`[lsp] ${spec.npm}@${version2}: TOFU sha256 mismatch \u2014 refusing to use ` + `tampered binary. Recorded ${installedMeta.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-install from scratch.`);
23009
23384
  return {
23010
23385
  started: false,
23011
23386
  reason: `TOFU sha256 mismatch on ${spec.npm}@${version2} \u2014 see plugin log`
@@ -23021,7 +23396,7 @@ async function ensureServerInstalled(spec, config2, fetchImpl, signal) {
23021
23396
  }
23022
23397
  }
23023
23398
  const ok = await runInstall(spec, version2, cachedPackageDir(spec.npm), signal).catch((err) => {
23024
- error48(`[lsp] background install ${spec.npm} crashed: ${err}`);
23399
+ error(`[lsp] background install ${spec.npm} crashed: ${err}`);
23025
23400
  return false;
23026
23401
  });
23027
23402
  if (!ok) {
@@ -23111,7 +23486,7 @@ function runAutoInstall(projectRoot, config2, fetchImpl = fetch) {
23111
23486
  installsStarted -= 1;
23112
23487
  const reason = err instanceof Error ? err.message : String(err);
23113
23488
  skipped.push({ id: spec.id, reason: `install error: ${reason}` });
23114
- error48(`[lsp] background install ${spec.npm} promise rejected: ${reason}`);
23489
+ error(`[lsp] background install ${spec.npm} promise rejected: ${reason}`);
23115
23490
  });
23116
23491
  installPromises.push(trackInFlightAutoInstall(controller, promise2));
23117
23492
  }
@@ -23138,10 +23513,10 @@ import {
23138
23513
  readdirSync as readdirSync2,
23139
23514
  readlinkSync,
23140
23515
  realpathSync,
23141
- renameSync,
23516
+ renameSync as renameSync2,
23142
23517
  rmSync as rmSync2,
23143
23518
  statSync as statSync4,
23144
- unlinkSync as unlinkSync3
23519
+ unlinkSync as unlinkSync4
23145
23520
  } from "fs";
23146
23521
  import { dirname as dirname3, join as join9, relative, resolve as resolve2 } from "path";
23147
23522
  import { Readable } from "stream";
@@ -23456,7 +23831,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
23456
23831
  await pipeline(nodeStream, createWriteStream(destPath), { signal: timeout.signal });
23457
23832
  } catch (err) {
23458
23833
  try {
23459
- unlinkSync3(destPath);
23834
+ unlinkSync4(destPath);
23460
23835
  } catch {}
23461
23836
  throw err;
23462
23837
  } finally {
@@ -23525,7 +23900,7 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
23525
23900
  try {
23526
23901
  rmSync2(destDir, { recursive: true, force: true });
23527
23902
  } catch {}
23528
- renameSync(stagingDir, destDir);
23903
+ renameSync2(stagingDir, destDir);
23529
23904
  } catch (err) {
23530
23905
  try {
23531
23906
  rmSync2(stagingDir, { recursive: true, force: true });
@@ -23583,16 +23958,16 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
23583
23958
  try {
23584
23959
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
23585
23960
  } catch (err) {
23586
- error48(`[lsp] download ${spec.id} failed: ${err}`);
23961
+ error(`[lsp] download ${spec.id} failed: ${err}`);
23587
23962
  return null;
23588
23963
  }
23589
23964
  let archiveSha256;
23590
23965
  try {
23591
23966
  archiveSha256 = await sha256OfFile(archivePath);
23592
23967
  } catch (err) {
23593
- error48(`[lsp] hash ${spec.id} failed: ${err}`);
23968
+ error(`[lsp] hash ${spec.id} failed: ${err}`);
23594
23969
  try {
23595
- unlinkSync3(archivePath);
23970
+ unlinkSync4(archivePath);
23596
23971
  } catch {}
23597
23972
  return null;
23598
23973
  }
@@ -23600,9 +23975,9 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
23600
23975
  const previousMeta = readInstalledMetaIn(ghPackageDir(spec));
23601
23976
  if (previousMeta && previousMeta.version === tag && previousMeta.sha256) {
23602
23977
  if (previousMeta.sha256 !== archiveSha256) {
23603
- error48(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch \u2014 refusing install. ` + `Previously installed sha256=${previousMeta.sha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
23978
+ error(`[lsp] ${spec.id} ${tag}: TOFU sha256 mismatch \u2014 refusing install. ` + `Previously installed sha256=${previousMeta.sha256}, downloaded sha256=${archiveSha256}. ` + `This means the published release for tag ${tag} changed. Investigate before proceeding. ` + `Run \`aft doctor --clear\` to wipe the cache and force a fresh install if you've verified the change.`);
23604
23979
  try {
23605
- unlinkSync3(archivePath);
23980
+ unlinkSync4(archivePath);
23606
23981
  } catch {}
23607
23982
  return null;
23608
23983
  }
@@ -23610,16 +23985,16 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
23610
23985
  try {
23611
23986
  extractArchiveSafely(archivePath, extractDir, expected.archive);
23612
23987
  } catch (err) {
23613
- error48(`[lsp] extract ${spec.id} failed: ${err}`);
23988
+ error(`[lsp] extract ${spec.id} failed: ${err}`);
23614
23989
  return null;
23615
23990
  } finally {
23616
23991
  try {
23617
- unlinkSync3(archivePath);
23992
+ unlinkSync4(archivePath);
23618
23993
  } catch {}
23619
23994
  }
23620
23995
  const innerBinaryPath = join9(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
23621
23996
  if (!existsSync6(innerBinaryPath)) {
23622
- error48(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
23997
+ error(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
23623
23998
  return null;
23624
23999
  }
23625
24000
  const targetBinary = ghBinaryPath(spec, platform2);
@@ -23631,7 +24006,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
23631
24006
  chmodSync2(targetBinary, 493);
23632
24007
  }
23633
24008
  } catch (err) {
23634
- error48(`[lsp] ${spec.id}: failed to place binary at ${targetBinary}: ${err}`);
24009
+ error(`[lsp] ${spec.id}: failed to place binary at ${targetBinary}: ${err}`);
23635
24010
  return null;
23636
24011
  }
23637
24012
  log(`[lsp] installed ${spec.id} ${tag} at ${targetBinary}`);
@@ -23662,7 +24037,7 @@ async function ensureGithubInstalled(spec, config2, fetchImpl, platform2, arch,
23662
24037
  }
23663
24038
  }
23664
24039
  const archiveSha256 = await downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl, signal).catch((err) => {
23665
- error48(`[lsp] github install ${spec.id} crashed: ${err}`);
24040
+ error(`[lsp] github install ${spec.id} crashed: ${err}`);
23666
24041
  return null;
23667
24042
  });
23668
24043
  if (!archiveSha256) {
@@ -23739,7 +24114,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
23739
24114
  installsStarted -= 1;
23740
24115
  const reason = err instanceof Error ? err.message : String(err);
23741
24116
  skipped.push({ id: spec.id, reason: `install error: ${reason}` });
23742
- error48(`[lsp] github install ${spec.id} promise rejected: ${reason}`);
24117
+ error(`[lsp] github install ${spec.id} promise rejected: ${reason}`);
23743
24118
  });
23744
24119
  installPromises.push(trackInFlightGithubInstall(controller, promise2));
23745
24120
  }
@@ -23859,7 +24234,7 @@ function normalizeToolMap(tools) {
23859
24234
  }
23860
24235
 
23861
24236
  // src/notifications.ts
23862
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
24237
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
23863
24238
  import { homedir as homedir6, platform as platform2 } from "os";
23864
24239
  import { join as join10 } from "path";
23865
24240
  function isTuiMode() {
@@ -24021,7 +24396,7 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
24021
24396
  if (storageDir) {
24022
24397
  try {
24023
24398
  mkdirSync4(storageDir, { recursive: true });
24024
- writeFileSync4(join10(storageDir, "last_announced_version"), version2);
24399
+ writeFileSync5(join10(storageDir, "last_announced_version"), version2);
24025
24400
  } catch {}
24026
24401
  }
24027
24402
  }
@@ -24048,7 +24423,7 @@ function writeWarnedTools(storageDir, warned) {
24048
24423
  try {
24049
24424
  mkdirSync4(storageDir, { recursive: true });
24050
24425
  const warnedToolsPath = join10(storageDir, WARNED_TOOLS_FILE);
24051
- writeFileSync4(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
24426
+ writeFileSync5(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
24052
24427
  `);
24053
24428
  } catch {}
24054
24429
  }
@@ -24157,8 +24532,8 @@ import {
24157
24532
  rmSync as rmSync3,
24158
24533
  statSync as statSync5,
24159
24534
  symlinkSync,
24160
- unlinkSync as unlinkSync4,
24161
- writeFileSync as writeFileSync5
24535
+ unlinkSync as unlinkSync5,
24536
+ writeFileSync as writeFileSync6
24162
24537
  } from "fs";
24163
24538
  import { dirname as dirname4, join as join11, relative as relative2, resolve as resolve3 } from "path";
24164
24539
  import { Readable as Readable2 } from "stream";
@@ -24231,7 +24606,7 @@ async function ensureOnnxRuntime(storageDir) {
24231
24606
  try {
24232
24607
  const currentHash = sha256File(libPath);
24233
24608
  if (currentHash !== meta3.sha256) {
24234
- error48(`ONNX Runtime at ${ortDir}: TOFU sha256 mismatch \u2014 refusing to use ` + `tampered binary. Recorded ${meta3.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-download from scratch.`);
24609
+ error(`ONNX Runtime at ${ortDir}: TOFU sha256 mismatch \u2014 refusing to use ` + `tampered binary. Recorded ${meta3.sha256}, current ${currentHash}. ` + `Run \`aft doctor --clear\` to re-download from scratch.`);
24235
24610
  } else {
24236
24611
  log(`ONNX Runtime found at ${ortDir} (TOFU verified)`);
24237
24612
  return ortDir;
@@ -24316,7 +24691,7 @@ async function downloadFileWithCap(url2, destPath) {
24316
24691
  await pipeline2(nodeStream, createWriteStream2(destPath), { signal: controller.signal });
24317
24692
  } catch (err) {
24318
24693
  try {
24319
- unlinkSync4(destPath);
24694
+ unlinkSync5(destPath);
24320
24695
  } catch {}
24321
24696
  throw err;
24322
24697
  } finally {
@@ -24377,7 +24752,7 @@ async function downloadOnnxRuntime(info, targetDir) {
24377
24752
  await extractZipArchive(archivePath, tmpDir);
24378
24753
  }
24379
24754
  try {
24380
- unlinkSync4(archivePath);
24755
+ unlinkSync5(archivePath);
24381
24756
  } catch {}
24382
24757
  validateExtractedTree(tmpDir);
24383
24758
  const extractedDir = join11(tmpDir, info.assetName, "lib");
@@ -24418,7 +24793,7 @@ async function downloadOnnxRuntime(info, targetDir) {
24418
24793
  for (const link of symlinks) {
24419
24794
  const dst = join11(targetDir, link.name);
24420
24795
  try {
24421
- unlinkSync4(dst);
24796
+ unlinkSync5(dst);
24422
24797
  } catch {}
24423
24798
  symlinkSync(link.target, dst);
24424
24799
  }
@@ -24434,7 +24809,7 @@ async function downloadOnnxRuntime(info, targetDir) {
24434
24809
  log(`ONNX Runtime v${ORT_VERSION} installed to ${targetDir}`);
24435
24810
  return targetDir;
24436
24811
  } catch (err) {
24437
- error48(`Failed to download ONNX Runtime: ${err}`);
24812
+ error(`Failed to download ONNX Runtime: ${err}`);
24438
24813
  try {
24439
24814
  rmSync3(tmpDir, { recursive: true, force: true });
24440
24815
  } catch {}
@@ -24465,7 +24840,7 @@ function writeOnnxInstalledMeta(installDir, version2, sha256, archiveSha256) {
24465
24840
  ...sha256 ? { sha256 } : {},
24466
24841
  archiveSha256
24467
24842
  };
24468
- writeFileSync5(join11(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
24843
+ writeFileSync6(join11(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
24469
24844
  } catch (err) {
24470
24845
  log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
24471
24846
  }
@@ -24499,7 +24874,7 @@ function acquireLock(lockPath2) {
24499
24874
  try {
24500
24875
  const fd = openSync2(lockPath2, "wx");
24501
24876
  try {
24502
- writeFileSync5(fd, `${process.pid}
24877
+ writeFileSync6(fd, `${process.pid}
24503
24878
  ${new Date().toISOString()}
24504
24879
  `);
24505
24880
  } finally {
@@ -24537,7 +24912,7 @@ ${new Date().toISOString()}
24537
24912
  }
24538
24913
  log(`[onnx] reclaiming install lock (owner_pid=${owningPid ?? "unknown"}, alive=${ownerAlive}, age_ms=${age})`);
24539
24914
  try {
24540
- unlinkSync4(lockPath2);
24915
+ unlinkSync5(lockPath2);
24541
24916
  } catch {}
24542
24917
  return tryClaim();
24543
24918
  }
@@ -24562,7 +24937,7 @@ function releaseLock(lockPath2) {
24562
24937
  return;
24563
24938
  }
24564
24939
  try {
24565
- unlinkSync4(lockPath2);
24940
+ unlinkSync5(lockPath2);
24566
24941
  } catch (unlinkErr) {
24567
24942
  const code = unlinkErr.code;
24568
24943
  if (code !== "ENOENT") {
@@ -24698,6 +25073,9 @@ class BinaryBridge {
24698
25073
  if (this._shuttingDown) {
24699
25074
  throw new Error(`[aft-plugin] Bridge is shutting down, cannot send "${command}"`);
24700
25075
  }
25076
+ if (Object.hasOwn(params, "id")) {
25077
+ throw new Error("params cannot contain reserved key 'id'");
25078
+ }
24701
25079
  this.ensureSpawned();
24702
25080
  if (!this.configured) {
24703
25081
  if (command !== "configure" && command !== "version") {
@@ -24726,18 +25104,37 @@ class BinaryBridge {
24726
25104
  }
24727
25105
  }
24728
25106
  const id = String(this.nextId++);
24729
- const request = { id, command, ...params };
25107
+ let request;
25108
+ if (Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
25109
+ const nested = { ...params };
25110
+ const reserved = {};
25111
+ for (const key of ["session_id", "lsp_hints"]) {
25112
+ if (Object.hasOwn(nested, key)) {
25113
+ reserved[key] = nested[key];
25114
+ delete nested[key];
25115
+ }
25116
+ }
25117
+ request = { id, command, ...reserved, params: nested };
25118
+ } else {
25119
+ request = { id, command, ...params };
25120
+ }
24730
25121
  const line = `${JSON.stringify(request)}
24731
25122
  `;
24732
- const effectiveTimeoutMs = options?.timeoutMs ?? this.timeoutMs;
25123
+ const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
25124
+ const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
24733
25125
  return new Promise((resolve4, reject) => {
24734
25126
  const timer = setTimeout(() => {
24735
25127
  this.pending.delete(id);
24736
- warn(`Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms \u2014 restarting bridge`);
25128
+ const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms \u2014 restarting bridge`;
25129
+ if (requestSessionId) {
25130
+ sessionWarn(requestSessionId, timeoutMsg);
25131
+ } else {
25132
+ warn(timeoutMsg);
25133
+ }
24737
25134
  reject(new Error(`[aft-plugin] Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
24738
25135
  this.handleTimeout();
24739
25136
  }, effectiveTimeoutMs);
24740
- this.pending.set(id, { resolve: resolve4, reject, timer });
25137
+ this.pending.set(id, { resolve: resolve4, reject, timer, onProgress: options?.onProgress });
24741
25138
  if (!this.process?.stdin?.writable) {
24742
25139
  this.pending.delete(id);
24743
25140
  clearTimeout(timer);
@@ -24865,7 +25262,7 @@ class BinaryBridge {
24865
25262
  child.on("error", (err) => {
24866
25263
  if (this.process !== currentChild)
24867
25264
  return;
24868
- error48(`Process error: ${err.message}${this.formatStderrTail()}`);
25265
+ error(`Process error: ${err.message}${this.formatStderrTail()}`);
24869
25266
  this.handleCrash();
24870
25267
  });
24871
25268
  child.on("exit", (code, signal) => {
@@ -24917,6 +25314,29 @@ class BinaryBridge {
24917
25314
  continue;
24918
25315
  try {
24919
25316
  const response = JSON.parse(line);
25317
+ if (response.type === "progress") {
25318
+ const requestId = response.request_id;
25319
+ const entry = requestId ? this.pending.get(requestId) : undefined;
25320
+ const kind = response.kind === "stderr" ? "stderr" : "stdout";
25321
+ const text = typeof response.chunk === "string" ? response.chunk : "";
25322
+ entry?.onProgress?.({ kind, text });
25323
+ continue;
25324
+ }
25325
+ if (response.type === "permission_ask") {
25326
+ const requestId = response.request_id;
25327
+ const entry = requestId ? this.pending.get(requestId) : undefined;
25328
+ if (requestId && entry) {
25329
+ this.pending.delete(requestId);
25330
+ clearTimeout(entry.timer);
25331
+ entry.resolve({
25332
+ success: false,
25333
+ code: "permission_required",
25334
+ message: "bash command requires permission",
25335
+ asks: response.asks
25336
+ });
25337
+ }
25338
+ continue;
25339
+ }
24920
25340
  const id = response.id;
24921
25341
  if (id && this.pending.has(id)) {
24922
25342
  const entry = this.pending.get(id);
@@ -24941,7 +25361,11 @@ class BinaryBridge {
24941
25361
  this.configured = false;
24942
25362
  const tail = this.formatStderrTail();
24943
25363
  this.stderrTail = [];
24944
- this.rejectAllPending(new Error(`[aft-plugin] Bridge restarted after timeout${tail}`));
25364
+ if (tail) {
25365
+ error(`Bridge killed after timeout.${tail}`);
25366
+ } else {
25367
+ warn(`Bridge killed after timeout (see ${getLogFilePath()})`);
25368
+ }
24945
25369
  }
24946
25370
  handleCrash(cause) {
24947
25371
  const proc = this.process;
@@ -24952,7 +25376,10 @@ class BinaryBridge {
24952
25376
  this.clearRestartResetTimer();
24953
25377
  this.configured = false;
24954
25378
  const tail = this.formatStderrTail();
24955
- this.rejectAllPending(new Error(`[aft-plugin] Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}${tail}`));
25379
+ if (tail) {
25380
+ error(`Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""}.${tail}`);
25381
+ }
25382
+ this.rejectAllPending(new Error(`[aft-plugin] Binary crashed (restarts: ${this._restartCount})${cause ? `: ${cause.message}` : ""} (see ${getLogFilePath()})`));
24956
25383
  if (this._restartCount < this.maxRestarts) {
24957
25384
  const delay = 100 * 2 ** this._restartCount;
24958
25385
  this._restartCount++;
@@ -24962,13 +25389,13 @@ class BinaryBridge {
24962
25389
  try {
24963
25390
  this.spawnProcess();
24964
25391
  } catch (err) {
24965
- error48(`Failed to restart: ${err.message}`);
25392
+ error(`Failed to restart: ${err.message}`);
24966
25393
  }
24967
25394
  }
24968
25395
  }, delay);
24969
25396
  this.scheduleRestartCountReset();
24970
25397
  } else {
24971
- error48(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
25398
+ error(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
24972
25399
  this.scheduleRestartCountReset();
24973
25400
  }
24974
25401
  }
@@ -25057,7 +25484,7 @@ class BridgePool {
25057
25484
  const now = Date.now();
25058
25485
  for (const [dir, entry] of this.bridges) {
25059
25486
  if (now - entry.lastUsed > this.idleTimeoutMs) {
25060
- entry.bridge.shutdown().catch((err) => error48("cleanup shutdown failed:", err));
25487
+ entry.bridge.shutdown().catch((err) => error("cleanup shutdown failed:", err));
25061
25488
  this.bridges.delete(dir);
25062
25489
  }
25063
25490
  }
@@ -25073,7 +25500,7 @@ class BridgePool {
25073
25500
  }
25074
25501
  if (oldestDir) {
25075
25502
  const entry = this.bridges.get(oldestDir);
25076
- entry?.bridge.shutdown().catch((err) => error48("eviction shutdown failed:", err));
25503
+ entry?.bridge.shutdown().catch((err) => error("eviction shutdown failed:", err));
25077
25504
  this.bridges.delete(oldestDir);
25078
25505
  }
25079
25506
  }
@@ -25103,7 +25530,7 @@ function normalizeKey(projectRoot) {
25103
25530
 
25104
25531
  // src/resolver.ts
25105
25532
  import { execSync, spawnSync } from "child_process";
25106
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, renameSync as renameSync2 } from "fs";
25533
+ import { chmodSync as chmodSync3, copyFileSync as copyFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, renameSync as renameSync3 } from "fs";
25107
25534
  import { createRequire } from "module";
25108
25535
  import { homedir as homedir8 } from "os";
25109
25536
  import { join as join13 } from "path";
@@ -25131,7 +25558,7 @@ function copyToVersionedCache(npmBinaryPath) {
25131
25558
  if (process.platform !== "win32") {
25132
25559
  chmodSync3(tmpPath, 493);
25133
25560
  }
25134
- renameSync2(tmpPath, cachedPath);
25561
+ renameSync3(tmpPath, cachedPath);
25135
25562
  log(`Copied npm binary to versioned cache: ${cachedPath}`);
25136
25563
  return cachedPath;
25137
25564
  } catch (err) {
@@ -25221,7 +25648,7 @@ async function findBinary() {
25221
25648
 
25222
25649
  // src/shared/rpc-server.ts
25223
25650
  import { randomBytes as randomBytes2 } from "crypto";
25224
- import { mkdirSync as mkdirSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "fs";
25651
+ import { mkdirSync as mkdirSync7, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync7 } from "fs";
25225
25652
  import { createServer } from "http";
25226
25653
  import { dirname as dirname5 } from "path";
25227
25654
 
@@ -25270,8 +25697,8 @@ class AftRpcServer {
25270
25697
  const dir = dirname5(this.portFilePath);
25271
25698
  mkdirSync7(dir, { recursive: true });
25272
25699
  const tmpPath = `${this.portFilePath}.tmp`;
25273
- writeFileSync6(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
25274
- renameSync3(tmpPath, this.portFilePath);
25700
+ writeFileSync7(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
25701
+ renameSync4(tmpPath, this.portFilePath);
25275
25702
  log(`RPC server listening on 127.0.0.1:${this.port}`);
25276
25703
  } catch (err) {
25277
25704
  warn(`Failed to write RPC port file: ${err}`);
@@ -25288,7 +25715,7 @@ class AftRpcServer {
25288
25715
  }
25289
25716
  this.token = null;
25290
25717
  try {
25291
- unlinkSync5(this.portFilePath);
25718
+ unlinkSync6(this.portFilePath);
25292
25719
  } catch {}
25293
25720
  }
25294
25721
  dispatch(req, res) {
@@ -25418,8 +25845,8 @@ function coerceAftStatus(response) {
25418
25845
  format_on_edit: readBoolean(features.format_on_edit),
25419
25846
  validate_on_edit: readString(features.validate_on_edit, "off"),
25420
25847
  restrict_to_project_root: readBoolean(features.restrict_to_project_root),
25421
- experimental_search_index: readBoolean(features.experimental_search_index),
25422
- experimental_semantic_search: readBoolean(features.experimental_semantic_search)
25848
+ search_index: readBoolean(features.search_index ?? features.experimental_search_index),
25849
+ semantic_search: readBoolean(features.semantic_search ?? features.experimental_semantic_search)
25423
25850
  },
25424
25851
  search_index: {
25425
25852
  status: readString(searchIndex.status, "unknown"),
@@ -25466,8 +25893,8 @@ function formatStatusMarkdown(status) {
25466
25893
  "",
25467
25894
  "### Enabled features",
25468
25895
  `- \`format_on_edit\`: ${formatFlag(status.features.format_on_edit)}`,
25469
- `- \`experimental_search_index\`: ${formatFlag(status.features.experimental_search_index)}`,
25470
- `- \`experimental_semantic_search\`: ${formatFlag(status.features.experimental_semantic_search)}`,
25896
+ `- \`search_index\`: ${formatFlag(status.features.search_index)}`,
25897
+ `- \`semantic_search\`: ${formatFlag(status.features.semantic_search)}`,
25471
25898
  "",
25472
25899
  "### Search index",
25473
25900
  `- **Status:** \`${status.search_index.status}\``,
@@ -25510,7 +25937,7 @@ function formatStatusMarkdown(status) {
25510
25937
 
25511
25938
  // src/shared/tui-config.ts
25512
25939
  var import_comment_json4 = __toESM(require_src2(), 1);
25513
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
25940
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
25514
25941
  import { dirname as dirname6, join as join16 } from "path";
25515
25942
 
25516
25943
  // src/shared/opencode-config-dir.ts
@@ -25567,7 +25994,7 @@ function ensureTuiPluginEntry() {
25567
25994
  plugins.push(PLUGIN_ENTRY);
25568
25995
  config2.plugin = plugins;
25569
25996
  mkdirSync8(dirname6(configPath), { recursive: true });
25570
- writeFileSync7(configPath, `${import_comment_json4.stringify(config2, null, 2)}
25997
+ writeFileSync8(configPath, `${import_comment_json4.stringify(config2, null, 2)}
25571
25998
  `);
25572
25999
  log(`[aft-plugin] added TUI plugin entry to ${configPath}`);
25573
26000
  return true;
@@ -25585,8 +26012,8 @@ import {
25585
26012
  mkdirSync as mkdirSync9,
25586
26013
  readdirSync as readdirSync4,
25587
26014
  readFileSync as readFileSync8,
25588
- unlinkSync as unlinkSync6,
25589
- writeFileSync as writeFileSync8
26015
+ unlinkSync as unlinkSync7,
26016
+ writeFileSync as writeFileSync9
25590
26017
  } from "fs";
25591
26018
  import { isIP } from "net";
25592
26019
  import { join as join17 } from "path";
@@ -25816,9 +26243,9 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
25816
26243
  const body = Buffer.concat(chunks);
25817
26244
  const contentFile = contentPath(storageDir, hash2, extension);
25818
26245
  const tmpContent = `${contentFile}.tmp-${process.pid}`;
25819
- writeFileSync8(tmpContent, body);
25820
- const { renameSync: renameSync4 } = await import("fs");
25821
- renameSync4(tmpContent, contentFile);
26246
+ writeFileSync9(tmpContent, body);
26247
+ const { renameSync: renameSync5 } = await import("fs");
26248
+ renameSync5(tmpContent, contentFile);
25822
26249
  const meta3 = {
25823
26250
  url: url2,
25824
26251
  contentType,
@@ -25826,8 +26253,8 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
25826
26253
  fetchedAt: Date.now()
25827
26254
  };
25828
26255
  const tmpMeta = `${metaFile}.tmp-${process.pid}`;
25829
- writeFileSync8(tmpMeta, JSON.stringify(meta3));
25830
- renameSync4(tmpMeta, metaFile);
26256
+ writeFileSync9(tmpMeta, JSON.stringify(meta3));
26257
+ renameSync5(tmpMeta, metaFile);
25831
26258
  log(`URL cached (${total} bytes): ${url2}`);
25832
26259
  return contentFile;
25833
26260
  }
@@ -25848,13 +26275,13 @@ function cleanupUrlCache(storageDir) {
25848
26275
  const hash2 = entry.slice(0, -".meta.json".length);
25849
26276
  const content = contentPath(storageDir, hash2, meta3.extension);
25850
26277
  if (existsSync11(content))
25851
- unlinkSync6(content);
25852
- unlinkSync6(metaFile);
26278
+ unlinkSync7(content);
26279
+ unlinkSync7(metaFile);
25853
26280
  removed++;
25854
26281
  }
25855
26282
  } catch {
25856
26283
  try {
25857
- unlinkSync6(metaFile);
26284
+ unlinkSync7(metaFile);
25858
26285
  removed++;
25859
26286
  } catch {}
25860
26287
  }
@@ -25950,7 +26377,7 @@ function projectRootFor(runtime) {
25950
26377
  function bridgeFor(ctx, runtime) {
25951
26378
  return ctx.pool.getBridge(projectRootFor(runtime));
25952
26379
  }
25953
- function callBridge(ctx, runtime, command, params = {}) {
26380
+ function callBridge(ctx, runtime, command, params = {}, options) {
25954
26381
  const merged = { ...params };
25955
26382
  if (runtime.sessionID) {
25956
26383
  merged.session_id = runtime.sessionID;
@@ -25958,9 +26385,13 @@ function callBridge(ctx, runtime, command, params = {}) {
25958
26385
  const timeoutMs = timeoutForCommand(command);
25959
26386
  const sendOptions = {
25960
26387
  ...timeoutMs !== undefined ? { timeoutMs } : {},
25961
- configureWarningClient: ctx.client
26388
+ configureWarningClient: ctx.client,
26389
+ ...options
25962
26390
  };
25963
- return bridgeFor(ctx, runtime).send(command, merged, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
26391
+ return bridgeFor(ctx, runtime).send(command, merged, Object.keys(sendOptions).length > 0 ? sendOptions : undefined).then((response) => {
26392
+ ingestBgCompletions(runtime.sessionID, response.bg_completions);
26393
+ return response;
26394
+ });
25964
26395
  }
25965
26396
 
25966
26397
  // src/tools/permissions.ts
@@ -26139,7 +26570,7 @@ ${hint}`;
26139
26570
 
26140
26571
  ` + `**Warning: This tool modifies files directly.** Use dryRun=true to preview. Consider creating an aft_safety checkpoint before bulk replacements.
26141
26572
 
26142
- ` + "Returns: Text summary \u2014 'Replaced N match(es) across M file(s)' (or '[DRY RUN] Would replace...') followed by file:line blocks with before/after text.",
26573
+ ` + "Returns: Text summary \u2014 'Replaced N match(es) across M file(s)' (or '[DRY RUN] Would replace...') followed by per-file output. In dry-run mode, each file is shown with its unified diff so you can verify the rewrite before applying (e.g. catch literal $$$ from anonymous-variadic typos). Diff preview is capped at 8KB total; remaining files are summarized.",
26143
26574
  args: {
26144
26575
  pattern: z2.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
26145
26576
  rewrite: z2.string().describe("Replacement pattern (can use $VAR from pattern)"),
@@ -26205,7 +26636,36 @@ ${data.scope_warnings.map((w) => ` ${w}`).join(`
26205
26636
  ` : `Replaced ${matchCount} match(es) in ${filesWithMatches} file(s) (${filesSearched} searched)
26206
26637
 
26207
26638
  `;
26208
- if (data.matches) {
26639
+ if (isDryRun && data.files && data.files.length > 0) {
26640
+ const MAX_DIFF_BYTES = 8 * 1024;
26641
+ let used = 0;
26642
+ let filesShown = 0;
26643
+ for (const f of data.files) {
26644
+ const relFile = f.file ?? "unknown";
26645
+ const reps = f.replacements ?? 0;
26646
+ const diff = f.diff ?? "";
26647
+ if (used + diff.length > MAX_DIFF_BYTES) {
26648
+ const remaining = data.files.length - filesShown;
26649
+ if (remaining > 0) {
26650
+ output += `
26651
+ ... (${remaining} more file(s) omitted from preview to stay under ${MAX_DIFF_BYTES / 1024}KB; total ${matchCount} replacements across ${filesWithMatches} files)
26652
+ `;
26653
+ }
26654
+ break;
26655
+ }
26656
+ output += `${relFile} (${reps} replacement${reps === 1 ? "" : "s"}):
26657
+ `;
26658
+ output += diff;
26659
+ if (!diff.endsWith(`
26660
+ `))
26661
+ output += `
26662
+ `;
26663
+ output += `
26664
+ `;
26665
+ used += diff.length;
26666
+ filesShown += 1;
26667
+ }
26668
+ } else if (data.matches) {
26209
26669
  for (const m of data.matches) {
26210
26670
  const relFile = m.file ?? "unknown";
26211
26671
  const line = m.line ?? 0;
@@ -26218,6 +26678,13 @@ ${data.scope_warnings.map((w) => ` ${w}`).join(`
26218
26678
  `;
26219
26679
  }
26220
26680
  output += `
26681
+ `;
26682
+ }
26683
+ } else if (data.files && data.files.length > 0) {
26684
+ for (const f of data.files) {
26685
+ const relFile = f.file ?? "unknown";
26686
+ const reps = f.replacements ?? 0;
26687
+ output += ` ${relFile}: ${reps} replacement${reps === 1 ? "" : "s"}
26221
26688
  `;
26222
26689
  }
26223
26690
  }
@@ -26253,7 +26720,7 @@ function conflictTools(ctx) {
26253
26720
  // src/tools/hoisted.ts
26254
26721
  import * as fs3 from "fs";
26255
26722
  import * as path4 from "path";
26256
- import { tool as tool3 } from "@opencode-ai/plugin";
26723
+ import { tool as tool4 } from "@opencode-ai/plugin";
26257
26724
 
26258
26725
  // src/patch-parser.ts
26259
26726
  function stripHeredoc(input) {
@@ -26388,6 +26855,9 @@ function parsePatch(patchText) {
26388
26855
  function normalizeUnicode(str) {
26389
26856
  return str.replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, "-").replace(/\u2026/g, "...").replace(/\u00A0/g, " ");
26390
26857
  }
26858
+ function normalizeIndent(str) {
26859
+ return str.replace(/^[\t ]+/, (m) => " ".repeat(m.length));
26860
+ }
26391
26861
  function tryMatch(lines, pattern, startIndex, compare, eof) {
26392
26862
  if (eof) {
26393
26863
  const fromEnd = lines.length - pattern.length;
@@ -26416,19 +26886,58 @@ function tryMatch(lines, pattern, startIndex, compare, eof) {
26416
26886
  }
26417
26887
  return -1;
26418
26888
  }
26419
- function seekSequence(lines, pattern, startIndex, eof = false) {
26889
+ function seekSequenceTiered(lines, pattern, startIndex, eof = false) {
26420
26890
  if (pattern.length === 0)
26421
- return -1;
26891
+ return null;
26422
26892
  const exact = tryMatch(lines, pattern, startIndex, (a, b) => a === b, eof);
26423
26893
  if (exact !== -1)
26424
- return exact;
26894
+ return { found: exact, tier: "exact" };
26425
26895
  const rstrip = tryMatch(lines, pattern, startIndex, (a, b) => a.trimEnd() === b.trimEnd(), eof);
26426
26896
  if (rstrip !== -1)
26427
- return rstrip;
26897
+ return { found: rstrip, tier: "rstrip" };
26428
26898
  const trim = tryMatch(lines, pattern, startIndex, (a, b) => a.trim() === b.trim(), eof);
26429
26899
  if (trim !== -1)
26430
- return trim;
26431
- return tryMatch(lines, pattern, startIndex, (a, b) => normalizeUnicode(a.trim()) === normalizeUnicode(b.trim()), eof);
26900
+ return { found: trim, tier: "trim" };
26901
+ const indent = tryMatch(lines, pattern, startIndex, (a, b) => normalizeIndent(a).trimEnd() === normalizeIndent(b).trimEnd(), eof);
26902
+ if (indent !== -1)
26903
+ return { found: indent, tier: "indent" };
26904
+ const unicode = tryMatch(lines, pattern, startIndex, (a, b) => normalizeUnicode(a.trim()) === normalizeUnicode(b.trim()), eof);
26905
+ if (unicode !== -1)
26906
+ return { found: unicode, tier: "unicode" };
26907
+ return null;
26908
+ }
26909
+ function seekSequence(lines, pattern, startIndex, eof = false) {
26910
+ const r = seekSequenceTiered(lines, pattern, startIndex, eof);
26911
+ return r ? r.found : -1;
26912
+ }
26913
+ function findClosestPartialMatch(lines, pattern) {
26914
+ if (pattern.length === 0 || lines.length === 0)
26915
+ return null;
26916
+ const compareAny = (a, b) => a === b || a.trimEnd() === b.trimEnd() || a.trim() === b.trim() || normalizeIndent(a).trimEnd() === normalizeIndent(b).trimEnd() || normalizeUnicode(a.trim()) === normalizeUnicode(b.trim());
26917
+ const candidates = [];
26918
+ for (let i = 0;i < lines.length && candidates.length < 16; i++) {
26919
+ if (compareAny(lines[i], pattern[0]))
26920
+ candidates.push(i);
26921
+ }
26922
+ if (candidates.length === 0)
26923
+ return null;
26924
+ let best = { lineNumber: -1, matchedLines: 0, firstDivergence: -1 };
26925
+ for (const start of candidates) {
26926
+ let matched = 0;
26927
+ for (let j = 0;j < pattern.length && start + j < lines.length; j++) {
26928
+ if (!compareAny(lines[start + j], pattern[j]))
26929
+ break;
26930
+ matched++;
26931
+ }
26932
+ if (matched > best.matchedLines) {
26933
+ best = {
26934
+ lineNumber: start + 1,
26935
+ matchedLines: matched,
26936
+ firstDivergence: matched
26937
+ };
26938
+ }
26939
+ }
26940
+ return best.lineNumber === -1 ? null : best;
26432
26941
  }
26433
26942
  function applyUpdateChunks(originalContent, filePath, chunks) {
26434
26943
  const originalLines = originalContent.split(`
@@ -26465,9 +26974,30 @@ function applyUpdateChunks(originalContent, filePath, chunks) {
26465
26974
  replacements.push([found, pattern.length, newSlice]);
26466
26975
  lineIndex = found + pattern.length;
26467
26976
  } else {
26977
+ const newSliceTrimmed = newSlice.filter((line) => line.trim().length > 0);
26978
+ const alreadyApplied = newSliceTrimmed.length > 0 && seekSequence(originalLines, newSliceTrimmed, 0, chunk.is_end_of_file) !== -1;
26979
+ const closest = findClosestPartialMatch(originalLines, pattern);
26980
+ let closestHint = "";
26981
+ if (closest && closest.matchedLines > 0) {
26982
+ const fileLineNo = closest.lineNumber + closest.firstDivergence;
26983
+ const expectedLine = pattern[closest.firstDivergence];
26984
+ const actualLine = fileLineNo - 1 < originalLines.length ? originalLines[fileLineNo - 1] : "<EOF>";
26985
+ closestHint = `
26986
+
26987
+ Closest match starts at line ${closest.lineNumber} ` + `(${closest.matchedLines} of ${pattern.length} lines matched).
26988
+ ` + `First divergence at line ${fileLineNo}:
26989
+ ` + ` expected: ${JSON.stringify(expectedLine)}
26990
+ ` + ` actual: ${JSON.stringify(actualLine)}`;
26991
+ }
26992
+ const triedTiers = "exact, trimEnd, trim, indent (tab/space), unicode";
26993
+ const alreadyAppliedHint = alreadyApplied ? `
26994
+
26995
+ Hint: the replacement content for this hunk already appears in the file. ` + "The patch may have been partially applied in a prior turn \u2014 re-read the file " + "to confirm which hunks still need to apply." : "";
26468
26996
  throw new Error(`Failed to find expected lines in ${filePath}:
26469
26997
  ${chunk.old_lines.join(`
26470
- `)}`);
26998
+ `)}
26999
+
27000
+ ` + `Tried match tiers: ${triedTiers}.${closestHint}${alreadyAppliedHint}`);
26471
27001
  }
26472
27002
  }
26473
27003
  replacements.sort((a, b) => a[0] - b[0]);
@@ -26483,14 +27013,163 @@ ${chunk.old_lines.join(`
26483
27013
  `);
26484
27014
  }
26485
27015
 
26486
- // src/tools/hoisted.ts
27016
+ // src/tools/bash.ts
27017
+ import { tool as tool3 } from "@opencode-ai/plugin";
27018
+ var z3 = tool3.schema;
27019
+ var METADATA_PREVIEW_LIMIT = 30 * 1024;
27020
+ var DEFAULT_BASH_TIMEOUT_MS = 120000;
27021
+ var BASH_TRANSPORT_TIMEOUT_OVERHEAD_MS = 5000;
27022
+ var BASH_DESCRIPTION = `Hoisted bash tool with output compression, command rewriting to AFT tools, and optional background execution. By default, output is compressed; pass compressed: false for raw output. Pass background: true to spawn in the background and get a task_id for bash_status/bash_kill.`;
27023
+ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
27024
+ const first = await bridgeCall(ctx, runtime, "bash", params, options);
27025
+ if (first.success !== false || first.code !== "permission_required")
27026
+ return first;
27027
+ const asks = Array.isArray(first.asks) ? first.asks : [];
27028
+ const permissionsGranted = [];
27029
+ for (const ask of asks) {
27030
+ const permission = ask.kind === "external_directory" ? "external_directory" : "bash";
27031
+ await runtime.ask({
27032
+ permission,
27033
+ patterns: ask.patterns,
27034
+ always: ask.always,
27035
+ metadata: {}
27036
+ });
27037
+ permissionsGranted.push(...ask.always.length > 0 ? ask.always : ask.patterns);
27038
+ }
27039
+ const second = await bridgeCall(ctx, runtime, "bash", { ...params, permissions_granted: permissionsGranted }, options);
27040
+ if (second.success === false && second.code === "permission_required") {
27041
+ throw new Error("bash permission retry failed");
27042
+ }
27043
+ return second;
27044
+ }
27045
+ function createBashTool(ctx) {
27046
+ return {
27047
+ description: BASH_DESCRIPTION,
27048
+ args: {
27049
+ command: z3.string().describe("Shell command to execute through AFT's unified bash schema. Supports normal shell syntax, pipes, redirection, and command rewriting to dedicated AFT tools when available."),
27050
+ timeout: z3.number().optional().describe("Maximum execution time in milliseconds for foreground commands. Defaults to 120000 (2 minutes) when omitted."),
27051
+ workdir: z3.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
27052
+ description: z3.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
27053
+ background: z3.boolean().optional().describe("When true, spawn the command in the background and return a task_id for bash_status/bash_kill instead of waiting for completion. Defaults to false."),
27054
+ compressed: z3.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output.")
27055
+ },
27056
+ execute: async (args, context) => {
27057
+ let accumulatedOutput = "";
27058
+ const description = args.description;
27059
+ const metadata = context.metadata;
27060
+ const command = args.command;
27061
+ const cwd = args.workdir ?? context.directory;
27062
+ const shellEnv = await ctx.plugin?.trigger?.("shell.env", { cwd, sessionID: context.sessionID, callID: getCallID(context) }, { env: {} });
27063
+ const data = await withPermissionLoop(ctx, context, {
27064
+ command,
27065
+ timeout: args.timeout,
27066
+ workdir: args.workdir,
27067
+ env: shellEnv?.env ?? {},
27068
+ description,
27069
+ background: args.background,
27070
+ compressed: args.compressed,
27071
+ permissions_requested: true
27072
+ }, callBridge, {
27073
+ transportTimeoutMs: bashTransportTimeoutMs(args.timeout),
27074
+ onProgress: ({ text }) => {
27075
+ accumulatedOutput = preview(accumulatedOutput + text);
27076
+ metadata?.({ output: accumulatedOutput, description });
27077
+ }
27078
+ });
27079
+ if (data.success === false) {
27080
+ throw new Error(data.message || "bash failed");
27081
+ }
27082
+ if (data.status === "running" && typeof data.task_id === "string") {
27083
+ const callID2 = getCallID(context);
27084
+ const taskId = data.task_id;
27085
+ trackBgTask(context.sessionID, taskId);
27086
+ const startedLine = `Background task started: ${taskId}`;
27087
+ const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
27088
+ metadata?.(metadataPayload2);
27089
+ if (callID2) {
27090
+ storeToolMetadata(context.sessionID, callID2, {
27091
+ title: description ?? shortenCommand(command),
27092
+ metadata: metadataPayload2
27093
+ });
27094
+ }
27095
+ return startedLine;
27096
+ }
27097
+ const output = data.output ?? "";
27098
+ const metadataOutput = preview(output);
27099
+ const exit = data.exit_code;
27100
+ const truncated = data.truncated;
27101
+ const outputPath = data.output_path;
27102
+ const timedOut = data.timed_out === true;
27103
+ const callID = getCallID(context);
27104
+ const metadataPayload = {
27105
+ description,
27106
+ output: metadataOutput,
27107
+ exit,
27108
+ truncated,
27109
+ ...outputPath ? { outputPath } : {}
27110
+ };
27111
+ metadata?.(metadataPayload);
27112
+ if (callID) {
27113
+ storeToolMetadata(context.sessionID, callID, {
27114
+ title: description ?? shortenCommand(command),
27115
+ metadata: metadataPayload
27116
+ });
27117
+ }
27118
+ let rendered = output;
27119
+ if (truncated && outputPath) {
27120
+ rendered += `
27121
+ [output truncated; full output at ${outputPath}]`;
27122
+ }
27123
+ if (timedOut) {
27124
+ rendered += `
27125
+ [command timed out]`;
27126
+ }
27127
+ if (typeof exit === "number" && exit !== 0) {
27128
+ rendered += `
27129
+ [exit code: ${exit}]`;
27130
+ }
27131
+ return rendered;
27132
+ }
27133
+ };
27134
+ }
27135
+ function bashTransportTimeoutMs(timeout) {
27136
+ const bashTimeout = timeout ?? DEFAULT_BASH_TIMEOUT_MS;
27137
+ return Math.max(30000, bashTimeout + BASH_TRANSPORT_TIMEOUT_OVERHEAD_MS);
27138
+ }
27139
+ function preview(output) {
27140
+ return output.length <= METADATA_PREVIEW_LIMIT ? output : output.slice(-METADATA_PREVIEW_LIMIT);
27141
+ }
26487
27142
  function getCallID(ctx) {
26488
27143
  const c = ctx;
26489
27144
  return c.callID ?? c.callId ?? c.call_id;
26490
27145
  }
27146
+ function shortenCommand(command) {
27147
+ const collapsed = command.replace(/\s+/g, " ").trim();
27148
+ return collapsed.length <= 80 ? collapsed : `${collapsed.slice(0, 77)}...`;
27149
+ }
27150
+
27151
+ // src/tools/hoisted.ts
27152
+ function getCallID2(ctx) {
27153
+ const c = ctx;
27154
+ return c.callID ?? c.callId ?? c.call_id;
27155
+ }
26491
27156
  function relativeToWorktree(fp, worktree) {
26492
27157
  return path4.relative(worktree, fp);
26493
27158
  }
27159
+ function formatReadFooter(agentSpecifiedRange, data) {
27160
+ if (agentSpecifiedRange)
27161
+ return "";
27162
+ if (!data.truncated)
27163
+ return "";
27164
+ const startLine = data.start_line;
27165
+ const endLine = data.end_line;
27166
+ const totalLines = data.total_lines;
27167
+ if (startLine === undefined || endLine === undefined || totalLines === undefined) {
27168
+ return "";
27169
+ }
27170
+ return `
27171
+ (Showing lines ${startLine}-${endLine} of ${totalLines}. Use startLine/endLine to read other sections.)`;
27172
+ }
26494
27173
  function buildUnifiedDiff(fp, before, after) {
26495
27174
  const SIZE_CAP = 100 * 1024;
26496
27175
  if (before.length > SIZE_CAP || after.length > SIZE_CAP) {
@@ -26677,7 +27356,7 @@ function inferBeforeStart(ops, from, beforeLen) {
26677
27356
  }
26678
27357
  return beforeLen;
26679
27358
  }
26680
- var z3 = tool3.schema;
27359
+ var z4 = tool4.schema;
26681
27360
  var READ_DESCRIPTION = `Read file contents or list directory entries.
26682
27361
 
26683
27362
  Use either startLine/endLine OR offset/limit to read a section of a file.
@@ -26701,11 +27380,11 @@ function createReadTool(ctx) {
26701
27380
  return {
26702
27381
  description: READ_DESCRIPTION,
26703
27382
  args: {
26704
- filePath: z3.string().describe("Path to file or directory (absolute or relative to project root)"),
26705
- startLine: z3.number().optional().describe("1-based line to start reading from"),
26706
- endLine: z3.number().optional().describe("1-based line to stop reading at (inclusive)"),
26707
- limit: z3.number().optional().describe("Max lines to return (default: 2000)"),
26708
- offset: z3.number().optional().describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
27383
+ filePath: z4.string().describe("Path to file or directory (absolute or relative to project root)"),
27384
+ startLine: z4.number().optional().describe("1-based line to start reading from"),
27385
+ endLine: z4.number().optional().describe("1-based line to stop reading at (inclusive)"),
27386
+ limit: z4.number().optional().describe("Max lines to return (default: 2000)"),
27387
+ offset: z4.number().optional().describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
26709
27388
  },
26710
27389
  execute: async (args, context) => {
26711
27390
  const file2 = args.filePath;
@@ -26743,7 +27422,7 @@ function createReadTool(ctx) {
26743
27422
  } catch {}
26744
27423
  const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
26745
27424
  const msg = `${label} read successfully`;
26746
- const imgCallID = getCallID(context);
27425
+ const imgCallID = getCallID2(context);
26747
27426
  if (imgCallID) {
26748
27427
  storeToolMetadata(context.sessionID, imgCallID, {
26749
27428
  title: path4.relative(context.worktree, filePath),
@@ -26776,7 +27455,7 @@ function createReadTool(ctx) {
26776
27455
  if (data.success === false) {
26777
27456
  throw new Error(data.message || "read failed");
26778
27457
  }
26779
- const readCallID = getCallID(context);
27458
+ const readCallID = getCallID2(context);
26780
27459
  if (data.entries) {
26781
27460
  if (readCallID) {
26782
27461
  const dp = relativeToWorktree(filePath, context.worktree) || file2;
@@ -26797,10 +27476,10 @@ function createReadTool(ctx) {
26797
27476
  storeToolMetadata(context.sessionID, readCallID, { title: dp, metadata: { title: dp } });
26798
27477
  }
26799
27478
  let output = data.content;
26800
- if (data.truncated) {
26801
- output += `
26802
- (Showing lines ${data.start_line}-${data.end_line} of ${data.total_lines}. Use startLine/endLine to read other sections.)`;
26803
- }
27479
+ const agentSpecifiedRange = args.startLine !== undefined || args.endLine !== undefined || args.offset !== undefined || args.limit !== undefined;
27480
+ const footer = formatReadFooter(agentSpecifiedRange, data);
27481
+ if (footer)
27482
+ output += footer;
26804
27483
  return output;
26805
27484
  }
26806
27485
  };
@@ -26826,8 +27505,8 @@ function createWriteTool(ctx, editToolName = "edit") {
26826
27505
  return {
26827
27506
  description: getWriteDescription(editToolName),
26828
27507
  args: {
26829
- filePath: z3.string().describe("Path to the file to write (absolute or relative to project root)"),
26830
- content: z3.string().describe("The full content to write to the file")
27508
+ filePath: z4.string().describe("Path to the file to write (absolute or relative to project root)"),
27509
+ content: z4.string().describe("The full content to write to the file")
26831
27510
  },
26832
27511
  execute: async (args, context) => {
26833
27512
  const file2 = args.filePath;
@@ -26867,8 +27546,20 @@ LSP errors detected, please fix:
26867
27546
  }
26868
27547
  }
26869
27548
  }
27549
+ const pendingServers = data.lsp_pending_servers;
27550
+ const exitedServers = data.lsp_exited_servers;
27551
+ if (pendingServers && pendingServers.length > 0) {
27552
+ output += `
27553
+
27554
+ Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete; rerun lsp_diagnostics later for a fresh check.`;
27555
+ }
27556
+ if (exitedServers && exitedServers.length > 0) {
27557
+ output += `
27558
+
27559
+ Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
27560
+ }
26870
27561
  const diff = data.diff;
26871
- const callID = getCallID(context);
27562
+ const callID = getCallID2(context);
26872
27563
  if (callID) {
26873
27564
  const dp = relativeToWorktree(filePath, context.worktree);
26874
27565
  const beforeContent = diff?.before ?? "";
@@ -26897,7 +27588,7 @@ function getEditDescription(writeToolName) {
26897
27588
 
26898
27589
  **Modes** (determined by which parameters you provide):
26899
27590
 
26900
- Mode priority: operations > edits > symbol (without oldString) > oldString (find/replace). If none match, the call is rejected \u2014 there is no implicit "write" fallback.
27591
+ Mode priority: operations > appendContent > edits > symbol (without oldString) > oldString (find/replace). If none match, the call is rejected \u2014 there is no implicit "write" fallback.
26901
27592
 
26902
27593
  1. **Multi-file transaction** \u2014 pass \`operations\` array
26903
27594
  Edits across multiple files with checkpoint-based rollback on failure.
@@ -26905,33 +27596,37 @@ Mode priority: operations > edits > symbol (without oldString) > oldString (find
26905
27596
  For \`edit_match\`: include \`match\`, \`replacement\`. For \`write\`: include \`content\`.
26906
27597
  Example: \`{ "operations": [{ "file": "a.ts", "command": "edit_match", "match": "old", "replacement": "new" }, { "file": "b.ts", "command": "write", "content": "..." }] }\`
26907
27598
 
26908
- 2. **Batch edits** \u2014 pass \`filePath\` + \`edits\` array
27599
+ 2. **Append** \u2014 pass \`filePath\` + \`appendContent\`
27600
+ Appends text to the end of a file, creating the file if it does not exist.
27601
+ Example: \`{ "filePath": "notes.txt", "appendContent": "new line\\n" }\`
27602
+
27603
+ 3. **Batch edits** \u2014 pass \`filePath\` + \`edits\` array
26909
27604
  Multiple edits in one file atomically. Each edit is either:
26910
27605
  - \`{ "oldString": "old", "newString": "new" }\` \u2014 find/replace
26911
27606
  - \`{ "startLine": 5, "endLine": 7, "content": "new lines" }\` \u2014 replace line range (1-based, both inclusive)
26912
27607
  Set content to empty string to delete lines.
26913
27608
 
26914
- 3. **Symbol replace** \u2014 pass \`filePath\` + \`symbol\` + \`content\`
27609
+ 4. **Symbol replace** \u2014 pass \`filePath\` + \`symbol\` + \`content\`
26915
27610
  Replaces an entire named symbol (function, class, type) with new content.
26916
27611
  Includes decorators, attributes, and doc comments in the replacement range.
26917
27612
  **Important:** You must NOT provide \`oldString\` when using symbol mode \u2014 if present, the tool silently falls back to find/replace mode.
26918
27613
  Example: \`{ "filePath": "src/app.ts", "symbol": "handleRequest", "content": "function handleRequest() { ... }" }\`
26919
27614
 
26920
- 4. **Find and replace** \u2014 pass \`filePath\` + \`oldString\` + \`newString\`
27615
+ 5. **Find and replace** \u2014 pass \`filePath\` + \`oldString\` + \`newString\`
26921
27616
  Finds the exact text in \`oldString\` and replaces it with \`newString\`.
26922
27617
  Supports fuzzy matching (handles whitespace differences automatically).
26923
27618
  If multiple matches exist, specify which one with \`occurrence\` or use \`replaceAll: true\`.
26924
27619
  Example: \`{ "filePath": "src/app.ts", "oldString": "const x = 1", "newString": "const x = 2" }\`
26925
27620
 
26926
- 5. **Replace all occurrences** \u2014 add \`replaceAll: true\`
27621
+ 6. **Replace all occurrences** \u2014 add \`replaceAll: true\`
26927
27622
  Replaces every occurrence of \`oldString\` in the file.
26928
27623
  Example: \`{ "filePath": "src/app.ts", "oldString": "oldName", "newString": "newName", "replaceAll": true }\`
26929
27624
 
26930
- 6. **Select specific occurrence** \u2014 add \`occurrence: N\` (0-indexed)
27625
+ 7. **Select specific occurrence** \u2014 add \`occurrence: N\` (0-indexed)
26931
27626
  When multiple matches exist, select the Nth one (0 = first, 1 = second, etc.).
26932
27627
  Example: \`{ "filePath": "src/app.ts", "oldString": "TODO", "newString": "DONE", "occurrence": 0 }\`
26933
27628
 
26934
- Note: Modes 5 and 6 are options on mode 4 (find/replace) \u2014 they require \`oldString\`.
27629
+ Note: Modes 6 and 7 are options on mode 5 (find/replace) \u2014 they require \`oldString\`.
26935
27630
 
26936
27631
  **Behavior:**
26937
27632
  - Backs up files before editing (recoverable via aft_safety undo)
@@ -26948,16 +27643,17 @@ function createEditTool(ctx, writeToolName = "write") {
26948
27643
  return {
26949
27644
  description: getEditDescription(writeToolName),
26950
27645
  args: {
26951
- filePath: z3.string().optional().describe("Path to the file to edit (absolute or relative to project root). Required for all modes except 'operations' multi-file transactions"),
26952
- oldString: z3.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
26953
- newString: z3.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
26954
- replaceAll: z3.boolean().optional().describe("Replace all occurrences"),
26955
- occurrence: z3.number().optional().describe("0-indexed occurrence to replace when multiple matches exist"),
26956
- symbol: z3.string().optional().describe("Named symbol to replace (function, class, type)"),
26957
- content: z3.string().optional().describe("New content for symbol replace or file write"),
26958
- edits: z3.array(z3.record(z3.string(), z3.unknown())).optional().describe("Batch edits \u2014 array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
26959
- operations: z3.array(z3.record(z3.string(), z3.unknown())).optional().describe("Transaction \u2014 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)"),
26960
- dryRun: z3.boolean().optional().describe("Preview changes without applying (returns diff, default: false)")
27646
+ filePath: z4.string().optional().describe("Path to the file to edit (absolute or relative to project root). Required for all modes except 'operations' multi-file transactions"),
27647
+ oldString: z4.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
27648
+ newString: z4.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
27649
+ replaceAll: z4.boolean().optional().describe("Replace all occurrences"),
27650
+ occurrence: z4.number().optional().describe("0-indexed occurrence to replace when multiple matches exist"),
27651
+ symbol: z4.string().optional().describe("Named symbol to replace (function, class, type)"),
27652
+ content: z4.string().optional().describe("New content for symbol replace or file write"),
27653
+ appendContent: z4.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
27654
+ edits: z4.array(z4.record(z4.string(), z4.unknown())).optional().describe("Batch edits \u2014 array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
27655
+ operations: z4.array(z4.record(z4.string(), z4.unknown())).optional().describe("Transaction \u2014 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)"),
27656
+ dryRun: z4.boolean().optional().describe("Preview changes without applying (returns diff, default: false)")
26961
27657
  },
26962
27658
  execute: async (args, context) => {
26963
27659
  const argsRecord = args;
@@ -26995,7 +27691,12 @@ function createEditTool(ctx, writeToolName = "write") {
26995
27691
  });
26996
27692
  const params = { file: filePath };
26997
27693
  let command;
26998
- if (Array.isArray(args.edits)) {
27694
+ if (typeof args.appendContent === "string") {
27695
+ command = "edit_match";
27696
+ params.op = "append";
27697
+ params.append_content = args.appendContent;
27698
+ params.create_dirs = true;
27699
+ } else if (Array.isArray(args.edits)) {
26999
27700
  command = "batch";
27000
27701
  params.edits = args.edits.map((edit) => {
27001
27702
  const translated = {};
@@ -27039,7 +27740,7 @@ function createEditTool(ctx, writeToolName = "write") {
27039
27740
  const data = await callBridge(ctx, context, command, params);
27040
27741
  if (!args.dryRun && data.success && data.diff) {
27041
27742
  const diff = data.diff;
27042
- const callID = getCallID(context);
27743
+ const callID = getCallID2(context);
27043
27744
  if (callID) {
27044
27745
  const dp = relativeToWorktree(filePath, context.worktree);
27045
27746
  const beforeContent = diff.before ?? "";
@@ -27074,6 +27775,18 @@ LSP errors detected, please fix:
27074
27775
  ${diagLines}`;
27075
27776
  }
27076
27777
  }
27778
+ const pendingServers = data.lsp_pending_servers;
27779
+ const exitedServers = data.lsp_exited_servers;
27780
+ if (pendingServers && pendingServers.length > 0) {
27781
+ result += `
27782
+
27783
+ Note: LSP server(s) did not respond in time: ${pendingServers.join(", ")}. Diagnostics may be incomplete; rerun lsp_diagnostics later for a fresh check.`;
27784
+ }
27785
+ if (exitedServers && exitedServers.length > 0) {
27786
+ result += `
27787
+
27788
+ Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their diagnostics could not be collected.`;
27789
+ }
27077
27790
  }
27078
27791
  return result;
27079
27792
  }
@@ -27126,7 +27839,7 @@ function createApplyPatchTool(ctx) {
27126
27839
  return {
27127
27840
  description: APPLY_PATCH_DESCRIPTION,
27128
27841
  args: {
27129
- patchText: z3.string().describe("The full patch text including Begin/End markers")
27842
+ patchText: z4.string().describe("The full patch text including Begin/End markers")
27130
27843
  },
27131
27844
  execute: async (args, context) => {
27132
27845
  const patchText = args.patchText;
@@ -27158,6 +27871,7 @@ function createApplyPatchTool(ctx) {
27158
27871
  }
27159
27872
  }
27160
27873
  const relPaths = Array.from(affectedAbs).map((abs) => path4.relative(context.worktree, abs));
27874
+ const multiFileWritePaths = Array.from(affectedAbs);
27161
27875
  await context.ask({
27162
27876
  permission: "edit",
27163
27877
  patterns: relPaths,
@@ -27177,12 +27891,18 @@ function createApplyPatchTool(ctx) {
27177
27891
  } catch {}
27178
27892
  }
27179
27893
  const results = [];
27894
+ const failures = [];
27180
27895
  const perFileDiffs = [];
27181
- let patchFailed = false;
27182
27896
  for (const hunk of hunks) {
27183
27897
  const filePath = path4.resolve(context.directory, hunk.path);
27184
27898
  switch (hunk.type) {
27185
27899
  case "add": {
27900
+ if (fs3.existsSync(filePath)) {
27901
+ const msg = `Failed to create ${hunk.path}: file already exists. Use *** Update File: to modify, or *** Delete File: first if you want to replace it entirely.`;
27902
+ results.push(msg);
27903
+ failures.push(hunk.path);
27904
+ break;
27905
+ }
27186
27906
  try {
27187
27907
  const content = hunk.contents.endsWith(`
27188
27908
  `) ? hunk.contents : `${hunk.contents}
@@ -27192,7 +27912,8 @@ function createApplyPatchTool(ctx) {
27192
27912
  content,
27193
27913
  create_dirs: true,
27194
27914
  diagnostics: true,
27195
- include_diff: true
27915
+ include_diff: true,
27916
+ multi_file_write_paths: multiFileWritePaths
27196
27917
  });
27197
27918
  const wrDiff = writeResult.diff;
27198
27919
  perFileDiffs.push({
@@ -27204,8 +27925,15 @@ function createApplyPatchTool(ctx) {
27204
27925
  });
27205
27926
  results.push(`Created ${hunk.path}`);
27206
27927
  } catch (e) {
27207
- patchFailed = true;
27208
- results.push(`Failed to create ${hunk.path}: ${e instanceof Error ? e.message : e}`);
27928
+ const msg = `Failed to create ${hunk.path}: ${e instanceof Error ? e.message : e}`;
27929
+ results.push(msg);
27930
+ failures.push(hunk.path);
27931
+ const filePath2 = path4.resolve(context.directory, hunk.path);
27932
+ if (fs3.existsSync(filePath2)) {
27933
+ try {
27934
+ await callBridge(ctx, context, "delete_file", { file: filePath2 });
27935
+ } catch {}
27936
+ }
27209
27937
  }
27210
27938
  break;
27211
27939
  }
@@ -27222,8 +27950,8 @@ function createApplyPatchTool(ctx) {
27222
27950
  });
27223
27951
  results.push(`Deleted ${hunk.path}`);
27224
27952
  } catch (e) {
27225
- patchFailed = true;
27226
27953
  results.push(`Failed to delete ${hunk.path}: ${e instanceof Error ? e.message : e}`);
27954
+ failures.push(hunk.path);
27227
27955
  }
27228
27956
  break;
27229
27957
  }
@@ -27237,7 +27965,8 @@ function createApplyPatchTool(ctx) {
27237
27965
  content: newContent,
27238
27966
  create_dirs: true,
27239
27967
  diagnostics: true,
27240
- include_diff: true
27968
+ include_diff: true,
27969
+ multi_file_write_paths: multiFileWritePaths
27241
27970
  });
27242
27971
  const diags = writeResult.lsp_diagnostics;
27243
27972
  if (diags && diags.length > 0) {
@@ -27265,51 +27994,60 @@ ${diagLines}`);
27265
27994
  deletions
27266
27995
  });
27267
27996
  if (hunk.move_path) {
27268
- await callBridge(ctx, context, "delete_file", { file: filePath });
27997
+ try {
27998
+ const deleteResult = await callBridge(ctx, context, "delete_file", {
27999
+ file: filePath
28000
+ });
28001
+ if (deleteResult.success === false) {
28002
+ throw new Error(deleteResult.message ?? "delete failed");
28003
+ }
28004
+ } catch (deleteError) {
28005
+ try {
28006
+ if (!checkpointCreated) {
28007
+ throw new Error("pre-patch checkpoint was not created");
28008
+ }
28009
+ const rollbackResult = await callBridge(ctx, context, "restore_checkpoint", {
28010
+ name: checkpointName
28011
+ });
28012
+ if (rollbackResult.success === false) {
28013
+ throw new Error(rollbackResult.message ?? "checkpoint restore failed");
28014
+ }
28015
+ if (newlyCreatedAbs.has(targetPath) && fs3.existsSync(targetPath)) {
28016
+ const cleanupResult = await callBridge(ctx, context, "delete_file", {
28017
+ file: targetPath
28018
+ });
28019
+ if (cleanupResult.success === false) {
28020
+ throw new Error(cleanupResult.message ?? "new destination cleanup failed");
28021
+ }
28022
+ }
28023
+ } catch (rollbackError) {
28024
+ throw new Error(`success: false; code: move_partial_failure; files: [${filePath}, ${targetPath}]; wrote destination ${targetPath}, but failed to delete source ${filePath} (${formatError2(deleteError)}) and failed to restore pre-patch checkpoint ${checkpointName} (${formatError2(rollbackError)}). Both copies may exist or destination content may be changed: ${filePath}, ${targetPath}`);
28025
+ }
28026
+ throw new Error(`source delete failed after writing move destination; restored pre-patch checkpoint ${checkpointName}: ${formatError2(deleteError)}`);
28027
+ }
27269
28028
  results.push(`Updated and moved ${hunk.path} \u2192 ${hunk.move_path}`);
27270
28029
  } else {
27271
28030
  results.push(`Updated ${hunk.path}`);
27272
28031
  }
27273
28032
  } catch (e) {
27274
- patchFailed = true;
27275
28033
  results.push(`Failed to update ${hunk.path}: ${e instanceof Error ? e.message : e}`);
28034
+ failures.push(hunk.path);
27276
28035
  break;
27277
28036
  }
27278
28037
  break;
27279
28038
  }
27280
28039
  }
27281
28040
  }
27282
- if (patchFailed) {
27283
- const rollbackNotes = [];
27284
- if (checkpointCreated) {
27285
- try {
27286
- await callBridge(ctx, context, "restore_checkpoint", { name: checkpointName });
27287
- rollbackNotes.push("restored pre-existing files from checkpoint");
27288
- } catch {
27289
- rollbackNotes.push("checkpoint restore FAILED, pre-existing files may be inconsistent");
27290
- }
27291
- } else if (checkpointPaths.length > 0) {
27292
- rollbackNotes.push("no checkpoint was created, pre-existing files may be inconsistent");
27293
- }
27294
- let newlyDeleted = 0;
27295
- for (const createdAbs of newlyCreatedAbs) {
27296
- if (!fs3.existsSync(createdAbs))
27297
- continue;
27298
- try {
27299
- await callBridge(ctx, context, "delete_file", { file: createdAbs });
27300
- newlyDeleted++;
27301
- } catch {
27302
- rollbackNotes.push(`failed to delete newly-created ${path4.relative(context.worktree, createdAbs)}`);
27303
- }
27304
- }
27305
- if (newlyDeleted > 0) {
27306
- rollbackNotes.push(`removed ${newlyDeleted} newly-created file(s)`);
27307
- }
27308
- results.push(rollbackNotes.length > 0 ? `Patch failed \u2014 ${rollbackNotes.join("; ")}.` : "Patch failed \u2014 nothing to roll back.");
27309
- return results.join(`
28041
+ if (failures.length > 0) {
28042
+ const partial2 = failures.length < hunks.length;
28043
+ const summary = partial2 ? `Patch partially applied \u2014 ${hunks.length - failures.length} of ${hunks.length} hunk(s) succeeded. Failed: ${failures.join(", ")}. Successful changes are kept; use \`aft_safety\` to revert if you want to abort.` : `Patch failed \u2014 none of the ${hunks.length} hunk(s) applied: ${failures.join(", ")}.`;
28044
+ results.push(summary);
28045
+ if (!partial2) {
28046
+ return results.join(`
27310
28047
  `);
28048
+ }
27311
28049
  }
27312
- const callID = getCallID(context);
28050
+ const callID = getCallID2(context);
27313
28051
  if (callID) {
27314
28052
  const diffByPath = new Map(perFileDiffs.map((d) => [d.filePath, d]));
27315
28053
  const files = hunks.map((h) => {
@@ -27362,7 +28100,7 @@ function createDeleteTool(ctx) {
27362
28100
  return {
27363
28101
  description: DELETE_DESCRIPTION,
27364
28102
  args: {
27365
- filePath: z3.string().describe("Path to file to delete")
28103
+ filePath: z4.string().describe("Path to file to delete")
27366
28104
  },
27367
28105
  execute: async (args, context) => {
27368
28106
  const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
@@ -27386,8 +28124,8 @@ function createMoveTool(ctx) {
27386
28124
  return {
27387
28125
  description: MOVE_DESCRIPTION,
27388
28126
  args: {
27389
- filePath: z3.string().describe("Source file path to move"),
27390
- destination: z3.string().describe("Destination file path")
28127
+ filePath: z4.string().describe("Source file path to move"),
28128
+ destination: z4.string().describe("Destination file path")
27391
28129
  },
27392
28130
  execute: async (args, context) => {
27393
28131
  const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
@@ -27411,6 +28149,7 @@ function createMoveTool(ctx) {
27411
28149
  }
27412
28150
  function hoistedTools(ctx) {
27413
28151
  return {
28152
+ bash: createBashTool(ctx),
27414
28153
  read: createReadTool(ctx),
27415
28154
  write: createWriteTool(ctx, "edit"),
27416
28155
  edit: createEditTool(ctx, "write"),
@@ -27419,6 +28158,9 @@ function hoistedTools(ctx) {
27419
28158
  aft_move: createMoveTool(ctx)
27420
28159
  };
27421
28160
  }
28161
+ function formatError2(error49) {
28162
+ return error49 instanceof Error ? error49.message : String(error49);
28163
+ }
27422
28164
  function aftPrefixedTools(ctx) {
27423
28165
  const aftEditTool = createEditTool(ctx, "aft_write");
27424
28166
  return {
@@ -27461,8 +28203,8 @@ function aftPrefixedTools(ctx) {
27461
28203
  }
27462
28204
 
27463
28205
  // src/tools/imports.ts
27464
- import { tool as tool4 } from "@opencode-ai/plugin";
27465
- var z4 = tool4.schema;
28206
+ import { tool as tool5 } from "@opencode-ai/plugin";
28207
+ var z5 = tool5.schema;
27466
28208
  function importTools(ctx) {
27467
28209
  return {
27468
28210
  aft_import: {
@@ -27479,15 +28221,15 @@ function importTools(ctx) {
27479
28221
  ` + `- remove: { file, removed, module, name?, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
27480
28222
  ` + "- organize: { file, groups: [{ name, count }], removed_duplicates, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
27481
28223
  args: {
27482
- op: z4.enum(["add", "remove", "organize"]).describe("Import operation"),
27483
- filePath: z4.string().describe("Path to the file (absolute or relative to project root)"),
27484
- module: z4.string().optional().describe("Module path (required for add, remove \u2014 e.g. 'react', './utils', 'std::fmt')"),
27485
- names: z4.array(z4.string()).optional().describe("Named imports to add (e.g. ['useState', 'useEffect'])"),
27486
- defaultImport: z4.string().optional().describe("Default import name (e.g. 'React')"),
27487
- typeOnly: z4.boolean().optional().describe("Type-only import (TS only, default: false)"),
27488
- removeName: z4.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
27489
- validate: z4.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)"),
27490
- dryRun: z4.boolean().optional().describe("Preview without modifying the file (default: false)")
28224
+ op: z5.enum(["add", "remove", "organize"]).describe("Import operation"),
28225
+ filePath: z5.string().describe("Path to the file (absolute or relative to project root)"),
28226
+ module: z5.string().optional().describe("Module path (required for add, remove \u2014 e.g. 'react', './utils', 'std::fmt')"),
28227
+ names: z5.array(z5.string()).optional().describe("Named imports to add (e.g. ['useState', 'useEffect'])"),
28228
+ defaultImport: z5.string().optional().describe("Default import name (e.g. 'React')"),
28229
+ typeOnly: z5.boolean().optional().describe("Type-only import (TS only, default: false)"),
28230
+ removeName: z5.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
28231
+ validate: z5.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)"),
28232
+ dryRun: z5.boolean().optional().describe("Preview without modifying the file (default: false)")
27491
28233
  },
27492
28234
  execute: async (args, context) => {
27493
28235
  const op = args.op;
@@ -27532,8 +28274,8 @@ function importTools(ctx) {
27532
28274
  }
27533
28275
 
27534
28276
  // src/tools/lsp.ts
27535
- import { tool as tool5 } from "@opencode-ai/plugin";
27536
- var z5 = tool5.schema;
28277
+ import { tool as tool6 } from "@opencode-ai/plugin";
28278
+ var z6 = tool6.schema;
27537
28279
  function lspTools(ctx) {
27538
28280
  const diagnosticsTool = {
27539
28281
  description: "On-demand LSP file/scope check. Spawns the relevant language server (if " + "registered for the file's extension), opens the document, prefers LSP 3.17 " + "pull diagnostics when supported, and falls back to push + waitMs otherwise. " + "NOT a project-wide type checker \u2014 for full coverage run `tsc --noEmit`, " + "`cargo check`, `pyright`, etc.\n" + `
@@ -27555,10 +28297,10 @@ function lspTools(ctx) {
27555
28297
  ` + "- `total: 0, complete: true, lsp_servers_used: [{status: 'pull_ok'}]` \u2192 file is genuinely clean.\n" + "- `total: 0, lsp_servers_used: []` \u2192 **nothing was checked** (no server registered for this extension). Tell the user, don't claim 'no errors'.\n" + "- `lsp_servers_used: [{status: 'binary_not_installed: <name>'}]` \u2192 server matched the extension but its binary isn't on PATH. Tell the user to install it.\n" + "- `lsp_servers_used: [{status: 'no_root_marker (...)'}]` \u2192 server is registered but couldn't find a workspace root marker walking up from this file. The user's project layout doesn't match what the server expects.\n" + "- `complete: false` (directory mode) \u2192 some files in the directory weren't checked; see `unchecked_files`.\n" + `
27556
28298
  ` + "**When this tool gives an unhelpful answer**, run `bunx --bun @cortexkit/aft doctor lsp <filePath>` from a terminal to get a full per-server breakdown (registered servers, binary resolution, root-marker resolution, spawn outcome).",
27557
28299
  args: {
27558
- filePath: z5.string().optional().describe("Path to a file to check. Mutually exclusive with 'directory'. Omit both to dump all cached diagnostics."),
27559
- directory: z5.string().optional().describe("Path to a directory. Returns cached diagnostics + workspace pull from active servers; lists files we have no info for in 'unchecked_files'. Capped at 200 walked files."),
27560
- severity: z5.enum(["error", "warning", "information", "hint", "all"]).optional().describe("Filter by severity (default: 'all')."),
27561
- waitMs: z5.number().optional().describe("Wait up to N ms (max 10000, default 0) for push diagnostics to arrive. Only matters for servers that don't support LSP 3.17 pull (bash-language-server, yaml-language-server). Use after an edit to let the server re-analyze.")
28300
+ filePath: z6.string().optional().describe("Path to a file to check. Mutually exclusive with 'directory'. Omit both to dump all cached diagnostics."),
28301
+ directory: z6.string().optional().describe("Path to a directory. Returns cached diagnostics + workspace pull from active servers; lists files we have no info for in 'unchecked_files'. Capped at 200 walked files."),
28302
+ severity: z6.enum(["error", "warning", "information", "hint", "all"]).optional().describe("Filter by severity (default: 'all')."),
28303
+ waitMs: z6.number().optional().describe("Wait up to N ms (max 10000, default 0) for push diagnostics to arrive. Only matters for servers that don't support LSP 3.17 pull (bash-language-server, yaml-language-server). Use after an edit to let the server re-analyze.")
27562
28304
  },
27563
28305
  execute: async (args, context) => {
27564
28306
  const filePath = args.filePath || undefined;
@@ -27589,8 +28331,8 @@ function lspTools(ctx) {
27589
28331
  }
27590
28332
 
27591
28333
  // src/tools/navigation.ts
27592
- import { tool as tool6 } from "@opencode-ai/plugin";
27593
- var z6 = tool6.schema;
28334
+ import { tool as tool7 } from "@opencode-ai/plugin";
28335
+ var z7 = tool7.schema;
27594
28336
  function navigationTools(ctx) {
27595
28337
  return {
27596
28338
  aft_navigate: {
@@ -27607,11 +28349,11 @@ function navigationTools(ctx) {
27607
28349
 
27608
28350
  `,
27609
28351
  args: {
27610
- op: z6.enum(["call_tree", "callers", "trace_to", "impact", "trace_data"]).describe("Navigation operation"),
27611
- filePath: z6.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
27612
- symbol: z6.string().describe("Name of the symbol to analyze"),
27613
- depth: z6.number().optional().describe("Max traversal depth (default: call_tree=5, callers=1, trace_to=10, impact=5, trace_data=5)"),
27614
- expression: z6.string().optional().describe("Expression to track through data flow (required for trace_data op)")
28352
+ op: z7.enum(["call_tree", "callers", "trace_to", "impact", "trace_data"]).describe("Navigation operation"),
28353
+ filePath: z7.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
28354
+ symbol: z7.string().describe("Name of the symbol to analyze"),
28355
+ depth: z7.number().optional().describe("Max traversal depth (default: call_tree=5, callers=1, trace_to=10, impact=5, trace_data=5)"),
28356
+ expression: z7.string().optional().describe("Expression to track through data flow (required for trace_data op)")
27615
28357
  },
27616
28358
  execute: async (args, context) => {
27617
28359
  const params = {
@@ -27638,7 +28380,7 @@ function navigationTools(ctx) {
27638
28380
  // src/tools/reading.ts
27639
28381
  import { readdir } from "fs/promises";
27640
28382
  import { extname as extname2, join as join18, resolve as resolve8 } from "path";
27641
- import { tool as tool7 } from "@opencode-ai/plugin";
28383
+ import { tool as tool8 } from "@opencode-ai/plugin";
27642
28384
  var OUTLINE_EXTENSIONS = new Set([
27643
28385
  ".ts",
27644
28386
  ".tsx",
@@ -27675,7 +28417,7 @@ var OUTLINE_EXTENSIONS = new Set([
27675
28417
  ".sh",
27676
28418
  ".bash"
27677
28419
  ]);
27678
- var z7 = tool7.schema;
28420
+ var z8 = tool8.schema;
27679
28421
  function readingTools(ctx) {
27680
28422
  return {
27681
28423
  aft_outline: {
@@ -27683,10 +28425,10 @@ function readingTools(ctx) {
27683
28425
 
27684
28426
  ` + "Provide exactly ONE of: 'filePath', 'files', 'directory', or 'url'.",
27685
28427
  args: {
27686
- filePath: z7.string().optional().describe("Path to a single file to outline"),
27687
- files: z7.array(z7.string()).optional().describe("Array of file paths to outline in one call"),
27688
- directory: z7.string().optional().describe("Directory to outline recursively (200 file limit)"),
27689
- url: z7.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and outline")
28428
+ filePath: z8.string().optional().describe("Path to a single file to outline"),
28429
+ files: z8.array(z8.string()).optional().describe("Array of file paths to outline in one call"),
28430
+ directory: z8.string().optional().describe("Directory to outline recursively (200 file limit)"),
28431
+ url: z8.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and outline")
27690
28432
  },
27691
28433
  execute: async (args, context) => {
27692
28434
  const filesArg = Array.isArray(args.files) ? args.files : undefined;
@@ -27756,11 +28498,11 @@ function readingTools(ctx) {
27756
28498
 
27757
28499
  Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lookup or 'symbols' for multiple in one call.`,
27758
28500
  args: {
27759
- filePath: z7.string().optional().describe("Path to file (absolute or relative to project root)"),
27760
- url: z7.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
27761
- symbol: z7.string().optional().describe("Symbol name for code, or heading text for Markdown/HTML"),
27762
- symbols: z7.array(z7.string()).optional().describe("Array of symbol names or heading texts for a single batched call"),
27763
- contextLines: z7.number().optional().describe("Lines of context before/after the symbol (default: 3)")
28501
+ filePath: z8.string().optional().describe("Path to file (absolute or relative to project root)"),
28502
+ url: z8.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
28503
+ symbol: z8.string().optional().describe("Symbol name for code, or heading text for Markdown/HTML"),
28504
+ symbols: z8.array(z8.string()).optional().describe("Array of symbol names or heading texts for a single batched call"),
28505
+ contextLines: z8.number().optional().describe("Lines of context before/after the symbol (default: 3)")
27764
28506
  },
27765
28507
  execute: async (args, context) => {
27766
28508
  const hasFilePath = typeof args.filePath === "string" && args.filePath.length > 0;
@@ -27861,7 +28603,7 @@ ${lines}`;
27861
28603
  }
27862
28604
 
27863
28605
  // src/tools/refactoring.ts
27864
- import { tool as tool8 } from "@opencode-ai/plugin";
28606
+ import { tool as tool9 } from "@opencode-ai/plugin";
27865
28607
 
27866
28608
  // src/lsp.ts
27867
28609
  var LSP_SYMBOL_KIND_MAP = {
@@ -27920,7 +28662,7 @@ async function queryLspHints(client, symbolName, directory) {
27920
28662
  }
27921
28663
 
27922
28664
  // src/tools/refactoring.ts
27923
- var z8 = tool8.schema;
28665
+ var z9 = tool9.schema;
27924
28666
  function refactoringTools(ctx) {
27925
28667
  return {
27926
28668
  aft_refactor: {
@@ -27938,16 +28680,16 @@ function refactoringTools(ctx) {
27938
28680
 
27939
28681
  ` + "Returns: move dry-run { ok, dry_run, diffs }; move apply { ok, files_modified, consumers_updated, checkpoint_name, results }. extract returns { file, name, parameters, return_type, syntax_valid, formatted, ... }. inline returns { file, symbol, call_context, substitutions, conflicts, syntax_valid, formatted, ... }.",
27940
28682
  args: {
27941
- op: z8.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
27942
- filePath: z8.string().describe("Path to the source file (absolute or relative to project root)"),
27943
- symbol: z8.string().optional().describe("Symbol name \u2014 required for 'move' and 'inline' ops"),
27944
- destination: z8.string().optional().describe("Target file path \u2014 required for 'move' op"),
27945
- scope: z8.string().optional().describe("Disambiguation scope for 'move' op \u2014 when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
27946
- name: z8.string().optional().describe("New function name \u2014 required for 'extract' op"),
27947
- startLine: z8.number().optional().describe("1-based start line \u2014 required for 'extract' op"),
27948
- endLine: z8.number().optional().describe("1-based end line (inclusive) \u2014 required for 'extract' op"),
27949
- callSiteLine: z8.number().optional().describe("1-based call site line \u2014 required for 'inline' op"),
27950
- dryRun: z8.boolean().optional().describe("Preview changes as diff without modifying files (default: false)")
28683
+ op: z9.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
28684
+ filePath: z9.string().describe("Path to the source file (absolute or relative to project root)"),
28685
+ symbol: z9.string().optional().describe("Symbol name \u2014 required for 'move' and 'inline' ops"),
28686
+ destination: z9.string().optional().describe("Target file path \u2014 required for 'move' op"),
28687
+ scope: z9.string().optional().describe("Disambiguation scope for 'move' op \u2014 when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
28688
+ name: z9.string().optional().describe("New function name \u2014 required for 'extract' op"),
28689
+ startLine: z9.number().optional().describe("1-based start line \u2014 required for 'extract' op"),
28690
+ endLine: z9.number().optional().describe("1-based end line (inclusive) \u2014 required for 'extract' op"),
28691
+ callSiteLine: z9.number().optional().describe("1-based call site line \u2014 required for 'inline' op"),
28692
+ dryRun: z9.boolean().optional().describe("Preview changes as diff without modifying files (default: false)")
27951
28693
  },
27952
28694
  execute: async (args, context) => {
27953
28695
  const op = args.op;
@@ -28017,8 +28759,8 @@ function refactoringTools(ctx) {
28017
28759
  }
28018
28760
 
28019
28761
  // src/tools/safety.ts
28020
- import { tool as tool9 } from "@opencode-ai/plugin";
28021
- var z9 = tool9.schema;
28762
+ import { tool as tool10 } from "@opencode-ai/plugin";
28763
+ var z10 = tool10.schema;
28022
28764
  function safetyTools(ctx) {
28023
28765
  return {
28024
28766
  aft_safety: {
@@ -28039,10 +28781,10 @@ function safetyTools(ctx) {
28039
28781
 
28040
28782
  ` + "Returns: undo { path, backup_id }. history { file, entries }. checkpoint { ok, name }. restore { ok, name }. list { checkpoints }.",
28041
28783
  args: {
28042
- op: z9.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
28043
- filePath: z9.string().optional().describe("File path (required for undo, history). Absolute or relative to project root"),
28044
- name: z9.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
28045
- files: z9.array(z9.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
28784
+ op: z10.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
28785
+ filePath: z10.string().optional().describe("File path (required for undo, history). Absolute or relative to project root"),
28786
+ name: z10.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
28787
+ files: z10.array(z10.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
28046
28788
  },
28047
28789
  execute: async (args, context) => {
28048
28790
  const op = args.op;
@@ -28244,8 +28986,8 @@ function semanticTools(ctx) {
28244
28986
  }
28245
28987
 
28246
28988
  // src/tools/structure.ts
28247
- import { tool as tool10 } from "@opencode-ai/plugin";
28248
- var z10 = tool10.schema;
28989
+ import { tool as tool11 } from "@opencode-ai/plugin";
28990
+ var z11 = tool11.schema;
28249
28991
  function structureTools(ctx) {
28250
28992
  return {
28251
28993
  aft_transform: {
@@ -28270,20 +29012,20 @@ function structureTools(ctx) {
28270
29012
  ` + `- add_decorator: { file, target, decorator, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
28271
29013
  ` + "- add_struct_tags: { file, target, field, tag_string, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
28272
29014
  args: {
28273
- op: z10.enum(["add_member", "add_derive", "wrap_try_catch", "add_decorator", "add_struct_tags"]).describe("Transformation operation"),
28274
- filePath: z10.string().describe("Path to the source file (absolute or relative to project root)"),
28275
- container: z10.string().optional().describe("Container name for add_member \u2014 the class, struct, or impl block to insert into. Appears as 'scope' in the response."),
28276
- code: z10.string().optional().describe("Member code to insert (add_member)"),
28277
- position: z10.string().optional().describe("For add_member: 'first', 'last' (default), 'before:name', 'after:name'. For add_decorator: 'first' (default) or 'last' only."),
28278
- target: z10.string().optional().describe("Target symbol name (add_derive: struct/enum, wrap_try_catch: function, add_decorator: function/class, add_struct_tags: struct)"),
28279
- derives: z10.array(z10.string()).optional().describe("Derive macro names (add_derive \u2014 e.g. ['Clone', 'Debug'])"),
28280
- catchBody: z10.string().optional().describe("Catch block body (wrap_try_catch \u2014 default: 'throw error;')"),
28281
- decorator: z10.string().optional().describe("Decorator text without @ (add_decorator \u2014 e.g. 'staticmethod')"),
28282
- field: z10.string().optional().describe("Struct field name (add_struct_tags)"),
28283
- tag: z10.string().optional().describe("Tag key (add_struct_tags \u2014 e.g. 'json')"),
28284
- value: z10.string().optional().describe("Tag value (add_struct_tags \u2014 e.g. 'user_name,omitempty')"),
28285
- validate: z10.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)"),
28286
- dryRun: z10.boolean().optional().describe("Preview without modifying the file (default: false)")
29015
+ op: z11.enum(["add_member", "add_derive", "wrap_try_catch", "add_decorator", "add_struct_tags"]).describe("Transformation operation"),
29016
+ filePath: z11.string().describe("Path to the source file (absolute or relative to project root)"),
29017
+ container: z11.string().optional().describe("Container name for add_member \u2014 the class, struct, or impl block to insert into. Appears as 'scope' in the response."),
29018
+ code: z11.string().optional().describe("Member code to insert (add_member)"),
29019
+ position: z11.string().optional().describe("For add_member: 'first', 'last' (default), 'before:name', 'after:name'. For add_decorator: 'first' (default) or 'last' only."),
29020
+ target: z11.string().optional().describe("Target symbol name (add_derive: struct/enum, wrap_try_catch: function, add_decorator: function/class, add_struct_tags: struct)"),
29021
+ derives: z11.array(z11.string()).optional().describe("Derive macro names (add_derive \u2014 e.g. ['Clone', 'Debug'])"),
29022
+ catchBody: z11.string().optional().describe("Catch block body (wrap_try_catch \u2014 default: 'throw error;')"),
29023
+ decorator: z11.string().optional().describe("Decorator text without @ (add_decorator \u2014 e.g. 'staticmethod')"),
29024
+ field: z11.string().optional().describe("Struct field name (add_struct_tags)"),
29025
+ tag: z11.string().optional().describe("Tag key (add_struct_tags \u2014 e.g. 'json')"),
29026
+ value: z11.string().optional().describe("Tag value (add_struct_tags \u2014 e.g. 'user_name,omitempty')"),
29027
+ validate: z11.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)"),
29028
+ dryRun: z11.boolean().optional().describe("Preview without modifying the file (default: false)")
28287
29029
  },
28288
29030
  execute: async (args, context) => {
28289
29031
  const op = args.op;
@@ -28404,8 +29146,16 @@ var PLUGIN_VERSION = (() => {
28404
29146
  return "0.0.0";
28405
29147
  }
28406
29148
  })();
28407
- var ANNOUNCEMENT_VERSION = "";
28408
- var ANNOUNCEMENT_FEATURES = [];
29149
+ var ANNOUNCEMENT_VERSION = "0.18.0";
29150
+ var ANNOUNCEMENT_FEATURES = [
29151
+ `New experimental features \u2014 AFT now optionally hoists bash:
29152
+ - Run bash scripts in the background.
29153
+ - Initial output compression for git, cargo, npm, bun, pnpm, pytest, tsc (more in 0.19).
29154
+ - Rewrite cat/grep/find/sed/ls into AFT counterparts for faster, formatted output.
29155
+ Check GitHub for how to enable.`,
29156
+ "Trigram grep/glob and semantic search (aft_search) graduated out of experimental.",
29157
+ "Lots of bugfixes and new end-to-end test coverage."
29158
+ ];
28409
29159
  var plugin = async (input) => {
28410
29160
  const binaryPath = await findBinary();
28411
29161
  const aftConfig = loadAftConfig(input.directory);
@@ -28420,10 +29170,12 @@ var plugin = async (input) => {
28420
29170
  if (aftConfig.checker !== undefined)
28421
29171
  configOverrides.checker = aftConfig.checker;
28422
29172
  configOverrides.restrict_to_project_root = aftConfig.restrict_to_project_root ?? true;
28423
- if (aftConfig.experimental_search_index !== undefined)
28424
- configOverrides.experimental_search_index = aftConfig.experimental_search_index;
28425
- if (aftConfig.experimental_semantic_search !== undefined)
28426
- configOverrides.experimental_semantic_search = aftConfig.experimental_semantic_search;
29173
+ configOverrides.bash_permissions = true;
29174
+ if (aftConfig.search_index !== undefined)
29175
+ configOverrides.search_index = aftConfig.search_index;
29176
+ if (aftConfig.semantic_search !== undefined)
29177
+ configOverrides.semantic_search = aftConfig.semantic_search;
29178
+ Object.assign(configOverrides, resolveExperimentalConfigForConfigure(aftConfig));
28427
29179
  Object.assign(configOverrides, resolveLspConfigForConfigure(aftConfig));
28428
29180
  if (aftConfig.semantic !== undefined)
28429
29181
  configOverrides.semantic = aftConfig.semantic;
@@ -28432,7 +29184,7 @@ var plugin = async (input) => {
28432
29184
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
28433
29185
  const dataHome = process.env.XDG_DATA_HOME || join19(homedir10(), ".local", "share");
28434
29186
  configOverrides.storage_dir = join19(dataHome, "opencode", "storage", "plugin", "aft");
28435
- if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend) {
29187
+ if (aftConfig.semantic_search && isFastembedSemanticBackend) {
28436
29188
  const storageDir2 = configOverrides.storage_dir;
28437
29189
  const ortDylibDir = await ensureOnnxRuntime(storageDir2).catch((err) => {
28438
29190
  warn(`ONNX Runtime setup failed: ${err instanceof Error ? err.message : String(err)}. Semantic search will be unavailable.`);
@@ -28516,12 +29268,12 @@ ${lines}
28516
29268
  log(`Found/downloaded compatible binary at ${path5}. Replacing running bridges...`);
28517
29269
  pool.replaceBinary(path5).then(() => {
28518
29270
  log("Binary replaced successfully. New bridges will use the updated binary.");
28519
- }, (err) => error48("Failed to replace binary:", err));
29271
+ }, (err) => error("Failed to replace binary:", err));
28520
29272
  } else {
28521
29273
  warn(`Could not find or download v${minVersion}. Continuing with v${binaryVersion}.`);
28522
29274
  }
28523
29275
  }, (err) => {
28524
- error48(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
29276
+ error(`Auto-download failed: ${err.message}. Install manually: cargo install agent-file-tools@${minVersion}`);
28525
29277
  });
28526
29278
  },
28527
29279
  onConfigureWarnings: async ({ projectRoot, sessionId, client, warnings }) => {
@@ -28542,6 +29294,7 @@ ${lines}
28542
29294
  const ctx = {
28543
29295
  pool,
28544
29296
  client: input.client,
29297
+ plugin: input.plugin,
28545
29298
  config: aftConfig,
28546
29299
  storageDir: configOverrides.storage_dir
28547
29300
  };
@@ -28580,14 +29333,14 @@ ${lines}
28580
29333
  if (storageDir && ANNOUNCEMENT_VERSION) {
28581
29334
  try {
28582
29335
  mkdirSync10(storageDir, { recursive: true });
28583
- writeFileSync9(join19(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
29336
+ writeFileSync10(join19(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
28584
29337
  } catch {}
28585
29338
  }
28586
29339
  return { success: true };
28587
29340
  });
28588
29341
  rpcServer.handle("get-warnings", async () => {
28589
29342
  const warnings = [];
28590
- if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend && !configOverrides._ort_dylib_dir) {
29343
+ if (aftConfig.semantic_search && isFastembedSemanticBackend && !configOverrides._ort_dylib_dir) {
28591
29344
  if (!isOrtAutoDownloadSupported()) {
28592
29345
  warnings.push(`Semantic search requires ONNX Runtime.
28593
29346
  Install: ${getManualInstallHint()}`);
@@ -28611,7 +29364,7 @@ Install: ${getManualInstallHint()}`);
28611
29364
  sendFeatureAnnouncement(notifyOpts, ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, storageDir).catch(() => {});
28612
29365
  }, 8000);
28613
29366
  }
28614
- if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend && !configOverrides._ort_dylib_dir) {
29367
+ if (aftConfig.semantic_search && isFastembedSemanticBackend && !configOverrides._ort_dylib_dir) {
28615
29368
  setTimeout(() => {
28616
29369
  if (!configOverrides._ort_dylib_dir && !isOrtAutoDownloadSupported()) {
28617
29370
  sendWarning(notifyOpts, `Semantic search requires ONNX Runtime.
@@ -28637,8 +29390,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
28637
29390
  ...structureTools(ctx),
28638
29391
  ...navigationTools(ctx),
28639
29392
  ...surface !== "minimal" && astTools(ctx),
28640
- ...surface !== "minimal" && aftConfig.experimental_semantic_search === true && semanticTools(ctx),
28641
- ...surface !== "minimal" && aftConfig.experimental_search_index === true && searchTools(ctx),
29393
+ ...surface !== "minimal" && aftConfig.semantic_search === true && semanticTools(ctx),
29394
+ ...surface !== "minimal" && aftConfig.search_index === true && searchTools(ctx),
28642
29395
  ...refactoringTools(ctx),
28643
29396
  ...surface !== "minimal" && lspTools(ctx),
28644
29397
  ...surface !== "minimal" && conflictTools(ctx)
@@ -28661,13 +29414,30 @@ Install: ${getManualInstallHint()}`).catch(() => {});
28661
29414
  }
28662
29415
  log(`Disabled ${disabled.size} tool(s): ${[...disabled].join(", ")}`);
28663
29416
  }
29417
+ const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
29418
+ enabled: true,
29419
+ autoUpdate: aftConfig.auto_update ?? true,
29420
+ signal: autoUpdateAbort.signal
29421
+ });
28664
29422
  return {
28665
29423
  tool: allTools,
28666
- event: createAutoUpdateCheckerHook(input, {
28667
- enabled: true,
28668
- autoUpdate: aftConfig.auto_update ?? true,
28669
- signal: autoUpdateAbort.signal
28670
- }),
29424
+ event: async (eventInput) => {
29425
+ await autoUpdateEventHook(eventInput);
29426
+ if (eventInput.event.type !== "session.idle")
29427
+ return;
29428
+ const sessionID = extractSessionID(eventInput.event.properties);
29429
+ if (!sessionID)
29430
+ return;
29431
+ await handleIdleBgCompletions({
29432
+ ctx,
29433
+ directory: input.directory,
29434
+ sessionID,
29435
+ client: input.client
29436
+ });
29437
+ },
29438
+ "chat.message": async (messageInput) => {
29439
+ resetBgWake(messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id);
29440
+ },
28671
29441
  "command.execute.before": async (commandInput, _output) => {
28672
29442
  if (isTuiMode2() || commandInput.command !== STATUS_COMMAND) {
28673
29443
  return;
@@ -28681,22 +29451,22 @@ Install: ${getManualInstallHint()}`).catch(() => {});
28681
29451
  await sendIgnoredMessage2(input.client, commandInput.sessionID, formatStatusMarkdown(status));
28682
29452
  throwSentinel(commandInput.command);
28683
29453
  },
28684
- "tool.execute.after": async (input2, output) => {
29454
+ "tool.execute.after": async (toolInput, output) => {
28685
29455
  if (!output)
28686
29456
  return;
28687
- const stored = consumeToolMetadata(input2.sessionID, input2.callID);
29457
+ const stored = consumeToolMetadata(toolInput.sessionID, toolInput.callID);
28688
29458
  if (stored) {
28689
29459
  if (stored.title)
28690
29460
  output.title = stored.title;
28691
29461
  if (stored.metadata)
28692
29462
  output.metadata = { ...output.metadata, ...stored.metadata };
28693
29463
  }
28694
- if (input2.tool === "bash" && output.output?.includes("Automatic merge failed; fix conflicts")) {
29464
+ if (toolInput.tool === "bash" && output.output?.includes("Automatic merge failed; fix conflicts")) {
28695
29465
  output.output += `
28696
29466
 
28697
29467
  [Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
28698
29468
  }
28699
- if (input2.tool === "bash" && output.output) {
29469
+ if (toolInput.tool === "bash" && output.output) {
28700
29470
  const firstLine = output.output.slice(0, 300).split(`
28701
29471
  `)[0] ?? "";
28702
29472
  if (/\b(rg|grep)\s/.test(firstLine)) {
@@ -28705,6 +29475,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
28705
29475
  [Hint] Use the grep tool instead of bash for faster indexed search.`;
28706
29476
  }
28707
29477
  }
29478
+ await appendInTurnBgCompletions({ ctx, directory: input.directory, sessionID: toolInput.sessionID }, output);
28708
29479
  },
28709
29480
  config: async (config2) => {
28710
29481
  config2.command = {