@cortexkit/aft-opencode 0.45.1 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11442,11 +11442,11 @@ class BridgePool {
11442
11442
  function normalizeKey(projectRoot) {
11443
11443
  return canonicalizeProjectRoot(projectRoot);
11444
11444
  }
11445
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/client.ts
11445
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/client.js
11446
11446
  import { promises as fs2 } from "node:fs";
11447
11447
  import { debuglog } from "node:util";
11448
11448
 
11449
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/auth.ts
11449
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/auth.js
11450
11450
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
11451
11451
  var NONCE_LEN = 32;
11452
11452
  var MAX_AUTH_MESSAGE_LEN = 4096;
@@ -11510,7 +11510,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
11510
11510
  await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
11511
11511
  }
11512
11512
 
11513
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/connection-file.ts
11513
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/connection-file.js
11514
11514
  import { promises as fs } from "node:fs";
11515
11515
  var SCHEMA_VERSION = 1;
11516
11516
  var MIN_KEY_LEN = 32;
@@ -11579,13 +11579,13 @@ async function readConnectionFile(path2) {
11579
11579
  return info;
11580
11580
  }
11581
11581
 
11582
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/envelope.ts
11582
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/envelope.js
11583
11583
  var PROTOCOL_VERSION = 1;
11584
11584
  var HEADER_LEN = 17;
11585
11585
  var FROZEN_PREFIX_LEN = 5;
11586
11586
  var MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
11587
11587
  var FrameType;
11588
- ((FrameType2) => {
11588
+ (function(FrameType2) {
11589
11589
  FrameType2[FrameType2["Request"] = 0] = "Request";
11590
11590
  FrameType2[FrameType2["Response"] = 1] = "Response";
11591
11591
  FrameType2[FrameType2["Push"] = 2] = "Push";
@@ -11598,11 +11598,17 @@ var FrameType;
11598
11598
  FrameType2[FrameType2["Hello"] = 9] = "Hello";
11599
11599
  FrameType2[FrameType2["HelloAck"] = 10] = "HelloAck";
11600
11600
  FrameType2[FrameType2["Goodbye"] = 11] = "Goodbye";
11601
- })(FrameType ||= {});
11602
- var FRAME_TYPE_MAX = 11 /* Goodbye */;
11601
+ })(FrameType || (FrameType = {}));
11602
+ var FRAME_TYPE_MAX = FrameType.Goodbye;
11603
11603
  function isPureHeader(ty) {
11604
- return ty === 6 /* Cancel */ || ty === 7 /* Ping */ || ty === 8 /* Pong */ || ty === 11 /* Goodbye */;
11605
- }
11604
+ return ty === FrameType.Cancel || ty === FrameType.Ping || ty === FrameType.Pong || ty === FrameType.Goodbye;
11605
+ }
11606
+ var Priority;
11607
+ (function(Priority2) {
11608
+ Priority2[Priority2["Passive"] = 0] = "Passive";
11609
+ Priority2[Priority2["Interactive"] = 1] = "Interactive";
11610
+ Priority2[Priority2["Background"] = 2] = "Background";
11611
+ })(Priority || (Priority = {}));
11606
11612
  var FLAG_BINARY = 1;
11607
11613
  var FLAG_PRIORITY_MASK = 6;
11608
11614
  var FLAG_PRIORITY_SHIFT = 1;
@@ -11686,7 +11692,7 @@ function encodeFrame(frame) {
11686
11692
  return out;
11687
11693
  }
11688
11694
 
11689
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/socket.ts
11695
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/socket.js
11690
11696
  import net from "node:net";
11691
11697
 
11692
11698
  class SocketClosedError extends Error {
@@ -11878,14 +11884,14 @@ class SubcSocket {
11878
11884
  }
11879
11885
  }
11880
11886
 
11881
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/client.ts
11887
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/client.js
11882
11888
  var debug = debuglog("subc-client");
11883
11889
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
11884
11890
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
11885
11891
  var TIMEOUT_ARBITRATION_GRACE_MS = 50;
11886
11892
  var REQUEST_DEADLINE_MARKER = "request_deadline";
11887
11893
  var DEADLINE_NO_DROP_CODE = "deadline_exceeded_no_drop_observed";
11888
- var ROUTE_OPEN_RETRY_DEADLINE_MS = 1e4;
11894
+ var ROUTE_OPEN_RETRY_DEADLINE_MS = 30000;
11889
11895
  var BODY_READ_TIMEOUT_MS = 30000;
11890
11896
  var EMPTY_BODY = new Uint8Array(0);
11891
11897
  var DEFAULT_MANAGED_TARGET_KIND = "management_surface";
@@ -11967,12 +11973,13 @@ class SubcClient {
11967
11973
  }
11968
11974
  async request(routeChannel, body, opts = {}) {
11969
11975
  const bytes = body instanceof Uint8Array ? body : this.encode(body);
11970
- const priority = opts.priority ?? 1 /* Interactive */;
11976
+ const priority = opts.priority ?? Priority.Interactive;
11971
11977
  const reply = await this.send(routeChannel, bytes, priority, opts.timeoutMs, opts.onProgress);
11972
11978
  return this.parseJson(reply);
11973
11979
  }
11974
11980
  async call(moduleId, method, params, opts = {}) {
11975
11981
  const body = params === undefined ? { method } : { method, params };
11982
+ let retriedUnknownChannel = false;
11976
11983
  for (;; ) {
11977
11984
  const routeChannel = await this.cachedRouteChannel(moduleId, opts);
11978
11985
  try {
@@ -11980,6 +11987,11 @@ class SubcClient {
11980
11987
  } catch (err) {
11981
11988
  if (!(err instanceof SubcCallError))
11982
11989
  throw this.terminalCallError("managed call failed", err);
11990
+ if (err.code === "unknown_channel" && !retriedUnknownChannel && !this.closeStarted) {
11991
+ retriedUnknownChannel = true;
11992
+ this.evictRouteChannel(routeChannel);
11993
+ continue;
11994
+ }
11983
11995
  if (err.kind === "not_sent") {
11984
11996
  try {
11985
11997
  await this.reconnectAfterDrop(err);
@@ -11997,7 +12009,7 @@ class SubcClient {
11997
12009
  }
11998
12010
  subscribe(routeChannel, body, onEvent, opts = {}) {
11999
12011
  const bytes = body instanceof Uint8Array ? body : this.encode(body);
12000
- const priority = opts.priority ?? 1 /* Interactive */;
12012
+ const priority = opts.priority ?? Priority.Interactive;
12001
12013
  const corr = this.nextCorr++;
12002
12014
  const key = `${routeChannel}:${corr}`;
12003
12015
  const closed = new Promise((resolve7, reject) => {
@@ -12013,7 +12025,7 @@ class SubcClient {
12013
12025
  timer: null,
12014
12026
  subscription: true
12015
12027
  });
12016
- const frame = buildFrame(0 /* Request */, buildFlags(false, priority, false), routeChannel, corr, bytes);
12028
+ const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false), routeChannel, corr, bytes);
12017
12029
  this.sock.write(encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch((err) => {
12018
12030
  const p = this.pending.get(key);
12019
12031
  if (p)
@@ -12025,7 +12037,7 @@ class SubcClient {
12025
12037
  if (cancelled)
12026
12038
  return;
12027
12039
  cancelled = true;
12028
- const cancel = buildFrame(6 /* Cancel */, buildFlags(false, priority, false), routeChannel, corr, EMPTY_BODY);
12040
+ const cancel = buildFrame(FrameType.Cancel, buildFlags(false, priority, false), routeChannel, corr, EMPTY_BODY);
12029
12041
  this.sock.write(encodeFrame(cancel), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {});
12030
12042
  };
12031
12043
  return { unsubscribe, closed };
@@ -12076,7 +12088,7 @@ class SubcClient {
12076
12088
  sendRouteGoodbye(channel) {
12077
12089
  if (this.closedErr)
12078
12090
  return;
12079
- const goodbye = buildFrame(11 /* Goodbye */, buildFlags(false, 1 /* Interactive */, false), channel, 0n, EMPTY_BODY);
12091
+ const goodbye = buildFrame(FrameType.Goodbye, buildFlags(false, Priority.Interactive, false), channel, 0n, EMPTY_BODY);
12080
12092
  this.sock.write(encodeFrame(goodbye), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {});
12081
12093
  }
12082
12094
  static async openConnection(opts) {
@@ -12093,14 +12105,14 @@ class SubcClient {
12093
12105
  return { sock, conn };
12094
12106
  }
12095
12107
  async controlRpc(body) {
12096
- return this.send(0, body, 1 /* Interactive */, undefined, undefined);
12108
+ return this.send(0, body, Priority.Interactive, undefined, undefined);
12097
12109
  }
12098
12110
  send(channel, body, priority, timeoutMs, onProgress) {
12099
12111
  if (this.closedErr)
12100
12112
  return Promise.reject(this.closedErr);
12101
12113
  const corr = this.nextCorr++;
12102
12114
  const key = `${channel}:${corr}`;
12103
- const frame = buildFrame(0 /* Request */, buildFlags(false, priority, false), channel, corr, body);
12115
+ const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false), channel, corr, body);
12104
12116
  return new Promise((resolve7, reject) => {
12105
12117
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
12106
12118
  const pending = {
@@ -12140,7 +12152,7 @@ class SubcClient {
12140
12152
  }
12141
12153
  async managedRequest(routeChannel, body, opts) {
12142
12154
  const bytes = body instanceof Uint8Array ? body : this.encode(body);
12143
- const priority = opts.priority ?? 1 /* Interactive */;
12155
+ const priority = opts.priority ?? Priority.Interactive;
12144
12156
  try {
12145
12157
  const reply = await this.sendManaged(routeChannel, bytes, priority, opts.timeoutMs, opts.onProgress);
12146
12158
  return this.parseJson(reply);
@@ -12156,7 +12168,7 @@ class SubcClient {
12156
12168
  }
12157
12169
  const corr = this.nextCorr++;
12158
12170
  const key = `${channel}:${corr}`;
12159
- const frame = buildFrame(0 /* Request */, buildFlags(false, priority, false), channel, corr, body);
12171
+ const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false), channel, corr, body);
12160
12172
  let handedToSocket = false;
12161
12173
  const classifyFailure = (err) => {
12162
12174
  if (!handedToSocket) {
@@ -12315,6 +12327,9 @@ class SubcClient {
12315
12327
  return;
12316
12328
  } catch (err) {
12317
12329
  if (!isConsumerReconnectTransient(err) || attempt >= this.opts.reconnectBackoff.maxAttempts) {
12330
+ if (err instanceof AuthError && attempt > 1) {
12331
+ throw new AuthError(`reconnect gave up after ${attempt} attempts: ${err.message} — the connection file and the daemon's key disagree persistently ` + `(daemon restarting in a loop, split connection-file paths, or a genuinely foreign daemon on this port); ` + `check the daemon, then restart this host app`);
12332
+ }
12318
12333
  throw err;
12319
12334
  }
12320
12335
  await this.opts.sleep(delay);
@@ -12383,26 +12398,27 @@ class SubcClient {
12383
12398
  const pending = this.pending.get(key);
12384
12399
  if (pending) {
12385
12400
  switch (frame.header.ty) {
12386
- case 2 /* Push */:
12387
- case 3 /* StreamData */:
12401
+ case FrameType.Push:
12402
+ case FrameType.StreamData:
12388
12403
  pending.onProgress?.(frame.body);
12389
12404
  return;
12390
- case 1 /* Response */:
12391
- case 4 /* StreamEnd */:
12405
+ case FrameType.Response:
12406
+ case FrameType.StreamEnd:
12392
12407
  this.settle(key, pending, () => pending.resolve(frame));
12393
12408
  return;
12394
- case 5 /* Error */:
12409
+ case FrameType.Error:
12395
12410
  this.settle(key, pending, () => pending.reject(this.errorFromFrame(frame)));
12396
12411
  return;
12397
12412
  default:
12398
12413
  return;
12399
12414
  }
12400
12415
  }
12401
- if (frame.header.ty === 11 /* Goodbye */) {
12416
+ if (frame.header.ty === FrameType.Goodbye) {
12402
12417
  this.failChannel(frame.header.channel, new SubcError("route closed by subc (GOODBYE)"));
12418
+ this.evictRouteChannel(frame.header.channel);
12403
12419
  return;
12404
12420
  }
12405
- if (frame.header.ty === 1 /* Response */ || frame.header.ty === 5 /* Error */ || frame.header.ty === 4 /* StreamEnd */) {
12421
+ if (frame.header.ty === FrameType.Response || frame.header.ty === FrameType.Error || frame.header.ty === FrameType.StreamEnd) {
12406
12422
  debug("dropped terminal frame with no waiter: type=%d channel=%d corr=%s port=%s", frame.header.ty, frame.header.channel, frame.header.corr, this.sock.localPort() ?? "?");
12407
12423
  return;
12408
12424
  }
@@ -12428,6 +12444,13 @@ class SubcClient {
12428
12444
  return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
12429
12445
  }
12430
12446
  }
12447
+ evictRouteChannel(channel) {
12448
+ for (const cached of this.routes.values()) {
12449
+ if (cached.channel === channel && cached.generation === this.generation) {
12450
+ cached.channel = null;
12451
+ }
12452
+ }
12453
+ }
12431
12454
  failChannel(channel, err) {
12432
12455
  for (const [key, pending] of this.pending) {
12433
12456
  if (pending.channel === channel) {
@@ -12474,7 +12497,9 @@ function isConsumerReconnectTransient(err) {
12474
12497
  return true;
12475
12498
  if (err instanceof SubcCallError)
12476
12499
  return err.kind === "not_sent" || err.kind === "outcome_unknown";
12477
- if (err instanceof SubcError || err instanceof ConnectionFileError || err instanceof AuthError)
12500
+ if (err instanceof AuthError)
12501
+ return true;
12502
+ if (err instanceof SubcError || err instanceof ConnectionFileError)
12478
12503
  return false;
12479
12504
  const code = errorCode(err);
12480
12505
  return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
@@ -12527,14 +12552,51 @@ function causeMessage(cause) {
12527
12552
  return "";
12528
12553
  return `: ${cause instanceof Error ? cause.message : String(cause)}`;
12529
12554
  }
12530
- // ../../node_modules/.bun/@cortexkit+subc-client@0.3.0/node_modules/@cortexkit/subc-client/src/provider.ts
12555
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.3.4/node_modules/@cortexkit/subc-client/dist/provider.js
12531
12556
  import { Buffer as Buffer2 } from "node:buffer";
12532
12557
  var DEFAULT_HANDSHAKE_TIMEOUT_MS2 = 1e4;
12533
12558
  var BODY_READ_TIMEOUT_MS2 = 30000;
12534
12559
  var WRITE_TIMEOUT_MS = 30000;
12535
12560
  var DEFAULT_RESTORED_DEBOUNCE_MS = 250;
12561
+ var DEFAULT_PROVIDER_HANDLER_CAPACITY = 64;
12562
+ var HEALTH_CHECK_OP = "health.check";
12536
12563
  var HELLO_CORR = 1n;
12537
12564
 
12565
+ class AsyncPermitPool {
12566
+ available;
12567
+ waiters = [];
12568
+ constructor(capacity) {
12569
+ if (!Number.isInteger(capacity) || capacity <= 0) {
12570
+ throw new SubcProviderError("provider handler capacity must be a positive integer", "invalid_handler_capacity");
12571
+ }
12572
+ this.available = capacity;
12573
+ }
12574
+ async acquire() {
12575
+ if (this.available > 0) {
12576
+ this.available -= 1;
12577
+ return this.releaseOnce();
12578
+ }
12579
+ await new Promise((resolve7) => {
12580
+ this.waiters.push(resolve7);
12581
+ });
12582
+ return this.releaseOnce();
12583
+ }
12584
+ releaseOnce() {
12585
+ let released = false;
12586
+ return () => {
12587
+ if (released)
12588
+ return;
12589
+ released = true;
12590
+ const next = this.waiters.shift();
12591
+ if (next) {
12592
+ next();
12593
+ } else {
12594
+ this.available += 1;
12595
+ }
12596
+ };
12597
+ }
12598
+ }
12599
+
12538
12600
  class SubcProviderError extends Error {
12539
12601
  code;
12540
12602
  constructor(message, code) {
@@ -12553,6 +12615,7 @@ class SubcProvider {
12553
12615
  closeStarted = false;
12554
12616
  closedErr = null;
12555
12617
  inflight = new Map;
12618
+ requestGate = new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY);
12556
12619
  reconnecting = null;
12557
12620
  generation = 1;
12558
12621
  connectionEpoch = 1;
@@ -12591,7 +12654,7 @@ class SubcProvider {
12591
12654
  this.cancelRestoredDebounce();
12592
12655
  const sock = this.sock;
12593
12656
  try {
12594
- await sendFrame(sock, buildFrame(11 /* Goodbye */, controlFlags(), 0, 0n, new Uint8Array(0)));
12657
+ await sendFrame(sock, buildFrame(FrameType.Goodbye, controlFlags(), 0, 0n, new Uint8Array(0)));
12595
12658
  } catch {} finally {
12596
12659
  sock.close();
12597
12660
  this.finishClosed();
@@ -12642,21 +12705,21 @@ class SubcProvider {
12642
12705
  }
12643
12706
  async dispatch(frame, sock, generation) {
12644
12707
  switch (frame.header.ty) {
12645
- case 7 /* Ping */:
12708
+ case FrameType.Ping:
12646
12709
  if (frame.header.channel === 0) {
12647
- await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, 8 /* Pong */, frame.header.flags, 0, frame.header.corr, new Uint8Array(0)));
12710
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Pong, frame.header.flags, 0, frame.header.corr, new Uint8Array(0)));
12648
12711
  }
12649
12712
  return true;
12650
- case 11 /* Goodbye */:
12713
+ case FrameType.Goodbye:
12651
12714
  if (frame.header.channel === 0)
12652
12715
  return false;
12653
12716
  this.abortChannel(generation, frame.header.channel);
12654
12717
  await this.opts.onRouteGone?.(frame.header.channel);
12655
12718
  return true;
12656
- case 6 /* Cancel */:
12719
+ case FrameType.Cancel:
12657
12720
  this.inflight.get(routeKey(generation, frame.header.channel, frame.header.corr))?.abort();
12658
12721
  return true;
12659
- case 0 /* Request */:
12722
+ case FrameType.Request:
12660
12723
  if (frame.header.channel === 0) {
12661
12724
  await this.handleControlRequest(frame, sock, generation);
12662
12725
  } else {
@@ -12691,6 +12754,14 @@ class SubcProvider {
12691
12754
  }
12692
12755
  async handleControlRequest(frame, sock, generation) {
12693
12756
  const request = parseJson(frame.body);
12757
+ if (request.op === HEALTH_CHECK_OP) {
12758
+ this.handleHealthRequest(frame, sock, generation).catch((err) => {
12759
+ if (!this.closeStarted && this.sock === sock && this.generation === generation) {
12760
+ console.warn("SubcProvider health handler failed after its request was dispatched", err);
12761
+ }
12762
+ });
12763
+ return;
12764
+ }
12694
12765
  if (request.op !== "route.bind") {
12695
12766
  throw new SubcProviderError(`unsupported module control request ${request.op ?? "<missing op>"}`);
12696
12767
  }
@@ -12706,41 +12777,67 @@ class SubcProvider {
12706
12777
  await this.sendError(frame, rejection.code, rejection.message, controlFlags(), sock, generation);
12707
12778
  return;
12708
12779
  }
12709
- await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, 1 /* Response */, controlFlags(), 0, frame.header.corr, encodeJson({ op: "route.bind" })));
12780
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Response, controlFlags(), 0, frame.header.corr, encodeJson({ op: "route.bind" })));
12710
12781
  }
12711
12782
  async handleDataRequest(frame, sock, generation) {
12712
12783
  const { channel, corr, ver } = frame.header;
12713
12784
  const key = routeKey(generation, channel, corr);
12714
12785
  const controller = new AbortController;
12715
12786
  this.inflight.set(key, controller);
12716
- const dataFlags = buildFlags(false, 1 /* Interactive */, false);
12787
+ const dataFlags = buildFlags(false, Priority.Interactive, false);
12717
12788
  const ctx = {
12718
12789
  signal: controller.signal,
12719
12790
  currentEpoch: () => this.connectionEpoch,
12720
12791
  emit: async (eventBody) => {
12721
12792
  if (controller.signal.aborted)
12722
12793
  return;
12723
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, 3 /* StreamData */, dataFlags, channel, corr, eventBody));
12794
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamData, dataFlags, channel, corr, eventBody));
12724
12795
  }
12725
12796
  };
12797
+ const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
12726
12798
  try {
12727
12799
  const body = await this.opts.handler(channel, frame.body, ctx);
12728
12800
  if (body === undefined) {
12729
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, 4 /* StreamEnd */, dataFlags, channel, corr, new Uint8Array(0)));
12801
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.StreamEnd, dataFlags, channel, corr, new Uint8Array(0)));
12730
12802
  } else if (body instanceof Uint8Array) {
12731
- await this.sendOn(sock, generation, buildFrameWithVersion(ver, 1 /* Response */, dataFlags, channel, corr, body));
12803
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, dataFlags, channel, corr, body));
12732
12804
  } else {
12733
12805
  throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
12734
12806
  }
12735
12807
  } catch (err) {
12736
12808
  await this.sendError(frame, err instanceof SubcProviderError && err.code ? err.code : "handler_error", err instanceof Error ? err.message : String(err), dataFlags, sock, generation);
12737
12809
  } finally {
12810
+ releasePermit();
12811
+ if (this.inflight.get(key) === controller)
12812
+ this.inflight.delete(key);
12813
+ }
12814
+ }
12815
+ async handleHealthRequest(frame, sock, generation) {
12816
+ const { channel, corr, ver } = frame.header;
12817
+ const key = routeKey(generation, channel, corr);
12818
+ const controller = new AbortController;
12819
+ this.inflight.set(key, controller);
12820
+ const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
12821
+ try {
12822
+ if (controller.signal.aborted)
12823
+ return;
12824
+ const report = await this.opts.health();
12825
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, FrameType.Response, controlFlags(), channel, corr, encodeJson({
12826
+ op: HEALTH_CHECK_OP,
12827
+ status: report.status,
12828
+ ...report.detail === undefined ? {} : { detail: report.detail },
12829
+ ...report.metrics === undefined ? {} : { metrics: report.metrics }
12830
+ })));
12831
+ } catch (err) {
12832
+ await this.sendError(frame, err instanceof SubcProviderError && err.code ? err.code : "health_error", err instanceof Error ? err.message : String(err), controlFlags(), sock, generation);
12833
+ } finally {
12834
+ releasePermit();
12738
12835
  if (this.inflight.get(key) === controller)
12739
12836
  this.inflight.delete(key);
12740
12837
  }
12741
12838
  }
12742
12839
  async sendError(frame, code, message, flags, sock, generation) {
12743
- await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, 5 /* Error */, flags, frame.header.channel, frame.header.corr, encodeJson({ code, message })));
12840
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, FrameType.Error, flags, frame.header.channel, frame.header.corr, encodeJson({ code, message })));
12744
12841
  }
12745
12842
  async sendOn(sock, generation, frame) {
12746
12843
  if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr)
@@ -12880,6 +12977,7 @@ function normalizeProviderConnectOptions(opts) {
12880
12977
  connectionFile: opts.connectionFile,
12881
12978
  manifest: opts.manifest,
12882
12979
  handler: opts.handler,
12980
+ health: opts.health ?? (() => ({ status: "ok" })),
12883
12981
  handshakeTimeoutMs: opts.handshakeTimeoutMs,
12884
12982
  controlOps: opts.controlOps,
12885
12983
  onBind: opts.onBind,
@@ -12891,12 +12989,19 @@ function normalizeProviderConnectOptions(opts) {
12891
12989
  launchNonce: opts.launchNonce
12892
12990
  };
12893
12991
  }
12992
+ function normalizedControlOps(controlOps) {
12993
+ if (controlOps === null)
12994
+ return null;
12995
+ const merged = new Set(controlOps ?? []);
12996
+ merged.add(HEALTH_CHECK_OP);
12997
+ return [...merged];
12998
+ }
12894
12999
  function buildHelloFrame(opts) {
12895
13000
  const nonce = launchNonce(opts);
12896
- return buildFrame(9 /* Hello */, controlFlags(), 0, HELLO_CORR, encodeJson({
13001
+ return buildFrame(FrameType.Hello, controlFlags(), 0, HELLO_CORR, encodeJson({
12897
13002
  manifest: normalizeManifest(opts.manifest),
12898
13003
  protocol_ver: PROTOCOL_VERSION,
12899
- control_ops: opts.controlOps === undefined ? null : opts.controlOps,
13004
+ control_ops: normalizedControlOps(opts.controlOps),
12900
13005
  ...nonce ? { launch_nonce: nonce } : {}
12901
13006
  }));
12902
13007
  }
@@ -12907,7 +13012,9 @@ function isProviderReconnectTransient(err) {
12907
13012
  return true;
12908
13013
  if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError)
12909
13014
  return true;
12910
- if (err instanceof ConnectionFileError || err instanceof AuthError)
13015
+ if (err instanceof AuthError)
13016
+ return true;
13017
+ if (err instanceof ConnectionFileError)
12911
13018
  return false;
12912
13019
  const code = errorCode2(err);
12913
13020
  return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
@@ -12924,7 +13031,7 @@ async function pauseBeforeStateRetry() {
12924
13031
  await new Promise((resolve7) => setTimeout(resolve7, 0));
12925
13032
  }
12926
13033
  function controlFlags() {
12927
- return buildFlags(false, 0 /* Passive */, false);
13034
+ return buildFlags(false, Priority.Passive, false);
12928
13035
  }
12929
13036
  async function sendFrame(sock, frame) {
12930
13037
  await sock.write(encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
@@ -12934,9 +13041,9 @@ async function expectHelloAck(sock, deadline) {
12934
13041
  const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, deadline);
12935
13042
  const frame = { header, body };
12936
13043
  switch (header.ty) {
12937
- case 10 /* HelloAck */:
13044
+ case FrameType.HelloAck:
12938
13045
  return parseJson(body);
12939
- case 5 /* Error */: {
13046
+ case FrameType.Error: {
12940
13047
  const error2 = parseJson(body);
12941
13048
  throw new SubcProviderError(`subc rejected HELLO: ${error2.code ?? "unknown"} — ${error2.message ?? "subc error"}`, error2.code);
12942
13049
  }
@@ -13002,6 +13109,7 @@ function normalizeProviderRole(role) {
13002
13109
  role: "tool_provider",
13003
13110
  tools: role.tools.map((tool) => ({
13004
13111
  name: tool.name,
13112
+ ...tool.description === undefined ? {} : { description: tool.description },
13005
13113
  execution_mode: tool.execution_mode,
13006
13114
  schema: tool.schema
13007
13115
  })),
@@ -13176,6 +13284,9 @@ class BgSubscription {
13176
13284
  function isRecord(value) {
13177
13285
  return typeof value === "object" && value !== null && !Array.isArray(value);
13178
13286
  }
13287
+ function isUnknownChannelError(err) {
13288
+ return err instanceof SubcError && err.code === "unknown_channel";
13289
+ }
13179
13290
  function safeCloseRoute(client, channel) {
13180
13291
  try {
13181
13292
  client.closeRouteChannel(channel).catch(() => {
@@ -13255,7 +13366,7 @@ class SubcTransport {
13255
13366
  if (!options)
13256
13367
  return {};
13257
13368
  const preview = options.preview;
13258
- const timeoutMs = options.timeoutMs;
13369
+ const timeoutMs = options.transportTimeoutMs ?? options.timeoutMs;
13259
13370
  const onProgress = options.onProgress;
13260
13371
  return { preview, timeoutMs, onProgress };
13261
13372
  }
@@ -13325,45 +13436,66 @@ class SubcTransportPool {
13325
13436
  record.inflight += 1;
13326
13437
  try {
13327
13438
  const client = await this.ensureClient();
13328
- if (!this.isCurrentSession(key, record)) {
13329
- throw new RouteTornDownError("subc session closed");
13330
- }
13331
- let channel;
13332
- let entry;
13333
- try {
13334
- ({ channel, entry } = await this.routeChannel(client, identity, record));
13439
+ const openRoute = async () => {
13335
13440
  if (!this.isCurrentSession(key, record)) {
13336
13441
  throw new RouteTornDownError("subc session closed");
13337
13442
  }
13338
- } catch (err) {
13339
- if (err instanceof RouteTornDownError)
13443
+ try {
13444
+ const opened = await this.routeChannel(client, identity, record);
13445
+ if (!this.isCurrentSession(key, record)) {
13446
+ throw new RouteTornDownError("subc session closed");
13447
+ }
13448
+ return opened;
13449
+ } catch (err) {
13450
+ if (err instanceof RouteTornDownError)
13451
+ throw err;
13452
+ if (isConsumerReconnectTransient(err) && this.isCurrentSession(key, record) && this.client === client) {
13453
+ this.dropClient(client);
13454
+ }
13340
13455
  throw err;
13341
- if (isConsumerReconnectTransient(err) && this.isCurrentSession(key, record) && this.client === client) {
13342
- this.dropClient(client);
13343
13456
  }
13344
- throw err;
13345
- }
13346
- try {
13347
- const reply = await client.request(channel, body, { timeoutMs, onProgress });
13348
- if (this.isCurrentSession(key, record) && this.client === client) {
13349
- this.transportFailures = 0;
13350
- }
13351
- this.ensureBgSubscription(identity, record);
13352
- return reply;
13353
- } catch (err) {
13354
- if (record.routeEntry === entry) {
13355
- entry.closed = true;
13457
+ };
13458
+ const clearRouteEntry = (entry2) => {
13459
+ if (record.routeEntry === entry2) {
13460
+ entry2.closed = true;
13356
13461
  record.routeEntry = null;
13357
13462
  }
13463
+ };
13464
+ const handleRequestFailure = (err, entry2) => {
13465
+ clearRouteEntry(entry2);
13358
13466
  if (this.isCurrentSession(key, record) && this.client === client) {
13359
13467
  if (isConsumerReconnectTransient(err)) {
13360
13468
  this.transportFailures = 0;
13361
13469
  this.dropClient(client);
13362
- } else if (++this.transportFailures >= MAX_CONSECUTIVE_TRANSPORT_FAILURES) {
13470
+ } else if (!isUnknownChannelError(err) && ++this.transportFailures >= MAX_CONSECUTIVE_TRANSPORT_FAILURES) {
13363
13471
  this.transportFailures = 0;
13364
13472
  this.dropClient(client);
13365
13473
  }
13366
13474
  }
13475
+ };
13476
+ const requestOnRoute = async (channel2) => {
13477
+ const reply = await client.request(channel2, body, { timeoutMs, onProgress });
13478
+ if (this.isCurrentSession(key, record) && this.client === client) {
13479
+ this.transportFailures = 0;
13480
+ }
13481
+ this.ensureBgSubscription(identity, record);
13482
+ return reply;
13483
+ };
13484
+ let { channel, entry } = await openRoute();
13485
+ try {
13486
+ return await requestOnRoute(channel);
13487
+ } catch (err) {
13488
+ if (isUnknownChannelError(err) && this.isCurrentSession(key, record) && this.client === client) {
13489
+ clearRouteEntry(entry);
13490
+ ({ channel, entry } = await openRoute());
13491
+ try {
13492
+ return await requestOnRoute(channel);
13493
+ } catch (retryErr) {
13494
+ handleRequestFailure(retryErr, entry);
13495
+ throw retryErr;
13496
+ }
13497
+ }
13498
+ handleRequestFailure(err, entry);
13367
13499
  throw err;
13368
13500
  }
13369
13501
  } finally {
@@ -28853,16 +28985,7 @@ var InspectConfigSchema = exports_external.object({
28853
28985
  tier2_soft_deadline_ms: exports_external.number().int().positive().optional(),
28854
28986
  max_drill_down_items: exports_external.number().int().positive().max(100).optional(),
28855
28987
  duplicates: exports_external.object({
28856
- lower_bound: exports_external.number().int().positive().optional(),
28857
- discard_cost: exports_external.number().int().min(0).optional(),
28858
- expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional(),
28859
- anonymize: exports_external.object({
28860
- variables: exports_external.boolean().optional(),
28861
- fields: exports_external.boolean().optional(),
28862
- methods: exports_external.boolean().optional(),
28863
- types: exports_external.boolean().optional(),
28864
- literals: exports_external.boolean().optional()
28865
- }).optional()
28988
+ expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional()
28866
28989
  }).optional()
28867
28990
  });
28868
28991
  var BackupConfigSchema = exports_external.object({
@@ -29214,16 +29337,9 @@ function mergeInspectConfig(baseInspect, overrideInspect) {
29214
29337
  ...overrideInspect,
29215
29338
  duplicates: baseInspect?.duplicates || overrideInspect?.duplicates ? {
29216
29339
  ...baseInspect?.duplicates,
29217
- ...overrideInspect?.duplicates,
29218
- anonymize: baseInspect?.duplicates?.anonymize || overrideInspect?.duplicates?.anonymize ? {
29219
- ...baseInspect?.duplicates?.anonymize,
29220
- ...overrideInspect?.duplicates?.anonymize
29221
- } : undefined
29340
+ ...overrideInspect?.duplicates
29222
29341
  } : undefined
29223
29342
  };
29224
- if (inspect.duplicates && inspect.duplicates.anonymize === undefined) {
29225
- delete inspect.duplicates.anonymize;
29226
- }
29227
29343
  if (Object.values(inspect).every((value) => value === undefined)) {
29228
29344
  return;
29229
29345
  }
@@ -32368,28 +32484,44 @@ function normalizeToolMap(tools) {
32368
32484
  async function sendIgnoredMessage2(client, sessionID, text) {
32369
32485
  const typedClient = client;
32370
32486
  let agent;
32487
+ let model;
32488
+ let variant;
32371
32489
  try {
32372
32490
  const ctx = await resolvePromptContext(client, sessionID);
32373
32491
  agent = ctx?.agent;
32492
+ model = ctx?.model;
32493
+ variant = ctx?.variant;
32374
32494
  } catch {
32375
32495
  agent = undefined;
32376
32496
  }
32377
- const body = {
32497
+ const send = async (body) => {
32498
+ const promptInput = { path: { id: sessionID }, body };
32499
+ if (typeof typedClient.session?.prompt === "function") {
32500
+ await Promise.resolve(typedClient.session.prompt(promptInput));
32501
+ return;
32502
+ }
32503
+ if (typeof typedClient.session?.promptAsync === "function") {
32504
+ await typedClient.session.promptAsync(promptInput);
32505
+ return;
32506
+ }
32507
+ throw new Error("[aft-plugin] client.session.prompt is unavailable");
32508
+ };
32509
+ const base = {
32378
32510
  noReply: true,
32379
32511
  parts: [{ type: "text", text, ignored: true }]
32380
32512
  };
32381
32513
  if (agent)
32382
- body.agent = agent;
32383
- const promptInput = { path: { id: sessionID }, body };
32384
- if (typeof typedClient.session?.prompt === "function") {
32385
- await Promise.resolve(typedClient.session.prompt(promptInput));
32386
- return;
32387
- }
32388
- if (typeof typedClient.session?.promptAsync === "function") {
32389
- await typedClient.session.promptAsync(promptInput);
32390
- return;
32514
+ base.agent = agent;
32515
+ if (model) {
32516
+ const withModel = { ...base, model };
32517
+ if (variant)
32518
+ withModel.variant = variant;
32519
+ try {
32520
+ await send(withModel);
32521
+ return;
32522
+ } catch {}
32391
32523
  }
32392
- throw new Error("[aft-plugin] client.session.prompt is unavailable");
32524
+ await send(base);
32393
32525
  }
32394
32526
 
32395
32527
  // src/shared/rpc-notifications.ts
@@ -33434,12 +33566,15 @@ async function callBashBridge(ctx, runtime, command, params = {}, options) {
33434
33566
  }
33435
33567
 
33436
33568
  // src/tools/permissions.ts
33569
+ import { execFileSync as execFileSync3 } from "node:child_process";
33437
33570
  import * as fs4 from "node:fs";
33438
33571
  import { tmpdir as tmpdir4 } from "node:os";
33439
33572
  import * as path4 from "node:path";
33440
33573
  var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.15.5 or newer for permission asks; please upgrade OpenCode";
33441
33574
  var RESTRICT_NOTICE_THROTTLE_MS = 5 * 60 * 1000;
33442
33575
  var restrictNoticeLastSentAt = new Map;
33576
+ var aftSearchExternalDecisionCache = new Map;
33577
+ var aftSearchExternalPendingAsks = new Map;
33443
33578
  var POSIX_SYSTEM_TEMP_ROOTS = ["/tmp", "/var/tmp", "/private/tmp", "/private/var/tmp"];
33444
33579
  var MACOS_SYSTEM_TEMP_ROOTS = ["/var/folders", "/private/var/folders"];
33445
33580
  function restrictNoticeWording(target) {
@@ -33610,6 +33745,78 @@ async function assertExternalDirectoryPermission(ctx, context, target, options)
33610
33745
  return "Permission denied (external directory).";
33611
33746
  }
33612
33747
  }
33748
+ function gitRootForNearestExistingParent(resolved) {
33749
+ const nearest = normalizeNearestExistingParent(resolved);
33750
+ let cwd = nearest;
33751
+ try {
33752
+ if (fs4.statSync(nearest).isFile())
33753
+ cwd = path4.dirname(nearest);
33754
+ } catch {}
33755
+ try {
33756
+ const out = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
33757
+ cwd,
33758
+ encoding: "utf8",
33759
+ stdio: ["ignore", "pipe", "ignore"]
33760
+ }).trim();
33761
+ return out ? normalizePath(out) : undefined;
33762
+ } catch {
33763
+ return;
33764
+ }
33765
+ }
33766
+ async function assertAftSearchExternalPermission(ctx, context, target) {
33767
+ if (!target)
33768
+ return;
33769
+ const resolved = resolveAbsolutePath(context, target);
33770
+ const absoluteTarget = normalizePath(resolved);
33771
+ const externalRoot = gitRootForNearestExistingParent(resolved) ?? normalizeNearestExistingParent(resolved);
33772
+ const root = projectRootFor(context);
33773
+ const directory = root ? normalizePath(root) : root;
33774
+ const rawWorktree = context.worktree;
33775
+ const worktree = rawWorktree && rawWorktree !== "/" ? normalizePath(rawWorktree) : rawWorktree;
33776
+ if (directory && containsPath(directory, externalRoot))
33777
+ return;
33778
+ if (worktree && worktree !== "/" && worktree !== directory && containsPath(worktree, externalRoot)) {
33779
+ return;
33780
+ }
33781
+ if (ctx.config.restrict_to_project_root === true) {
33782
+ notifyRestrictBlocked(ctx, context, externalRoot);
33783
+ return restrictDenialMessage(externalRoot);
33784
+ }
33785
+ if (typeof context.ask !== "function")
33786
+ return UNSUPPORTED_ASK_HOST;
33787
+ const sessionKey = context.sessionID ?? "unknown-session";
33788
+ const cacheKey = `${sessionKey}\x00${externalRoot}`;
33789
+ if (aftSearchExternalDecisionCache.has(cacheKey)) {
33790
+ return aftSearchExternalDecisionCache.get(cacheKey);
33791
+ }
33792
+ const pending = aftSearchExternalPendingAsks.get(cacheKey);
33793
+ if (pending)
33794
+ return pending;
33795
+ const rawGlob = process.platform === "win32" ? normalizePathPattern(path4.join(externalRoot, "*")) : path4.join(externalRoot, "*").replaceAll("\\", "/");
33796
+ const askPromise = (async () => {
33797
+ try {
33798
+ await runAsk(context.ask({
33799
+ permission: "aft_search_external",
33800
+ patterns: [rawGlob],
33801
+ always: [rawGlob],
33802
+ metadata: {
33803
+ filepath: absoluteTarget,
33804
+ root: externalRoot
33805
+ }
33806
+ }));
33807
+ aftSearchExternalDecisionCache.set(cacheKey, undefined);
33808
+ return;
33809
+ } catch (error53) {
33810
+ const message = error53 instanceof Error && error53.message ? error53.message : "Permission denied (aft_search_external).";
33811
+ aftSearchExternalDecisionCache.set(cacheKey, message);
33812
+ return message;
33813
+ } finally {
33814
+ aftSearchExternalPendingAsks.delete(cacheKey);
33815
+ }
33816
+ })();
33817
+ aftSearchExternalPendingAsks.set(cacheKey, askPromise);
33818
+ return askPromise;
33819
+ }
33613
33820
  async function askSearchPatternPermission(context, permission, pattern, metadata = {}) {
33614
33821
  if (typeof context.ask !== "function")
33615
33822
  return UNSUPPORTED_ASK_HOST;
@@ -34882,6 +35089,7 @@ function createWriteTool(ctx, editToolName = "edit") {
34882
35089
  const diff = data.diff;
34883
35090
  if (!diff)
34884
35091
  return output;
35092
+ const truncated = diff.truncated === true;
34885
35093
  const dp = relativeToWorktree(filePath, projectRoot);
34886
35094
  const beforeContent = diff.before ?? "";
34887
35095
  const afterContent = diff.after ?? content;
@@ -34889,13 +35097,15 @@ function createWriteTool(ctx, editToolName = "edit") {
34889
35097
  output,
34890
35098
  title: dp,
34891
35099
  metadata: {
34892
- diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
34893
- filediff: {
34894
- file: filePath,
34895
- before: beforeContent,
34896
- after: afterContent,
34897
- additions: diff?.additions ?? 0,
34898
- deletions: diff?.deletions ?? 0
35100
+ diff: truncated ? typeof preview2.preview_diff === "string" ? preview2.preview_diff : "" : buildUnifiedDiff(filePath, beforeContent, afterContent),
35101
+ ...truncated ? {} : {
35102
+ filediff: {
35103
+ file: filePath,
35104
+ before: beforeContent,
35105
+ after: afterContent,
35106
+ additions: diff?.additions ?? 0,
35107
+ deletions: diff?.deletions ?? 0
35108
+ }
34899
35109
  },
34900
35110
  diagnostics: {}
34901
35111
  }
@@ -35016,16 +35226,19 @@ function createEditTool(ctx, writeToolName = "write") {
35016
35226
  const diff = data.diff;
35017
35227
  if (!diff)
35018
35228
  return output;
35229
+ const truncated = diff.truncated === true;
35019
35230
  const beforeContent = diff.before ?? "";
35020
35231
  const afterContent = diff.after ?? "";
35021
35232
  const uiMeta = {
35022
- diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
35023
- filediff: {
35024
- file: filePath,
35025
- before: beforeContent,
35026
- after: afterContent,
35027
- additions: diff.additions ?? 0,
35028
- deletions: diff.deletions ?? 0
35233
+ diff: truncated ? typeof preview2.preview_diff === "string" ? preview2.preview_diff : "" : buildUnifiedDiff(filePath, beforeContent, afterContent),
35234
+ ...truncated ? {} : {
35235
+ filediff: {
35236
+ file: filePath,
35237
+ before: beforeContent,
35238
+ after: afterContent,
35239
+ additions: diff.additions ?? 0,
35240
+ deletions: diff.deletions ?? 0
35241
+ }
35029
35242
  },
35030
35243
  diagnostics: {}
35031
35244
  };
@@ -36283,7 +36496,8 @@ function semanticTools(ctx) {
36283
36496
  query: arg3(z15.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
36284
36497
  topK: arg3(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)")),
36285
36498
  hint: arg3(z15.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'.")),
36286
- includeTests: arg3(z15.boolean().optional().describe("Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."))
36499
+ includeTests: arg3(z15.boolean().optional().describe("Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false.")),
36500
+ path: arg3(z15.string().optional().describe("Search a different project root (absolute or ~ path). Requires that project to have been indexed by AFT."))
36287
36501
  },
36288
36502
  execute: async (args, context) => {
36289
36503
  if (isEmptyParam(args.query) || typeof args.query !== "string" || args.query.trim().length === 0) {
@@ -36291,11 +36505,17 @@ function semanticTools(ctx) {
36291
36505
  }
36292
36506
  const query = args.query;
36293
36507
  const hint = typeof args.hint === "string" ? args.hint : undefined;
36508
+ const pathArg = typeof args.path === "string" && args.path.trim() ? args.path.trim() : undefined;
36294
36509
  if (hint !== "semantic") {
36295
36510
  const denied = await askSearchPermission(context, query);
36296
36511
  if (denied)
36297
36512
  return permissionDeniedResponse(denied);
36298
36513
  }
36514
+ if (pathArg) {
36515
+ const denied = await assertAftSearchExternalPermission(ctx, context, pathArg);
36516
+ if (denied)
36517
+ return permissionDeniedResponse(denied);
36518
+ }
36299
36519
  const rawArgs = { query };
36300
36520
  const topK = coerceOptionalInt(args.topK, "topK", 1, 100);
36301
36521
  if (topK !== undefined)
@@ -36304,6 +36524,8 @@ function semanticTools(ctx) {
36304
36524
  rawArgs.hint = hint;
36305
36525
  if (typeof args.includeTests === "boolean")
36306
36526
  rawArgs.includeTests = args.includeTests;
36527
+ if (pathArg)
36528
+ rawArgs.path = pathArg;
36307
36529
  const response = await callToolCall(ctx, context, "search", rawArgs);
36308
36530
  if (response.success === false) {
36309
36531
  const message = typeof response.text === "string" && response.text.length > 0 ? response.text : typeof response.message === "string" && response.message.length > 0 ? response.message : "semantic_search failed";
@@ -36431,13 +36653,13 @@ var PLUGIN_VERSION = (() => {
36431
36653
  return "0.0.0";
36432
36654
  }
36433
36655
  })();
36434
- var ANNOUNCEMENT_VERSION = "0.45.0";
36656
+ var ANNOUNCEMENT_VERSION = "0.46.0";
36435
36657
  var ANNOUNCEMENT_FEATURES = [
36436
- "Code Health verified against 12 real repositories: dead code is only reported for languages we can actually analyze (Java/Kotlin now say 'not analyzed' instead of guessing), and generated code (gen/ trees, *_pb.ts, DO NOT EDIT banners) moves out of the headline counts into its own bucket.",
36437
- "Config files (playwright/eslint/tsup/vitest *.config.ts), Storybook stories, Mocha hooks, and SvelteKit hooks/$lib imports are no longer flagged as dead code.",
36438
- "Rust module aliases, inline-module calls, and cross-crate pub use re-exports now resolve; Go interface methods (sort.Interface, Bubble Tea) are no longer flagged dead.",
36439
- "Fixed npx @cortexkit/aft doctor --fix and setup failing to download the binary on fresh installs.",
36440
- "Bridges now detect an updated aft binary on disk and respawn onto it automatically."
36658
+ "TUI sidebar fixed on OpenCode 1.17.x: the panel is precompiled against the host runtime (no more import failures or frozen values), and it now works when the TUI is attached to a serve/Desktop host from another directory.",
36659
+ "Search index changes persist on clean shutdown, so restarts and cross-worktree searches see current results.",
36660
+ "Cross-project search: sessions can search sibling checkouts read-only, serving borrowed indexes with drift warnings instead of failing.",
36661
+ "Duplicate detection requires a 10-line minimum span, ending flags on 3-4 line idiomatic blocks.",
36662
+ "Pi edit tool supports batch edits[] arrays, matching OpenCode."
36441
36663
  ];
36442
36664
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
36443
36665
  var plugin = async (input) => initializePluginForDirectory(input);
@@ -36755,6 +36977,7 @@ ${lines}
36755
36977
  return {
36756
36978
  success: true,
36757
36979
  status: "not_initialized",
36980
+ verified_directory: verifiedDir,
36758
36981
  message: "AFT bridge is now spawned lazily, information here will be populated after first tool call."
36759
36982
  };
36760
36983
  }
@@ -36767,6 +36990,7 @@ ${lines}
36767
36990
  return {
36768
36991
  success: true,
36769
36992
  status: "not_initialized",
36993
+ ...verifiedDir ? { verified_directory: verifiedDir } : {},
36770
36994
  message: "AFT bridge is now spawned lazily, information here will be populated after first tool call."
36771
36995
  };
36772
36996
  }