@cortexkit/aft-opencode 0.19.6 → 0.20.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
@@ -7803,10 +7803,10 @@ 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 writeFileSync10 } from "fs";
7806
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
7807
7807
  import { createRequire as createRequire2 } from "module";
7808
7808
  import { homedir as homedir11 } from "os";
7809
- import { join as join18 } from "path";
7809
+ import { join as join19 } from "path";
7810
7810
 
7811
7811
  // ../aft-bridge/dist/active-logger.js
7812
7812
  var active;
@@ -7942,6 +7942,7 @@ class BinaryBridge {
7942
7942
  onVersionMismatch;
7943
7943
  onConfigureWarnings;
7944
7944
  onBashCompletion;
7945
+ onBashLongRunning;
7945
7946
  configureWarningClients = new Map;
7946
7947
  restartResetTimer = null;
7947
7948
  errorPrefix;
@@ -7955,6 +7956,7 @@ class BinaryBridge {
7955
7956
  this.onVersionMismatch = options?.onVersionMismatch;
7956
7957
  this.onConfigureWarnings = options?.onConfigureWarnings;
7957
7958
  this.onBashCompletion = options?.onBashCompletion;
7959
+ this.onBashLongRunning = options?.onBashLongRunning;
7958
7960
  this.errorPrefix = options?.errorPrefix ?? "[aft-bridge]";
7959
7961
  }
7960
7962
  get restartCount() {
@@ -8267,6 +8269,10 @@ class BinaryBridge {
8267
8269
  this.onBashCompletion?.(response, this);
8268
8270
  continue;
8269
8271
  }
8272
+ if (response.type === "bash_long_running") {
8273
+ this.onBashLongRunning?.(response, this);
8274
+ continue;
8275
+ }
8270
8276
  if (response.type === "configure_warnings") {
8271
8277
  this.handleConfigureWarningsFrame(response).catch((err) => {
8272
8278
  warn(`configure warning delivery failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -9091,7 +9097,8 @@ class BridgePool {
9091
9097
  minVersion: options.minVersion,
9092
9098
  onVersionMismatch: options.onVersionMismatch,
9093
9099
  onConfigureWarnings: options.onConfigureWarnings,
9094
- onBashCompletion: options.onBashCompletion
9100
+ onBashCompletion: options.onBashCompletion,
9101
+ onBashLongRunning: options.onBashLongRunning
9095
9102
  };
9096
9103
  this.configOverrides = configOverrides;
9097
9104
  if (Number.isFinite(this.idleTimeoutMs)) {
@@ -9905,20 +9912,26 @@ async function handlePushedBgCompletion(drainContext, completion) {
9905
9912
  ingestBgCompletions(drainContext.sessionID, [completion]);
9906
9913
  await triggerWakeIfPending(drainContext, true);
9907
9914
  }
9915
+ async function handlePushedBgLongRunning(drainContext, reminder) {
9916
+ stateFor(drainContext.sessionID).pendingLongRunning.push(reminder);
9917
+ await triggerWakeIfPending(drainContext, true);
9918
+ }
9908
9919
  async function appendInTurnBgCompletions(drainContext, output) {
9909
9920
  if (!output)
9910
9921
  return;
9911
9922
  const state = stateFor(drainContext.sessionID);
9912
- if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0)
9923
+ if (state.outstandingTaskIds.size === 0 && state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0) {
9913
9924
  return;
9925
+ }
9914
9926
  if (state.outstandingTaskIds.size > 0) {
9915
9927
  await drainCompletions(drainContext);
9916
9928
  }
9917
- if (state.pendingCompletions.length === 0)
9929
+ if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
9918
9930
  return;
9919
- const reminder = formatSystemReminder(state.pendingCompletions);
9931
+ const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning);
9920
9932
  output.output = appendReminder(output.output ?? "", reminder);
9921
9933
  state.pendingCompletions = [];
9934
+ state.pendingLongRunning = [];
9922
9935
  }
9923
9936
  async function handleIdleBgCompletions(drainContext) {
9924
9937
  await triggerWakeIfPending(drainContext, false);
@@ -9930,7 +9943,7 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
9930
9943
  if (!skipDrain && state.outstandingTaskIds.size > 0) {
9931
9944
  await drainCompletions(drainContext);
9932
9945
  }
9933
- if (state.pendingCompletions.length === 0)
9946
+ if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
9934
9947
  return;
9935
9948
  scheduleWake(state, async (reminder) => {
9936
9949
  const client = drainContext.client;
@@ -9975,6 +9988,23 @@ For truncated tasks, use bash_status({ taskId: "..." }) to retrieve full output.
9975
9988
  ${bullets}${tail}
9976
9989
  </system-reminder>`;
9977
9990
  }
9991
+ function formatLongRunningReminder(reminders) {
9992
+ const bullets = reminders.map((reminder) => `- ${reminder.task_id} still running after ${formatDurationMs(reminder.elapsed_ms)}: ${shorten(reminder.command, 120)}`).join(`
9993
+ `);
9994
+ return `<system-reminder>
9995
+ [BACKGROUND BASH STILL RUNNING]
9996
+ ${bullets}
9997
+ Use bash_status({ taskId: "..." }) to inspect output or bash_kill({ taskId: "..." }) to terminate.
9998
+ </system-reminder>`;
9999
+ }
10000
+ function formatCombinedSystemReminder(completions, longRunning) {
10001
+ if (completions.length === 0)
10002
+ return formatLongRunningReminder(longRunning);
10003
+ if (longRunning.length === 0)
10004
+ return formatSystemReminder(completions);
10005
+ return `${formatSystemReminder(completions)}
10006
+ ${formatLongRunningReminder(longRunning)}`;
10007
+ }
9978
10008
  function extractSessionID(value) {
9979
10009
  if (!value || typeof value !== "object")
9980
10010
  return;
@@ -10008,7 +10038,8 @@ async function drainCompletions({ ctx, directory, sessionID }) {
10008
10038
  }
10009
10039
  function scheduleWake(state, sendWake, onSendFailure) {
10010
10040
  const now = Date.now();
10011
- if (state.debounceTimer && state.pendingCompletions.length <= state.scheduledCompletionCount) {
10041
+ const pendingCount = state.pendingCompletions.length + state.pendingLongRunning.length;
10042
+ if (state.debounceTimer && pendingCount <= state.scheduledCompletionCount) {
10012
10043
  return;
10013
10044
  }
10014
10045
  if (state.firstCompletionAt === null) {
@@ -10018,14 +10049,16 @@ function scheduleWake(state, sendWake, onSendFailure) {
10018
10049
  const previousFireAt = state.scheduledFireAt ?? now;
10019
10050
  state.scheduledFireAt = Math.min(previousFireAt + DEBOUNCE_STEP_MS, state.firstCompletionAt + DEBOUNCE_CAP_MS);
10020
10051
  }
10021
- state.scheduledCompletionCount = state.pendingCompletions.length;
10052
+ state.scheduledCompletionCount = pendingCount;
10022
10053
  if (state.debounceTimer)
10023
10054
  clearTimeout(state.debounceTimer);
10024
10055
  const delay = state.retryDelayMs ?? Math.max(0, (state.scheduledFireAt ?? now) - now);
10025
10056
  state.debounceTimer = setTimeout(() => {
10026
10057
  const pending = state.pendingCompletions;
10027
- const reminder = formatSystemReminder(pending);
10058
+ const pendingLongRunning = state.pendingLongRunning;
10059
+ const reminder = formatCombinedSystemReminder(pending, pendingLongRunning);
10028
10060
  state.pendingCompletions = [];
10061
+ state.pendingLongRunning = [];
10029
10062
  state.debounceTimer = null;
10030
10063
  state.firstCompletionAt = null;
10031
10064
  state.scheduledFireAt = null;
@@ -10035,6 +10068,7 @@ function scheduleWake(state, sendWake, onSendFailure) {
10035
10068
  state.wakeFiredThisIdle = true;
10036
10069
  }).catch((err) => {
10037
10070
  state.pendingCompletions = [...pending, ...state.pendingCompletions];
10071
+ state.pendingLongRunning = [...pendingLongRunning, ...state.pendingLongRunning];
10038
10072
  state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
10039
10073
  onSendFailure(err);
10040
10074
  scheduleWake(state, sendWake, onSendFailure);
@@ -10051,6 +10085,7 @@ function stateFor(sessionID) {
10051
10085
  state = {
10052
10086
  outstandingTaskIds: new Set,
10053
10087
  pendingCompletions: [],
10088
+ pendingLongRunning: [],
10054
10089
  debounceTimer: null,
10055
10090
  wakeFiredThisIdle: false,
10056
10091
  firstCompletionAt: null,
@@ -10101,6 +10136,17 @@ function appendReminder(output, reminder) {
10101
10136
 
10102
10137
  ${reminder}` : reminder;
10103
10138
  }
10139
+ function formatDurationMs(ms) {
10140
+ if (!Number.isFinite(ms) || ms < 1000)
10141
+ return `${Math.max(0, Math.round(ms))}ms`;
10142
+ const totalSeconds = Math.round(ms / 1000);
10143
+ const minutes = Math.floor(totalSeconds / 60);
10144
+ const seconds = totalSeconds % 60;
10145
+ return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
10146
+ }
10147
+ function shorten(value, limit) {
10148
+ return value.length <= limit ? value : `${value.slice(0, limit - 1)}\u2026`;
10149
+ }
10104
10150
  function formatCompletion(completion) {
10105
10151
  const status = formatStatus(completion);
10106
10152
  const duration = formatDuration(completion);
@@ -23739,7 +23785,9 @@ var ExperimentalConfigSchema = exports_external.object({
23739
23785
  bash: exports_external.object({
23740
23786
  rewrite: exports_external.boolean().optional(),
23741
23787
  compress: exports_external.boolean().optional(),
23742
- background: exports_external.boolean().optional()
23788
+ background: exports_external.boolean().optional(),
23789
+ long_running_reminder_enabled: exports_external.boolean().optional(),
23790
+ long_running_reminder_interval_ms: exports_external.number().int().positive().optional()
23743
23791
  }).optional(),
23744
23792
  lsp_ty: exports_external.boolean().optional()
23745
23793
  });
@@ -23820,6 +23868,12 @@ function resolveExperimentalConfigForConfigure(config2) {
23820
23868
  if (config2.experimental?.bash?.background !== undefined) {
23821
23869
  overrides.experimental_bash_background = config2.experimental.bash.background;
23822
23870
  }
23871
+ if (config2.experimental?.bash?.long_running_reminder_enabled !== undefined) {
23872
+ overrides.bash_long_running_reminder_enabled = config2.experimental.bash.long_running_reminder_enabled;
23873
+ }
23874
+ if (config2.experimental?.bash?.long_running_reminder_interval_ms !== undefined) {
23875
+ overrides.bash_long_running_reminder_interval_ms = config2.experimental.bash.long_running_reminder_interval_ms;
23876
+ }
23823
23877
  if (config2.experimental?.lsp_ty !== undefined) {
23824
23878
  overrides.experimental_lsp_ty = config2.experimental.lsp_ty;
23825
23879
  }
@@ -24150,6 +24204,10 @@ function loadAftConfig(projectDirectory) {
24150
24204
  return config2;
24151
24205
  }
24152
24206
 
24207
+ // src/hooks/auto-update-checker/index.ts
24208
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
24209
+ import { dirname as dirname4, join as join11 } from "path";
24210
+
24153
24211
  // src/hooks/auto-update-checker/cache.ts
24154
24212
  var import_comment_json3 = __toESM(require_src2(), 1);
24155
24213
  import { spawn as spawn2 } from "child_process";
@@ -24547,6 +24605,9 @@ async function runNpmInstallSafe(installDir, options = {}) {
24547
24605
  }
24548
24606
 
24549
24607
  // src/hooks/auto-update-checker/index.ts
24608
+ var DEFAULT_CHECK_INTERVAL_MS = 60 * 60 * 1000;
24609
+ var DEFAULT_INIT_DELAY_MS = 5000;
24610
+ var TIMESTAMP_FILENAME = "last-update-check.json";
24550
24611
  function createAutoUpdateCheckerHook(ctx, options = {}) {
24551
24612
  const {
24552
24613
  enabled = true,
@@ -24554,40 +24615,68 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
24554
24615
  autoUpdate = true,
24555
24616
  npmRegistryUrl = NPM_REGISTRY_URL,
24556
24617
  fetchTimeoutMs = NPM_FETCH_TIMEOUT,
24557
- signal = new AbortController().signal
24618
+ signal = new AbortController().signal,
24619
+ storageDir = null,
24620
+ checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS,
24621
+ initDelayMs = DEFAULT_INIT_DELAY_MS
24558
24622
  } = options;
24559
- let hasChecked = false;
24560
- return async ({ event }) => {
24561
- if (!enabled)
24562
- return;
24563
- if (event.type !== "session.created")
24564
- return;
24565
- if (hasChecked)
24566
- return;
24567
- if (getParentId(event.properties))
24568
- return;
24569
- hasChecked = true;
24570
- setTimeout(() => {
24571
- runStartupCheck(ctx, {
24572
- showStartupToast,
24573
- autoUpdate,
24574
- npmRegistryUrl,
24575
- fetchTimeoutMs,
24576
- signal
24577
- }).catch((err) => {
24578
- warn2(`[auto-update-checker] Background update check failed: ${String(err)}`);
24579
- });
24580
- }, 0);
24581
- };
24623
+ if (!enabled) {
24624
+ return async (_input) => {};
24625
+ }
24626
+ const initTimer = setTimeout(() => {
24627
+ maybeRunCheck(ctx, {
24628
+ showStartupToast,
24629
+ autoUpdate,
24630
+ npmRegistryUrl,
24631
+ fetchTimeoutMs,
24632
+ signal,
24633
+ storageDir,
24634
+ checkIntervalMs,
24635
+ initDelayMs
24636
+ }).catch((err) => {
24637
+ warn2(`[auto-update-checker] Background update check failed: ${String(err)}`);
24638
+ });
24639
+ }, initDelayMs);
24640
+ if (typeof initTimer === "object" && initTimer !== null && "unref" in initTimer) {
24641
+ initTimer.unref();
24642
+ }
24643
+ signal.addEventListener("abort", () => {
24644
+ clearTimeout(initTimer);
24645
+ }, { once: true });
24646
+ return async (_input) => {};
24582
24647
  }
24583
- function getParentId(properties) {
24584
- if (!properties || typeof properties !== "object" || Array.isArray(properties))
24585
- return null;
24586
- const info = properties.info;
24587
- if (!info || typeof info !== "object" || Array.isArray(info))
24588
- return null;
24589
- const parentID = info.parentID;
24590
- return typeof parentID === "string" && parentID.length > 0 ? parentID : null;
24648
+ async function maybeRunCheck(ctx, options) {
24649
+ if (options.signal.aborted)
24650
+ return;
24651
+ if (!claimCheckSlot(options.storageDir, options.checkIntervalMs)) {
24652
+ log2("[auto-update-checker] Skipping check (another instance ran one recently)");
24653
+ return;
24654
+ }
24655
+ await runStartupCheck(ctx, options);
24656
+ }
24657
+ function claimCheckSlot(storageDir, intervalMs) {
24658
+ if (!storageDir)
24659
+ return true;
24660
+ try {
24661
+ const file2 = join11(storageDir, TIMESTAMP_FILENAME);
24662
+ if (existsSync8(file2)) {
24663
+ try {
24664
+ const raw = JSON.parse(readFileSync6(file2, "utf-8"));
24665
+ const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
24666
+ if (Number.isFinite(last) && Date.now() - last < intervalMs) {
24667
+ return false;
24668
+ }
24669
+ } catch {}
24670
+ }
24671
+ mkdirSync5(dirname4(file2), { recursive: true });
24672
+ const tmp = `${file2}.tmp.${process.pid}`;
24673
+ writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
24674
+ renameSync3(tmp, file2);
24675
+ return true;
24676
+ } catch (err) {
24677
+ warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
24678
+ return true;
24679
+ }
24591
24680
  }
24592
24681
  async function runStartupCheck(ctx, options) {
24593
24682
  if (options.signal.aborted)
@@ -24675,43 +24764,43 @@ import { createReadStream, statSync as statSync4 } from "fs";
24675
24764
  // src/lsp-cache.ts
24676
24765
  import {
24677
24766
  closeSync as closeSync2,
24678
- mkdirSync as mkdirSync5,
24767
+ mkdirSync as mkdirSync6,
24679
24768
  openSync as openSync2,
24680
- readFileSync as readFileSync6,
24769
+ readFileSync as readFileSync7,
24681
24770
  statSync as statSync3,
24682
24771
  unlinkSync as unlinkSync5,
24683
- writeFileSync as writeFileSync6
24772
+ writeFileSync as writeFileSync7
24684
24773
  } from "fs";
24685
24774
  import { homedir as homedir8 } from "os";
24686
- import { join as join11 } from "path";
24775
+ import { join as join12 } from "path";
24687
24776
  function aftCacheBase() {
24688
24777
  const override = process.env.AFT_CACHE_DIR;
24689
24778
  if (override && override.length > 0)
24690
24779
  return override;
24691
24780
  if (process.platform === "win32") {
24692
24781
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
24693
- const base2 = localAppData || join11(homedir8(), "AppData", "Local");
24694
- return join11(base2, "aft");
24782
+ const base2 = localAppData || join12(homedir8(), "AppData", "Local");
24783
+ return join12(base2, "aft");
24695
24784
  }
24696
- const base = process.env.XDG_CACHE_HOME || join11(homedir8(), ".cache");
24697
- return join11(base, "aft");
24785
+ const base = process.env.XDG_CACHE_HOME || join12(homedir8(), ".cache");
24786
+ return join12(base, "aft");
24698
24787
  }
24699
24788
  function lspCacheRoot() {
24700
- return join11(aftCacheBase(), "lsp-packages");
24789
+ return join12(aftCacheBase(), "lsp-packages");
24701
24790
  }
24702
24791
  function lspPackageDir(npmPackage) {
24703
- return join11(lspCacheRoot(), encodeURIComponent(npmPackage));
24792
+ return join12(lspCacheRoot(), encodeURIComponent(npmPackage));
24704
24793
  }
24705
24794
  function lspBinaryPath(npmPackage, binary) {
24706
- return join11(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
24795
+ return join12(lspPackageDir(npmPackage), "node_modules", ".bin", binary);
24707
24796
  }
24708
24797
  function lspBinDir(npmPackage) {
24709
- return join11(lspPackageDir(npmPackage), "node_modules", ".bin");
24798
+ return join12(lspPackageDir(npmPackage), "node_modules", ".bin");
24710
24799
  }
24711
24800
  function isInstalled(npmPackage, binary) {
24712
24801
  for (const candidate of lspBinaryCandidates(binary)) {
24713
24802
  try {
24714
- if (statSync3(join11(lspBinDir(npmPackage), candidate)).isFile())
24803
+ if (statSync3(join12(lspBinDir(npmPackage), candidate)).isFile())
24715
24804
  return true;
24716
24805
  } catch {}
24717
24806
  }
@@ -24725,23 +24814,23 @@ function lspBinaryCandidates(binary) {
24725
24814
  var INSTALLED_META_FILE = ".aft-installed";
24726
24815
  function writeInstalledMetaIn(installDir, version2, sha256) {
24727
24816
  try {
24728
- mkdirSync5(installDir, { recursive: true });
24817
+ mkdirSync6(installDir, { recursive: true });
24729
24818
  const meta3 = {
24730
24819
  version: version2,
24731
24820
  installedAt: new Date().toISOString(),
24732
24821
  ...sha256 ? { sha256 } : {}
24733
24822
  };
24734
- writeFileSync6(join11(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
24823
+ writeFileSync7(join12(installDir, INSTALLED_META_FILE), JSON.stringify(meta3), "utf8");
24735
24824
  } catch (err) {
24736
24825
  log2(`[lsp-cache] failed to write installed-meta in ${installDir}: ${err}`);
24737
24826
  }
24738
24827
  }
24739
24828
  function readInstalledMetaIn(installDir) {
24740
- const path2 = join11(installDir, INSTALLED_META_FILE);
24829
+ const path2 = join12(installDir, INSTALLED_META_FILE);
24741
24830
  try {
24742
24831
  if (!statSync3(path2).isFile())
24743
24832
  return null;
24744
- const raw = readFileSync6(path2, "utf8");
24833
+ const raw = readFileSync7(path2, "utf8");
24745
24834
  const parsed = JSON.parse(raw);
24746
24835
  if (typeof parsed.version !== "string" || parsed.version.length === 0)
24747
24836
  return null;
@@ -24761,17 +24850,17 @@ function readInstalledMeta(packageKey) {
24761
24850
  return readInstalledMetaIn(lspPackageDir(packageKey));
24762
24851
  }
24763
24852
  function lockPath(npmPackage) {
24764
- return join11(lspPackageDir(npmPackage), ".aft-installing");
24853
+ return join12(lspPackageDir(npmPackage), ".aft-installing");
24765
24854
  }
24766
24855
  var STALE_LOCK_MS2 = 30 * 60 * 1000;
24767
24856
  function acquireInstallLock(lockKey) {
24768
- mkdirSync5(lspPackageDir(lockKey), { recursive: true });
24857
+ mkdirSync6(lspPackageDir(lockKey), { recursive: true });
24769
24858
  const lock = lockPath(lockKey);
24770
24859
  const tryClaim = () => {
24771
24860
  try {
24772
24861
  const fd = openSync2(lock, "wx");
24773
24862
  try {
24774
- writeFileSync6(fd, `${process.pid}
24863
+ writeFileSync7(fd, `${process.pid}
24775
24864
  ${new Date().toISOString()}
24776
24865
  `);
24777
24866
  } finally {
@@ -24791,7 +24880,7 @@ ${new Date().toISOString()}
24791
24880
  let owningPid = null;
24792
24881
  let lockMtimeMs = 0;
24793
24882
  try {
24794
- const raw = readFileSync6(lock, "utf8");
24883
+ const raw = readFileSync7(lock, "utf8");
24795
24884
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
24796
24885
  const parsed = Number.parseInt(firstLine, 10);
24797
24886
  if (Number.isFinite(parsed) && parsed > 0)
@@ -24829,7 +24918,7 @@ function releaseInstallLock(lockKey) {
24829
24918
  try {
24830
24919
  let owningPid = null;
24831
24920
  try {
24832
- const raw = readFileSync6(lock, "utf8");
24921
+ const raw = readFileSync7(lock, "utf8");
24833
24922
  const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
24834
24923
  const parsed = Number.parseInt(firstLine, 10);
24835
24924
  if (Number.isFinite(parsed) && parsed > 0)
@@ -24868,9 +24957,9 @@ async function withInstallLock(lockKey, task) {
24868
24957
  }
24869
24958
  var VERSION_CHECK_FILE = ".aft-version-check";
24870
24959
  function readVersionCheck(npmPackage) {
24871
- const file2 = join11(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
24960
+ const file2 = join12(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
24872
24961
  try {
24873
- const raw = readFileSync6(file2, "utf8");
24962
+ const raw = readFileSync7(file2, "utf8");
24874
24963
  const parsed = JSON.parse(raw);
24875
24964
  if (typeof parsed.last_checked === "string") {
24876
24965
  return {
@@ -24884,13 +24973,13 @@ function readVersionCheck(npmPackage) {
24884
24973
  }
24885
24974
  }
24886
24975
  function writeVersionCheck(npmPackage, latest) {
24887
- mkdirSync5(lspPackageDir(npmPackage), { recursive: true });
24888
- const file2 = join11(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
24976
+ mkdirSync6(lspPackageDir(npmPackage), { recursive: true });
24977
+ const file2 = join12(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
24889
24978
  const record2 = {
24890
24979
  last_checked: new Date().toISOString(),
24891
24980
  latest_eligible: latest
24892
24981
  };
24893
- writeFileSync6(file2, JSON.stringify(record2, null, 2));
24982
+ writeFileSync7(file2, JSON.stringify(record2, null, 2));
24894
24983
  }
24895
24984
  function shouldRecheckVersion(record2, weeklyCheckIntervalMs = 7 * 24 * 60 * 60 * 1000) {
24896
24985
  if (!record2)
@@ -25033,8 +25122,8 @@ var NPM_LSP_TABLE = [
25033
25122
  ];
25034
25123
 
25035
25124
  // src/lsp-project-relevance.ts
25036
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
25037
- import { join as join12 } from "path";
25125
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
25126
+ import { join as join13 } from "path";
25038
25127
  var MAX_WALK_DIRS = 200;
25039
25128
  var MAX_WALK_DEPTH = 4;
25040
25129
  var NOISE_DIRS = new Set([
@@ -25051,7 +25140,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
25051
25140
  if (!rootMarkers)
25052
25141
  return false;
25053
25142
  for (const marker of rootMarkers) {
25054
- if (existsSync8(join12(projectRoot, marker)))
25143
+ if (existsSync9(join13(projectRoot, marker)))
25055
25144
  return true;
25056
25145
  }
25057
25146
  return false;
@@ -25077,7 +25166,7 @@ function relevantExtensionsInProject(projectRoot, extToServer) {
25077
25166
  for (const entry of entries) {
25078
25167
  if (entry.isDirectory()) {
25079
25168
  if (current.depth < MAX_WALK_DEPTH && !NOISE_DIRS.has(entry.name.toLowerCase())) {
25080
- queue.push({ dir: join12(current.dir, entry.name), depth: current.depth + 1 });
25169
+ queue.push({ dir: join13(current.dir, entry.name), depth: current.depth + 1 });
25081
25170
  }
25082
25171
  continue;
25083
25172
  }
@@ -25416,18 +25505,18 @@ import {
25416
25505
  copyFileSync as copyFileSync3,
25417
25506
  createReadStream as createReadStream2,
25418
25507
  createWriteStream as createWriteStream2,
25419
- existsSync as existsSync9,
25508
+ existsSync as existsSync10,
25420
25509
  lstatSync as lstatSync2,
25421
- mkdirSync as mkdirSync6,
25510
+ mkdirSync as mkdirSync7,
25422
25511
  readdirSync as readdirSync4,
25423
25512
  readlinkSync as readlinkSync2,
25424
25513
  realpathSync as realpathSync3,
25425
- renameSync as renameSync3,
25514
+ renameSync as renameSync4,
25426
25515
  rmSync as rmSync3,
25427
25516
  statSync as statSync5,
25428
25517
  unlinkSync as unlinkSync6
25429
25518
  } from "fs";
25430
- import { dirname as dirname4, join as join13, relative as relative2, resolve as resolve3 } from "path";
25519
+ import { dirname as dirname5, join as join14, relative as relative2, resolve as resolve3 } from "path";
25431
25520
  import { Readable as Readable2 } from "stream";
25432
25521
  import { pipeline as pipeline2 } from "stream/promises";
25433
25522
 
@@ -25517,25 +25606,25 @@ function detectHostPlatform() {
25517
25606
 
25518
25607
  // src/lsp-github-install.ts
25519
25608
  function ghCacheRoot() {
25520
- return join13(aftCacheBase(), "lsp-binaries");
25609
+ return join14(aftCacheBase(), "lsp-binaries");
25521
25610
  }
25522
25611
  function ghPackageDir(spec) {
25523
- return join13(ghCacheRoot(), spec.id);
25612
+ return join14(ghCacheRoot(), spec.id);
25524
25613
  }
25525
25614
  function ghBinDir(spec) {
25526
- return join13(ghPackageDir(spec), "bin");
25615
+ return join14(ghPackageDir(spec), "bin");
25527
25616
  }
25528
25617
  function ghExtractDir(spec) {
25529
- return join13(ghPackageDir(spec), "extracted");
25618
+ return join14(ghPackageDir(spec), "extracted");
25530
25619
  }
25531
25620
  function ghBinaryPath(spec, platform2) {
25532
25621
  const ext = platform2 === "win32" ? ".exe" : "";
25533
- return join13(ghBinDir(spec), `${spec.binary}${ext}`);
25622
+ return join14(ghBinDir(spec), `${spec.binary}${ext}`);
25534
25623
  }
25535
25624
  function isGithubInstalled(spec, platform2) {
25536
25625
  for (const candidate of ghBinaryCandidates(spec, platform2)) {
25537
25626
  try {
25538
- if (statSync5(join13(ghBinDir(spec), candidate)).isFile())
25627
+ if (statSync5(join14(ghBinDir(spec), candidate)).isFile())
25539
25628
  return true;
25540
25629
  } catch {}
25541
25630
  }
@@ -25723,7 +25812,7 @@ async function downloadFile(url2, destPath, fetchImpl, assetSize, signal) {
25723
25812
  if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
25724
25813
  throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
25725
25814
  }
25726
- mkdirSync6(dirname4(destPath), { recursive: true });
25815
+ mkdirSync7(dirname5(destPath), { recursive: true });
25727
25816
  let bytesWritten = 0;
25728
25817
  const guard = new TransformStream({
25729
25818
  transform(chunk, controller) {
@@ -25758,7 +25847,7 @@ function validateExtraction(stagingRoot) {
25758
25847
  throw new Error(`failed to read staging dir ${dir}: ${err}`);
25759
25848
  }
25760
25849
  for (const entry of entries) {
25761
- const full = join13(dir, entry);
25850
+ const full = join14(dir, entry);
25762
25851
  let lst;
25763
25852
  try {
25764
25853
  lst = lstatSync2(full);
@@ -25802,14 +25891,14 @@ function extractArchiveSafely(archivePath, destDir, archiveType) {
25802
25891
  try {
25803
25892
  rmSync3(stagingDir, { recursive: true, force: true });
25804
25893
  } catch {}
25805
- mkdirSync6(stagingDir, { recursive: true });
25894
+ mkdirSync7(stagingDir, { recursive: true });
25806
25895
  try {
25807
25896
  runPlatformExtractor(archivePath, stagingDir, archiveType);
25808
25897
  validateExtraction(stagingDir);
25809
25898
  try {
25810
25899
  rmSync3(destDir, { recursive: true, force: true });
25811
25900
  } catch {}
25812
- renameSync3(stagingDir, destDir);
25901
+ renameSync4(stagingDir, destDir);
25813
25902
  } catch (err) {
25814
25903
  try {
25815
25904
  rmSync3(stagingDir, { recursive: true, force: true });
@@ -25862,7 +25951,7 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
25862
25951
  }
25863
25952
  const pkgDir = ghPackageDir(spec);
25864
25953
  const extractDir = ghExtractDir(spec);
25865
- const archivePath = join13(pkgDir, expected.name);
25954
+ const archivePath = join14(pkgDir, expected.name);
25866
25955
  log2(`[lsp] downloading ${spec.id} ${tag} \u2192 ${matchingAsset.url}`);
25867
25956
  try {
25868
25957
  await downloadFile(matchingAsset.url, archivePath, fetchImpl, matchingAsset.size, signal);
@@ -25901,13 +25990,13 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl,
25901
25990
  unlinkSync6(archivePath);
25902
25991
  } catch {}
25903
25992
  }
25904
- const innerBinaryPath = join13(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
25905
- if (!existsSync9(innerBinaryPath)) {
25993
+ const innerBinaryPath = join14(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
25994
+ if (!existsSync10(innerBinaryPath)) {
25906
25995
  error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
25907
25996
  return null;
25908
25997
  }
25909
25998
  const targetBinary = ghBinaryPath(spec, platform2);
25910
- mkdirSync6(dirname4(targetBinary), { recursive: true });
25999
+ mkdirSync7(dirname5(targetBinary), { recursive: true });
25911
26000
  try {
25912
26001
  copyFileSync3(innerBinaryPath, targetBinary);
25913
26002
  if (platform2 !== "win32") {
@@ -25984,7 +26073,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl = fetch) {
25984
26073
  if (!host) {
25985
26074
  for (const spec of GITHUB_LSP_TABLE) {
25986
26075
  try {
25987
- if (existsSync9(ghBinDir(spec))) {
26076
+ if (existsSync10(ghBinDir(spec))) {
25988
26077
  cachedBinDirs.push(ghBinDir(spec));
25989
26078
  }
25990
26079
  } catch {}
@@ -26147,9 +26236,9 @@ function normalizeToolMap(tools) {
26147
26236
  }
26148
26237
 
26149
26238
  // src/notifications.ts
26150
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
26239
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
26151
26240
  import { homedir as homedir9, platform as platform2 } from "os";
26152
- import { join as join14 } from "path";
26241
+ import { join as join15 } from "path";
26153
26242
  function isTuiMode() {
26154
26243
  return process.env.OPENCODE_CLIENT === "cli";
26155
26244
  }
@@ -26173,25 +26262,25 @@ function getDesktopStatePath() {
26173
26262
  const os2 = platform2();
26174
26263
  const home = homedir9();
26175
26264
  if (os2 === "darwin") {
26176
- return join14(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
26265
+ return join15(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
26177
26266
  }
26178
26267
  if (os2 === "linux") {
26179
- const xdgConfig = process.env.XDG_CONFIG_HOME || join14(home, ".config");
26180
- return join14(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
26268
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join15(home, ".config");
26269
+ return join15(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
26181
26270
  }
26182
26271
  if (os2 === "win32") {
26183
- const appData = process.env.APPDATA || join14(home, "AppData", "Roaming");
26184
- return join14(appData, "ai.opencode.desktop", "opencode.global.dat");
26272
+ const appData = process.env.APPDATA || join15(home, "AppData", "Roaming");
26273
+ return join15(appData, "ai.opencode.desktop", "opencode.global.dat");
26185
26274
  }
26186
26275
  return null;
26187
26276
  }
26188
26277
  function readDesktopState(directory) {
26189
26278
  const statePath = getDesktopStatePath();
26190
- if (!statePath || !existsSync10(statePath)) {
26279
+ if (!statePath || !existsSync11(statePath)) {
26191
26280
  return { sessionId: null, serverUrl: null };
26192
26281
  }
26193
26282
  try {
26194
- const raw = readFileSync7(statePath, "utf-8");
26283
+ const raw = readFileSync8(statePath, "utf-8");
26195
26284
  const state = JSON.parse(raw);
26196
26285
  let serverUrl = null;
26197
26286
  const serverStr = state.server;
@@ -26285,10 +26374,10 @@ async function sendWarning(opts, message) {
26285
26374
  }
26286
26375
  async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
26287
26376
  if (storageDir) {
26288
- const versionFile = join14(storageDir, "last_announced_version");
26377
+ const versionFile = join15(storageDir, "last_announced_version");
26289
26378
  try {
26290
- if (existsSync10(versionFile)) {
26291
- const lastVersion = readFileSync7(versionFile, "utf-8").trim();
26379
+ if (existsSync11(versionFile)) {
26380
+ const lastVersion = readFileSync8(versionFile, "utf-8").trim();
26292
26381
  if (lastVersion === version2)
26293
26382
  return;
26294
26383
  }
@@ -26308,17 +26397,17 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
26308
26397
  }
26309
26398
  if (storageDir) {
26310
26399
  try {
26311
- mkdirSync7(storageDir, { recursive: true });
26312
- writeFileSync7(join14(storageDir, "last_announced_version"), version2);
26400
+ mkdirSync8(storageDir, { recursive: true });
26401
+ writeFileSync8(join15(storageDir, "last_announced_version"), version2);
26313
26402
  } catch {}
26314
26403
  }
26315
26404
  }
26316
26405
  function readWarnedTools(storageDir) {
26317
26406
  try {
26318
- const warnedToolsPath = join14(storageDir, WARNED_TOOLS_FILE);
26319
- if (!existsSync10(warnedToolsPath))
26407
+ const warnedToolsPath = join15(storageDir, WARNED_TOOLS_FILE);
26408
+ if (!existsSync11(warnedToolsPath))
26320
26409
  return {};
26321
- const parsed = JSON.parse(readFileSync7(warnedToolsPath, "utf-8"));
26410
+ const parsed = JSON.parse(readFileSync8(warnedToolsPath, "utf-8"));
26322
26411
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
26323
26412
  return {};
26324
26413
  const warned = {};
@@ -26334,9 +26423,9 @@ function readWarnedTools(storageDir) {
26334
26423
  }
26335
26424
  function writeWarnedTools(storageDir, warned) {
26336
26425
  try {
26337
- mkdirSync7(storageDir, { recursive: true });
26338
- const warnedToolsPath = join14(storageDir, WARNED_TOOLS_FILE);
26339
- writeFileSync7(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
26426
+ mkdirSync8(storageDir, { recursive: true });
26427
+ const warnedToolsPath = join15(storageDir, WARNED_TOOLS_FILE);
26428
+ writeFileSync8(warnedToolsPath, `${JSON.stringify(warned, null, 2)}
26340
26429
  `);
26341
26430
  } catch {}
26342
26431
  }
@@ -26429,20 +26518,20 @@ async function cleanupWarnings(opts) {
26429
26518
 
26430
26519
  // src/shared/rpc-server.ts
26431
26520
  import { randomBytes as randomBytes2 } from "crypto";
26432
- import { mkdirSync as mkdirSync8, renameSync as renameSync4, unlinkSync as unlinkSync7, writeFileSync as writeFileSync8 } from "fs";
26521
+ import { mkdirSync as mkdirSync9, renameSync as renameSync5, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "fs";
26433
26522
  import { createServer } from "http";
26434
- import { dirname as dirname5 } from "path";
26523
+ import { dirname as dirname6 } from "path";
26435
26524
 
26436
26525
  // src/shared/rpc-utils.ts
26437
26526
  import { createHash as createHash5 } from "crypto";
26438
- import { join as join15 } from "path";
26527
+ import { join as join16 } from "path";
26439
26528
  function projectHash(directory) {
26440
26529
  const normalized = directory.replace(/\/+$/, "");
26441
26530
  return createHash5("sha256").update(normalized).digest("hex").slice(0, 16);
26442
26531
  }
26443
26532
  function rpcPortFilePath(storageDir, directory) {
26444
26533
  const hash2 = projectHash(directory);
26445
- return join15(storageDir, "rpc", hash2, "port");
26534
+ return join16(storageDir, "rpc", hash2, "port");
26446
26535
  }
26447
26536
 
26448
26537
  // src/shared/rpc-server.ts
@@ -26475,11 +26564,11 @@ class AftRpcServer {
26475
26564
  this.token = randomBytes2(32).toString("hex");
26476
26565
  this.server = server;
26477
26566
  try {
26478
- const dir = dirname5(this.portFilePath);
26479
- mkdirSync8(dir, { recursive: true });
26567
+ const dir = dirname6(this.portFilePath);
26568
+ mkdirSync9(dir, { recursive: true });
26480
26569
  const tmpPath = `${this.portFilePath}.tmp`;
26481
- writeFileSync8(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
26482
- renameSync4(tmpPath, this.portFilePath);
26570
+ writeFileSync9(tmpPath, JSON.stringify({ port: this.port, token: this.token }), "utf-8");
26571
+ renameSync5(tmpPath, this.portFilePath);
26483
26572
  log2(`RPC server listening on 127.0.0.1:${this.port}`);
26484
26573
  } catch (err) {
26485
26574
  warn2(`Failed to write RPC port file: ${err}`);
@@ -26774,21 +26863,21 @@ function formatStatusMarkdown(status) {
26774
26863
 
26775
26864
  // src/shared/tui-config.ts
26776
26865
  var import_comment_json4 = __toESM(require_src2(), 1);
26777
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
26778
- import { dirname as dirname6, join as join17 } from "path";
26866
+ import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
26867
+ import { dirname as dirname7, join as join18 } from "path";
26779
26868
 
26780
26869
  // src/shared/opencode-config-dir.ts
26781
26870
  import { homedir as homedir10 } from "os";
26782
- import { join as join16, resolve as resolve4 } from "path";
26871
+ import { join as join17, resolve as resolve4 } from "path";
26783
26872
  function getCliConfigDir() {
26784
26873
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
26785
26874
  if (envConfigDir) {
26786
26875
  return resolve4(envConfigDir);
26787
26876
  }
26788
26877
  if (process.platform === "win32") {
26789
- return join16(homedir10(), ".config", "opencode");
26878
+ return join17(homedir10(), ".config", "opencode");
26790
26879
  }
26791
- return join16(process.env.XDG_CONFIG_HOME || join16(homedir10(), ".config"), "opencode");
26880
+ return join17(process.env.XDG_CONFIG_HOME || join17(homedir10(), ".config"), "opencode");
26792
26881
  }
26793
26882
  function getOpenCodeConfigDir2(_options) {
26794
26883
  return getCliConfigDir();
@@ -26797,10 +26886,10 @@ function getOpenCodeConfigPaths(options) {
26797
26886
  const configDir = getOpenCodeConfigDir2(options);
26798
26887
  return {
26799
26888
  configDir,
26800
- configJson: join16(configDir, "opencode.json"),
26801
- configJsonc: join16(configDir, "opencode.jsonc"),
26802
- packageJson: join16(configDir, "package.json"),
26803
- omoConfig: join16(configDir, "magic-context.jsonc")
26889
+ configJson: join17(configDir, "opencode.json"),
26890
+ configJsonc: join17(configDir, "opencode.jsonc"),
26891
+ packageJson: join17(configDir, "package.json"),
26892
+ omoConfig: join17(configDir, "magic-context.jsonc")
26804
26893
  };
26805
26894
  }
26806
26895
 
@@ -26809,11 +26898,11 @@ var PLUGIN_NAME = "@cortexkit/aft-opencode";
26809
26898
  var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
26810
26899
  function resolveTuiConfigPath() {
26811
26900
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
26812
- const jsoncPath = join17(configDir, "tui.jsonc");
26813
- const jsonPath = join17(configDir, "tui.json");
26814
- if (existsSync11(jsoncPath))
26901
+ const jsoncPath = join18(configDir, "tui.jsonc");
26902
+ const jsonPath = join18(configDir, "tui.json");
26903
+ if (existsSync12(jsoncPath))
26815
26904
  return jsoncPath;
26816
- if (existsSync11(jsonPath))
26905
+ if (existsSync12(jsonPath))
26817
26906
  return jsonPath;
26818
26907
  return jsonPath;
26819
26908
  }
@@ -26821,8 +26910,8 @@ function ensureTuiPluginEntry() {
26821
26910
  try {
26822
26911
  const configPath = resolveTuiConfigPath();
26823
26912
  let config2 = {};
26824
- if (existsSync11(configPath)) {
26825
- config2 = import_comment_json4.parse(readFileSync8(configPath, "utf-8")) ?? {};
26913
+ if (existsSync12(configPath)) {
26914
+ config2 = import_comment_json4.parse(readFileSync9(configPath, "utf-8")) ?? {};
26826
26915
  }
26827
26916
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
26828
26917
  if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
@@ -26830,8 +26919,8 @@ function ensureTuiPluginEntry() {
26830
26919
  }
26831
26920
  plugins.push(PLUGIN_ENTRY);
26832
26921
  config2.plugin = plugins;
26833
- mkdirSync9(dirname6(configPath), { recursive: true });
26834
- writeFileSync9(configPath, `${import_comment_json4.stringify(config2, null, 2)}
26922
+ mkdirSync10(dirname7(configPath), { recursive: true });
26923
+ writeFileSync10(configPath, `${import_comment_json4.stringify(config2, null, 2)}
26835
26924
  `);
26836
26925
  log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
26837
26926
  return true;
@@ -27568,8 +27657,9 @@ ${chunk.old_lines.join(`
27568
27657
  import { tool as tool3 } from "@opencode-ai/plugin";
27569
27658
  var z3 = tool3.schema;
27570
27659
  var METADATA_PREVIEW_LIMIT = 30 * 1024;
27571
- var DEFAULT_BASH_TIMEOUT_MS = 30000;
27572
- var BASH_TRANSPORT_TIMEOUT_OVERHEAD_MS = 5000;
27660
+ var FOREGROUND_WAIT_WINDOW_MS = 5000;
27661
+ var FOREGROUND_POLL_INTERVAL_MS = 100;
27662
+ var BASH_TRANSPORT_TIMEOUT_MS = 30000;
27573
27663
  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.`;
27574
27664
  async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
27575
27665
  const first = await bridgeCall(ctx, runtime, "bash", params, options);
@@ -27598,7 +27688,7 @@ function createBashTool(ctx) {
27598
27688
  description: BASH_DESCRIPTION,
27599
27689
  args: {
27600
27690
  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."),
27601
- timeout: z3.number().optional().describe("Maximum execution time in milliseconds for foreground commands. Defaults to 30000 (30 seconds) when omitted. For commands expected to run longer than 30s (builds, installs, full test suites), use background: true instead."),
27691
+ timeout: z3.number().int().positive().optional().describe("Hard kill cap in milliseconds (positive integer). When omitted, the task can run up to 30 minutes. Foreground bash returns inline if the command finishes within ~5s; otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes."),
27602
27692
  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."),
27603
27693
  description: z3.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
27604
27694
  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."),
@@ -27618,10 +27708,11 @@ function createBashTool(ctx) {
27618
27708
  env: shellEnv?.env ?? {},
27619
27709
  description,
27620
27710
  background: args.background,
27711
+ notify_on_completion: args.background === true,
27621
27712
  compressed: args.compressed,
27622
27713
  permissions_requested: true
27623
27714
  }, callBridge, {
27624
- transportTimeoutMs: bashTransportTimeoutMs(args.timeout),
27715
+ transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS,
27625
27716
  keepBridgeOnTimeout: true,
27626
27717
  onProgress: ({ text }) => {
27627
27718
  accumulatedOutput = preview(accumulatedOutput + text);
@@ -27634,17 +27725,58 @@ function createBashTool(ctx) {
27634
27725
  if (data.status === "running" && typeof data.task_id === "string") {
27635
27726
  const callID2 = getCallID(context);
27636
27727
  const taskId = data.task_id;
27637
- trackBgTask(context.sessionID, taskId);
27638
- const startedLine = `Background task started: ${taskId}. A completion reminder will be delivered automatically; don't poll bash_status.`;
27639
- const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
27640
- metadata?.(metadataPayload2);
27641
- if (callID2) {
27642
- storeToolMetadata(context.sessionID, callID2, {
27643
- title: description ?? shortenCommand(command),
27644
- metadata: metadataPayload2
27645
- });
27728
+ if (args.background === true) {
27729
+ trackBgTask(context.sessionID, taskId);
27730
+ const startedLine = formatBackgroundLaunch(taskId);
27731
+ const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
27732
+ metadata?.(metadataPayload2);
27733
+ if (callID2) {
27734
+ storeToolMetadata(context.sessionID, callID2, {
27735
+ title: description ?? shortenCommand(command),
27736
+ metadata: metadataPayload2
27737
+ });
27738
+ }
27739
+ return startedLine;
27740
+ }
27741
+ const argTimeout = args.timeout;
27742
+ const waitTimeoutMs = argTimeout !== undefined ? Math.min(argTimeout, FOREGROUND_WAIT_WINDOW_MS) : FOREGROUND_WAIT_WINDOW_MS;
27743
+ const startedAt = Date.now();
27744
+ while (true) {
27745
+ const status = await callBridge(ctx, context, "bash_status", { task_id: taskId });
27746
+ if (status.success === false) {
27747
+ throw new Error(status.message ?? "bash_status failed");
27748
+ }
27749
+ if (isTerminalStatus(status.status)) {
27750
+ const rendered2 = formatForegroundResult(status);
27751
+ const metadataPayload2 = foregroundMetadata(description, status, rendered2);
27752
+ metadata?.(metadataPayload2);
27753
+ if (callID2) {
27754
+ storeToolMetadata(context.sessionID, callID2, {
27755
+ title: description ?? shortenCommand(command),
27756
+ metadata: metadataPayload2
27757
+ });
27758
+ }
27759
+ return rendered2;
27760
+ }
27761
+ if (Date.now() - startedAt >= waitTimeoutMs) {
27762
+ const promoted = await callBridge(ctx, context, "bash_promote", { task_id: taskId });
27763
+ if (promoted.success === false) {
27764
+ throw new Error(promoted.message ?? "bash_promote failed");
27765
+ }
27766
+ trackBgTask(context.sessionID, taskId);
27767
+ const message = formatPromotionMessage(taskId, args.timeout);
27768
+ const metadataPayload2 = { description, output: message, status: "running", taskId };
27769
+ metadata?.(metadataPayload2);
27770
+ if (callID2) {
27771
+ storeToolMetadata(context.sessionID, callID2, {
27772
+ title: description ?? shortenCommand(command),
27773
+ metadata: metadataPayload2
27774
+ });
27775
+ }
27776
+ return message;
27777
+ }
27778
+ await sleep(FOREGROUND_POLL_INTERVAL_MS);
27646
27779
  }
27647
- return startedLine;
27648
27780
  }
27649
27781
  const output = data.output ?? "";
27650
27782
  const metadataOutput = preview(output);
@@ -27731,13 +27863,53 @@ function createBashKillTool(ctx) {
27731
27863
  }
27732
27864
  };
27733
27865
  }
27734
- function bashTransportTimeoutMs(timeout) {
27735
- const bashTimeout = timeout ?? DEFAULT_BASH_TIMEOUT_MS;
27736
- return Math.max(30000, bashTimeout + BASH_TRANSPORT_TIMEOUT_OVERHEAD_MS);
27737
- }
27738
27866
  function preview(output) {
27739
27867
  return output.length <= METADATA_PREVIEW_LIMIT ? output : output.slice(-METADATA_PREVIEW_LIMIT);
27740
27868
  }
27869
+ function isTerminalStatus(status) {
27870
+ return status === "completed" || status === "failed" || status === "killed" || status === "timed_out";
27871
+ }
27872
+ function formatBackgroundLaunch(taskId) {
27873
+ return `Background task started: ${taskId}. A completion reminder will be delivered automatically; don't poll bash_status.`;
27874
+ }
27875
+ function formatPromotionMessage(taskId, timeout) {
27876
+ const waited = timeout !== undefined ? Math.min(timeout, FOREGROUND_WAIT_WINDOW_MS) : FOREGROUND_WAIT_WINDOW_MS;
27877
+ return `Foreground bash didn't finish within ${waited}ms and was promoted to background: ${taskId}. A completion reminder will be delivered automatically; use bash_status({ taskId: "${taskId}" }) to inspect output or bash_kill({ taskId: "${taskId}" }) to terminate.`;
27878
+ }
27879
+ function formatForegroundResult(data) {
27880
+ const output = data.output_preview ?? "";
27881
+ const outputPath = data.output_path;
27882
+ const truncated = data.output_truncated === true;
27883
+ const status = data.status;
27884
+ const exit = data.exit_code;
27885
+ let rendered = output;
27886
+ if (truncated && outputPath) {
27887
+ rendered += `
27888
+ [output truncated; full output at ${outputPath}]`;
27889
+ }
27890
+ if (status === "timed_out") {
27891
+ rendered += `
27892
+ [command timed out]`;
27893
+ }
27894
+ if (typeof exit === "number" && exit !== 0) {
27895
+ rendered += `
27896
+ [exit code: ${exit}]`;
27897
+ }
27898
+ return rendered;
27899
+ }
27900
+ function foregroundMetadata(description, data, rendered) {
27901
+ const outputPath = data.output_path;
27902
+ return {
27903
+ description,
27904
+ output: preview(rendered),
27905
+ exit: data.exit_code,
27906
+ truncated: data.output_truncated,
27907
+ ...outputPath ? { outputPath } : {}
27908
+ };
27909
+ }
27910
+ function sleep(ms) {
27911
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
27912
+ }
27741
27913
  function getCallID(ctx) {
27742
27914
  const c = ctx;
27743
27915
  return c.callID ?? c.callId ?? c.call_id;
@@ -28797,8 +28969,6 @@ function hoistedTools(ctx) {
28797
28969
  const anyBashExperimental = bashRewrite || bashCompress || bashBackground;
28798
28970
  if (anyBashExperimental) {
28799
28971
  tools.bash = createBashTool(ctx);
28800
- }
28801
- if (bashBackground) {
28802
28972
  tools.bash_status = createBashStatusTool(ctx);
28803
28973
  tools.bash_kill = createBashKillTool(ctx);
28804
28974
  }
@@ -29445,6 +29615,36 @@ function normalizeGlob(pattern) {
29445
29615
  }
29446
29616
  return pattern;
29447
29617
  }
29618
+ function splitIncludeArg(raw) {
29619
+ const out = [];
29620
+ let depth = 0;
29621
+ let buf = "";
29622
+ for (const ch of raw) {
29623
+ if (ch === "{") {
29624
+ depth++;
29625
+ buf += ch;
29626
+ continue;
29627
+ }
29628
+ if (ch === "}") {
29629
+ if (depth > 0)
29630
+ depth--;
29631
+ buf += ch;
29632
+ continue;
29633
+ }
29634
+ if (ch === "," && depth === 0) {
29635
+ const trimmed = buf.trim();
29636
+ if (trimmed.length > 0)
29637
+ out.push(trimmed);
29638
+ buf = "";
29639
+ continue;
29640
+ }
29641
+ buf += ch;
29642
+ }
29643
+ const tail = buf.trim();
29644
+ if (tail.length > 0)
29645
+ out.push(tail);
29646
+ return out;
29647
+ }
29448
29648
  function searchTools(ctx) {
29449
29649
  const grepTool = {
29450
29650
  description: "Search file contents using regular expressions. Returns matching lines with file paths, line numbers, and context.",
@@ -29457,7 +29657,7 @@ function searchTools(ctx) {
29457
29657
  const response = await callBridge(ctx, context, "grep", {
29458
29658
  pattern: args.pattern,
29459
29659
  case_sensitive: true,
29460
- include: args.include ? String(args.include).split(",").map((s) => normalizeGlob(s.trim())).filter(Boolean) : undefined,
29660
+ include: args.include ? splitIncludeArg(String(args.include)).map(normalizeGlob).filter(Boolean) : undefined,
29461
29661
  path: args.path,
29462
29662
  max_results: 100
29463
29663
  });
@@ -29707,12 +29907,8 @@ function buildWorkflowHints(opts) {
29707
29907
  ].join(`
29708
29908
  `));
29709
29909
  }
29710
- if (hasBash) {
29711
- if (hasBgBash) {
29712
- sections.push(`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns immediately with a \`taskId\`. A completion reminder is delivered automatically \u2014 do not poll \`${bashStatusName}({ taskId })\`. Use \`${bashStatusName}\` only after the reminder arrives, or to inspect a task you already know is complete.`);
29713
- } else {
29714
- sections.push(`**Long-running bash commands**: foreground \`${bashName}\` times out after 30 seconds. Pass \`timeout\` in milliseconds to extend it for commands you know will take longer.`);
29715
- }
29910
+ if (hasBash && hasBgBash) {
29911
+ sections.push(`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns immediately with a \`taskId\`. A completion reminder is delivered automatically \u2014 do not poll \`${bashStatusName}({ taskId })\`. Use \`${bashStatusName}\` only after the reminder arrives, or to inspect a task you already know is complete.`);
29716
29912
  }
29717
29913
  if (sections.length === 0) {
29718
29914
  return null;
@@ -29839,8 +30035,8 @@ var plugin = async (input) => {
29839
30035
  if (aftConfig.max_callgraph_files !== undefined)
29840
30036
  configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
29841
30037
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
29842
- const dataHome = process.env.XDG_DATA_HOME || join18(homedir11(), ".local", "share");
29843
- configOverrides.storage_dir = join18(dataHome, "opencode", "storage", "plugin", "aft");
30038
+ const dataHome = process.env.XDG_DATA_HOME || join19(homedir11(), ".local", "share");
30039
+ configOverrides.storage_dir = join19(dataHome, "opencode", "storage", "plugin", "aft");
29844
30040
  let onnxRuntimePromise = null;
29845
30041
  if (aftConfig.semantic_search && isFastembedSemanticBackend) {
29846
30042
  const storageDir2 = configOverrides.storage_dir;
@@ -29916,7 +30112,7 @@ ${lines}
29916
30112
  warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
29917
30113
  }
29918
30114
  let versionUpgradeAttempted = null;
29919
- const pool = new BridgePool(binaryPath, {
30115
+ const poolOptions = {
29920
30116
  errorPrefix: "[aft-plugin]",
29921
30117
  minVersion: PLUGIN_VERSION,
29922
30118
  onVersionMismatch: (binaryVersion, minVersion) => {
@@ -29959,8 +30155,19 @@ ${lines}
29959
30155
  client: input.client,
29960
30156
  isActive: () => bridge.hasPendingRequests()
29961
30157
  }, completion);
30158
+ },
30159
+ onBashLongRunning: (reminder, bridge) => {
30160
+ const sessionDir = getSessionDirectoryCached(reminder.session_id) ?? input.directory;
30161
+ handlePushedBgLongRunning({
30162
+ ctx,
30163
+ directory: sessionDir,
30164
+ sessionID: reminder.session_id,
30165
+ client: input.client,
30166
+ isActive: () => bridge.hasPendingRequests()
30167
+ }, reminder);
29962
30168
  }
29963
- }, configOverrides);
30169
+ };
30170
+ const pool = new BridgePool(binaryPath, poolOptions, configOverrides);
29964
30171
  const ctx = {
29965
30172
  pool,
29966
30173
  client: input.client,
@@ -30036,10 +30243,10 @@ ${lines}
30036
30243
  return { show: false };
30037
30244
  }
30038
30245
  if (storageDir) {
30039
- const versionFile = join18(storageDir, "last_announced_version");
30246
+ const versionFile = join19(storageDir, "last_announced_version");
30040
30247
  try {
30041
- if (existsSync13(versionFile)) {
30042
- const lastVersion = readFileSync9(versionFile, "utf-8").trim();
30248
+ if (existsSync14(versionFile)) {
30249
+ const lastVersion = readFileSync10(versionFile, "utf-8").trim();
30043
30250
  if (lastVersion === ANNOUNCEMENT_VERSION)
30044
30251
  return { show: false };
30045
30252
  }
@@ -30050,8 +30257,8 @@ ${lines}
30050
30257
  rpcServer.handle("mark-announced", async () => {
30051
30258
  if (storageDir && ANNOUNCEMENT_VERSION) {
30052
30259
  try {
30053
- mkdirSync10(storageDir, { recursive: true });
30054
- writeFileSync10(join18(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
30260
+ mkdirSync11(storageDir, { recursive: true });
30261
+ writeFileSync11(join19(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
30055
30262
  } catch {}
30056
30263
  }
30057
30264
  return { success: true };
@@ -30135,7 +30342,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
30135
30342
  const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
30136
30343
  enabled: true,
30137
30344
  autoUpdate: aftConfig.auto_update ?? true,
30138
- signal: autoUpdateAbort.signal
30345
+ signal: autoUpdateAbort.signal,
30346
+ storageDir: ctx.storageDir
30139
30347
  });
30140
30348
  const HINTS_TOOL_NAMES = [
30141
30349
  "aft_outline",