@cortexkit/aft-opencode 0.39.2 → 0.39.3

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
@@ -12864,6 +12864,208 @@ class BinaryBridge {
12864
12864
  }
12865
12865
  }
12866
12866
  }
12867
+ // ../aft-bridge/dist/callgraph-format.js
12868
+ import { homedir as homedir3 } from "node:os";
12869
+ var PLAIN_CALLGRAPH_THEME = {
12870
+ fg: (_role, text) => text
12871
+ };
12872
+ function asRecord(value) {
12873
+ if (!value || typeof value !== "object" || Array.isArray(value))
12874
+ return;
12875
+ return value;
12876
+ }
12877
+ function asRecords(value) {
12878
+ return Array.isArray(value) ? value.map(asRecord).filter(Boolean) : [];
12879
+ }
12880
+ function asString(value) {
12881
+ return typeof value === "string" ? value : undefined;
12882
+ }
12883
+ function asNumber(value) {
12884
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12885
+ }
12886
+ function asBoolean(value) {
12887
+ return typeof value === "boolean" ? value : undefined;
12888
+ }
12889
+ function shortenPath(path2) {
12890
+ const home = homedir3();
12891
+ if (path2.startsWith(home))
12892
+ return `~${path2.slice(home.length)}`;
12893
+ return path2;
12894
+ }
12895
+ function joinNonEmpty(parts, separator = " · ") {
12896
+ return parts.filter((part) => Boolean(part && part.length > 0)).join(separator);
12897
+ }
12898
+ function treeLine(depth, text) {
12899
+ return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
12900
+ }
12901
+ function renderCallTreeNode(node, depth, lines, theme) {
12902
+ const name = asString(node.name) ?? "(unknown)";
12903
+ const file = shortenPath(asString(node.file) ?? "(unknown file)");
12904
+ const line = asNumber(node.line);
12905
+ const unresolved = node.resolved === false ? ` ${theme.fg("warning", "[unresolved]")}` : "";
12906
+ const location = line !== undefined ? `[${file}:${line}]` : `[${file}]`;
12907
+ lines.push(treeLine(depth, `${name} ${location}${unresolved}`));
12908
+ asRecords(node.children).forEach((child) => {
12909
+ renderCallTreeNode(child, depth + 1, lines, theme);
12910
+ });
12911
+ }
12912
+ function depthWarning(response, theme, depthField = "depth_limited", truncatedField = "truncated") {
12913
+ const limited = asBoolean(response[depthField]);
12914
+ const truncated = asNumber(response[truncatedField]) ?? 0;
12915
+ if (!limited && truncated === 0)
12916
+ return "";
12917
+ const detail = truncated > 0 ? `, ${truncated} truncated` : "";
12918
+ return theme.fg("warning", `(depth limited${detail})`);
12919
+ }
12920
+ function renderTracePath(path2, index, lines) {
12921
+ lines.push(`Path ${index + 1}`);
12922
+ asRecords(path2.hops).forEach((hop, hopIndex) => {
12923
+ const symbol = asString(hop.symbol) ?? "(unknown)";
12924
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
12925
+ const line = asNumber(hop.line);
12926
+ const entry = hop.is_entry_point === true ? " [entry]" : "";
12927
+ lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
12928
+ });
12929
+ }
12930
+ function renderCallersGroupLines(group, theme) {
12931
+ const file = shortenPath(asString(group.file) ?? "(unknown file)");
12932
+ const lines = [theme.fg("accent", file)];
12933
+ const callers = asRecords(group.callers);
12934
+ const bySymbol = new Map;
12935
+ for (const caller of callers) {
12936
+ const symbol = asString(caller.symbol) ?? "(unknown)";
12937
+ const line = asNumber(caller.line);
12938
+ const bucket = bySymbol.get(symbol) ?? [];
12939
+ if (line !== undefined)
12940
+ bucket.push(line);
12941
+ bySymbol.set(symbol, bucket);
12942
+ }
12943
+ const symbols = [...bySymbol.keys()].sort((a, b) => a.localeCompare(b));
12944
+ for (const symbol of symbols) {
12945
+ const lineNums = (bySymbol.get(symbol) ?? []).sort((a, b) => a - b);
12946
+ const linePart = lineNums.length > 0 ? lineNums.map(String).join(", ") : "?";
12947
+ lines.push(` ↳ ${symbol}:${linePart}`);
12948
+ }
12949
+ return lines;
12950
+ }
12951
+ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
12952
+ const record = asRecord(response);
12953
+ if (!record)
12954
+ return [theme.fg("muted", "No navigation result.")];
12955
+ if (op === "call_tree") {
12956
+ const lines = [];
12957
+ renderCallTreeNode(record, 0, lines, theme);
12958
+ const warning = depthWarning(record, theme);
12959
+ if (warning)
12960
+ lines.push(warning);
12961
+ return lines.length > 0 ? lines : [theme.fg("muted", "No call tree available.")];
12962
+ }
12963
+ if (op === "callers") {
12964
+ const groups = asRecords(record.callers);
12965
+ const warning = depthWarning(record, theme);
12966
+ const total = asNumber(record.total_callers) ?? 0;
12967
+ const sections2 = [
12968
+ joinNonEmpty([
12969
+ theme.fg("success", `${total} caller${total === 1 ? "" : "s"}`),
12970
+ theme.fg("muted", `${groups.length} file group${groups.length === 1 ? "" : "s"}`),
12971
+ warning
12972
+ ])
12973
+ ];
12974
+ groups.forEach((group) => {
12975
+ sections2.push(renderCallersGroupLines(group, theme).join(`
12976
+ `));
12977
+ });
12978
+ return sections2;
12979
+ }
12980
+ if (op === "trace_to_symbol") {
12981
+ const path2 = asRecords(record.path);
12982
+ const complete = asBoolean(record.complete);
12983
+ const reason = asString(record.reason);
12984
+ if (path2.length === 0) {
12985
+ const prefix = complete === false ? theme.fg("warning", "No complete path") : theme.fg("muted", "No path");
12986
+ return [`${prefix}${reason ? ` (${reason})` : ""}`];
12987
+ }
12988
+ const lines = [theme.fg("success", `${path2.length} hop${path2.length === 1 ? "" : "s"}`)];
12989
+ path2.forEach((hop, index) => {
12990
+ const symbol = asString(hop.symbol) ?? "(unknown)";
12991
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
12992
+ const line = asNumber(hop.line);
12993
+ lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
12994
+ });
12995
+ return lines;
12996
+ }
12997
+ if (op === "trace_to") {
12998
+ const paths = asRecords(record.paths);
12999
+ const warning = depthWarning(record, theme, "max_depth_reached", "truncated_paths");
13000
+ const totalPaths = asNumber(record.total_paths) ?? paths.length;
13001
+ const entryPoints = asNumber(record.entry_points_found) ?? 0;
13002
+ const sections2 = [
13003
+ joinNonEmpty([
13004
+ theme.fg("success", `${totalPaths} path${totalPaths === 1 ? "" : "s"}`),
13005
+ theme.fg("muted", `${entryPoints} entry point${entryPoints === 1 ? "" : "s"}`),
13006
+ warning
13007
+ ])
13008
+ ];
13009
+ if (paths.length === 0)
13010
+ sections2.push(theme.fg("muted", "No entry paths found."));
13011
+ paths.forEach((path2, index) => {
13012
+ const lines = [];
13013
+ renderTracePath(path2, index, lines);
13014
+ sections2.push(lines.join(`
13015
+ `));
13016
+ });
13017
+ return sections2;
13018
+ }
13019
+ if (op === "impact") {
13020
+ const callers = asRecords(record.callers);
13021
+ const warning = depthWarning(record, theme);
13022
+ const totalAffected = asNumber(record.total_affected) ?? callers.length;
13023
+ const affectedFiles = asNumber(record.affected_files) ?? 0;
13024
+ const sections2 = [
13025
+ joinNonEmpty([
13026
+ theme.fg("warning", `${totalAffected} affected call site${totalAffected === 1 ? "" : "s"}`),
13027
+ theme.fg("muted", `${affectedFiles} file${affectedFiles === 1 ? "" : "s"}`),
13028
+ warning
13029
+ ])
13030
+ ];
13031
+ if (callers.length === 0)
13032
+ sections2.push(theme.fg("muted", "No impacted callers found."));
13033
+ callers.forEach((caller) => {
13034
+ const file = shortenPath(asString(caller.caller_file) ?? "(unknown file)");
13035
+ const symbol = asString(caller.caller_symbol) ?? "(unknown)";
13036
+ const line = asNumber(caller.line) ?? 0;
13037
+ const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
13038
+ const expression = asString(caller.call_expression);
13039
+ const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
13040
+ sections2.push([
13041
+ `${theme.fg("accent", file)}:${line}`,
13042
+ ` ↳ ${symbol}${entry}`,
13043
+ expression ? ` ${theme.fg("muted", expression)}` : undefined,
13044
+ params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
13045
+ ].filter(Boolean).join(`
13046
+ `));
13047
+ });
13048
+ return sections2;
13049
+ }
13050
+ const hops = asRecords(record.hops);
13051
+ const sections = [
13052
+ joinNonEmpty([
13053
+ theme.fg("success", `${hops.length} hop${hops.length === 1 ? "" : "s"}`),
13054
+ asBoolean(record.depth_limited) ? theme.fg("warning", "(depth limited)") : undefined
13055
+ ])
13056
+ ];
13057
+ if (hops.length === 0)
13058
+ sections.push(theme.fg("muted", "No data-flow hops found."));
13059
+ hops.forEach((hop, index) => {
13060
+ const file = shortenPath(asString(hop.file) ?? "(unknown file)");
13061
+ const symbol = asString(hop.symbol) ?? "(unknown)";
13062
+ const variable = asString(hop.variable) ?? "(unknown)";
13063
+ const line = asNumber(hop.line) ?? 0;
13064
+ const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
13065
+ sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}`));
13066
+ });
13067
+ return sections;
13068
+ }
12867
13069
  // ../aft-bridge/dist/coerce.js
12868
13070
  function coerceStringArray(value) {
12869
13071
  if (Array.isArray(value)) {
@@ -12900,7 +13102,7 @@ function isEmptyParam(value) {
12900
13102
  import { spawnSync } from "node:child_process";
12901
13103
  import { createHash, randomUUID } from "node:crypto";
12902
13104
  import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
12903
- import { homedir as homedir3 } from "node:os";
13105
+ import { homedir as homedir4 } from "node:os";
12904
13106
  import { join as join3 } from "node:path";
12905
13107
  import { Readable } from "node:stream";
12906
13108
  import { pipeline } from "node:stream/promises";
@@ -12958,10 +13160,10 @@ function isExpectedCachedBinary(binaryPath, tag) {
12958
13160
  function getCacheDir() {
12959
13161
  if (process.platform === "win32") {
12960
13162
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
12961
- const base2 = localAppData || join3(homedir3(), "AppData", "Local");
13163
+ const base2 = localAppData || join3(homedir4(), "AppData", "Local");
12962
13164
  return join3(base2, "aft", "bin");
12963
13165
  }
12964
- const base = process.env.XDG_CACHE_HOME || join3(homedir3(), ".cache");
13166
+ const base = process.env.XDG_CACHE_HOME || join3(homedir4(), ".cache");
12965
13167
  return join3(base, "aft", "bin");
12966
13168
  }
12967
13169
  function getBinaryName() {
@@ -13234,7 +13436,7 @@ function stripJsoncSymbols(value) {
13234
13436
  // ../aft-bridge/dist/migration.js
13235
13437
  import { spawnSync as spawnSync2 } from "node:child_process";
13236
13438
  import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
13237
- import { homedir as homedir5, tmpdir } from "node:os";
13439
+ import { homedir as homedir6, tmpdir } from "node:os";
13238
13440
  import { dirname as dirname2, join as join6 } from "node:path";
13239
13441
 
13240
13442
  // ../aft-bridge/dist/paths.js
@@ -13291,7 +13493,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
13291
13493
  import { execSync } from "node:child_process";
13292
13494
  import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
13293
13495
  import { createRequire as createRequire2 } from "node:module";
13294
- import { homedir as homedir4 } from "node:os";
13496
+ import { homedir as homedir5 } from "node:os";
13295
13497
  import { join as join5 } from "node:path";
13296
13498
  var ensureBinaryForResolver = ensureBinary;
13297
13499
  function copyToVersionedCache(npmBinaryPath, knownVersion) {
@@ -13333,7 +13535,7 @@ function normalizeBareVersion(version) {
13333
13535
  return version.startsWith("v") ? version.slice(1) : version;
13334
13536
  }
13335
13537
  function homeDirFromEnv(env) {
13336
- return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir4();
13538
+ return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir5();
13337
13539
  }
13338
13540
  function cacheDirFromEnv(env) {
13339
13541
  if (process.platform === "win32") {
@@ -13515,8 +13717,8 @@ function dataHome() {
13515
13717
  }
13516
13718
  function homeDir() {
13517
13719
  if (process.platform === "win32")
13518
- return process.env.USERPROFILE || process.env.HOME || homedir5();
13519
- return process.env.HOME || homedir5();
13720
+ return process.env.USERPROFILE || process.env.HOME || homedir6();
13721
+ return process.env.HOME || homedir6();
13520
13722
  }
13521
13723
  function resolveLegacyStorageRoot(harness) {
13522
13724
  if (harness === "pi")
@@ -13606,13 +13808,13 @@ async function ensureStorageMigrated(opts) {
13606
13808
  }
13607
13809
  // ../aft-bridge/dist/npm-resolver.js
13608
13810
  import { readdirSync, statSync as statSync2 } from "node:fs";
13609
- import { homedir as homedir6 } from "node:os";
13811
+ import { homedir as homedir7 } from "node:os";
13610
13812
  import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join7 } from "node:path";
13611
13813
  function defaultDeps() {
13612
13814
  return {
13613
13815
  platform: process.platform,
13614
13816
  env: process.env,
13615
- home: homedir6(),
13817
+ home: homedir7(),
13616
13818
  execPath: process.execPath
13617
13819
  };
13618
13820
  }
@@ -15379,9 +15581,6 @@ function write(level, message, data, sessionId) {
15379
15581
  function log2(message, data) {
15380
15582
  write("INFO", message, data);
15381
15583
  }
15382
- function debug(message, data) {
15383
- write("DEBUG", message, data);
15384
- }
15385
15584
  function warn2(message, data) {
15386
15585
  write("WARN", message, data);
15387
15586
  }
@@ -15391,9 +15590,6 @@ function error2(message, data) {
15391
15590
  function sessionLog(sessionId, message, data) {
15392
15591
  write("INFO", message, data, sessionId);
15393
15592
  }
15394
- function sessionDebug(sessionId, message, data) {
15395
- write("DEBUG", message, data, sessionId);
15396
- }
15397
15593
  function sessionWarn(sessionId, message, data) {
15398
15594
  write("WARN", message, data, sessionId);
15399
15595
  }
@@ -15487,84 +15683,6 @@ async function resolvePromptContext(client, sessionId) {
15487
15683
  return result;
15488
15684
  }
15489
15685
 
15490
- // src/shared/live-server-client.ts
15491
- import { createOpencodeClient } from "@opencode-ai/sdk";
15492
- var clientCache = new Map;
15493
- function cacheKey(serverUrl, directory) {
15494
- return `${serverUrl}|${directory}`;
15495
- }
15496
- function normalizeServerUrl(serverUrl) {
15497
- try {
15498
- return new URL(serverUrl).toString();
15499
- } catch {
15500
- return serverUrl;
15501
- }
15502
- }
15503
- function serverAuthHeaders() {
15504
- const password = process.env.OPENCODE_SERVER_PASSWORD;
15505
- if (!password)
15506
- return;
15507
- const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
15508
- return {
15509
- Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
15510
- };
15511
- }
15512
- function getLiveServerClient(serverUrl, directory) {
15513
- const key = cacheKey(serverUrl, directory);
15514
- const cached = clientCache.get(key);
15515
- if (cached)
15516
- return cached;
15517
- const client = createOpencodeClient({
15518
- baseUrl: serverUrl,
15519
- directory,
15520
- headers: serverAuthHeaders(),
15521
- fetch: globalThis.fetch
15522
- });
15523
- clientCache.set(key, client);
15524
- return client;
15525
- }
15526
- var liveServerWakeAvailableByServerUrl = new Map;
15527
- var legacyLiveServerWakeAvailable = false;
15528
- async function probeServerReachable(serverUrl, timeoutMs = 1500) {
15529
- if (!serverUrl)
15530
- return false;
15531
- const normalizedServerUrl = normalizeServerUrl(serverUrl);
15532
- const controller = new AbortController;
15533
- const timer = setTimeout(() => controller.abort(), timeoutMs);
15534
- let reachable = false;
15535
- try {
15536
- const probeUrl = new URL("/session", serverUrl).toString();
15537
- const res = await globalThis.fetch(probeUrl, {
15538
- method: "GET",
15539
- headers: serverAuthHeaders(),
15540
- signal: controller.signal
15541
- });
15542
- reachable = res.ok || res.status === 401 || res.status === 403;
15543
- } catch {
15544
- reachable = false;
15545
- } finally {
15546
- clearTimeout(timer);
15547
- liveServerWakeAvailableByServerUrl.set(normalizedServerUrl, reachable);
15548
- }
15549
- return reachable;
15550
- }
15551
- function setLiveServerWakeAvailable(serverUrlOrAvailable, available) {
15552
- if (typeof serverUrlOrAvailable === "boolean") {
15553
- legacyLiveServerWakeAvailable = serverUrlOrAvailable;
15554
- return;
15555
- }
15556
- if (!serverUrlOrAvailable) {
15557
- legacyLiveServerWakeAvailable = available ?? false;
15558
- return;
15559
- }
15560
- liveServerWakeAvailableByServerUrl.set(normalizeServerUrl(serverUrlOrAvailable), available ?? false);
15561
- }
15562
- function useLiveServerWake(serverUrl) {
15563
- if (!serverUrl)
15564
- return legacyLiveServerWakeAvailable;
15565
- return liveServerWakeAvailableByServerUrl.get(normalizeServerUrl(serverUrl)) ?? false;
15566
- }
15567
-
15568
15686
  // src/bg-notifications.ts
15569
15687
  function hashReminder(text) {
15570
15688
  return createHash3("sha256").update(text).digest("hex").slice(0, 16);
@@ -15791,15 +15909,15 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
15791
15909
  directory: drainContext.directory,
15792
15910
  attempt: state.wakeRetryAttempts + 1
15793
15911
  });
15794
- throw new Error("no wake transport available: live-server unreachable and input.client absent");
15912
+ throw new Error("in-process wake client unavailable: input.client absent");
15795
15913
  }
15796
15914
  return drainContext.client;
15797
15915
  };
15798
- const sendPrompt = async (client, clientPath) => {
15799
- if (typeof client.session?.promptAsync !== "function") {
15800
- throw new Error(`wake client.session.promptAsync is unavailable (path=${clientPath})`);
15916
+ const sendPrompt = async (client2) => {
15917
+ if (typeof client2.session?.promptAsync !== "function") {
15918
+ throw new Error("wake client.session.promptAsync is unavailable");
15801
15919
  }
15802
- const promptContext = await resolvePromptContext(client, drainContext.sessionID);
15920
+ const promptContext = await resolvePromptContext(client2, drainContext.sessionID);
15803
15921
  const body = {
15804
15922
  noReply: false,
15805
15923
  parts: [{ type: "text", text: reminder, synthetic: true }]
@@ -15822,7 +15940,6 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
15822
15940
  directory: drainContext.directory,
15823
15941
  reminder_sha256: hashReminder(reminder),
15824
15942
  reminder_chars: reminder.length,
15825
- wake_client_path: clientPath,
15826
15943
  prompt_context: promptContext ? {
15827
15944
  agent: promptContext.agent,
15828
15945
  model: promptContext.model ? {
@@ -15837,18 +15954,16 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
15837
15954
  ...wakeMeta
15838
15955
  });
15839
15956
  try {
15840
- await client.session.promptAsync({
15957
+ await client2.session.promptAsync({
15841
15958
  path: { id: drainContext.sessionID },
15842
15959
  body
15843
15960
  });
15844
15961
  } catch (err) {
15845
- const logPromptError = clientPath === "live-server" ? sessionDebug : sessionWarn;
15846
- logPromptError(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
15962
+ sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
15847
15963
  event: "bash_completion_wake_prompt_async_error",
15848
15964
  delivery_id: deliveryID2,
15849
15965
  attempt: state.wakeRetryAttempts + 1,
15850
15966
  task_ids: taskIDs,
15851
- wake_client_path: clientPath,
15852
15967
  error: err instanceof Error ? err.message : String(err)
15853
15968
  });
15854
15969
  throw err;
@@ -15857,38 +15972,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
15857
15972
  event: "bash_completion_wake_prompt_async_ok",
15858
15973
  delivery_id: deliveryID2,
15859
15974
  attempt: state.wakeRetryAttempts + 1,
15860
- task_ids: taskIDs,
15861
- wake_client_path: clientPath
15975
+ task_ids: taskIDs
15862
15976
  });
15863
15977
  return deliveryID2;
15864
15978
  };
15865
- if (useLiveServerWake(drainContext.serverUrl) && drainContext.serverUrl) {
15866
- try {
15867
- const liveClient = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
15868
- const deliveryID2 = await sendPrompt(liveClient, "live-server");
15869
- await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
15870
- return;
15871
- } catch (err) {
15872
- setLiveServerWakeAvailable(drainContext.serverUrl, false);
15873
- sessionDebug(drainContext.sessionID, `${LOG_PREFIX} live-server wake failed; falling back`, {
15874
- event: "bash_completion_wake_live_server_fallback",
15875
- task_ids: taskIDs,
15876
- directory: drainContext.directory,
15877
- server_url: drainContext.serverUrl,
15878
- attempt: state.wakeRetryAttempts + 1,
15879
- error: err instanceof Error ? err.message : String(err)
15880
- });
15881
- const fallbackClient2 = getInProcessClient();
15882
- const deliveryID2 = await sendPrompt(fallbackClient2, "in-process-fallback");
15883
- state.retryDelayMs = null;
15884
- state.wakeRetryAttempts = 0;
15885
- state.wakeHardStopped = false;
15886
- await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
15887
- return;
15888
- }
15889
- }
15890
- const fallbackClient = getInProcessClient();
15891
- const deliveryID = await sendPrompt(fallbackClient, "in-process-fallback");
15979
+ const client = getInProcessClient();
15980
+ const deliveryID = await sendPrompt(client);
15892
15981
  await ackCompletions(drainContext, deliveredCompletions, deliveryID);
15893
15982
  }, (err, hardStopped) => {
15894
15983
  sessionWarn(drainContext.sessionID, hardStopped ? `${LOG_PREFIX} wake send failed ${MAX_WAKE_SEND_ATTEMPTS} times; stopping retries: ${err instanceof Error ? err.message : String(err)}` : `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -16271,7 +16360,7 @@ function formatDuration(completion) {
16271
16360
 
16272
16361
  // src/config.ts
16273
16362
  import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
16274
- import { homedir as homedir7 } from "node:os";
16363
+ import { homedir as homedir8 } from "node:os";
16275
16364
  import { join as join10 } from "node:path";
16276
16365
  var import_comment_json = __toESM(require_src2(), 1);
16277
16366
 
@@ -29922,6 +30011,8 @@ var AftConfigSchema = exports_external.object({
29922
30011
  restrict_to_project_root: exports_external.boolean().optional(),
29923
30012
  search_index: exports_external.boolean().optional(),
29924
30013
  semantic_search: exports_external.boolean().optional(),
30014
+ callgraph_store: exports_external.boolean().optional(),
30015
+ callgraph_chunk_size: exports_external.number().optional(),
29925
30016
  inspect: InspectConfigSchema.optional(),
29926
30017
  bash: BashConfigSchema.optional(),
29927
30018
  experimental: ExperimentalConfigSchema.optional(),
@@ -30000,6 +30091,10 @@ function resolveProjectOverridesForConfigure(config2) {
30000
30091
  overrides.search_index = config2.search_index;
30001
30092
  if (config2.semantic_search !== undefined)
30002
30093
  overrides.semantic_search = config2.semantic_search;
30094
+ if (config2.callgraph_store !== undefined)
30095
+ overrides.callgraph_store = config2.callgraph_store;
30096
+ if (config2.callgraph_chunk_size !== undefined)
30097
+ overrides.callgraph_chunk_size = config2.callgraph_chunk_size;
30003
30098
  Object.assign(overrides, resolveExperimentalConfigForConfigure(config2));
30004
30099
  Object.assign(overrides, resolveLspConfigForConfigure(config2));
30005
30100
  if (config2.semantic !== undefined)
@@ -30274,6 +30369,17 @@ function parseConfigPartially(rawConfig) {
30274
30369
  }
30275
30370
  return partialConfig;
30276
30371
  }
30372
+ var configLoadErrors = [];
30373
+ function getConfigLoadErrors() {
30374
+ return configLoadErrors;
30375
+ }
30376
+ function formatConfigParseFailureMessage(configPath, errorMessage) {
30377
+ return `AFT config at ${configPath} failed to parse and was ignored (running on defaults): ${errorMessage}. ` + "Fix the syntax or run `npx @cortexkit/aft doctor`.";
30378
+ }
30379
+ function recordConfigParseFailure(configPath, errorMessage) {
30380
+ configLoadErrors.push({ path: configPath, message: errorMessage });
30381
+ warn2(formatConfigParseFailureMessage(configPath, errorMessage));
30382
+ }
30277
30383
  function loadConfigFromPath(configPath) {
30278
30384
  try {
30279
30385
  if (!existsSync6(configPath)) {
@@ -30294,6 +30400,7 @@ function loadConfigFromPath(configPath) {
30294
30400
  } catch (err) {
30295
30401
  const errorMsg = err instanceof Error ? err.message : String(err);
30296
30402
  error2(`Error loading config from ${configPath}: ${errorMsg}`);
30403
+ recordConfigParseFailure(configPath, errorMsg);
30297
30404
  return null;
30298
30405
  }
30299
30406
  }
@@ -30417,6 +30524,8 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
30417
30524
  "configure_warnings_delivery",
30418
30525
  "search_index",
30419
30526
  "semantic_search",
30527
+ "callgraph_store",
30528
+ "callgraph_chunk_size",
30420
30529
  "inspect",
30421
30530
  "experimental",
30422
30531
  "bash"
@@ -30484,10 +30593,11 @@ function getOpenCodeConfigDir() {
30484
30593
  if (envDir) {
30485
30594
  return envDir;
30486
30595
  }
30487
- const xdgConfig = process.env.XDG_CONFIG_HOME || join10(homedir7(), ".config");
30596
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join10(homedir8(), ".config");
30488
30597
  return join10(xdgConfig, "opencode");
30489
30598
  }
30490
30599
  function loadAftConfig(projectDirectory) {
30600
+ configLoadErrors = [];
30491
30601
  const configDir = getOpenCodeConfigDir();
30492
30602
  const userBasePath = join10(configDir, "aft");
30493
30603
  migrateAftConfigFile(`${userBasePath}.jsonc`);
@@ -30520,7 +30630,7 @@ function loadAftConfig(projectDirectory) {
30520
30630
 
30521
30631
  // src/notifications.ts
30522
30632
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
30523
- import { homedir as homedir8, platform } from "node:os";
30633
+ import { homedir as homedir9, platform } from "node:os";
30524
30634
  import { join as join11 } from "node:path";
30525
30635
  function isTuiMode() {
30526
30636
  return process.env.OPENCODE_CLIENT === "cli";
@@ -30542,7 +30652,7 @@ var WARNING_MARKER = `${AFT_MARKER} ⚠️`;
30542
30652
  var STATUS_MARKER = `${AFT_MARKER} ✅`;
30543
30653
  function getDesktopStatePath() {
30544
30654
  const os3 = platform();
30545
- const home = homedir8();
30655
+ const home = homedir9();
30546
30656
  if (os3 === "darwin") {
30547
30657
  return join11(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
30548
30658
  }
@@ -30821,6 +30931,8 @@ function warningTitle(warning) {
30821
30931
  return "Checker is not installed";
30822
30932
  case "lsp_binary_missing":
30823
30933
  return "LSP binary is missing";
30934
+ case "config_parse_failed":
30935
+ return "Config failed to parse";
30824
30936
  }
30825
30937
  }
30826
30938
  function formatConfigureWarningLine(warning) {
@@ -30942,12 +31054,36 @@ async function cleanupWarnings(opts) {
30942
31054
 
30943
31055
  // src/configure-warnings.ts
30944
31056
  var pendingEagerWarnings = new Map;
31057
+ var pendingConfigParseWarnings = new Map;
30945
31058
  var pendingBySession = new Map;
30946
31059
  function isConfigureWarning(value) {
30947
31060
  if (!value || typeof value !== "object" || Array.isArray(value))
30948
31061
  return false;
30949
31062
  const warning = value;
30950
- return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing") && typeof warning.hint === "string";
31063
+ return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed") && typeof warning.hint === "string";
31064
+ }
31065
+ function configParseWarningsFromErrors(errors3) {
31066
+ return errors3.map((entry) => ({
31067
+ kind: "config_parse_failed",
31068
+ hint: formatConfigParseFailureMessage(entry.path, entry.message)
31069
+ }));
31070
+ }
31071
+ function enqueueConfigParseWarnings(projectRoot, errors3) {
31072
+ if (!projectRoot || errors3.length === 0)
31073
+ return;
31074
+ const incoming = configParseWarningsFromErrors(errors3);
31075
+ const existing = pendingConfigParseWarnings.get(projectRoot) ?? [];
31076
+ for (const warning of incoming) {
31077
+ if (!existing.some((item) => item.hint === warning.hint)) {
31078
+ existing.push(warning);
31079
+ }
31080
+ }
31081
+ pendingConfigParseWarnings.set(projectRoot, existing);
31082
+ }
31083
+ function drainPendingConfigParseWarnings(projectRoot) {
31084
+ const pending = pendingConfigParseWarnings.get(projectRoot) ?? [];
31085
+ pendingConfigParseWarnings.delete(projectRoot);
31086
+ return pending;
30951
31087
  }
30952
31088
  function coerceConfigureWarnings(warnings) {
30953
31089
  return warnings.filter(isConfigureWarning);
@@ -30958,7 +31094,10 @@ function drainPendingEagerWarnings(projectRoot) {
30958
31094
  return pending;
30959
31095
  }
30960
31096
  function enqueueConfigureWarningsForSession(context) {
30961
- const validWarnings = coerceConfigureWarnings(context.warnings);
31097
+ const validWarnings = [
31098
+ ...drainPendingConfigParseWarnings(context.projectRoot),
31099
+ ...coerceConfigureWarnings(context.warnings)
31100
+ ];
30962
31101
  if (!context.sessionId) {
30963
31102
  if (validWarnings.length === 0)
30964
31103
  return;
@@ -31028,27 +31167,27 @@ var import_comment_json3 = __toESM(require_src2(), 1);
31028
31167
  // src/hooks/auto-update-checker/checker.ts
31029
31168
  var import_comment_json2 = __toESM(require_src2(), 1);
31030
31169
  import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
31031
- import { homedir as homedir10 } from "node:os";
31170
+ import { homedir as homedir11 } from "node:os";
31032
31171
  import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, resolve as resolve3 } from "node:path";
31033
31172
  import { fileURLToPath } from "node:url";
31034
31173
 
31035
31174
  // src/hooks/auto-update-checker/constants.ts
31036
- import { homedir as homedir9, platform as platform2 } from "node:os";
31175
+ import { homedir as homedir10, platform as platform2 } from "node:os";
31037
31176
  import { join as join12 } from "node:path";
31038
31177
  var PACKAGE_NAME = "@cortexkit/aft-opencode";
31039
31178
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
31040
31179
  var NPM_FETCH_TIMEOUT = 1e4;
31041
31180
  function getOpenCodeCacheRoot() {
31042
31181
  if (platform2() === "win32") {
31043
- return join12(process.env.LOCALAPPDATA ?? homedir9(), "opencode");
31182
+ return join12(process.env.LOCALAPPDATA ?? homedir10(), "opencode");
31044
31183
  }
31045
- return join12(homedir9(), ".cache", "opencode");
31184
+ return join12(homedir10(), ".cache", "opencode");
31046
31185
  }
31047
31186
  function getOpenCodeConfigRoot() {
31048
31187
  if (platform2() === "win32") {
31049
- return join12(process.env.APPDATA ?? join12(homedir9(), "AppData", "Roaming"), "opencode");
31188
+ return join12(process.env.APPDATA ?? join12(homedir10(), "AppData", "Roaming"), "opencode");
31050
31189
  }
31051
- return join12(process.env.XDG_CONFIG_HOME ?? join12(homedir9(), ".config"), "opencode");
31190
+ return join12(process.env.XDG_CONFIG_HOME ?? join12(homedir10(), ".config"), "opencode");
31052
31191
  }
31053
31192
  var CACHE_DIR = join12(getOpenCodeCacheRoot(), "packages");
31054
31193
  var USER_OPENCODE_CONFIG = join12(getOpenCodeConfigRoot(), "opencode.json");
@@ -31227,7 +31366,7 @@ function getCachedVersion(spec) {
31227
31366
  getCurrentRuntimePackageJsonPath(),
31228
31367
  spec ? getSpecCachePackageJsonPath(spec) : null,
31229
31368
  getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
31230
- join13(homedir10(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
31369
+ join13(homedir11(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
31231
31370
  ].filter(isString);
31232
31371
  for (const packageJsonPath of candidates) {
31233
31372
  try {
@@ -31730,7 +31869,7 @@ import {
31730
31869
  unlinkSync as unlinkSync5,
31731
31870
  writeFileSync as writeFileSync7
31732
31871
  } from "node:fs";
31733
- import { homedir as homedir11 } from "node:os";
31872
+ import { homedir as homedir12 } from "node:os";
31734
31873
  import { join as join15 } from "node:path";
31735
31874
  function aftCacheBase() {
31736
31875
  const override = process.env.AFT_CACHE_DIR;
@@ -31738,10 +31877,10 @@ function aftCacheBase() {
31738
31877
  return override;
31739
31878
  if (process.platform === "win32") {
31740
31879
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
31741
- const base2 = localAppData || join15(homedir11(), "AppData", "Local");
31880
+ const base2 = localAppData || join15(homedir12(), "AppData", "Local");
31742
31881
  return join15(base2, "aft");
31743
31882
  }
31744
- const base = process.env.XDG_CACHE_HOME || join15(homedir11(), ".cache");
31883
+ const base = process.env.XDG_CACHE_HOME || join15(homedir12(), ".cache");
31745
31884
  return join15(base, "aft");
31746
31885
  }
31747
31886
  function lspCacheRoot() {
@@ -33824,7 +33963,7 @@ async function verifySessionDirectory(client, sessionId) {
33824
33963
  }
33825
33964
 
33826
33965
  // src/shared/status.ts
33827
- function asRecord(value) {
33966
+ function asRecord2(value) {
33828
33967
  return typeof value === "object" && value !== null ? value : {};
33829
33968
  }
33830
33969
  function readString(value, fallback = "") {
@@ -33843,7 +33982,7 @@ function readOptionalNumber(value) {
33843
33982
  return typeof value === "number" && Number.isFinite(value) ? value : null;
33844
33983
  }
33845
33984
  function readCompressionAggregate(value) {
33846
- const aggregate = asRecord(value);
33985
+ const aggregate = asRecord2(value);
33847
33986
  return {
33848
33987
  events: readNumber(aggregate.events),
33849
33988
  original_tokens: readNumber(aggregate.original_tokens),
@@ -33854,7 +33993,7 @@ function readCompressionAggregate(value) {
33854
33993
  function readCompression(value) {
33855
33994
  if (typeof value !== "object" || value === null)
33856
33995
  return;
33857
- const compression = asRecord(value);
33996
+ const compression = asRecord2(value);
33858
33997
  return {
33859
33998
  project: readCompressionAggregate(compression.project),
33860
33999
  session: readCompressionAggregate(compression.session)
@@ -33863,7 +34002,7 @@ function readCompression(value) {
33863
34002
  function readStatusBar(value) {
33864
34003
  if (typeof value !== "object" || value === null)
33865
34004
  return;
33866
- const bar = asRecord(value);
34005
+ const bar = asRecord2(value);
33867
34006
  return {
33868
34007
  errors: readNumber(bar.errors),
33869
34008
  warnings: readNumber(bar.warnings),
@@ -33907,16 +34046,16 @@ function formatBytes(bytes) {
33907
34046
  return `${value.toFixed(decimals)} ${units[unitIndex]}`;
33908
34047
  }
33909
34048
  function coerceAftStatus(response) {
33910
- const features = asRecord(response.features);
33911
- const searchIndex = asRecord(response.search_index);
33912
- const semanticIndex = asRecord(response.semantic_index);
34049
+ const features = asRecord2(response.features);
34050
+ const searchIndex = asRecord2(response.search_index);
34051
+ const semanticIndex = asRecord2(response.semantic_index);
33913
34052
  const semanticConfig = {
33914
- ...asRecord(response.semantic),
33915
- ...asRecord(response.semantic_config)
34053
+ ...asRecord2(response.semantic),
34054
+ ...asRecord2(response.semantic_config)
33916
34055
  };
33917
- const disk = asRecord(response.disk);
33918
- const symbolCache = asRecord(response.symbol_cache);
33919
- const session = asRecord(response.session);
34056
+ const disk = asRecord2(response.disk);
34057
+ const symbolCache = asRecord2(response.symbol_cache);
34058
+ const session = asRecord2(response.session);
33920
34059
  return {
33921
34060
  version: readString(response.version, "unknown"),
33922
34061
  project_root: readNullableString(response.project_root),
@@ -34038,7 +34177,7 @@ import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as r
34038
34177
  import { dirname as dirname10, join as join22 } from "node:path";
34039
34178
 
34040
34179
  // src/shared/opencode-config-dir.ts
34041
- import { homedir as homedir12 } from "node:os";
34180
+ import { homedir as homedir13 } from "node:os";
34042
34181
  import { join as join21, resolve as resolve5 } from "node:path";
34043
34182
  function getCliConfigDir() {
34044
34183
  const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
@@ -34046,9 +34185,9 @@ function getCliConfigDir() {
34046
34185
  return resolve5(envConfigDir);
34047
34186
  }
34048
34187
  if (process.platform === "win32") {
34049
- return join21(homedir12(), ".config", "opencode");
34188
+ return join21(homedir13(), ".config", "opencode");
34050
34189
  }
34051
- return join21(process.env.XDG_CONFIG_HOME || join21(homedir12(), ".config"), "opencode");
34190
+ return join21(process.env.XDG_CONFIG_HOME || join21(homedir13(), ".config"), "opencode");
34052
34191
  }
34053
34192
  function getOpenCodeConfigDir2(_options) {
34054
34193
  return getCliConfigDir();
@@ -34888,18 +35027,43 @@ function resolveForegroundWaitMs(configured) {
34888
35027
  function bashToolDescription(aftSearchRegistered, compressionOn, backgroundOn) {
34889
35028
  const searchSteer = aftSearchRegistered ? "use aft_search (concepts, identifiers, regex, literals), read, aft_outline, or aft_zoom instead" : "use the grep tool, read, aft_outline, or aft_zoom instead";
34890
35029
  const compression = compressionOn ? " Output is compressed by default; pass compressed: false for raw output." : "";
34891
- const tasks = backgroundOn ? ' Pass background: true to run in the background and get a taskId for bash_status/bash_kill. Pass pty: true for interactive programs (REPLs, TUIs) and drive them with bash_status({ outputMode: "screen" }) plus bash_write (pty implies background automatically).' : " Commands that outlive the foreground wait window are promoted to background tasks; inspect them with bash_status({ taskId }) or terminate with bash_kill.";
34892
- return `Execute shell commands.${compression}${tasks} Use bash_watch to wait for output patterns or exit events.
35030
+ const tasks = backgroundOn ? ' Pass background: true to run in the background and get a taskId for bash_status/bash_watch/bash_kill. Pass pty: true for interactive programs (REPLs, TUIs) and drive them with bash_status({ outputMode: "screen" }) plus bash_write (pty implies background automatically). Use bash_watch to wait for exit or output patterns (sync blocks, async notifies). Do not loop bash_status to wait.' : " Commands run in the foreground to completion; timeout is the hard kill cap (default 30 minutes).";
35031
+ return `Execute shell commands.${compression}${tasks}
34893
35032
 
34894
35033
  DO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — ${searchSteer}.`;
34895
35034
  }
35035
+ function pushUnique(target, values) {
35036
+ for (const value of values) {
35037
+ if (!target.includes(value))
35038
+ target.push(value);
35039
+ }
35040
+ }
35041
+ function groupBashPermissionAsks(asks) {
35042
+ const grouped = [];
35043
+ let bashAsk;
35044
+ for (const ask of asks) {
35045
+ if (ask.kind === "bash") {
35046
+ if (!bashAsk) {
35047
+ bashAsk = { kind: "bash", patterns: [], always: [] };
35048
+ grouped.push(bashAsk);
35049
+ }
35050
+ pushUnique(bashAsk.patterns, ask.patterns);
35051
+ pushUnique(bashAsk.always, ask.always);
35052
+ continue;
35053
+ }
35054
+ grouped.push(ask);
35055
+ }
35056
+ return grouped;
35057
+ }
35058
+ function permissionsGrantedForRetry(asks) {
35059
+ return asks.flatMap((ask) => ask.always.length > 0 ? ask.always : ask.patterns);
35060
+ }
34896
35061
  async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
34897
35062
  const first = await bridgeCall(ctx, runtime, "bash", params, options);
34898
35063
  if (first.success !== false || first.code !== "permission_required")
34899
35064
  return first;
34900
35065
  const asks = Array.isArray(first.asks) ? first.asks : [];
34901
- const permissionsGranted = [];
34902
- for (const ask of asks) {
35066
+ for (const ask of groupBashPermissionAsks(asks)) {
34903
35067
  const permission = ask.kind === "external_directory" ? "external_directory" : "bash";
34904
35068
  await runAsk(runtime.ask({
34905
35069
  permission,
@@ -34907,54 +35071,60 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
34907
35071
  always: ask.always,
34908
35072
  metadata: {}
34909
35073
  }));
34910
- permissionsGranted.push(...ask.always.length > 0 ? ask.always : ask.patterns);
34911
35074
  }
34912
- const second = await bridgeCall(ctx, runtime, "bash", { ...params, permissions_granted: permissionsGranted }, options);
35075
+ const second = await bridgeCall(ctx, runtime, "bash", { ...params, permissions_granted: permissionsGrantedForRetry(asks) }, options);
34913
35076
  if (second.success === false && second.code === "permission_required") {
34914
35077
  throw new Error("bash permission retry failed");
34915
35078
  }
34916
35079
  return second;
34917
35080
  }
34918
35081
  function createBashTool(ctx, aftSearchRegisteredOverride) {
35082
+ const initialBashCfg = resolveBashConfig(ctx.config);
35083
+ const backgroundFlagArg = initialBashCfg.background ? {
35084
+ background: z4.boolean().optional().describe("When true, spawn the command in the background and return a taskId for bash_status/bash_kill instead of waiting for completion. Defaults to false.")
35085
+ } : {};
35086
+ const ptyArgs = initialBashCfg.background ? {
35087
+ pty: z4.boolean().optional().describe('When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Implies background: true automatically. Unavailable in subagent sessions. Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.'),
35088
+ ptyRows: optionalInt(1, 60).describe("PTY terminal height in rows — ignored when pty is false. Defaults to 24 when pty: true. Minimum 1, maximum 60."),
35089
+ ptyCols: optionalInt(1, 140).describe("PTY terminal width in columns — ignored when pty is false. Defaults to 80 when pty: true. Minimum 1, maximum 140.")
35090
+ } : {};
35091
+ const args = {
35092
+ command: z4.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
35093
+ timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe(initialBashCfg.background ? "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 ~8s (configurable via bash.foreground_wait_window_ms); otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes." : "Hard kill cap in milliseconds (positive integer). When omitted, the foreground command can run up to 30 minutes and returns inline when it finishes."),
35094
+ workdir: z4.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
35095
+ description: z4.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
35096
+ ...backgroundFlagArg,
35097
+ compressed: z4.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
35098
+ ...ptyArgs
35099
+ };
34919
35100
  return {
34920
- description: (() => {
34921
- const cfg = resolveBashConfig(ctx.config);
34922
- return bashToolDescription(false, cfg.compress, cfg.background);
34923
- })(),
34924
- args: {
34925
- command: z4.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
34926
- timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).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 ~8s (configurable via bash.foreground_wait_window_ms); otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes."),
34927
- workdir: z4.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
34928
- description: z4.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
34929
- background: z4.boolean().optional().describe("When true, spawn the command in the background and return a taskId for bash_status/bash_kill instead of waiting for completion. Defaults to false."),
34930
- compressed: z4.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
34931
- pty: z4.boolean().optional().describe('When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Implies background: true automatically. Unavailable in subagent sessions. Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.'),
34932
- ptyRows: optionalInt(1, 60).describe("PTY terminal height in rows — ignored when pty is false. Defaults to 24 when pty: true. Minimum 1, maximum 60."),
34933
- ptyCols: optionalInt(1, 140).describe("PTY terminal width in columns — ignored when pty is false. Defaults to 80 when pty: true. Minimum 1, maximum 140.")
34934
- },
34935
- execute: async (args, context) => {
35101
+ description: bashToolDescription(false, initialBashCfg.compress, initialBashCfg.background),
35102
+ args,
35103
+ execute: async (args2, context) => {
34936
35104
  const bashCfg = resolveBashConfig(ctx.config);
34937
35105
  const ctxAftSearchRegistered = ctx.aftSearchRegistered === true;
34938
35106
  const aftSearchRegistered = aftSearchRegisteredOverride ?? ctxAftSearchRegistered;
34939
35107
  let accumulatedOutput = "";
34940
- const description = args.description;
35108
+ const description = args2.description;
34941
35109
  const metadata = context.metadata;
34942
- const rawCommand = args.command;
34943
- const compressionEnabled = bashCfg.compress && args.compressed !== false;
35110
+ const rawCommand = args2.command;
35111
+ const compressionEnabled = bashCfg.compress && args2.compressed !== false;
34944
35112
  const pipeStrip = maybeStripCompressorPipe(rawCommand, compressionEnabled);
34945
35113
  const command = pipeStrip.command;
34946
- const cwd = args.workdir ?? context.directory;
35114
+ const cwd = args2.workdir ?? context.directory;
34947
35115
  const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
34948
- const requestedPty = args.pty === true;
34949
- const requestedBackground = args.background === true || requestedPty;
35116
+ const backgroundDisabled = !bashCfg.background;
35117
+ const requestedPty = !backgroundDisabled && args2.pty === true;
35118
+ const requestedBackground = !backgroundDisabled && (args2.background === true || requestedPty);
34950
35119
  if (requestedPty && isSubagent) {
34951
35120
  throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
34952
35121
  }
34953
35122
  const allowSubagentBg = bashCfg.subagent_background;
34954
35123
  const subagentForcedForeground = isSubagent && !allowSubagentBg;
34955
- const effectiveBackground = subagentForcedForeground ? false : requestedBackground;
34956
- const rawTimeout = args.timeout;
34957
- const effectiveTimeout = effectiveBackground ? rawTimeout : resolveBashKillTimeout(rawTimeout, bashCfg.foreground_wait_window_ms);
35124
+ const blockToCompletion = subagentForcedForeground || backgroundDisabled;
35125
+ const effectiveBackground = blockToCompletion ? false : requestedBackground;
35126
+ const rawTimeout = args2.timeout;
35127
+ const effectiveTimeout = effectiveBackground || backgroundDisabled ? rawTimeout : resolveBashKillTimeout(rawTimeout, bashCfg.foreground_wait_window_ms);
34958
35128
  if (subagentForcedForeground && requestedBackground) {
34959
35129
  sessionLog(context.sessionID, "[bash] subagent + background:true → converting to foreground (subagent would lose task_id)");
34960
35130
  }
@@ -34962,15 +35132,15 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
34962
35132
  const data = await withPermissionLoop(ctx, context, {
34963
35133
  command,
34964
35134
  timeout: effectiveTimeout,
34965
- workdir: args.workdir,
35135
+ workdir: args2.workdir,
34966
35136
  env: shellEnv?.env ?? {},
34967
35137
  description,
34968
35138
  background: effectiveBackground,
34969
35139
  notify_on_completion: effectiveBackground,
34970
- compressed: args.compressed,
35140
+ compressed: args2.compressed,
34971
35141
  pty: requestedPty,
34972
- pty_rows: args.ptyRows,
34973
- pty_cols: args.ptyCols,
35142
+ pty_rows: args2.ptyRows,
35143
+ pty_cols: args2.ptyCols,
34974
35144
  permissions_requested: true
34975
35145
  }, callBashBridge, {
34976
35146
  onProgress: ({ text }) => {
@@ -34995,7 +35165,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
34995
35165
  return { output: startedLine, title: uiTitle, metadata: metadataPayload2 };
34996
35166
  }
34997
35167
  const foregroundWaitMs = resolveForegroundWaitMs(bashCfg.foreground_wait_window_ms);
34998
- const waitTimeoutMs = subagentForcedForeground ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
35168
+ const waitTimeoutMs = blockToCompletion ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
34999
35169
  const startedAt = Date.now();
35000
35170
  while (true) {
35001
35171
  const status = await callBashBridge(ctx, context, "bash_status", { task_id: taskId });
@@ -35009,7 +35179,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35009
35179
  return { output: rendered2, title: uiTitle, metadata: metadataPayload2 };
35010
35180
  }
35011
35181
  if (Date.now() - startedAt >= waitTimeoutMs) {
35012
- if (subagentForcedForeground) {
35182
+ if (blockToCompletion) {
35013
35183
  await sleep(FOREGROUND_POLL_INTERVAL_MS);
35014
35184
  continue;
35015
35185
  }
@@ -35069,7 +35239,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
35069
35239
  }
35070
35240
  function createBashStatusTool(ctx) {
35071
35241
  return {
35072
- description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Never waits. Use bash_watch to block on or register for pattern matches and exit events.",
35242
+ description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Never waits. One look to check on a task is fine — never loop it to wait for completion. To wait, use bash_watch.",
35073
35243
  args: {
35074
35244
  taskId: z4.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047."),
35075
35245
  outputMode: z4.enum(["screen", "raw", "both"]).optional().describe("PTY output rendering mode. Defaults to screen for PTY tasks and preserves existing behavior for piped tasks when omitted.")
@@ -35547,16 +35717,16 @@ import { tool as tool6 } from "@opencode-ai/plugin";
35547
35717
  var z6 = tool6.schema;
35548
35718
  var BASH_WAIT_POLL_INTERVAL_MS = 100;
35549
35719
  var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
35550
- var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 300000;
35720
+ var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 30 * 60 * 1000;
35551
35721
  var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
35552
35722
  function createBashWatchTool(ctx) {
35553
35723
  return {
35554
- description: "Watch a background bash task. Two modes. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately — use this to be pinged when a specific line appears or the task exits, without freezing your turn. Sync (default) blocks until a pattern matches/the task exits/timeout, and is ONLY for short bounded waits (seconds, e.g. a dev server printing a readiness line). Do NOT sync-wait for a long task (build/test/install): blocking locks the user out until it ends instead end your turn and let the automatic completion reminder arrive, or use async mode.",
35724
+ description: "Watch a background bash task. Sync (default) blocks until a pattern matches, the task exits, or timeout — use it when the result is the next thing you need, even for long builds/tests/installs (pass timeoutMs up to 30 min for those). The user can interrupt anytime; the wait auto-converts to an async notification. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately use when you have parallel work or want to end your turn. Never loop bash_status to wait.",
35555
35725
  args: {
35556
35726
  taskId: z6.string().describe("Background task ID returned by bash({ background: true })."),
35557
35727
  pattern: z6.union([z6.string(), z6.object({ regex: z6.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true. Sync substring watches keep only the overlap tail needed for boundary matches; sync regex watches use a 64 KB rolling output window."),
35558
35728
  background: z6.boolean().optional().describe("When true, register an async watch and return immediately. Defaults to false (sync wait)."),
35559
- timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max 300000."),
35729
+ timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max 1800000 (30 min)."),
35560
35730
  once: z6.boolean().optional().describe("Async-only. Defaults true; false keeps the watch sticky until task exit.")
35561
35731
  },
35562
35732
  execute: async (args, context) => {
@@ -37116,12 +37286,15 @@ function hoistedTools(ctx) {
37116
37286
  aft_delete: createDeleteTool(ctx),
37117
37287
  aft_move: createMoveTool(ctx)
37118
37288
  };
37119
- if (resolveBashConfig(ctx.config).enabled) {
37289
+ const bashCfg = resolveBashConfig(ctx.config);
37290
+ if (bashCfg.enabled) {
37120
37291
  tools.bash = createBashTool(ctx);
37121
- tools.bash_status = createBashStatusTool(ctx);
37122
- tools.bash_write = createBashWriteTool(ctx);
37123
- tools.bash_watch = createBashWatchTool(ctx);
37124
- tools.bash_kill = createBashKillTool(ctx);
37292
+ if (bashCfg.background) {
37293
+ tools.bash_status = createBashStatusTool(ctx);
37294
+ tools.bash_write = createBashWriteTool(ctx);
37295
+ tools.bash_watch = createBashWatchTool(ctx);
37296
+ tools.bash_kill = createBashKillTool(ctx);
37297
+ }
37125
37298
  }
37126
37299
  return tools;
37127
37300
  }
@@ -37175,12 +37348,15 @@ function aftPrefixedTools(ctx) {
37175
37348
  aft_delete: createDeleteTool(ctx),
37176
37349
  aft_move: createMoveTool(ctx)
37177
37350
  };
37178
- if (resolveBashConfig(ctx.config).enabled) {
37351
+ const bashCfg = resolveBashConfig(ctx.config);
37352
+ if (bashCfg.enabled) {
37179
37353
  tools.aft_bash = createBashTool(ctx);
37180
- tools.bash_status = createBashStatusTool(ctx);
37181
- tools.bash_write = createBashWriteTool(ctx);
37182
- tools.bash_watch = createBashWatchTool(ctx);
37183
- tools.bash_kill = createBashKillTool(ctx);
37354
+ if (bashCfg.background) {
37355
+ tools.bash_status = createBashStatusTool(ctx);
37356
+ tools.bash_write = createBashWriteTool(ctx);
37357
+ tools.bash_watch = createBashWatchTool(ctx);
37358
+ tools.bash_kill = createBashKillTool(ctx);
37359
+ }
37184
37360
  }
37185
37361
  return tools;
37186
37362
  }
@@ -37264,15 +37440,15 @@ function importTools(ctx) {
37264
37440
  // src/tools/inspect.ts
37265
37441
  import { tool as tool10 } from "@opencode-ai/plugin";
37266
37442
  var z10 = tool10.schema;
37267
- function asRecord2(value) {
37443
+ function asRecord3(value) {
37268
37444
  if (!value || typeof value !== "object" || Array.isArray(value))
37269
37445
  return;
37270
37446
  return value;
37271
37447
  }
37272
- function asNumber(value) {
37448
+ function asNumber2(value) {
37273
37449
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
37274
37450
  }
37275
- function asString(value) {
37451
+ function asString2(value) {
37276
37452
  return typeof value === "string" ? value : undefined;
37277
37453
  }
37278
37454
  function asStringArray(value) {
@@ -37289,16 +37465,16 @@ function diagnosticsServerSummary(section) {
37289
37465
  return parts.length > 0 ? parts.join("; ") : "none reported";
37290
37466
  }
37291
37467
  function formatDiagnosticsSummary(summary) {
37292
- const section = asRecord2(summary?.diagnostics);
37468
+ const section = asRecord3(summary?.diagnostics);
37293
37469
  if (!section)
37294
37470
  return;
37295
- const errors3 = asNumber(section.errors);
37296
- const warnings = asNumber(section.warnings);
37297
- const info = asNumber(section.info);
37298
- const hints = asNumber(section.hints);
37471
+ const errors3 = asNumber2(section.errors);
37472
+ const warnings = asNumber2(section.warnings);
37473
+ const info = asNumber2(section.info);
37474
+ const hints = asNumber2(section.hints);
37299
37475
  const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
37300
37476
  const counts = `${errors3 ?? 0} errors, ${warnings ?? 0} warnings, ${info ?? 0} info, ${hints ?? 0} hints`;
37301
- const status = asString(section.status);
37477
+ const status = asString2(section.status);
37302
37478
  if (status === "pending") {
37303
37479
  return hasCounts ? `diagnostics: ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics: pending (servers: ${diagnosticsServerSummary(section)})`;
37304
37480
  }
@@ -37311,9 +37487,9 @@ function formatDiagnosticsSummary(summary) {
37311
37487
  return;
37312
37488
  }
37313
37489
  function formatDiagnosticLocation(diagnostic) {
37314
- const file2 = asString(diagnostic.file) ?? "(unknown file)";
37315
- const line = asNumber(diagnostic.line);
37316
- const column = asNumber(diagnostic.column);
37490
+ const file2 = asString2(diagnostic.file) ?? "(unknown file)";
37491
+ const line = asNumber2(diagnostic.line);
37492
+ const column = asNumber2(diagnostic.column);
37317
37493
  if (line === undefined)
37318
37494
  return file2;
37319
37495
  if (column === undefined)
@@ -37321,21 +37497,21 @@ function formatDiagnosticLocation(diagnostic) {
37321
37497
  return `${file2}:${line}:${column}`;
37322
37498
  }
37323
37499
  function formatDiagnosticsDetails(details) {
37324
- const diagnostics = Array.isArray(details?.diagnostics) ? details.diagnostics.map(asRecord2).filter(Boolean) : [];
37500
+ const diagnostics = Array.isArray(details?.diagnostics) ? details.diagnostics.map(asRecord3).filter(Boolean) : [];
37325
37501
  return diagnostics.map((diagnostic) => {
37326
- const severity = asString(diagnostic.severity) ?? "information";
37327
- const message = asString(diagnostic.message) ?? "(no message)";
37328
- const source = asString(diagnostic.source);
37502
+ const severity = asString2(diagnostic.severity) ?? "information";
37503
+ const message = asString2(diagnostic.message) ?? "(no message)";
37504
+ const source = asString2(diagnostic.source);
37329
37505
  const suffix = source ? ` [${source}]` : "";
37330
37506
  return `${formatDiagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`;
37331
37507
  });
37332
37508
  }
37333
37509
  function renderInspectDiagnostics(response) {
37334
37510
  const lines = [];
37335
- const summaryLine = formatDiagnosticsSummary(asRecord2(response.summary));
37511
+ const summaryLine = formatDiagnosticsSummary(asRecord3(response.summary));
37336
37512
  if (summaryLine)
37337
37513
  lines.push(summaryLine);
37338
- const detailLines = formatDiagnosticsDetails(asRecord2(response.details));
37514
+ const detailLines = formatDiagnosticsDetails(asRecord3(response.details));
37339
37515
  if (detailLines.length > 0) {
37340
37516
  lines.push("diagnostics details:", ...detailLines.map((line) => `- ${line}`));
37341
37517
  }
@@ -37520,7 +37696,8 @@ function navigationTools(ctx) {
37520
37696
  }
37521
37697
  throw new Error(message);
37522
37698
  }
37523
- return JSON.stringify(response);
37699
+ return formatCallgraphSections(args.op, response).join(`
37700
+ `);
37524
37701
  }
37525
37702
  }
37526
37703
  };
@@ -38568,11 +38745,11 @@ function buildWorkflowHints(opts) {
38568
38745
  }
38569
38746
  if (hasBash && hasBgBash) {
38570
38747
  sections.push([
38571
- `**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`taskId\` immediately. Then do ONE of these:`,
38572
- "1. Keep working on something independent of the result.",
38573
- "2. End your turn a completion reminder with the result arrives automatically.",
38574
- `3. Need to react to a specific output line early? Register \`bash_watch({ taskId, pattern, background: true })\`, then end your turn — it pings you the moment the pattern appears.`,
38575
- `\`${bashStatusName}({ taskId })\` is for inspecting output AFTER the reminder arrives, or one quick look at a live task — never call it repeatedly to wait: the reminder already delivers the result, and each poll wastes a turn. Sync \`bash_watch\` (without \`background: true\`) blocks your turn and locks the user out reserve it for waits of a few seconds (a dev server printing its ready line), never for builds or test suites.`
38748
+ `**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`taskId\` immediately.`,
38749
+ "1. Nothing else useful to do (a build/test/validation whose result is the next thing you need) → sync `bash_watch` to block until it exits (pass a longer timeoutMs for long commands; the user can interrupt).",
38750
+ "2. Useful parallel work available → end your turn; the completion reminder delivers the result. (Or spawn a subagent for the side work.)",
38751
+ "3. Want to react to a specific early output line async `bash_watch` (background:true + pattern).",
38752
+ "Never loop `bash_status` to wait — it's a one-shot inspector, not a wait primitive."
38576
38753
  ].join(`
38577
38754
  `));
38578
38755
  sections.push(`**PTY / interactive commands**: PTY mode is for interactive REPLs and terminal apps (python, node, bash itself, vim). Start with \`${bashName}({ command: "python", pty: true, background: true })\`, read the screen with \`${bashStatusName}({ taskId, outputMode: "screen" })\`, and send input with \`${bashWriteName}({ taskId, input: "..." })\`.`);
@@ -38657,6 +38834,7 @@ async function initializePluginForDirectory(input) {
38657
38834
  const binaryPath = await findBinary(PLUGIN_VERSION);
38658
38835
  await ensureStorageMigrated({ harness: "opencode", binaryPath, logger: bridgeLogger });
38659
38836
  const aftConfig = loadAftConfig(input.directory);
38837
+ enqueueConfigParseWarnings(input.directory, getConfigLoadErrors());
38660
38838
  const autoUpdateAbort = new AbortController;
38661
38839
  const configOverrides = {
38662
38840
  ...resolveProjectOverridesForConfigure(aftConfig),
@@ -38747,6 +38925,7 @@ ${lines}
38747
38925
  projectConfigLoader: (projectRoot) => {
38748
38926
  try {
38749
38927
  const projectConfig = loadAftConfig(projectRoot);
38928
+ enqueueConfigParseWarnings(projectRoot, getConfigLoadErrors());
38750
38929
  return resolveProjectOverridesForConfigure(projectConfig);
38751
38930
  } catch (err) {
38752
38931
  warn2(`loadAftConfig(${projectRoot}) failed; falling back to plugin-init config: ${err instanceof Error ? err.message : String(err)}`);
@@ -38805,8 +38984,7 @@ ${lines}
38805
38984
  ctx,
38806
38985
  directory: sessionDir,
38807
38986
  sessionID: completion.session_id,
38808
- client: input.client,
38809
- serverUrl: input.serverUrl?.toString()
38987
+ client: input.client
38810
38988
  }, completion);
38811
38989
  },
38812
38990
  onBashLongRunning: (reminder, bridge) => {
@@ -38815,8 +38993,7 @@ ${lines}
38815
38993
  ctx,
38816
38994
  directory: sessionDir,
38817
38995
  sessionID: reminder.session_id,
38818
- client: input.client,
38819
- serverUrl: input.serverUrl?.toString()
38996
+ client: input.client
38820
38997
  }, reminder);
38821
38998
  },
38822
38999
  onBashPatternMatch: (frame, bridge) => {
@@ -38825,8 +39002,7 @@ ${lines}
38825
39002
  ctx,
38826
39003
  directory: sessionDir,
38827
39004
  sessionID: frame.session_id,
38828
- client: input.client,
38829
- serverUrl: input.serverUrl?.toString()
39005
+ client: input.client
38830
39006
  }, frame);
38831
39007
  }
38832
39008
  };
@@ -38839,16 +39015,6 @@ ${lines}
38839
39015
  config: aftConfig,
38840
39016
  storageDir: configOverrides.storage_dir
38841
39017
  };
38842
- probeServerReachable(input.serverUrl?.toString()).then((reachable) => {
38843
- setLiveServerWakeAvailable(reachable);
38844
- if (reachable) {
38845
- log2("Live OpenCode HTTP listener reachable; bg-notifications wake path = live-server (anomalyco/opencode#28202 workaround active).");
38846
- } else {
38847
- debug("Live OpenCode HTTP listener unreachable; bg-notifications wake path = in-process-fallback. Wakes will still arrive but the upstream duplicate-runner bug (anomalyco/opencode#28202) is not worked around. Launch with `opencode --port 0` in TUI mode to activate the workaround.");
38848
- }
38849
- }).catch(() => {
38850
- setLiveServerWakeAvailable(false);
38851
- });
38852
39018
  if (onnxRuntimePromise) {
38853
39019
  onnxRuntimePromise.then((ortDylibDir) => {
38854
39020
  if (ortDylibDir) {
@@ -39083,9 +39249,24 @@ Install: ${getManualInstallHint()}`).catch(() => {});
39083
39249
  ctx,
39084
39250
  directory: sessionDir,
39085
39251
  sessionID,
39086
- client: input.client,
39087
- serverUrl: input.serverUrl?.toString()
39252
+ client: input.client
39088
39253
  });
39254
+ const configParseWarnings = drainPendingConfigParseWarnings(sessionDir);
39255
+ if (configParseWarnings.length > 0) {
39256
+ const bridge = pool.getActiveBridgeForRoot(sessionDir) ?? pool.getBridge(sessionDir);
39257
+ enqueueConfigureWarningsForSession({
39258
+ projectRoot: sessionDir,
39259
+ sessionId: sessionID,
39260
+ client: input.client,
39261
+ bridge,
39262
+ warnings: configParseWarnings,
39263
+ fallbackClient: input.client,
39264
+ storageDir: configOverrides.storage_dir,
39265
+ pluginVersion: PLUGIN_VERSION,
39266
+ serverUrl: input.serverUrl?.toString(),
39267
+ delivery: aftConfig.configure_warnings_delivery ?? "toast"
39268
+ });
39269
+ }
39089
39270
  await flushConfigureWarningsOnIdle(sessionID);
39090
39271
  },
39091
39272
  "chat.message": async (messageInput) => {