@cortexkit/aft-opencode 0.39.3 → 0.39.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqNlD;;;;;;;;;;;;;;;;GAgBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;AA63B5E,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA0KlD;;;;;;;;;;;;;;;;GAgBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;AA63B5E,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -12101,6 +12101,21 @@ class BridgeReplacedDuringVersionCheck extends Error {
12101
12101
  }
12102
12102
  }
12103
12103
 
12104
+ class BridgeTransportTimeoutError extends Error {
12105
+ command;
12106
+ timeoutMs;
12107
+ code = "transport_timeout";
12108
+ constructor(command, timeoutMs, message) {
12109
+ super(message);
12110
+ this.command = command;
12111
+ this.timeoutMs = timeoutMs;
12112
+ this.name = "BridgeTransportTimeoutError";
12113
+ }
12114
+ }
12115
+ function isBridgeTransportTimeout(err) {
12116
+ return err instanceof Error && err.code === "transport_timeout";
12117
+ }
12118
+
12104
12119
  class BinaryBridge {
12105
12120
  static RESTART_RESET_MS = 5 * 60 * 1000;
12106
12121
  static STDERR_TAIL_MAX = 20;
@@ -12328,7 +12343,7 @@ class BinaryBridge {
12328
12343
  } else {
12329
12344
  this.warnVia(timeoutMsg2);
12330
12345
  }
12331
- entry.reject(new Error(`${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
12346
+ entry.reject(new BridgeTransportTimeoutError(command, effectiveTimeoutMs, `${this.errorPrefix} Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
12332
12347
  return;
12333
12348
  }
12334
12349
  const childActiveSinceRequest = this.lastChildActivityAt > requestSentAt;
@@ -12898,13 +12913,17 @@ function joinNonEmpty(parts, separator = " · ") {
12898
12913
  function treeLine(depth, text) {
12899
12914
  return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
12900
12915
  }
12916
+ function nameMatchEdgeMarker(record, theme) {
12917
+ return asString(record.resolved_by) === "name_match" ? ` ${theme.fg("warning", "~")}` : "";
12918
+ }
12901
12919
  function renderCallTreeNode(node, depth, lines, theme) {
12902
12920
  const name = asString(node.name) ?? "(unknown)";
12903
12921
  const file = shortenPath(asString(node.file) ?? "(unknown file)");
12904
12922
  const line = asNumber(node.line);
12905
12923
  const unresolved = node.resolved === false ? ` ${theme.fg("warning", "[unresolved]")}` : "";
12924
+ const nameMatch = nameMatchEdgeMarker(node, theme);
12906
12925
  const location = line !== undefined ? `[${file}:${line}]` : `[${file}]`;
12907
- lines.push(treeLine(depth, `${name} ${location}${unresolved}`));
12926
+ lines.push(treeLine(depth, `${name} ${location}${unresolved}${nameMatch}`));
12908
12927
  asRecords(node.children).forEach((child) => {
12909
12928
  renderCallTreeNode(child, depth + 1, lines, theme);
12910
12929
  });
@@ -12917,34 +12936,39 @@ function depthWarning(response, theme, depthField = "depth_limited", truncatedFi
12917
12936
  const detail = truncated > 0 ? `, ${truncated} truncated` : "";
12918
12937
  return theme.fg("warning", `(depth limited${detail})`);
12919
12938
  }
12920
- function renderTracePath(path2, index, lines) {
12939
+ function renderTracePath(path2, index, lines, theme) {
12921
12940
  lines.push(`Path ${index + 1}`);
12922
12941
  asRecords(path2.hops).forEach((hop, hopIndex) => {
12923
12942
  const symbol = asString(hop.symbol) ?? "(unknown)";
12924
12943
  const file = shortenPath(asString(hop.file) ?? "(unknown file)");
12925
12944
  const line = asNumber(hop.line);
12926
12945
  const entry = hop.is_entry_point === true ? " [entry]" : "";
12927
- lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
12946
+ const nameMatch = nameMatchEdgeMarker(hop, theme);
12947
+ lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}${nameMatch}`));
12928
12948
  });
12929
12949
  }
12930
12950
  function renderCallersGroupLines(group, theme) {
12931
12951
  const file = shortenPath(asString(group.file) ?? "(unknown file)");
12932
12952
  const lines = [theme.fg("accent", file)];
12933
12953
  const callers = asRecords(group.callers);
12934
- const bySymbol = new Map;
12954
+ const bySymbolProvenance = new Map;
12935
12955
  for (const caller of callers) {
12936
12956
  const symbol = asString(caller.symbol) ?? "(unknown)";
12957
+ const provenanceKey = asString(caller.resolved_by) === "name_match" ? `${symbol}\x00name_match` : `${symbol}\x00exact`;
12937
12958
  const line = asNumber(caller.line);
12938
- const bucket = bySymbol.get(symbol) ?? [];
12959
+ const bucket = bySymbolProvenance.get(provenanceKey) ?? [];
12939
12960
  if (line !== undefined)
12940
12961
  bucket.push(line);
12941
- bySymbol.set(symbol, bucket);
12962
+ bySymbolProvenance.set(provenanceKey, bucket);
12942
12963
  }
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);
12964
+ const keys = [...bySymbolProvenance.keys()].sort((a, b) => a.localeCompare(b));
12965
+ for (const key of keys) {
12966
+ const symbol = key.split("\x00")[0] ?? "(unknown)";
12967
+ const isNameMatch = key.endsWith("\x00name_match");
12968
+ const lineNums = (bySymbolProvenance.get(key) ?? []).sort((a, b) => a - b);
12946
12969
  const linePart = lineNums.length > 0 ? lineNums.map(String).join(", ") : "?";
12947
- lines.push(` ${symbol}:${linePart}`);
12970
+ const marker = isNameMatch ? ` ${theme.fg("warning", "~")}` : "";
12971
+ lines.push(` ↳ ${symbol}:${linePart}${marker}`);
12948
12972
  }
12949
12973
  return lines;
12950
12974
  }
@@ -12990,7 +13014,8 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
12990
13014
  const symbol = asString(hop.symbol) ?? "(unknown)";
12991
13015
  const file = shortenPath(asString(hop.file) ?? "(unknown file)");
12992
13016
  const line = asNumber(hop.line);
12993
- lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
13017
+ const nameMatch = nameMatchEdgeMarker(hop, theme);
13018
+ lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}${nameMatch}`));
12994
13019
  });
12995
13020
  return lines;
12996
13021
  }
@@ -13010,7 +13035,7 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13010
13035
  sections2.push(theme.fg("muted", "No entry paths found."));
13011
13036
  paths.forEach((path2, index) => {
13012
13037
  const lines = [];
13013
- renderTracePath(path2, index, lines);
13038
+ renderTracePath(path2, index, lines, theme);
13014
13039
  sections2.push(lines.join(`
13015
13040
  `));
13016
13041
  });
@@ -13035,11 +13060,12 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13035
13060
  const symbol = asString(caller.caller_symbol) ?? "(unknown)";
13036
13061
  const line = asNumber(caller.line) ?? 0;
13037
13062
  const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
13063
+ const nameMatch = nameMatchEdgeMarker(caller, theme);
13038
13064
  const expression = asString(caller.call_expression);
13039
13065
  const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
13040
13066
  sections2.push([
13041
13067
  `${theme.fg("accent", file)}:${line}`,
13042
- ` ↳ ${symbol}${entry}`,
13068
+ ` ↳ ${symbol}${entry}${nameMatch}`,
13043
13069
  expression ? ` ${theme.fg("muted", expression)}` : undefined,
13044
13070
  params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
13045
13071
  ].filter(Boolean).join(`
@@ -13062,7 +13088,8 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
13062
13088
  const variable = asString(hop.variable) ?? "(unknown)";
13063
13089
  const line = asNumber(hop.line) ?? 0;
13064
13090
  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}`));
13091
+ const nameMatch = nameMatchEdgeMarker(hop, theme);
13092
+ sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}${nameMatch}`));
13066
13093
  });
13067
13094
  return sections;
13068
13095
  }
@@ -13405,7 +13432,7 @@ function formatEditSummary(data) {
13405
13432
  if (data.created === true) {
13406
13433
  let s2 = `Created file (${counts}).`;
13407
13434
  if (data.formatted)
13408
- s2 += " Auto-formatted.";
13435
+ s2 += formatAutoFormattedSuffix(data);
13409
13436
  return s2;
13410
13437
  }
13411
13438
  let detail = counts;
@@ -13416,9 +13443,21 @@ function formatEditSummary(data) {
13416
13443
  }
13417
13444
  let s = `Edited (${detail}).`;
13418
13445
  if (data.formatted)
13419
- s += " Auto-formatted.";
13446
+ s += formatAutoFormattedSuffix(data);
13420
13447
  return s;
13421
13448
  }
13449
+ function formatAutoFormattedSuffix(data) {
13450
+ const reflowText = data.reformatted?.text;
13451
+ if (typeof reflowText === "string" && reflowText.length > 0) {
13452
+ return `
13453
+ Auto-formatted — the formatter reflowed your edit. On disk now:
13454
+ ${reflowText}`;
13455
+ }
13456
+ if (data.reformatted?.extensive === true) {
13457
+ return " Auto-formatted — extensive reflow; re-read the file before your next anchored edit.";
13458
+ }
13459
+ return " Auto-formatted.";
13460
+ }
13422
13461
  // ../aft-bridge/dist/jsonc.js
13423
13462
  function stripJsoncSymbols(value) {
13424
13463
  if (Array.isArray(value)) {
@@ -33555,6 +33594,34 @@ function normalizeToolMap(tools) {
33555
33594
  }
33556
33595
  return tools;
33557
33596
  }
33597
+ // src/shared/ignored-message.ts
33598
+ async function sendIgnoredMessage2(client, sessionID, text) {
33599
+ const typedClient = client;
33600
+ let agent;
33601
+ try {
33602
+ const ctx = await resolvePromptContext(client, sessionID);
33603
+ agent = ctx?.agent;
33604
+ } catch {
33605
+ agent = undefined;
33606
+ }
33607
+ const body = {
33608
+ noReply: true,
33609
+ parts: [{ type: "text", text, ignored: true }]
33610
+ };
33611
+ if (agent)
33612
+ body.agent = agent;
33613
+ const promptInput = { path: { id: sessionID }, body };
33614
+ if (typeof typedClient.session?.prompt === "function") {
33615
+ await Promise.resolve(typedClient.session.prompt(promptInput));
33616
+ return;
33617
+ }
33618
+ if (typeof typedClient.session?.promptAsync === "function") {
33619
+ await typedClient.session.promptAsync(promptInput);
33620
+ return;
33621
+ }
33622
+ throw new Error("[aft-plugin] client.session.prompt is unavailable");
33623
+ }
33624
+
33558
33625
  // src/shared/pty-cache.ts
33559
33626
  var import_headless = __toESM(require_xterm_headless(), 1);
33560
33627
  import * as fs2 from "node:fs/promises";
@@ -34495,6 +34562,27 @@ async function callBashBridge(ctx, runtime, command, params = {}, options) {
34495
34562
  import * as fs4 from "node:fs";
34496
34563
  import * as path4 from "node:path";
34497
34564
  var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.15.5 or newer for permission asks; please upgrade OpenCode";
34565
+ var RESTRICT_NOTICE_THROTTLE_MS = 5 * 60 * 1000;
34566
+ var restrictNoticeLastSentAt = new Map;
34567
+ function restrictNoticeWording(target) {
34568
+ return `AFT blocked access to a path outside the project:
34569
+ ${target}
34570
+ ` + "`restrict_to_project_root` is enabled (full isolation), so AFT does not access paths " + "outside the project root. To allow external paths, set `restrict_to_project_root: false` " + "in your aft.jsonc.";
34571
+ }
34572
+ function notifyRestrictBlocked(ctx, context, target) {
34573
+ const sessionID = context.sessionID;
34574
+ if (!sessionID)
34575
+ return;
34576
+ const now = Date.now();
34577
+ const last = restrictNoticeLastSentAt.get(sessionID);
34578
+ if (last !== undefined && now - last < RESTRICT_NOTICE_THROTTLE_MS)
34579
+ return;
34580
+ restrictNoticeLastSentAt.set(sessionID, now);
34581
+ sendIgnoredMessage2(ctx.client, sessionID, restrictNoticeWording(target)).catch(() => {});
34582
+ }
34583
+ function restrictDenialMessage(target) {
34584
+ return `Blocked: '${target}' is outside the project root and restrict_to_project_root is enabled ` + "(AFT full isolation). Not overridable per-call; set restrict_to_project_root: false in " + "aft.jsonc to allow external paths.";
34585
+ }
34498
34586
  async function runAsk(maybe) {
34499
34587
  await maybe;
34500
34588
  }
@@ -34574,11 +34662,9 @@ function normalizePathPattern(p) {
34574
34662
  const dir = /^[A-Za-z]:$/.test(match[1]) ? `${match[1]}\\` : match[1];
34575
34663
  return path4.join(normalizePath(dir), match[2]);
34576
34664
  }
34577
- async function assertExternalDirectoryPermission(context, target, options) {
34665
+ async function assertExternalDirectoryPermission(ctx, context, target, options) {
34578
34666
  if (!target)
34579
34667
  return;
34580
- if (typeof context.ask !== "function")
34581
- return UNSUPPORTED_ASK_HOST;
34582
34668
  const resolved = resolveAbsolutePath(context, target);
34583
34669
  const absoluteTarget = normalizePath(resolved);
34584
34670
  const root = projectRootFor(context);
@@ -34590,6 +34676,12 @@ async function assertExternalDirectoryPermission(context, target, options) {
34590
34676
  if (worktree && worktree !== "/" && worktree !== directory && containsPath(worktree, absoluteTarget)) {
34591
34677
  return;
34592
34678
  }
34679
+ if (ctx.config.restrict_to_project_root === true) {
34680
+ notifyRestrictBlocked(ctx, context, absoluteTarget);
34681
+ return restrictDenialMessage(absoluteTarget);
34682
+ }
34683
+ if (typeof context.ask !== "function")
34684
+ return UNSUPPORTED_ASK_HOST;
34593
34685
  const kind = options?.kind ?? "file";
34594
34686
  const parentDir = kind === "directory" ? absoluteTarget : path4.dirname(absoluteTarget);
34595
34687
  const rawGlob = process.platform === "win32" ? normalizePathPattern(path4.join(parentDir, "*")) : path4.join(parentDir, "*").replaceAll("\\", "/");
@@ -34686,12 +34778,12 @@ async function resolveAstPaths(ctx, context, paths) {
34686
34778
  const resolved = await Promise.all(paths.filter((p) => typeof p === "string" && p.length > 0).map((p) => resolvePathArg(ctx, context, p)));
34687
34779
  return resolved.length > 0 ? resolved : undefined;
34688
34780
  }
34689
- async function checkAstPathsPermission(context, paths) {
34781
+ async function checkAstPathsPermission(ctx, context, paths) {
34690
34782
  if (paths === undefined)
34691
34783
  return;
34692
34784
  const uniquePaths = Array.from(new Set(paths));
34693
34785
  for (const p of uniquePaths) {
34694
- const denial = await assertExternalDirectoryPermission(context, p, { kind: "directory" });
34786
+ const denial = await assertExternalDirectoryPermission(ctx, context, p, { kind: "directory" });
34695
34787
  if (denial)
34696
34788
  return denial;
34697
34789
  }
@@ -34725,7 +34817,7 @@ function astTools(ctx) {
34725
34817
  },
34726
34818
  execute: async (args, context) => {
34727
34819
  const paths = await resolveAstPaths(ctx, context, args.paths);
34728
- const externalDenied = await checkAstPathsPermission(context, paths);
34820
+ const externalDenied = await checkAstPathsPermission(ctx, context, paths);
34729
34821
  if (externalDenied)
34730
34822
  return permissionDeniedResponse(externalDenied);
34731
34823
  const params = {
@@ -34823,13 +34915,13 @@ ${hint}`;
34823
34915
  execute: async (args, context) => {
34824
34916
  const isDryRun = args.dryRun === true;
34825
34917
  const paths = await resolveAstPaths(ctx, context, args.paths);
34826
- const externalDenied = await checkAstPathsPermission(context, paths);
34918
+ const externalDenied = await checkAstPathsPermission(ctx, context, paths);
34827
34919
  if (externalDenied)
34828
34920
  return permissionDeniedResponse(externalDenied);
34829
34921
  if (!isDryRun) {
34830
34922
  if (!Array.isArray(args.paths)) {
34831
34923
  const targetPath = await resolvePathArg(ctx, context, ".");
34832
- const denial = await assertExternalDirectoryPermission(context, targetPath, {
34924
+ const denial = await assertExternalDirectoryPermission(ctx, context, targetPath, {
34833
34925
  kind: "directory"
34834
34926
  });
34835
34927
  if (denial)
@@ -35391,7 +35483,7 @@ function conflictTools(ctx) {
35391
35483
  const expanded = expandTilde2(String(args.path));
35392
35484
  const projectRoot = await resolveProjectRoot(ctx, context);
35393
35485
  const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
35394
- const denied = await assertExternalDirectoryPermission(context, resolved, {
35486
+ const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
35395
35487
  kind: "directory"
35396
35488
  });
35397
35489
  if (denied)
@@ -35719,6 +35811,9 @@ var BASH_WAIT_POLL_INTERVAL_MS = 100;
35719
35811
  var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
35720
35812
  var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 30 * 60 * 1000;
35721
35813
  var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
35814
+ function unavailableSnapshot() {
35815
+ return { status: "unknown" };
35816
+ }
35722
35817
  function createBashWatchTool(ctx) {
35723
35818
  return {
35724
35819
  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.",
@@ -35823,6 +35918,9 @@ Waited ${waited.elapsed_ms}ms; matched ${JSON.stringify(waited.match ?? "")} at
35823
35918
  } else if (waited.reason === "timeout") {
35824
35919
  text += `
35825
35920
  Waited ${waited.elapsed_ms}ms; timeout reached without match.`;
35921
+ } else if (waited.reason === "unavailable") {
35922
+ text += `
35923
+ Waited ${waited.elapsed_ms}ms; the bridge stayed busy and status couldn't be read. The task may still be running — check with bash_status({ taskId }).`;
35826
35924
  } else {
35827
35925
  const stat = String(data.status ?? "unknown");
35828
35926
  const e = typeof data.exit_code === "number" ? `, exit ${data.exit_code}` : "";
@@ -35856,9 +35954,31 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
35856
35954
  clearSyncWatchAbort(runtime.sessionID);
35857
35955
  markTaskWaiting(runtime.sessionID, taskId);
35858
35956
  let sawTerminal = false;
35957
+ let lastData;
35859
35958
  try {
35860
35959
  while (true) {
35861
- const data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, bridgeOptions);
35960
+ let data;
35961
+ try {
35962
+ data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, bridgeOptions);
35963
+ } catch (err) {
35964
+ if (!isBridgeTransportTimeout(err))
35965
+ throw err;
35966
+ if (isSyncWatchAborted(runtime.sessionID)) {
35967
+ return withWaited(lastData ?? unavailableSnapshot(), {
35968
+ reason: "user_message",
35969
+ elapsed_ms: Date.now() - startedAt
35970
+ });
35971
+ }
35972
+ if (Date.now() >= deadline) {
35973
+ return withWaited(lastData ?? unavailableSnapshot(), {
35974
+ reason: "unavailable",
35975
+ elapsed_ms: Date.now() - startedAt
35976
+ });
35977
+ }
35978
+ await sleep(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
35979
+ continue;
35980
+ }
35981
+ lastData = data;
35862
35982
  const terminal = isTerminalStatus(data.status);
35863
35983
  if (waitFor) {
35864
35984
  const scan = await readNewTaskOutput(runtime, taskId, data, spillCursor);
@@ -36392,7 +36512,7 @@ function createReadTool(ctx) {
36392
36512
  const projectRoot = await resolveProjectRoot(ctx, context);
36393
36513
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
36394
36514
  {
36395
- const denial = await assertExternalDirectoryPermission(context, filePath);
36515
+ const denial = await assertExternalDirectoryPermission(ctx, context, filePath);
36396
36516
  if (denial)
36397
36517
  return permissionDeniedResponse(denial);
36398
36518
  }
@@ -36503,7 +36623,7 @@ function createWriteTool(ctx, editToolName = "edit") {
36503
36623
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
36504
36624
  const relPath = path5.relative(projectRoot, filePath);
36505
36625
  {
36506
- const denial2 = await assertExternalDirectoryPermission(context, filePath);
36626
+ const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
36507
36627
  if (denial2)
36508
36628
  return permissionDeniedResponse(denial2);
36509
36629
  }
@@ -36654,7 +36774,7 @@ function createEditTool(ctx, writeToolName = "write") {
36654
36774
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
36655
36775
  const relPath = path5.relative(projectRoot, filePath);
36656
36776
  {
36657
- const denial2 = await assertExternalDirectoryPermission(context, filePath);
36777
+ const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
36658
36778
  if (denial2)
36659
36779
  return permissionDeniedResponse(denial2);
36660
36780
  }
@@ -36890,7 +37010,7 @@ function createApplyPatchTool(ctx) {
36890
37010
  if (asked.has(filePath))
36891
37011
  continue;
36892
37012
  asked.add(filePath);
36893
- const denial2 = await assertExternalDirectoryPermission(context, filePath);
37013
+ const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
36894
37014
  if (denial2)
36895
37015
  return permissionDeniedResponse(denial2);
36896
37016
  }
@@ -37201,7 +37321,7 @@ function createDeleteTool(ctx) {
37201
37321
  if (asked.has(filePath))
37202
37322
  continue;
37203
37323
  asked.add(filePath);
37204
- const denial = await assertExternalDirectoryPermission(context, filePath);
37324
+ const denial = await assertExternalDirectoryPermission(ctx, context, filePath);
37205
37325
  if (denial)
37206
37326
  return permissionDeniedResponse(denial);
37207
37327
  }
@@ -37249,13 +37369,13 @@ function createMoveTool(ctx) {
37249
37369
  const filePath = resolvePathFromProjectRoot(projectRoot, args.filePath);
37250
37370
  const destPath = resolvePathFromProjectRoot(projectRoot, args.destination);
37251
37371
  {
37252
- const sourceDenial = await assertExternalDirectoryPermission(context, filePath, {
37372
+ const sourceDenial = await assertExternalDirectoryPermission(ctx, context, filePath, {
37253
37373
  kind: "file"
37254
37374
  });
37255
37375
  if (sourceDenial)
37256
37376
  return permissionDeniedResponse(sourceDenial);
37257
37377
  if (destPath !== filePath) {
37258
- const destDenial = await assertExternalDirectoryPermission(context, destPath);
37378
+ const destDenial = await assertExternalDirectoryPermission(ctx, context, destPath);
37259
37379
  if (destDenial)
37260
37380
  return permissionDeniedResponse(destDenial);
37261
37381
  }
@@ -37317,7 +37437,7 @@ function aftPrefixedTools(ctx) {
37317
37437
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
37318
37438
  const relPath = path5.relative(projectRoot, filePath);
37319
37439
  {
37320
- const denial2 = await assertExternalDirectoryPermission(context, filePath);
37440
+ const denial2 = await assertExternalDirectoryPermission(ctx, context, filePath);
37321
37441
  if (denial2)
37322
37442
  return permissionDeniedResponse(denial2);
37323
37443
  }
@@ -37394,7 +37514,7 @@ function importTools(ctx) {
37394
37514
  }
37395
37515
  const filePath = await resolvePathArg(ctx, context, args.filePath);
37396
37516
  {
37397
- const denial = await assertExternalDirectoryPermission(context, filePath);
37517
+ const denial = await assertExternalDirectoryPermission(ctx, context, filePath);
37398
37518
  if (denial)
37399
37519
  return permissionDeniedResponse(denial);
37400
37520
  }
@@ -37544,7 +37664,7 @@ async function resolveAndGateScope(ctx, context, scope) {
37544
37664
  if (checked.has(target))
37545
37665
  continue;
37546
37666
  checked.add(target);
37547
- const denial = await assertExternalDirectoryPermission(context, target);
37667
+ const denial = await assertExternalDirectoryPermission(ctx, context, target);
37548
37668
  if (denial)
37549
37669
  return { scope: undefined, denial };
37550
37670
  }
@@ -37641,6 +37761,7 @@ function navigationTools(ctx) {
37641
37761
 
37642
37762
  ` + `All ops require both 'filePath' and 'symbol'. 'expression' is additionally required for trace_data; 'toSymbol' for trace_to_symbol.
37643
37763
 
37764
+ ` + `Markers: ~ = edge resolved by name only (may point at the wrong same-named symbol); [unresolved] = callee not resolved to a definition, so the location shown is the call site. Unmarked edges are resolved exactly.
37644
37765
  `,
37645
37766
  args: {
37646
37767
  op: z11.enum(["call_tree", "callers", "trace_to", "trace_to_symbol", "impact", "trace_data"]).describe("Navigation operation"),
@@ -37671,7 +37792,7 @@ function navigationTools(ctx) {
37671
37792
  if (checked.has(target))
37672
37793
  continue;
37673
37794
  checked.add(target);
37674
- const denial = await assertExternalDirectoryPermission(context, target);
37795
+ const denial = await assertExternalDirectoryPermission(ctx, context, target);
37675
37796
  if (denial)
37676
37797
  return permissionDeniedResponse(denial);
37677
37798
  }
@@ -37748,7 +37869,7 @@ function readingTools(ctx) {
37748
37869
  throw new Error("'target' must be a non-empty string or array of strings");
37749
37870
  }
37750
37871
  const resolvedTargets = await Promise.all(target.map((entry) => resolvePathArg(ctx, context, entry)));
37751
- const permissionDenied3 = await assertPathExternalPermissions(context, resolvedTargets, "directory");
37872
+ const permissionDenied3 = await assertPathExternalPermissions(ctx, context, resolvedTargets, "directory");
37752
37873
  if (permissionDenied3)
37753
37874
  return permissionDeniedResponse(permissionDenied3);
37754
37875
  const response3 = await callBridge(ctx, context, "outline", {
@@ -37770,7 +37891,7 @@ function readingTools(ctx) {
37770
37891
  const st = await stat(resolvedPath);
37771
37892
  isDirectory2 = st.isDirectory();
37772
37893
  } catch {}
37773
- const permissionDenied2 = await assertPathExternalPermissions(context, resolvedPath, isDirectory2 ? "directory" : "file");
37894
+ const permissionDenied2 = await assertPathExternalPermissions(ctx, context, resolvedPath, isDirectory2 ? "directory" : "file");
37774
37895
  if (permissionDenied2)
37775
37896
  return permissionDeniedResponse(permissionDenied2);
37776
37897
  const params = isDirectory2 ? { directory: resolvedPath, files: true } : { file: resolvedPath, files: true };
@@ -37789,7 +37910,7 @@ function readingTools(ctx) {
37789
37910
  }
37790
37911
  if (isArray) {
37791
37912
  const resolvedTargets = await Promise.all(target.map((entry) => resolvePathArg(ctx, context, entry)));
37792
- const permissionDenied2 = await assertPathExternalPermissions(context, resolvedTargets);
37913
+ const permissionDenied2 = await assertPathExternalPermissions(ctx, context, resolvedTargets);
37793
37914
  if (permissionDenied2)
37794
37915
  return permissionDeniedResponse(permissionDenied2);
37795
37916
  const response2 = await callBridge(ctx, context, "outline", {
@@ -37810,7 +37931,7 @@ function readingTools(ctx) {
37810
37931
  const st = await stat(resolvedTarget);
37811
37932
  isDirectory = st.isDirectory();
37812
37933
  } catch {}
37813
- const permissionDenied = await assertPathExternalPermissions(context, resolvedTarget, isDirectory ? "directory" : "file");
37934
+ const permissionDenied = await assertPathExternalPermissions(ctx, context, resolvedTarget, isDirectory ? "directory" : "file");
37814
37935
  if (permissionDenied)
37815
37936
  return permissionDeniedResponse(permissionDenied);
37816
37937
  if (isDirectory) {
@@ -37905,7 +38026,7 @@ function readingTools(ctx) {
37905
38026
  }
37906
38027
  }
37907
38028
  const resolvedTargets = await Promise.all(targets.map((t) => resolvePathArg(ctx, context, t.filePath)));
37908
- const permissionDenied = await assertPathExternalPermissions(context, resolvedTargets);
38029
+ const permissionDenied = await assertPathExternalPermissions(ctx, context, resolvedTargets);
37909
38030
  if (permissionDenied)
37910
38031
  return permissionDeniedResponse(permissionDenied);
37911
38032
  const responses = await Promise.all(targets.map((t, index) => {
@@ -37937,7 +38058,7 @@ function readingTools(ctx) {
37937
38058
  }
37938
38059
  const file2 = hasUrl ? args.url : await resolvePathArg(ctx, context, args.filePath);
37939
38060
  if (!hasUrl) {
37940
- const permissionDenied = await assertPathExternalPermissions(context, file2);
38061
+ const permissionDenied = await assertPathExternalPermissions(ctx, context, file2);
37941
38062
  if (permissionDenied)
37942
38063
  return permissionDeniedResponse(permissionDenied);
37943
38064
  }
@@ -38009,7 +38130,7 @@ function formatZoomBatchResult(targetLabel, symbols, responses) {
38009
38130
  `) };
38010
38131
  }
38011
38132
  var MAX_UNCHECKED_FILES_IN_FOOTER = 10;
38012
- async function assertPathExternalPermissions(context, target, kind = "file") {
38133
+ async function assertPathExternalPermissions(ctx, context, target, kind = "file") {
38013
38134
  const targets = Array.isArray(target) ? target : [target];
38014
38135
  const checked = new Set;
38015
38136
  for (const resolvedPath of targets) {
@@ -38019,7 +38140,7 @@ async function assertPathExternalPermissions(context, target, kind = "file") {
38019
38140
  if (checked.has(key))
38020
38141
  continue;
38021
38142
  checked.add(key);
38022
- const denial = await assertExternalDirectoryPermission(context, resolvedPath, { kind });
38143
+ const denial = await assertExternalDirectoryPermission(ctx, context, resolvedPath, { kind });
38023
38144
  if (denial)
38024
38145
  return denial;
38025
38146
  }
@@ -38185,7 +38306,7 @@ function refactoringTools(ctx) {
38185
38306
  if (asked.has(affectedPath))
38186
38307
  continue;
38187
38308
  asked.add(affectedPath);
38188
- const denial = await assertExternalDirectoryPermission(context, affectedPath);
38309
+ const denial = await assertExternalDirectoryPermission(ctx, context, affectedPath);
38189
38310
  if (denial)
38190
38311
  return permissionDeniedResponse(denial);
38191
38312
  }
@@ -38293,7 +38414,7 @@ function safetyTools(ctx) {
38293
38414
  }
38294
38415
  const previewPaths = Array.from(new Set(responsePaths(preview2)));
38295
38416
  for (const filePath2 of previewPaths) {
38296
- const denial = await assertExternalDirectoryPermission(context, filePath2);
38417
+ const denial = await assertExternalDirectoryPermission(ctx, context, filePath2);
38297
38418
  if (denial)
38298
38419
  return permissionDeniedResponse(denial);
38299
38420
  }
@@ -38317,7 +38438,7 @@ function safetyTools(ctx) {
38317
38438
  if (uniqueParents.has(parent))
38318
38439
  continue;
38319
38440
  uniqueParents.add(parent);
38320
- const denial = await assertExternalDirectoryPermission(context, abs, {
38441
+ const denial = await assertExternalDirectoryPermission(ctx, context, abs, {
38321
38442
  kind: "file"
38322
38443
  });
38323
38444
  if (denial)
@@ -38331,7 +38452,7 @@ function safetyTools(ctx) {
38331
38452
  throw new Error(bridgeErrorMessage(preview2, "checkpoint path preview failed"));
38332
38453
  }
38333
38454
  for (const filePath of new Set(responsePaths(preview2))) {
38334
- const denial = await assertExternalDirectoryPermission(context, filePath);
38455
+ const denial = await assertExternalDirectoryPermission(ctx, context, filePath);
38335
38456
  if (denial)
38336
38457
  return permissionDeniedResponse(denial);
38337
38458
  }
@@ -38526,7 +38647,7 @@ function searchTools(ctx) {
38526
38647
  return permissionDeniedResponse(grepDenied);
38527
38648
  if (pathSplit) {
38528
38649
  for (const target of searchPathTargets(projectRoot, pathSplit, "file")) {
38529
- const externalDenied = await assertExternalDirectoryPermission(context, target.target, {
38650
+ const externalDenied = await assertExternalDirectoryPermission(ctx, context, target.target, {
38530
38651
  kind: target.kind
38531
38652
  });
38532
38653
  if (externalDenied)
@@ -38579,7 +38700,7 @@ function searchTools(ctx) {
38579
38700
  return permissionDeniedResponse(globDenied);
38580
38701
  if (pathSplit) {
38581
38702
  for (const target of searchPathTargets(projectRoot, pathSplit, "directory")) {
38582
- const externalDenied = await assertExternalDirectoryPermission(context, target.target, {
38703
+ const externalDenied = await assertExternalDirectoryPermission(ctx, context, target.target, {
38583
38704
  kind: target.kind
38584
38705
  });
38585
38706
  if (externalDenied)
@@ -38787,32 +38908,6 @@ function isTuiMode2() {
38787
38908
  function throwSentinel(command) {
38788
38909
  throw new Error(`${SENTINEL_PREFIX}${command.toUpperCase().replace(/-/g, "_")}_HANDLED__`);
38789
38910
  }
38790
- async function sendIgnoredMessage2(client, sessionID, text) {
38791
- const typedClient = client;
38792
- let agent;
38793
- try {
38794
- const ctx = await resolvePromptContext(client, sessionID);
38795
- agent = ctx?.agent;
38796
- } catch {
38797
- agent = undefined;
38798
- }
38799
- const body = {
38800
- noReply: true,
38801
- parts: [{ type: "text", text, ignored: true }]
38802
- };
38803
- if (agent)
38804
- body.agent = agent;
38805
- const promptInput = { path: { id: sessionID }, body };
38806
- if (typeof typedClient.session?.prompt === "function") {
38807
- await Promise.resolve(typedClient.session.prompt(promptInput));
38808
- return;
38809
- }
38810
- if (typeof typedClient.session?.promptAsync === "function") {
38811
- await typedClient.session.promptAsync(promptInput);
38812
- return;
38813
- }
38814
- throw new Error("[aft-plugin] client.session.prompt is unavailable");
38815
- }
38816
38911
  var PLUGIN_VERSION = (() => {
38817
38912
  try {
38818
38913
  const req = createRequire3(import.meta.url);
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Append an `ignored: true` synthetic user message to a session.
3
+ *
4
+ * Used for user-facing informational panels that must NOT trigger an agent
5
+ * turn (e.g. status output, the external-directory restriction notice). The
6
+ * message renders under the current agent (resolved from recent messages) so
7
+ * it shows in the right place in the OpenCode UI, and carries `noReply: true`
8
+ * so no LLM call is made.
9
+ *
10
+ * IMPORTANT (cache + crash safety): this path deliberately passes ONLY `agent`
11
+ * (never model/variant). OpenCode crashes if model/variant are supplied on a
12
+ * `noReply: true` prompt, and omitting them keeps the synthetic message from
13
+ * busting the provider prefix cache the previous assistant turn warmed.
14
+ *
15
+ * Lives in `shared/` (not `index.ts`) because `index.ts` must export only the
16
+ * plugin default; both the plugin entry and `tools/permissions.ts` call this.
17
+ */
18
+ export declare function sendIgnoredMessage(client: unknown, sessionID: string, text: string): Promise<void>;
19
+ //# sourceMappingURL=ignored-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignored-message.d.ts","sourceRoot":"","sources":["../../src/shared/ignored-message.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAqCf"}
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/tools/ast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmFjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA8S3E"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/tools/ast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoFjD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA8S3E"}
@@ -8,7 +8,7 @@ export type BashWaitPattern = {
8
8
  source: string;
9
9
  };
10
10
  export type BashStatusWaited = {
11
- reason: "matched" | "exited" | "timeout" | "user_message";
11
+ reason: "matched" | "exited" | "timeout" | "user_message" | "unavailable";
12
12
  elapsed_ms: number;
13
13
  match?: string;
14
14
  match_offset?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"bash_watch.d.ts","sourceRoot":"","sources":["../../src/tools/bash_watch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAcvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASjD,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACtC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,cAAc,CAAC;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AACF,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAGlF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwGtE;AA8GD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,eAAe,GAAG,SAAS,EACpC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAgF7B;AA6DD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAI5E;AAkGD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAEtC"}
1
+ {"version":3,"file":"bash_watch.d.ts","sourceRoot":"","sources":["../../src/tools/bash_watch.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAcvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASjD,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACtC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,cAAc,GAAG,aAAa,CAAC;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAUF,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAGlF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwGtE;AAgHD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,eAAe,GAAG,SAAS,EACpC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,kBAAkB,CAAC,CA0G7B;AA6DD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAI5E;AAkGD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAEtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkGlF"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmGlF"}
@@ -1,4 +1,5 @@
1
1
  import type { ToolContext } from "@opencode-ai/plugin";
2
+ import type { PluginContext } from "../types.js";
2
3
  /**
3
4
  * Execute a `ctx.ask(...)` result.
4
5
  *
@@ -61,7 +62,7 @@ export declare const _permissionsInternalsForTest: {
61
62
  * external-directory rule is `allow` (e.g. for `${os.tmpdir()}/opencode/*`), the
62
63
  * call short-circuits and the regular permission flow continues normally.
63
64
  */
64
- export declare function assertExternalDirectoryPermission(context: ToolContext, target: string, options?: {
65
+ export declare function assertExternalDirectoryPermission(ctx: PluginContext, context: ToolContext, target: string, options?: {
65
66
  kind?: "file" | "directory";
66
67
  }): Promise<string | undefined>;
67
68
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMvD;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,MAAM,CAER;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAazF;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAE9D;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AAED;;;GAGG;AACH,iBAAS,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI5D;AA6CD;;;;;;;;;;;;GAYG;AACH,iBAAS,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO/C;AAED,eAAO,MAAM,4BAA4B;;;CAAyC,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,iCAAiC,CACrD,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAsD7B;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOhE"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoDjD;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,MAAM,GACnB,MAAM,CAER;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAazF;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAE9D;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AAED;;;GAGG;AACH,iBAAS,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI5D;AA6CD;;;;;;;;;;;;GAYG;AACH,iBAAS,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO/C;AAED,eAAO,MAAM,4BAA4B;;;CAAyC,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,iCAAiC,CACrD,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmE7B;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOhE"}
@@ -1 +1 @@
1
- {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAe,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAEnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoCjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA8X/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB;AA+CD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2ChF"}
1
+ {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAe,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAEnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoCjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAyY/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB;AAgDD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2ChF"}
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAuJjD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4J9E"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAuJjD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAsK9E"}
package/dist/tui.js CHANGED
@@ -7790,7 +7790,7 @@ var require_src2 = __commonJS((exports, module) => {
7790
7790
  // src/tui/index.tsx
7791
7791
  import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
7792
7792
  // package.json
7793
- var version = "0.39.3";
7793
+ var version = "0.39.4";
7794
7794
 
7795
7795
  // src/shared/rpc-client.ts
7796
7796
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.39.3",
3
+ "version": "0.39.4",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
6
6
  "main": "dist/index.js",
@@ -30,19 +30,19 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.2.0",
33
- "@cortexkit/aft-bridge": "0.39.3",
33
+ "@cortexkit/aft-bridge": "0.39.4",
34
34
  "@xterm/headless": "^5.5.0",
35
35
  "comment-json": "^4.6.2",
36
36
  "undici": "^7.25.0",
37
37
  "zod": "^4.1.8"
38
38
  },
39
39
  "optionalDependencies": {
40
- "@cortexkit/aft-darwin-arm64": "0.39.3",
41
- "@cortexkit/aft-darwin-x64": "0.39.3",
42
- "@cortexkit/aft-linux-arm64": "0.39.3",
43
- "@cortexkit/aft-linux-x64": "0.39.3",
44
- "@cortexkit/aft-win32-arm64": "0.39.3",
45
- "@cortexkit/aft-win32-x64": "0.39.3"
40
+ "@cortexkit/aft-darwin-arm64": "0.39.4",
41
+ "@cortexkit/aft-darwin-x64": "0.39.4",
42
+ "@cortexkit/aft-linux-arm64": "0.39.4",
43
+ "@cortexkit/aft-linux-x64": "0.39.4",
44
+ "@cortexkit/aft-win32-arm64": "0.39.4",
45
+ "@cortexkit/aft-win32-x64": "0.39.4"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opencode-ai/plugin": "^1.17.7",
@@ -0,0 +1,61 @@
1
+ import { resolvePromptContext } from "./last-assistant-model.js";
2
+
3
+ /**
4
+ * Append an `ignored: true` synthetic user message to a session.
5
+ *
6
+ * Used for user-facing informational panels that must NOT trigger an agent
7
+ * turn (e.g. status output, the external-directory restriction notice). The
8
+ * message renders under the current agent (resolved from recent messages) so
9
+ * it shows in the right place in the OpenCode UI, and carries `noReply: true`
10
+ * so no LLM call is made.
11
+ *
12
+ * IMPORTANT (cache + crash safety): this path deliberately passes ONLY `agent`
13
+ * (never model/variant). OpenCode crashes if model/variant are supplied on a
14
+ * `noReply: true` prompt, and omitting them keeps the synthetic message from
15
+ * busting the provider prefix cache the previous assistant turn warmed.
16
+ *
17
+ * Lives in `shared/` (not `index.ts`) because `index.ts` must export only the
18
+ * plugin default; both the plugin entry and `tools/permissions.ts` call this.
19
+ */
20
+ export async function sendIgnoredMessage(
21
+ client: unknown,
22
+ sessionID: string,
23
+ text: string,
24
+ ): Promise<void> {
25
+ const typedClient = client as {
26
+ session?: {
27
+ prompt?: (input: unknown) => unknown;
28
+ promptAsync?: (input: unknown) => unknown;
29
+ };
30
+ };
31
+
32
+ let agent: string | undefined;
33
+ try {
34
+ const ctx = await resolvePromptContext(
35
+ client as Parameters<typeof resolvePromptContext>[0],
36
+ sessionID,
37
+ );
38
+ agent = ctx?.agent;
39
+ } catch {
40
+ agent = undefined;
41
+ }
42
+
43
+ const body: Record<string, unknown> = {
44
+ noReply: true,
45
+ parts: [{ type: "text", text, ignored: true }],
46
+ };
47
+ if (agent) body.agent = agent;
48
+ const promptInput = { path: { id: sessionID }, body };
49
+
50
+ if (typeof typedClient.session?.prompt === "function") {
51
+ await Promise.resolve(typedClient.session.prompt(promptInput));
52
+ return;
53
+ }
54
+
55
+ if (typeof typedClient.session?.promptAsync === "function") {
56
+ await typedClient.session.promptAsync(promptInput);
57
+ return;
58
+ }
59
+
60
+ throw new Error("[aft-plugin] client.session.prompt is unavailable");
61
+ }