@cortexkit/aft-opencode 0.45.0 → 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.
Files changed (34) hide show
  1. package/dist/config.d.ts +0 -22
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/hooks/auto-update-checker/types.d.ts +0 -3
  4. package/dist/hooks/auto-update-checker/types.d.ts.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +347 -127
  7. package/dist/lsp-github-table.d.ts +0 -8
  8. package/dist/lsp-github-table.d.ts.map +1 -1
  9. package/dist/notifications.d.ts +2 -9
  10. package/dist/notifications.d.ts.map +1 -1
  11. package/dist/shared/ignored-message.d.ts +9 -7
  12. package/dist/shared/ignored-message.d.ts.map +1 -1
  13. package/dist/shared/rpc-client.d.ts +23 -1
  14. package/dist/shared/rpc-client.d.ts.map +1 -1
  15. package/dist/subc-tool-schemas.d.ts.map +1 -1
  16. package/dist/tools/_shared.d.ts +0 -7
  17. package/dist/tools/_shared.d.ts.map +1 -1
  18. package/dist/tools/hoisted.d.ts.map +1 -1
  19. package/dist/tools/permissions.d.ts +7 -0
  20. package/dist/tools/permissions.d.ts.map +1 -1
  21. package/dist/tools/semantic.d.ts.map +1 -1
  22. package/dist/tui/notification-socket.d.ts.map +1 -1
  23. package/package.json +17 -14
  24. package/src/shared/ignored-message.ts +38 -19
  25. package/src/shared/rpc-client.ts +116 -4
  26. package/src/tui/entry.mjs +16 -0
  27. package/src/tui/notification-socket.ts +28 -1
  28. package/src/tui-compiled/badge-contrast.ts +43 -0
  29. package/src/tui-compiled/index.tsx +992 -0
  30. package/src/tui-compiled/notification-socket.ts +448 -0
  31. package/src/tui-compiled/preferences.ts +243 -0
  32. package/src/tui-compiled/sidebar.tsx +936 -0
  33. package/src/tui-compiled/types/opencode-plugin-tui.d.ts +239 -0
  34. package/dist/tui.js +0 -13462
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
- }
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
13456
  }
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 {
@@ -28810,9 +28942,6 @@ var LspServerEntrySchema = exports_external.object({
28810
28942
  env: exports_external.record(exports_external.string().min(1), exports_external.string()).optional(),
28811
28943
  initialization_options: exports_external.unknown().optional()
28812
28944
  });
28813
- var LspServerSchema = LspServerEntrySchema.extend({
28814
- id: exports_external.string().trim().min(1)
28815
- });
28816
28945
  var LspConfigSchema = exports_external.object({
28817
28946
  servers: exports_external.record(exports_external.string().trim().min(1), LspServerEntrySchema).optional(),
28818
28947
  disabled: exports_external.array(exports_external.string().trim().min(1)).optional(),
@@ -28856,16 +28985,7 @@ var InspectConfigSchema = exports_external.object({
28856
28985
  tier2_soft_deadline_ms: exports_external.number().int().positive().optional(),
28857
28986
  max_drill_down_items: exports_external.number().int().positive().max(100).optional(),
28858
28987
  duplicates: exports_external.object({
28859
- lower_bound: exports_external.number().int().positive().optional(),
28860
- discard_cost: exports_external.number().int().min(0).optional(),
28861
- expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional(),
28862
- anonymize: exports_external.object({
28863
- variables: exports_external.boolean().optional(),
28864
- fields: exports_external.boolean().optional(),
28865
- methods: exports_external.boolean().optional(),
28866
- types: exports_external.boolean().optional(),
28867
- literals: exports_external.boolean().optional()
28868
- }).optional()
28988
+ expected_mirrors: exports_external.array(exports_external.tuple([exports_external.string().trim().min(1), exports_external.string().trim().min(1)])).optional()
28869
28989
  }).optional()
28870
28990
  });
28871
28991
  var BackupConfigSchema = exports_external.object({
@@ -29217,16 +29337,9 @@ function mergeInspectConfig(baseInspect, overrideInspect) {
29217
29337
  ...overrideInspect,
29218
29338
  duplicates: baseInspect?.duplicates || overrideInspect?.duplicates ? {
29219
29339
  ...baseInspect?.duplicates,
29220
- ...overrideInspect?.duplicates,
29221
- anonymize: baseInspect?.duplicates?.anonymize || overrideInspect?.duplicates?.anonymize ? {
29222
- ...baseInspect?.duplicates?.anonymize,
29223
- ...overrideInspect?.duplicates?.anonymize
29224
- } : undefined
29340
+ ...overrideInspect?.duplicates
29225
29341
  } : undefined
29226
29342
  };
29227
- if (inspect.duplicates && inspect.duplicates.anonymize === undefined) {
29228
- delete inspect.duplicates.anonymize;
29229
- }
29230
29343
  if (Object.values(inspect).every((value) => value === undefined)) {
29231
29344
  return;
29232
29345
  }
@@ -29441,7 +29554,6 @@ async function showTuiToast(client, title, message, variant = "info", duration3
29441
29554
  var AFT_MARKER = "\uD83D\uDD27 AFT:";
29442
29555
  var FEATURE_MARKER = `${AFT_MARKER} New in`;
29443
29556
  var WARNING_MARKER = `${AFT_MARKER} ⚠️`;
29444
- var STATUS_MARKER = `${AFT_MARKER} ✅`;
29445
29557
  function getDesktopStatePath() {
29446
29558
  const os3 = platform();
29447
29559
  const home = homedir9();
@@ -32372,28 +32484,44 @@ function normalizeToolMap(tools) {
32372
32484
  async function sendIgnoredMessage2(client, sessionID, text) {
32373
32485
  const typedClient = client;
32374
32486
  let agent;
32487
+ let model;
32488
+ let variant;
32375
32489
  try {
32376
32490
  const ctx = await resolvePromptContext(client, sessionID);
32377
32491
  agent = ctx?.agent;
32492
+ model = ctx?.model;
32493
+ variant = ctx?.variant;
32378
32494
  } catch {
32379
32495
  agent = undefined;
32380
32496
  }
32381
- 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 = {
32382
32510
  noReply: true,
32383
32511
  parts: [{ type: "text", text, ignored: true }]
32384
32512
  };
32385
32513
  if (agent)
32386
- body.agent = agent;
32387
- const promptInput = { path: { id: sessionID }, body };
32388
- if (typeof typedClient.session?.prompt === "function") {
32389
- await Promise.resolve(typedClient.session.prompt(promptInput));
32390
- return;
32391
- }
32392
- if (typeof typedClient.session?.promptAsync === "function") {
32393
- await typedClient.session.promptAsync(promptInput);
32394
- 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 {}
32395
32523
  }
32396
- throw new Error("[aft-plugin] client.session.prompt is unavailable");
32524
+ await send(base);
32397
32525
  }
32398
32526
 
32399
32527
  // src/shared/rpc-notifications.ts
@@ -33438,12 +33566,15 @@ async function callBashBridge(ctx, runtime, command, params = {}, options) {
33438
33566
  }
33439
33567
 
33440
33568
  // src/tools/permissions.ts
33569
+ import { execFileSync as execFileSync3 } from "node:child_process";
33441
33570
  import * as fs4 from "node:fs";
33442
33571
  import { tmpdir as tmpdir4 } from "node:os";
33443
33572
  import * as path4 from "node:path";
33444
33573
  var UNSUPPORTED_ASK_HOST = "AFT requires OpenCode 1.15.5 or newer for permission asks; please upgrade OpenCode";
33445
33574
  var RESTRICT_NOTICE_THROTTLE_MS = 5 * 60 * 1000;
33446
33575
  var restrictNoticeLastSentAt = new Map;
33576
+ var aftSearchExternalDecisionCache = new Map;
33577
+ var aftSearchExternalPendingAsks = new Map;
33447
33578
  var POSIX_SYSTEM_TEMP_ROOTS = ["/tmp", "/var/tmp", "/private/tmp", "/private/var/tmp"];
33448
33579
  var MACOS_SYSTEM_TEMP_ROOTS = ["/var/folders", "/private/var/folders"];
33449
33580
  function restrictNoticeWording(target) {
@@ -33614,6 +33745,78 @@ async function assertExternalDirectoryPermission(ctx, context, target, options)
33614
33745
  return "Permission denied (external directory).";
33615
33746
  }
33616
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
+ }
33617
33820
  async function askSearchPatternPermission(context, permission, pattern, metadata = {}) {
33618
33821
  if (typeof context.ask !== "function")
33619
33822
  return UNSUPPORTED_ASK_HOST;
@@ -34886,6 +35089,7 @@ function createWriteTool(ctx, editToolName = "edit") {
34886
35089
  const diff = data.diff;
34887
35090
  if (!diff)
34888
35091
  return output;
35092
+ const truncated = diff.truncated === true;
34889
35093
  const dp = relativeToWorktree(filePath, projectRoot);
34890
35094
  const beforeContent = diff.before ?? "";
34891
35095
  const afterContent = diff.after ?? content;
@@ -34893,13 +35097,15 @@ function createWriteTool(ctx, editToolName = "edit") {
34893
35097
  output,
34894
35098
  title: dp,
34895
35099
  metadata: {
34896
- diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
34897
- filediff: {
34898
- file: filePath,
34899
- before: beforeContent,
34900
- after: afterContent,
34901
- additions: diff?.additions ?? 0,
34902
- 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
+ }
34903
35109
  },
34904
35110
  diagnostics: {}
34905
35111
  }
@@ -35020,16 +35226,19 @@ function createEditTool(ctx, writeToolName = "write") {
35020
35226
  const diff = data.diff;
35021
35227
  if (!diff)
35022
35228
  return output;
35229
+ const truncated = diff.truncated === true;
35023
35230
  const beforeContent = diff.before ?? "";
35024
35231
  const afterContent = diff.after ?? "";
35025
35232
  const uiMeta = {
35026
- diff: buildUnifiedDiff(filePath, beforeContent, afterContent),
35027
- filediff: {
35028
- file: filePath,
35029
- before: beforeContent,
35030
- after: afterContent,
35031
- additions: diff.additions ?? 0,
35032
- 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
+ }
35033
35242
  },
35034
35243
  diagnostics: {}
35035
35244
  };
@@ -36287,7 +36496,8 @@ function semanticTools(ctx) {
36287
36496
  query: arg3(z15.string().describe("Concept, regex, literal text, filename, or capability to find. Examples: 'fuzzy match with whitespace tolerance', '^export', 'Cargo.lock'.")),
36288
36497
  topK: arg3(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)")),
36289
36498
  hint: arg3(z15.enum(["regex", "literal", "semantic", "auto"]).optional().describe("Optional routing hint. Defaults to 'auto'.")),
36290
- 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."))
36291
36501
  },
36292
36502
  execute: async (args, context) => {
36293
36503
  if (isEmptyParam(args.query) || typeof args.query !== "string" || args.query.trim().length === 0) {
@@ -36295,11 +36505,17 @@ function semanticTools(ctx) {
36295
36505
  }
36296
36506
  const query = args.query;
36297
36507
  const hint = typeof args.hint === "string" ? args.hint : undefined;
36508
+ const pathArg = typeof args.path === "string" && args.path.trim() ? args.path.trim() : undefined;
36298
36509
  if (hint !== "semantic") {
36299
36510
  const denied = await askSearchPermission(context, query);
36300
36511
  if (denied)
36301
36512
  return permissionDeniedResponse(denied);
36302
36513
  }
36514
+ if (pathArg) {
36515
+ const denied = await assertAftSearchExternalPermission(ctx, context, pathArg);
36516
+ if (denied)
36517
+ return permissionDeniedResponse(denied);
36518
+ }
36303
36519
  const rawArgs = { query };
36304
36520
  const topK = coerceOptionalInt(args.topK, "topK", 1, 100);
36305
36521
  if (topK !== undefined)
@@ -36308,6 +36524,8 @@ function semanticTools(ctx) {
36308
36524
  rawArgs.hint = hint;
36309
36525
  if (typeof args.includeTests === "boolean")
36310
36526
  rawArgs.includeTests = args.includeTests;
36527
+ if (pathArg)
36528
+ rawArgs.path = pathArg;
36311
36529
  const response = await callToolCall(ctx, context, "search", rawArgs);
36312
36530
  if (response.success === false) {
36313
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";
@@ -36435,13 +36653,13 @@ var PLUGIN_VERSION = (() => {
36435
36653
  return "0.0.0";
36436
36654
  }
36437
36655
  })();
36438
- var ANNOUNCEMENT_VERSION = "0.45.0";
36656
+ var ANNOUNCEMENT_VERSION = "0.46.0";
36439
36657
  var ANNOUNCEMENT_FEATURES = [
36440
- "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.",
36441
- "Config files (playwright/eslint/tsup/vitest *.config.ts), Storybook stories, Mocha hooks, and SvelteKit hooks/$lib imports are no longer flagged as dead code.",
36442
- "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.",
36443
- "Fixed npx @cortexkit/aft doctor --fix and setup failing to download the binary on fresh installs.",
36444
- "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."
36445
36663
  ];
36446
36664
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
36447
36665
  var plugin = async (input) => initializePluginForDirectory(input);
@@ -36759,6 +36977,7 @@ ${lines}
36759
36977
  return {
36760
36978
  success: true,
36761
36979
  status: "not_initialized",
36980
+ verified_directory: verifiedDir,
36762
36981
  message: "AFT bridge is now spawned lazily, information here will be populated after first tool call."
36763
36982
  };
36764
36983
  }
@@ -36771,6 +36990,7 @@ ${lines}
36771
36990
  return {
36772
36991
  success: true,
36773
36992
  status: "not_initialized",
36993
+ ...verifiedDir ? { verified_directory: verifiedDir } : {},
36774
36994
  message: "AFT bridge is now spawned lazily, information here will be populated after first tool call."
36775
36995
  };
36776
36996
  }