@cortexkit/aft-opencode 0.19.5 → 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;
@@ -26951,6 +27040,10 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
26951
27040
 
26952
27041
  // src/tools/permissions.ts
26953
27042
  import * as path3 from "path";
27043
+ import { Effect } from "effect";
27044
+ async function runAsk(maybe) {
27045
+ await Effect.runPromise(maybe);
27046
+ }
26954
27047
  function resolveAbsolutePath(context, target) {
26955
27048
  return path3.isAbsolute(target) ? target : path3.resolve(context.directory, target);
26956
27049
  }
@@ -26976,12 +27069,12 @@ function workspacePattern(_context) {
26976
27069
  }
26977
27070
  async function askEditPermission(context, patterns, metadata = {}) {
26978
27071
  try {
26979
- await context.ask({
27072
+ await runAsk(context.ask({
26980
27073
  permission: "edit",
26981
27074
  patterns: patterns.length > 0 ? patterns : [workspacePattern(context)],
26982
27075
  always: ["*"],
26983
27076
  metadata
26984
- });
27077
+ }));
26985
27078
  return;
26986
27079
  } catch (error50) {
26987
27080
  if (error50 instanceof Error && error50.message) {
@@ -27564,8 +27657,9 @@ ${chunk.old_lines.join(`
27564
27657
  import { tool as tool3 } from "@opencode-ai/plugin";
27565
27658
  var z3 = tool3.schema;
27566
27659
  var METADATA_PREVIEW_LIMIT = 30 * 1024;
27567
- var DEFAULT_BASH_TIMEOUT_MS = 30000;
27568
- 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;
27569
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.`;
27570
27664
  async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
27571
27665
  const first = await bridgeCall(ctx, runtime, "bash", params, options);
@@ -27575,12 +27669,12 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
27575
27669
  const permissionsGranted = [];
27576
27670
  for (const ask of asks) {
27577
27671
  const permission = ask.kind === "external_directory" ? "external_directory" : "bash";
27578
- await runtime.ask({
27672
+ await runAsk(runtime.ask({
27579
27673
  permission,
27580
27674
  patterns: ask.patterns,
27581
27675
  always: ask.always,
27582
27676
  metadata: {}
27583
- });
27677
+ }));
27584
27678
  permissionsGranted.push(...ask.always.length > 0 ? ask.always : ask.patterns);
27585
27679
  }
27586
27680
  const second = await bridgeCall(ctx, runtime, "bash", { ...params, permissions_granted: permissionsGranted }, options);
@@ -27594,7 +27688,7 @@ function createBashTool(ctx) {
27594
27688
  description: BASH_DESCRIPTION,
27595
27689
  args: {
27596
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."),
27597
- 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."),
27598
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."),
27599
27693
  description: z3.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
27600
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."),
@@ -27614,10 +27708,11 @@ function createBashTool(ctx) {
27614
27708
  env: shellEnv?.env ?? {},
27615
27709
  description,
27616
27710
  background: args.background,
27711
+ notify_on_completion: args.background === true,
27617
27712
  compressed: args.compressed,
27618
27713
  permissions_requested: true
27619
27714
  }, callBridge, {
27620
- transportTimeoutMs: bashTransportTimeoutMs(args.timeout),
27715
+ transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS,
27621
27716
  keepBridgeOnTimeout: true,
27622
27717
  onProgress: ({ text }) => {
27623
27718
  accumulatedOutput = preview(accumulatedOutput + text);
@@ -27630,17 +27725,58 @@ function createBashTool(ctx) {
27630
27725
  if (data.status === "running" && typeof data.task_id === "string") {
27631
27726
  const callID2 = getCallID(context);
27632
27727
  const taskId = data.task_id;
27633
- trackBgTask(context.sessionID, taskId);
27634
- const startedLine = `Background task started: ${taskId}. A completion reminder will be delivered automatically; don't poll bash_status.`;
27635
- const metadataPayload2 = { description, output: startedLine, status: "running", taskId };
27636
- metadata?.(metadataPayload2);
27637
- if (callID2) {
27638
- storeToolMetadata(context.sessionID, callID2, {
27639
- title: description ?? shortenCommand(command),
27640
- metadata: metadataPayload2
27641
- });
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);
27642
27779
  }
27643
- return startedLine;
27644
27780
  }
27645
27781
  const output = data.output ?? "";
27646
27782
  const metadataOutput = preview(output);
@@ -27727,13 +27863,53 @@ function createBashKillTool(ctx) {
27727
27863
  }
27728
27864
  };
27729
27865
  }
27730
- function bashTransportTimeoutMs(timeout) {
27731
- const bashTimeout = timeout ?? DEFAULT_BASH_TIMEOUT_MS;
27732
- return Math.max(30000, bashTimeout + BASH_TRANSPORT_TIMEOUT_OVERHEAD_MS);
27733
- }
27734
27866
  function preview(output) {
27735
27867
  return output.length <= METADATA_PREVIEW_LIMIT ? output : output.slice(-METADATA_PREVIEW_LIMIT);
27736
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
+ }
27737
27913
  function getCallID(ctx) {
27738
27914
  const c = ctx;
27739
27915
  return c.callID ?? c.callId ?? c.call_id;
@@ -27985,12 +28161,12 @@ function createReadTool(ctx) {
27985
28161
  execute: async (args, context) => {
27986
28162
  const file2 = args.filePath;
27987
28163
  const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
27988
- await context.ask({
28164
+ await runAsk(context.ask({
27989
28165
  permission: "read",
27990
28166
  patterns: [filePath],
27991
28167
  always: ["*"],
27992
28168
  metadata: {}
27993
- });
28169
+ }));
27994
28170
  const ext = path4.extname(filePath).toLowerCase();
27995
28171
  const mimeMap = {
27996
28172
  ".png": "image/png",
@@ -28109,12 +28285,12 @@ function createWriteTool(ctx, editToolName = "edit") {
28109
28285
  const content = args.content;
28110
28286
  const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
28111
28287
  const relPath = path4.relative(context.worktree, filePath);
28112
- await context.ask({
28288
+ await runAsk(context.ask({
28113
28289
  permission: "edit",
28114
28290
  patterns: [relPath],
28115
28291
  always: ["*"],
28116
28292
  metadata: { filepath: filePath }
28117
- });
28293
+ }));
28118
28294
  const data = await callBridge(ctx, context, "write", {
28119
28295
  file: filePath,
28120
28296
  content,
@@ -28259,12 +28435,12 @@ function createEditTool(ctx, writeToolName = "write") {
28259
28435
  if (Array.isArray(args.operations)) {
28260
28436
  const ops = args.operations;
28261
28437
  const files = ops.map((op) => op.file).filter(Boolean);
28262
- await context.ask({
28438
+ await runAsk(context.ask({
28263
28439
  permission: "edit",
28264
28440
  patterns: files.map((f) => path4.relative(context.worktree, path4.resolve(context.directory, f))),
28265
28441
  always: ["*"],
28266
28442
  metadata: {}
28267
- });
28443
+ }));
28268
28444
  const resolvedOps = ops.map((op) => ({
28269
28445
  ...op,
28270
28446
  file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
@@ -28279,12 +28455,12 @@ function createEditTool(ctx, writeToolName = "write") {
28279
28455
  throw new Error("'filePath' parameter is required");
28280
28456
  const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
28281
28457
  const relPath = path4.relative(context.worktree, filePath);
28282
- await context.ask({
28458
+ await runAsk(context.ask({
28283
28459
  permission: "edit",
28284
28460
  patterns: [relPath],
28285
28461
  always: ["*"],
28286
28462
  metadata: { filepath: filePath }
28287
- });
28463
+ }));
28288
28464
  const params = { file: filePath };
28289
28465
  let command;
28290
28466
  if (typeof args.appendContent === "string") {
@@ -28481,12 +28657,12 @@ function createApplyPatchTool(ctx) {
28481
28657
  }
28482
28658
  const relPaths = Array.from(affectedAbs).map((abs) => path4.relative(context.worktree, abs));
28483
28659
  const multiFileWritePaths = Array.from(affectedAbs);
28484
- await context.ask({
28660
+ await runAsk(context.ask({
28485
28661
  permission: "edit",
28486
28662
  patterns: relPaths,
28487
28663
  always: ["*"],
28488
28664
  metadata: {}
28489
- });
28665
+ }));
28490
28666
  const checkpointPaths = Array.from(affectedAbs).filter((abs) => !newlyCreatedAbs.has(abs));
28491
28667
  const checkpointName = `apply_patch_${Date.now()}`;
28492
28668
  let checkpointCreated = false;
@@ -28716,12 +28892,12 @@ function createDeleteTool(ctx) {
28716
28892
  execute: async (args, context) => {
28717
28893
  const inputs = args.files;
28718
28894
  const absolutePaths = inputs.map((f) => path4.isAbsolute(f) ? f : path4.resolve(context.directory, f));
28719
- await context.ask({
28895
+ await runAsk(context.ask({
28720
28896
  permission: "edit",
28721
28897
  patterns: absolutePaths,
28722
28898
  always: ["*"],
28723
28899
  metadata: { action: "delete", count: absolutePaths.length }
28724
- });
28900
+ }));
28725
28901
  const deleted = [];
28726
28902
  const skipped = [];
28727
28903
  for (const filePath of absolutePaths) {
@@ -28761,12 +28937,12 @@ function createMoveTool(ctx) {
28761
28937
  execute: async (args, context) => {
28762
28938
  const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
28763
28939
  const destPath = path4.isAbsolute(args.destination) ? args.destination : path4.resolve(context.directory, args.destination);
28764
- await context.ask({
28940
+ await runAsk(context.ask({
28765
28941
  permission: "edit",
28766
28942
  patterns: [filePath, destPath],
28767
28943
  always: ["*"],
28768
28944
  metadata: { action: "move" }
28769
- });
28945
+ }));
28770
28946
  const result = await callBridge(ctx, context, "move_file", {
28771
28947
  file: filePath,
28772
28948
  destination: destPath
@@ -28793,8 +28969,6 @@ function hoistedTools(ctx) {
28793
28969
  const anyBashExperimental = bashRewrite || bashCompress || bashBackground;
28794
28970
  if (anyBashExperimental) {
28795
28971
  tools.bash = createBashTool(ctx);
28796
- }
28797
- if (bashBackground) {
28798
28972
  tools.bash_status = createBashStatusTool(ctx);
28799
28973
  tools.bash_kill = createBashKillTool(ctx);
28800
28974
  }
@@ -28817,12 +28991,12 @@ function aftPrefixedTools(ctx) {
28817
28991
  const file2 = normalizedArgs.filePath;
28818
28992
  const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
28819
28993
  const relPath = path4.relative(context.worktree, filePath);
28820
- await context.ask({
28994
+ await runAsk(context.ask({
28821
28995
  permission: "edit",
28822
28996
  patterns: [relPath],
28823
28997
  always: ["*"],
28824
28998
  metadata: { filepath: filePath }
28825
- });
28999
+ }));
28826
29000
  const writeParams = {
28827
29001
  file: filePath,
28828
29002
  content: normalizedArgs.content,
@@ -29441,6 +29615,36 @@ function normalizeGlob(pattern) {
29441
29615
  }
29442
29616
  return pattern;
29443
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
+ }
29444
29648
  function searchTools(ctx) {
29445
29649
  const grepTool = {
29446
29650
  description: "Search file contents using regular expressions. Returns matching lines with file paths, line numbers, and context.",
@@ -29453,7 +29657,7 @@ function searchTools(ctx) {
29453
29657
  const response = await callBridge(ctx, context, "grep", {
29454
29658
  pattern: args.pattern,
29455
29659
  case_sensitive: true,
29456
- 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,
29457
29661
  path: args.path,
29458
29662
  max_results: 100
29459
29663
  });
@@ -29703,12 +29907,8 @@ function buildWorkflowHints(opts) {
29703
29907
  ].join(`
29704
29908
  `));
29705
29909
  }
29706
- if (hasBash) {
29707
- if (hasBgBash) {
29708
- 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.`);
29709
- } else {
29710
- 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.`);
29711
- }
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.`);
29712
29912
  }
29713
29913
  if (sections.length === 0) {
29714
29914
  return null;
@@ -29835,8 +30035,8 @@ var plugin = async (input) => {
29835
30035
  if (aftConfig.max_callgraph_files !== undefined)
29836
30036
  configOverrides.max_callgraph_files = aftConfig.max_callgraph_files;
29837
30037
  const isFastembedSemanticBackend = (aftConfig.semantic?.backend ?? "fastembed") === "fastembed";
29838
- const dataHome = process.env.XDG_DATA_HOME || join18(homedir11(), ".local", "share");
29839
- 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");
29840
30040
  let onnxRuntimePromise = null;
29841
30041
  if (aftConfig.semantic_search && isFastembedSemanticBackend) {
29842
30042
  const storageDir2 = configOverrides.storage_dir;
@@ -29912,7 +30112,7 @@ ${lines}
29912
30112
  warn2(`[lsp] auto-install setup failed: ${err instanceof Error ? err.message : String(err)}`);
29913
30113
  }
29914
30114
  let versionUpgradeAttempted = null;
29915
- const pool = new BridgePool(binaryPath, {
30115
+ const poolOptions = {
29916
30116
  errorPrefix: "[aft-plugin]",
29917
30117
  minVersion: PLUGIN_VERSION,
29918
30118
  onVersionMismatch: (binaryVersion, minVersion) => {
@@ -29955,8 +30155,19 @@ ${lines}
29955
30155
  client: input.client,
29956
30156
  isActive: () => bridge.hasPendingRequests()
29957
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);
29958
30168
  }
29959
- }, configOverrides);
30169
+ };
30170
+ const pool = new BridgePool(binaryPath, poolOptions, configOverrides);
29960
30171
  const ctx = {
29961
30172
  pool,
29962
30173
  client: input.client,
@@ -30032,10 +30243,10 @@ ${lines}
30032
30243
  return { show: false };
30033
30244
  }
30034
30245
  if (storageDir) {
30035
- const versionFile = join18(storageDir, "last_announced_version");
30246
+ const versionFile = join19(storageDir, "last_announced_version");
30036
30247
  try {
30037
- if (existsSync13(versionFile)) {
30038
- const lastVersion = readFileSync9(versionFile, "utf-8").trim();
30248
+ if (existsSync14(versionFile)) {
30249
+ const lastVersion = readFileSync10(versionFile, "utf-8").trim();
30039
30250
  if (lastVersion === ANNOUNCEMENT_VERSION)
30040
30251
  return { show: false };
30041
30252
  }
@@ -30046,8 +30257,8 @@ ${lines}
30046
30257
  rpcServer.handle("mark-announced", async () => {
30047
30258
  if (storageDir && ANNOUNCEMENT_VERSION) {
30048
30259
  try {
30049
- mkdirSync10(storageDir, { recursive: true });
30050
- writeFileSync10(join18(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
30260
+ mkdirSync11(storageDir, { recursive: true });
30261
+ writeFileSync11(join19(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
30051
30262
  } catch {}
30052
30263
  }
30053
30264
  return { success: true };
@@ -30131,7 +30342,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
30131
30342
  const autoUpdateEventHook = createAutoUpdateCheckerHook(input, {
30132
30343
  enabled: true,
30133
30344
  autoUpdate: aftConfig.auto_update ?? true,
30134
- signal: autoUpdateAbort.signal
30345
+ signal: autoUpdateAbort.signal,
30346
+ storageDir: ctx.storageDir
30135
30347
  });
30136
30348
  const HINTS_TOOL_NAMES = [
30137
30349
  "aft_outline",