@cortexkit/aft-opencode 0.57.1 → 0.57.2

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.
@@ -11966,17 +11966,17 @@ async function renameIfPresent(from, to) {
11966
11966
  throw error;
11967
11967
  }
11968
11968
  }
11969
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/client.js
11969
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/client.js
11970
11970
  import { promises as fs3 } from "node:fs";
11971
11971
  import { debuglog } from "node:util";
11972
11972
 
11973
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/auth.js
11973
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/auth.js
11974
11974
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
11975
11975
 
11976
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/socket.js
11976
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/socket.js
11977
11977
  import net from "node:net";
11978
11978
 
11979
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/envelope.js
11979
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/envelope.js
11980
11980
  var PROTOCOL_VERSION = 2;
11981
11981
  var HEADER_LEN = 21;
11982
11982
  var FROZEN_PREFIX_LEN = 5;
@@ -12018,7 +12018,7 @@ var FLAG_PRIORITY_SHIFT = 1;
12018
12018
  var FLAG_LAST = 8;
12019
12019
  var FLAG_ADMISSION_MASK = 48;
12020
12020
  var FLAG_ADMISSION_SHIFT = 4;
12021
- var FLAG_RESERVED_MASK = 192;
12021
+ var FLAG_RESERVED_MASK = 0;
12022
12022
  function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Normal) {
12023
12023
  let flags = 0;
12024
12024
  if (binary)
@@ -12029,6 +12029,9 @@ function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Norm
12029
12029
  flags |= admissionClass << FLAG_ADMISSION_SHIFT;
12030
12030
  return flags;
12031
12031
  }
12032
+ function hasBinary(flags) {
12033
+ return (flags & FLAG_BINARY) !== 0;
12034
+ }
12032
12035
  function encodeHeader(header) {
12033
12036
  const buffer = new Uint8Array(HEADER_LEN);
12034
12037
  const view = new DataView(buffer.buffer);
@@ -12145,7 +12148,7 @@ function encodeFrame(frame) {
12145
12148
  return output;
12146
12149
  }
12147
12150
 
12148
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/socket.js
12151
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/socket.js
12149
12152
  class SocketClosedError extends Error {
12150
12153
  }
12151
12154
 
@@ -12378,7 +12381,7 @@ class SubcSocket {
12378
12381
  }
12379
12382
  }
12380
12383
 
12381
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/auth.js
12384
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/auth.js
12382
12385
  var NONCE_LEN = 32;
12383
12386
  var MAX_AUTH_MESSAGE_LEN = 4096;
12384
12387
  var SERVER_PROOF_DOMAIN = "subc-server-v1";
@@ -12452,7 +12455,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
12452
12455
  await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
12453
12456
  }
12454
12457
 
12455
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/connection-file.js
12458
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/connection-file.js
12456
12459
  import { promises as fs2 } from "node:fs";
12457
12460
  var SCHEMA_VERSION = 1;
12458
12461
  var MIN_KEY_LEN = 32;
@@ -12530,13 +12533,49 @@ async function readConnectionFile(path) {
12530
12533
  return info;
12531
12534
  }
12532
12535
 
12533
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/route-handle.js
12536
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/route-handle.js
12534
12537
  var connectionToken = new WeakMap;
12538
+ var reverseRequests = new WeakMap;
12539
+
12540
+ class ReverseRequestRegistrationError extends Error {
12541
+ methodFamily;
12542
+ code = "reverse_request_capability_not_declared";
12543
+ constructor(methodFamily) {
12544
+ super(`reverse-request capability ${JSON.stringify(methodFamily)} was not registered before route.open`);
12545
+ this.methodFamily = methodFamily;
12546
+ this.name = "ReverseRequestRegistrationError";
12547
+ }
12548
+ }
12549
+
12550
+ class ReverseRequestRegistry {
12551
+ handlers = new Map;
12552
+ declaredFamilies = null;
12553
+ onRequest(methodFamily, handler) {
12554
+ if (!isMethodFamily(methodFamily)) {
12555
+ throw new TypeError(`reverse-request method family must be a capability identifier, got ${JSON.stringify(methodFamily)}`);
12556
+ }
12557
+ if (typeof handler !== "function")
12558
+ throw new TypeError("reverse-request handler must be a function");
12559
+ if (this.declaredFamilies && !this.declaredFamilies.has(methodFamily)) {
12560
+ throw new ReverseRequestRegistrationError(methodFamily);
12561
+ }
12562
+ this.handlers.set(methodFamily, handler);
12563
+ }
12564
+ capabilities() {
12565
+ return [...this.handlers.keys()].sort();
12566
+ }
12567
+ handler(methodFamily) {
12568
+ return this.handlers.get(methodFamily);
12569
+ }
12570
+ seal() {
12571
+ this.declaredFamilies ??= new Set(this.handlers.keys());
12572
+ }
12573
+ }
12535
12574
 
12536
12575
  class RouteHandle {
12537
12576
  channel;
12538
12577
  epoch;
12539
- constructor(channel, epoch, token) {
12578
+ constructor(channel, epoch, token, registry) {
12540
12579
  if (!Number.isInteger(channel) || channel <= 0 || channel > 65535) {
12541
12580
  throw new RangeError(`route channel must be an integer in 1..65535, got ${channel}`);
12542
12581
  }
@@ -12545,16 +12584,27 @@ class RouteHandle {
12545
12584
  }
12546
12585
  this.channel = channel;
12547
12586
  this.epoch = epoch;
12587
+ registry.seal();
12548
12588
  connectionToken.set(this, token);
12589
+ reverseRequests.set(this, registry);
12549
12590
  Object.freeze(this);
12550
12591
  }
12551
- static create(channel, epoch, token) {
12552
- return new RouteHandle(channel, epoch, token);
12592
+ onRequest(methodFamily, handler) {
12593
+ reverseRequests.get(this).onRequest(methodFamily, handler);
12594
+ }
12595
+ static create(channel, epoch, token, registry) {
12596
+ return new RouteHandle(channel, epoch, token, registry);
12553
12597
  }
12554
12598
  }
12555
- function createRouteHandle(channel, epoch, token) {
12599
+ function createRouteHandle(channel, epoch, token, registry = new ReverseRequestRegistry) {
12556
12600
  const factory = RouteHandle;
12557
- return factory.create(channel, epoch, token);
12601
+ return factory.create(channel, epoch, token, registry);
12602
+ }
12603
+ function reverseRequestHandler(handle, methodFamily) {
12604
+ return reverseRequests.get(handle)?.handler(methodFamily);
12605
+ }
12606
+ function isMethodFamily(value) {
12607
+ return /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(value);
12558
12608
  }
12559
12609
 
12560
12610
  class StaleRouteHandleError extends Error {
@@ -12576,7 +12626,7 @@ function sameRouteHandle(left, right) {
12576
12626
  return left === right;
12577
12627
  }
12578
12628
 
12579
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/client.js
12629
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/client.js
12580
12630
  var debug = debuglog("subc-client");
12581
12631
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
12582
12632
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
@@ -12607,16 +12657,26 @@ class SubcCallError extends Error {
12607
12657
  this.cause = cause;
12608
12658
  this.name = "SubcCallError";
12609
12659
  }
12660
+ get detail() {
12661
+ return this.cause instanceof SubcError ? this.cause.detail : undefined;
12662
+ }
12610
12663
  }
12611
12664
 
12612
12665
  class SubcError extends Error {
12613
12666
  code;
12614
- constructor(message, code) {
12667
+ detail;
12668
+ constructor(message, code, detail) {
12615
12669
  super(message);
12616
12670
  this.code = code;
12671
+ this.detail = detail;
12617
12672
  }
12618
12673
  }
12619
-
12674
+ function requireBinaryBody(body) {
12675
+ if (body instanceof Uint8Array)
12676
+ return body;
12677
+ const type = body === null ? "null" : Array.isArray(body) ? "array" : typeof body;
12678
+ throw new SubcError(`binary request body must be a Uint8Array; got ${type}`, "binary_body_required");
12679
+ }
12620
12680
  class SubcClient {
12621
12681
  sock;
12622
12682
  currentConn;
@@ -12653,15 +12713,34 @@ class SubcClient {
12653
12713
  const parsed = this.parseJson(reply);
12654
12714
  return parsed.modules ?? [];
12655
12715
  }
12716
+ async resolveProvider(capability) {
12717
+ const claimants = await this.resolveProviders(capability);
12718
+ if (claimants.length === 0) {
12719
+ throw new SubcError(`no catalog claimant for capability ${capability}`, "capability_unprovided");
12720
+ }
12721
+ if (claimants.length > 1) {
12722
+ throw new SubcError(`multiple catalog claimants for capability ${capability}: ${claimants.join(", ")}`, "capability_ambiguous");
12723
+ }
12724
+ return claimants[0];
12725
+ }
12726
+ async resolveProviders(capability) {
12727
+ if (!isValidCapabilityIdentifier(capability)) {
12728
+ throw new SubcError(`malformed capability identifier ${JSON.stringify(capability)}`, "invalid_capability_identifier");
12729
+ }
12730
+ const modules = await this.catalogList();
12731
+ return modules.filter((module) => module.capabilities?.provides.includes(capability) ?? false).map((module) => module.module_id).sort();
12732
+ }
12656
12733
  async routeOpen(target, identity, opts = {}) {
12657
12734
  const consumerIdentity = routeOpenConsumerIdentity(opts);
12658
- const consumerCapabilities = opts.consumerCapabilities;
12735
+ const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
12736
+ reverseRequests.seal();
12737
+ const consumerCapabilities = reverseRequests.capabilities();
12659
12738
  const body = this.encode({
12660
12739
  op: "route.open",
12661
12740
  target,
12662
12741
  identity,
12663
12742
  ...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
12664
- ...consumerCapabilities !== undefined ? { consumer_capabilities: consumerCapabilities } : {}
12743
+ ...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {}
12665
12744
  });
12666
12745
  let installed = null;
12667
12746
  const install = (frame) => {
@@ -12671,7 +12750,7 @@ class SubcClient {
12671
12750
  if (typeof parsed.route_channel !== "number" || typeof parsed.route_epoch !== "number") {
12672
12751
  throw new SubcError(`route.open returned no route handle: ${JSON.stringify(parsed)}`);
12673
12752
  }
12674
- installed = this.installRoute(parsed.route_channel, parsed.route_epoch);
12753
+ installed = this.installRoute(parsed.route_channel, parsed.route_epoch, reverseRequests);
12675
12754
  return true;
12676
12755
  };
12677
12756
  const closeLateRoute = (frame) => {
@@ -12696,14 +12775,25 @@ class SubcClient {
12696
12775
  }
12697
12776
  async request(handle, body, opts = {}) {
12698
12777
  this.assertLiveHandle(handle);
12699
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
12778
+ const binary = opts.binary ?? false;
12779
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
12700
12780
  const priority = opts.priority ?? Priority.Interactive;
12701
12781
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
12702
- const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
12703
- return this.parseJson(reply);
12782
+ const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, undefined, undefined, binary, opts.signal);
12783
+ return this.decodeReply(reply);
12704
12784
  }
12705
12785
  async call(moduleId, method, params, opts = {}) {
12786
+ if (opts.binary) {
12787
+ throw new SubcError("call() builds a JSON body and cannot send a binary request; use callBinary(moduleId, body, opts) with a Uint8Array", "binary_call_requires_call_binary");
12788
+ }
12706
12789
  const body = params === undefined ? { method } : { method, params };
12790
+ return await this.managedCall(moduleId, body, opts);
12791
+ }
12792
+ async callBinary(moduleId, body, opts = {}) {
12793
+ const bytes = requireBinaryBody(body);
12794
+ return this.managedCall(moduleId, bytes, { ...opts, binary: true });
12795
+ }
12796
+ async managedCall(moduleId, body, opts) {
12707
12797
  let retriedUnknownChannel = false;
12708
12798
  for (;; ) {
12709
12799
  const routeHandle = await this.cachedRouteHandle(moduleId, opts);
@@ -12781,6 +12871,22 @@ class SubcClient {
12781
12871
  };
12782
12872
  return { unsubscribe, closed };
12783
12873
  }
12874
+ attachCancelSignal(signal, key, handle, corr, priority) {
12875
+ if (!handle)
12876
+ return;
12877
+ const emit = () => {
12878
+ if (!this.pending.has(key))
12879
+ return;
12880
+ try {
12881
+ this.cancel(handle, corr, priority);
12882
+ } catch {}
12883
+ };
12884
+ if (signal.aborted) {
12885
+ emit();
12886
+ return;
12887
+ }
12888
+ signal.addEventListener("abort", emit, { once: true });
12889
+ }
12784
12890
  cancel(handle, corr, priority = Priority.Interactive) {
12785
12891
  this.assertLiveHandle(handle);
12786
12892
  const cancel = buildFrame(FrameType.Cancel, buildFlags(false, priority, false), handle.channel, handle.epoch, corr, EMPTY_BODY);
@@ -12821,7 +12927,7 @@ class SubcClient {
12821
12927
  this.sendRouteGoodbye(handle);
12822
12928
  }
12823
12929
  async closeManagedRoute(target, identity, opts = {}) {
12824
- const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts));
12930
+ const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities());
12825
12931
  const cached = this.routes.get(key);
12826
12932
  if (!cached)
12827
12933
  return;
@@ -12889,7 +12995,7 @@ class SubcClient {
12889
12995
  async controlRpc(body, acceptFrame, onLateResponse) {
12890
12996
  return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
12891
12997
  }
12892
- send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse) {
12998
+ send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary = false, signal) {
12893
12999
  if (handle)
12894
13000
  this.assertLiveHandle(handle);
12895
13001
  if (this.closedErr)
@@ -12903,7 +13009,7 @@ class SubcClient {
12903
13009
  const key = pendingKey(handle, corr);
12904
13010
  const channel = handle?.channel ?? 0;
12905
13011
  const epoch = handle?.epoch ?? 0;
12906
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), channel, epoch, corr, body);
13012
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), channel, epoch, corr, body);
12907
13013
  return new Promise((resolve, reject) => {
12908
13014
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
12909
13015
  const pending = {
@@ -12917,6 +13023,8 @@ class SubcClient {
12917
13023
  };
12918
13024
  pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
12919
13025
  this.pending.set(key, pending);
13026
+ if (signal)
13027
+ this.attachCancelSignal(signal, key, handle, corr, priority);
12920
13028
  writeBorrowed(this.sock, encodeFrame(frame), Date.now() + ms).catch((error) => {
12921
13029
  const current = this.pending.get(key);
12922
13030
  if (current)
@@ -12944,19 +13052,20 @@ class SubcClient {
12944
13052
  setImmediate(arbitrate);
12945
13053
  }
12946
13054
  async managedRequest(handle, body, opts) {
12947
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
13055
+ const binary = opts.binary ?? false;
13056
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
12948
13057
  const priority = opts.priority ?? Priority.Interactive;
12949
13058
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
12950
13059
  try {
12951
- const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
12952
- return this.parseJson(reply);
13060
+ const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, binary, opts.signal);
13061
+ return this.decodeReply(reply);
12953
13062
  } catch (error) {
12954
13063
  if (error instanceof SubcCallError)
12955
13064
  throw error;
12956
13065
  throw this.terminalCallError("managed call failed", error);
12957
13066
  }
12958
13067
  }
12959
- sendManaged(handle, body, priority, admission, timeoutMs, onProgress) {
13068
+ sendManaged(handle, body, priority, admission, timeoutMs, onProgress, binary = false, signal) {
12960
13069
  try {
12961
13070
  this.assertLiveHandle(handle);
12962
13071
  } catch (error) {
@@ -12972,7 +13081,7 @@ class SubcClient {
12972
13081
  return Promise.reject(this.notSentCallError("request correlation allocator was exhausted", error));
12973
13082
  }
12974
13083
  const key = pendingKey(handle, corr);
12975
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, body);
13084
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), handle.channel, handle.epoch, corr, body);
12976
13085
  let handedToSocket = false;
12977
13086
  const classifyFailure = (error) => {
12978
13087
  if (!handedToSocket)
@@ -12994,6 +13103,8 @@ class SubcClient {
12994
13103
  };
12995
13104
  pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
12996
13105
  this.pending.set(key, pending);
13106
+ if (signal)
13107
+ this.attachCancelSignal(signal, key, handle, corr, priority);
12997
13108
  const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
12998
13109
  handedToSocket = write.queued;
12999
13110
  write.completed.catch((error) => {
@@ -13010,7 +13121,9 @@ class SubcClient {
13010
13121
  }
13011
13122
  const target = { kind: opts.targetKind ?? this.opts.targetKind, module_id: moduleId };
13012
13123
  const consumerIdentity = routeOpenConsumerIdentity(opts);
13013
- const key = routeCacheKey(target, identity, consumerIdentity);
13124
+ const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
13125
+ reverseRequests.seal();
13126
+ const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities());
13014
13127
  let cached = this.routes.get(key);
13015
13128
  if (!cached) {
13016
13129
  cached = {
@@ -13019,6 +13132,7 @@ class SubcClient {
13019
13132
  target,
13020
13133
  identity,
13021
13134
  consumerIdentity,
13135
+ reverseRequests,
13022
13136
  handle: null,
13023
13137
  opening: null
13024
13138
  };
@@ -13034,7 +13148,7 @@ class SubcClient {
13034
13148
  return cached.opening;
13035
13149
  }
13036
13150
  async openCachedRoute(cached) {
13037
- const routeRetryDeadline = Date.now() + ROUTE_OPEN_RETRY_DEADLINE_MS;
13151
+ const routeRetryDeadline = Date.now() + this.opts.routeOpenRetryDeadlineMs;
13038
13152
  let routeRetryDelay = this.opts.reconnectBackoff.baseMs;
13039
13153
  let routeRetryAttempt = 0;
13040
13154
  for (;; ) {
@@ -13049,7 +13163,8 @@ class SubcClient {
13049
13163
  return cached.handle;
13050
13164
  try {
13051
13165
  const handle = await this.routeOpen(cached.target, cached.identity, {
13052
- consumerIdentity: cached.consumerIdentity ?? null
13166
+ consumerIdentity: cached.consumerIdentity ?? null,
13167
+ reverseRequests: cached.reverseRequests
13053
13168
  });
13054
13169
  if (cached.closed) {
13055
13170
  this.liveRoutes.delete(handle.channel);
@@ -13071,12 +13186,12 @@ class SubcClient {
13071
13186
  }
13072
13187
  if (!this.closeStarted && error instanceof SubcError && isRetryableRouteOpenCode(error.code)) {
13073
13188
  routeRetryAttempt += 1;
13074
- if (routeRetryAttempt < this.opts.reconnectBackoff.maxAttempts && Date.now() < routeRetryDeadline) {
13189
+ if (Date.now() < routeRetryDeadline) {
13075
13190
  await this.opts.sleep(routeRetryDelay);
13076
13191
  routeRetryDelay = Math.min(routeRetryDelay * 2, this.opts.reconnectBackoff.capMs);
13077
13192
  continue;
13078
13193
  }
13079
- throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry budget exhausted)`, error);
13194
+ throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry deadline exhausted after ${routeRetryAttempt} attempts)`, error);
13080
13195
  }
13081
13196
  throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, error);
13082
13197
  }
@@ -13188,20 +13303,34 @@ class SubcClient {
13188
13303
  this.readLoop(opened.sock, this.generation);
13189
13304
  }
13190
13305
  async reopenCachedRoutes() {
13191
- for (const cached of this.routes.values())
13192
- cached.handle = null;
13193
- for (const cached of this.routes.values()) {
13194
- if (cached.closed)
13195
- continue;
13196
- const handle = await this.routeOpen(cached.target, cached.identity, {
13197
- consumerIdentity: cached.consumerIdentity ?? null
13198
- });
13199
- if (cached.closed) {
13200
- this.liveRoutes.delete(handle.channel);
13201
- this.sendRouteGoodbye(handle);
13306
+ const routeKeys = [...this.routes.keys()];
13307
+ for (const key of routeKeys) {
13308
+ const cached = this.routes.get(key);
13309
+ if (cached)
13310
+ cached.handle = null;
13311
+ }
13312
+ for (const key of routeKeys) {
13313
+ const cached = this.routes.get(key);
13314
+ if (!cached || cached.closed)
13202
13315
  continue;
13316
+ try {
13317
+ const handle = await this.routeOpen(cached.target, cached.identity, {
13318
+ consumerIdentity: cached.consumerIdentity ?? null,
13319
+ reverseRequests: cached.reverseRequests
13320
+ });
13321
+ if (cached.closed) {
13322
+ this.liveRoutes.delete(handle.channel);
13323
+ this.sendRouteGoodbye(handle);
13324
+ continue;
13325
+ }
13326
+ cached.handle = handle;
13327
+ } catch (error) {
13328
+ if (isRouteOpenRefusal(error)) {
13329
+ this.routes.delete(key);
13330
+ continue;
13331
+ }
13332
+ throw error;
13203
13333
  }
13204
- cached.handle = handle;
13205
13334
  }
13206
13335
  }
13207
13336
  timeoutMessage(channel, corr, ms) {
@@ -13259,6 +13388,10 @@ class SubcClient {
13259
13388
  return;
13260
13389
  }
13261
13390
  }
13391
+ if (frame.header.ty === FrameType.Request && handle) {
13392
+ this.dispatchReverseRequest(frame, handle);
13393
+ return;
13394
+ }
13262
13395
  const key = pendingKey(handle, frame.header.corr);
13263
13396
  const pending = this.pending.get(key);
13264
13397
  if (pending) {
@@ -13312,10 +13445,49 @@ class SubcClient {
13312
13445
  rejectPending(key, pending, err) {
13313
13446
  this.settle(key, pending, () => pending.reject(pending.classifyFailure?.(err) ?? err));
13314
13447
  }
13448
+ dispatchReverseRequest(frame, handle) {
13449
+ const method = reverseRequestMethod(frame.body);
13450
+ const methodFamily = method?.split("/", 1)[0];
13451
+ const handler = methodFamily ? reverseRequestHandler(handle, methodFamily) : undefined;
13452
+ const sock = this.sock;
13453
+ const generation = this.generation;
13454
+ const reply = async () => {
13455
+ if (!handler || !method) {
13456
+ await this.sendReverseError(frame, handle, "reverse_request_unhandled", methodFamily ? `no reverse-request handler is registered for ${methodFamily}` : "reverse request has no valid method", sock, generation);
13457
+ return;
13458
+ }
13459
+ let body;
13460
+ try {
13461
+ body = await handler(frame.body, { corr: frame.header.corr, method });
13462
+ if (!(body instanceof Uint8Array)) {
13463
+ throw new TypeError("reverse-request handler must return a Uint8Array");
13464
+ }
13465
+ } catch (error) {
13466
+ await this.sendReverseError(frame, handle, "reverse_request_unhandled", error instanceof Error ? error.message : String(error), sock, generation);
13467
+ return;
13468
+ }
13469
+ await this.sendReverseReply(frame, handle, FrameType.Response, body, sock, generation);
13470
+ };
13471
+ reply().catch((error) => {
13472
+ if (this.sock === sock && this.generation === generation && !this.closeStarted) {
13473
+ debug("failed to answer reverse request: %s", causeMessage(error));
13474
+ }
13475
+ });
13476
+ }
13477
+ async sendReverseError(request, handle, code, message, sock, generation) {
13478
+ await this.sendReverseReply(request, handle, FrameType.Error, this.encode({ code, message }), sock, generation);
13479
+ }
13480
+ async sendReverseReply(request, handle, type, body, sock, generation) {
13481
+ if (this.sock !== sock || this.generation !== generation || !this.isLiveHandle(handle) || this.closeStarted || this.closedErr) {
13482
+ return;
13483
+ }
13484
+ const frame = buildFrame(type, buildFlags(false, Priority.Interactive, false), handle.channel, handle.epoch, request.header.corr, body);
13485
+ await writeBorrowed(sock, encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS);
13486
+ }
13315
13487
  errorFromFrame(frame) {
13316
13488
  try {
13317
13489
  const parsed = JSON.parse(Buffer.from(frame.body).toString("utf8"));
13318
- return new SubcError(parsed.message ?? "subc error", parsed.code);
13490
+ return new SubcError(parsed.message ?? "subc error", parsed.code, parsed.detail);
13319
13491
  } catch {
13320
13492
  return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
13321
13493
  }
@@ -13360,8 +13532,8 @@ class SubcClient {
13360
13532
  get droppedIngressFrames() {
13361
13533
  return this.ingressEpochDropCount;
13362
13534
  }
13363
- installRoute(channel, epoch) {
13364
- const handle = createRouteHandle(channel, epoch, this.connectionToken);
13535
+ installRoute(channel, epoch, reverseRequests = new ReverseRequestRegistry) {
13536
+ const handle = createRouteHandle(channel, epoch, this.connectionToken, reverseRequests);
13365
13537
  this.liveRoutes.set(channel, handle);
13366
13538
  return handle;
13367
13539
  }
@@ -13398,6 +13570,9 @@ class SubcClient {
13398
13570
  encode(value) {
13399
13571
  return Buffer.from(JSON.stringify(value), "utf8");
13400
13572
  }
13573
+ decodeReply(frame) {
13574
+ return hasBinary(frame.header.flags) ? frame.body : this.parseJson(frame);
13575
+ }
13401
13576
  parseJson(frame) {
13402
13577
  const b = frame.body;
13403
13578
  return JSON.parse(Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString("utf8"));
@@ -13417,7 +13592,12 @@ function isConsumerReconnectTransient(err) {
13417
13592
  const code = errorCode(err);
13418
13593
  return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
13419
13594
  }
13595
+ function isRouteOpenRefusal(err) {
13596
+ return err instanceof SubcError && typeof err.code === "string";
13597
+ }
13420
13598
  function isRetryableRouteOpenCode(code) {
13599
+ if (code === "capability_forbidden")
13600
+ return false;
13421
13601
  return code === "unknown_module" || code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout";
13422
13602
  }
13423
13603
  async function connectionFileExists(path) {
@@ -13436,14 +13616,35 @@ function normalizeConnectOptions(opts) {
13436
13616
  targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
13437
13617
  reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
13438
13618
  sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
13619
+ routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
13439
13620
  timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
13440
13621
  livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
13441
13622
  onControlPush: opts.onControlPush
13442
13623
  };
13443
13624
  }
13444
- function routeCacheKey(target, identity, consumerIdentity) {
13625
+ function isValidCapabilityIdentifier(identifier) {
13626
+ if (/\s/u.test(identifier))
13627
+ return false;
13628
+ const separator = identifier.indexOf("/v");
13629
+ if (separator <= 0 || identifier.indexOf("/v", separator + 1) !== -1)
13630
+ return false;
13631
+ const name = identifier.slice(0, separator);
13632
+ const version = identifier.slice(separator + 2);
13633
+ if (new TextEncoder().encode(name).byteLength > 64 || version.length === 0)
13634
+ return false;
13635
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/u.test(name) && !/^[a-z]$/u.test(name))
13636
+ return false;
13637
+ if (name.includes("--"))
13638
+ return false;
13639
+ if (!/^\d+$/u.test(version) || version.length > 1 && version.startsWith("0"))
13640
+ return false;
13641
+ const numericVersion = Number(version);
13642
+ return Number.isSafeInteger(numericVersion) && numericVersion >= 1 && numericVersion <= 4294967295;
13643
+ }
13644
+ function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities) {
13445
13645
  const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
13446
- return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}`;
13646
+ const capabilitiesPart = [...consumerCapabilities].sort().join("\x00");
13647
+ return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}\x00${capabilitiesPart}`;
13447
13648
  }
13448
13649
  function routeOpenConsumerIdentity(opts = {}) {
13449
13650
  if (opts.consumerIdentity !== undefined)
@@ -13470,6 +13671,14 @@ function causeMessage(cause) {
13470
13671
  function pendingKey(handle, corr) {
13471
13672
  return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
13472
13673
  }
13674
+ function reverseRequestMethod(body) {
13675
+ try {
13676
+ const parsed = JSON.parse(Buffer.from(body).toString("utf8"));
13677
+ return typeof parsed.method === "string" && parsed.method.length > 0 ? parsed.method : undefined;
13678
+ } catch {
13679
+ return;
13680
+ }
13681
+ }
13473
13682
  // ../aft-bridge/dist/subc-transport.js
13474
13683
  import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
13475
13684
 
@@ -13957,8 +14166,16 @@ var AFT_MODULE_ID = "aft";
13957
14166
  var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
13958
14167
  var RECONNECT_RETRY_FLOOR_MS = 100;
13959
14168
  var RECONNECT_RETRY_CAP_MS = 2000;
13960
- var ROUTE_OPEN_RELOAD_WAIT_CAP_MS = 15000;
13961
- var ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX = " The AFT daemon module did not return within the 15s reload window.";
14169
+ var SUBC_DEFAULT_REQUEST_TIMEOUT_MS = 30000;
14170
+ var ROUTE_OPEN_RELOAD_WAIT_CEILING_MS = 45000;
14171
+ function reloadWaitExhaustedSuffix(callDeadlineMs) {
14172
+ if (callDeadlineMs > ROUTE_OPEN_RELOAD_WAIT_CEILING_MS) {
14173
+ const ceilingSeconds = ROUTE_OPEN_RELOAD_WAIT_CEILING_MS / 1000;
14174
+ return ` The AFT daemon module did not return within the ${ceilingSeconds}s reload-wait ceiling.`;
14175
+ }
14176
+ const seconds = Math.round(callDeadlineMs / 100) / 10;
14177
+ return ` The AFT daemon module did not return within this call's ${seconds}s deadline.`;
14178
+ }
13962
14179
  var BG_STABLE_MS = 5000;
13963
14180
  function reconnectBackoffMs(attempt) {
13964
14181
  return Math.min(RECONNECT_RETRY_FLOOR_MS * 2 ** Math.min(attempt, 6), RECONNECT_RETRY_CAP_MS);
@@ -14267,14 +14484,15 @@ function isAbsentRootRouteError(err) {
14267
14484
  function absentRootError(root) {
14268
14485
  return new SubcError(`invalid route project root: project root does not exist: ${root}`, "config_divergence");
14269
14486
  }
14270
- function reloadWindowExhaustedError(error) {
14487
+ function reloadWindowExhaustedError(error, callDeadlineMs) {
14488
+ const suffix = reloadWaitExhaustedSuffix(callDeadlineMs);
14271
14489
  if (error instanceof Error) {
14272
14490
  try {
14273
- error.message += ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX;
14491
+ error.message += suffix;
14274
14492
  return error;
14275
14493
  } catch {}
14276
14494
  }
14277
- return new Error(`${String(error)}${ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX}`);
14495
+ return new Error(`${String(error)}${suffix}`);
14278
14496
  }
14279
14497
  function safeCloseRoute(client, route) {
14280
14498
  try {
@@ -14879,18 +15097,20 @@ class SubcTransportPool {
14879
15097
  throw error;
14880
15098
  }
14881
15099
  };
15100
+ const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
15101
+ const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
15102
+ let reloadWaitedMs = 0;
14882
15103
  const openRouteAfterReloadWindow = async () => {
14883
- let reloadWaitedMs = 0;
14884
15104
  while (true) {
14885
15105
  try {
14886
15106
  return await openRoute();
14887
15107
  } catch (error) {
14888
15108
  if (!isRouteOpenReloadWindowError(error))
14889
15109
  throw error;
14890
- const { delayMs, wait } = this.waitForRouteReopenBackoff();
14891
- if (reloadWaitedMs + delayMs > ROUTE_OPEN_RELOAD_WAIT_CAP_MS) {
14892
- throw reloadWindowExhaustedError(error);
15110
+ if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
15111
+ throw reloadWindowExhaustedError(error, callDeadlineMs);
14893
15112
  }
15113
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
14894
15114
  reloadWaitedMs += delayMs;
14895
15115
  await wait;
14896
15116
  }
@@ -14936,7 +15156,8 @@ class SubcTransportPool {
14936
15156
  };
14937
15157
  abortSignal?.addEventListener("abort", onAbort, { once: true });
14938
15158
  try {
14939
- const request = client.request(route, body, { timeoutMs, onProgress });
15159
+ const requestTimeoutMs = reloadWaitedMs > 0 ? callDeadlineMs - reloadWaitedMs : timeoutMs;
15160
+ const request = client.request(route, body, { timeoutMs: requestTimeoutMs, onProgress });
14940
15161
  const reply = abortSignal ? await Promise.race([request, aborted]) : await request;
14941
15162
  if (!this.isCurrentSession(key, record)) {
14942
15163
  if (this.isReapInduced(record))
@@ -14953,28 +15174,41 @@ class SubcTransportPool {
14953
15174
  }
14954
15175
  };
14955
15176
  let routeAndEntry = await openRouteAfterReloadWindow();
14956
- try {
14957
- return await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
14958
- } catch (error) {
14959
- if (this.isReapInduced(record))
14960
- throw this.annotateReapError(error, record);
14961
- if (isRouteProvenAbsentError(error) && this.isCurrentSession(key, record) && this.client === client) {
14962
- clearRouteEntry(routeAndEntry.entry);
14963
- await this.waitForRouteReopenBackoff().wait;
14964
- routeAndEntry = await openRouteAfterReloadWindow();
14965
- try {
14966
- const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
15177
+ let reopened = false;
15178
+ let retriedAbsentRoute = false;
15179
+ while (true) {
15180
+ try {
15181
+ const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
15182
+ if (reopened)
14967
15183
  this.resetRouteReopenBackoff();
14968
- return reply;
14969
- } catch (retryError) {
14970
- if (this.isReapInduced(record))
14971
- throw this.annotateReapError(retryError, record);
14972
- handleRequestFailure(retryError, routeAndEntry.entry);
14973
- throw retryError;
15184
+ return reply;
15185
+ } catch (error) {
15186
+ if (this.isReapInduced(record))
15187
+ throw this.annotateReapError(error, record);
15188
+ const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
15189
+ if (ownsRoute && isRouteRequestReloadRefusal(error)) {
15190
+ clearRouteEntry(routeAndEntry.entry);
15191
+ if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
15192
+ throw reloadWindowExhaustedError(error, callDeadlineMs);
15193
+ }
15194
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
15195
+ reloadWaitedMs += delayMs;
15196
+ await wait;
15197
+ routeAndEntry = await openRouteAfterReloadWindow();
15198
+ reopened = true;
15199
+ continue;
15200
+ }
15201
+ if (ownsRoute && !retriedAbsentRoute && isRouteProvenAbsentError(error)) {
15202
+ retriedAbsentRoute = true;
15203
+ clearRouteEntry(routeAndEntry.entry);
15204
+ await this.waitForRouteReopenBackoff().wait;
15205
+ routeAndEntry = await openRouteAfterReloadWindow();
15206
+ reopened = true;
15207
+ continue;
14974
15208
  }
15209
+ handleRequestFailure(error, routeAndEntry.entry);
15210
+ throw error;
14975
15211
  }
14976
- handleRequestFailure(error, routeAndEntry.entry);
14977
- throw error;
14978
15212
  }
14979
15213
  } catch (error) {
14980
15214
  if (this.isReapInduced(record))
@@ -15003,6 +15237,11 @@ class SubcTransportPool {
15003
15237
  this.routeReopenRetryMs = delayMs;
15004
15238
  return { delayMs, wait: retry };
15005
15239
  }
15240
+ nextRouteReopenDelayMs() {
15241
+ if (this.routeReopenRetry && this.routeReopenRetryMs !== null)
15242
+ return this.routeReopenRetryMs;
15243
+ return this.routeReopenRetryDelayMs;
15244
+ }
15006
15245
  resetRouteReopenBackoff() {
15007
15246
  this.routeReopenRetryDelayMs = RECONNECT_RETRY_FLOOR_MS;
15008
15247
  }
@@ -15294,6 +15533,12 @@ function isRouteOpenReloadWindowError(error) {
15294
15533
  const code = error.code;
15295
15534
  return code === "module_reloading" || code === "module_warming" || code === "target_unavailable";
15296
15535
  }
15536
+ function isRouteRequestReloadRefusal(error) {
15537
+ if (error instanceof SubcCallError) {
15538
+ return error.kind === "not_sent" && error.code === "module_reloading";
15539
+ }
15540
+ return error instanceof SubcError && error.code === "module_reloading";
15541
+ }
15297
15542
  function hasEngineResponse(error) {
15298
15543
  const response = error.response;
15299
15544
  if (response !== null && typeof response === "object")
@@ -23322,6 +23567,13 @@ function sessionWarn(sessionId, message, data) {
23322
23567
  function sessionError(sessionId, message, data) {
23323
23568
  write("ERROR", message, data, sessionId);
23324
23569
  }
23570
+ function flushLog() {
23571
+ if (flushTimer) {
23572
+ clearTimeout(flushTimer);
23573
+ flushTimer = null;
23574
+ }
23575
+ flush();
23576
+ }
23325
23577
  var bridgeLogger = {
23326
23578
  log(message, meta) {
23327
23579
  sessionLog(meta?.sessionId, message);
@@ -42272,6 +42524,7 @@ function coerceAftStatus(response) {
42272
42524
  },
42273
42525
  compression: readCompression(response.compression),
42274
42526
  status_bar: readStatusBar(response.status_bar),
42527
+ status_bar_values: readStatusBar(response.status_bar_values),
42275
42528
  message: readString(response.message, "")
42276
42529
  };
42277
42530
  }
@@ -42615,14 +42868,13 @@ function isSystemTempPath(target) {
42615
42868
  const normalizedTarget = normalizePath(target);
42616
42869
  return systemTempRoots().some((root) => containsPath(normalizePath(root), normalizedTarget));
42617
42870
  }
42618
- function isBashTaskArtifactPath(target) {
42619
- const storageRoot = normalizePath(resolveAftStorageRoot());
42620
- const normalizedTarget = normalizePath(target);
42621
- if (!containsPath(storageRoot, normalizedTarget))
42871
+ async function isSessionOwnedBashArtifact(ctx, context, target) {
42872
+ try {
42873
+ const response = await callBridge(ctx, context, "bash_artifact_owned", { path: target });
42874
+ return response.success !== false && response.owned === true;
42875
+ } catch {
42622
42876
  return false;
42623
- const relative = normalizedTarget.slice(storageRoot.length).replace(/^[/\\]+/, "");
42624
- const segments = relative.split(/[/\\]+/);
42625
- return segments.length === 4 && segments[1] === "bash-tasks";
42877
+ }
42626
42878
  }
42627
42879
  function windowsPath(p) {
42628
42880
  if (process.platform !== "win32")
@@ -42686,7 +42938,7 @@ async function assertExternalDirectoryPermission(ctx, context, target, options)
42686
42938
  }
42687
42939
  if (isSystemTempPath(absoluteTarget))
42688
42940
  return;
42689
- if (options?.serverValidatedRead === true && isBashTaskArtifactPath(absoluteTarget)) {
42941
+ if (options?.serverValidatedRead === true && await isSessionOwnedBashArtifact(ctx, context, absoluteTarget)) {
42690
42942
  return;
42691
42943
  }
42692
42944
  if (typeof context.ask !== "function")
@@ -43261,6 +43513,9 @@ var z4 = tool.schema;
43261
43513
  var METADATA_PREVIEW_LIMIT = 30 * 1024;
43262
43514
  var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
43263
43515
  var BASH_TRANSPORT_MARGIN_MS = 1e4;
43516
+ var ABORT_REGISTRATION_WAIT_MS = 5000;
43517
+ var ABORT_REGISTRATION_POLL_MS = 25;
43518
+ var ABORT_ATTEMPT_TIMEOUT_MS = 1000;
43264
43519
  function resolveForegroundWaitMs(configured) {
43265
43520
  const override = process.env.AFT_TEST_FOREGROUND_WAIT_MS;
43266
43521
  if (override !== undefined) {
@@ -43274,20 +43529,75 @@ function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeou
43274
43529
  const waitBudget = blockToCompletion || wait ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : foregroundWaitMs;
43275
43530
  return waitBudget + BASH_TRANSPORT_MARGIN_MS;
43276
43531
  }
43532
+ async function abortForegroundWhenRegistered(ctx, runtime, disposed) {
43533
+ const startedAt = Date.now();
43534
+ const deadline = startedAt + ABORT_REGISTRATION_WAIT_MS;
43535
+ const results = [];
43536
+ let attempts = 0;
43537
+ let lastResponse;
43538
+ let lastError;
43539
+ let outcome = "timed_out";
43540
+ while (Date.now() < deadline) {
43541
+ if (disposed()) {
43542
+ outcome = "call_settled";
43543
+ break;
43544
+ }
43545
+ attempts += 1;
43546
+ try {
43547
+ lastResponse = await callBashBridge(ctx, runtime, "bash_abort_inflight", {}, {
43548
+ transportTimeoutMs: Math.max(1, Math.min(ABORT_ATTEMPT_TIMEOUT_MS, deadline - Date.now()))
43549
+ });
43550
+ lastError = undefined;
43551
+ recordAbortAttempt(results, `killed=${String(lastResponse.killed)}`);
43552
+ if (typeof lastResponse.killed === "number" && lastResponse.killed > 0) {
43553
+ outcome = "killed";
43554
+ break;
43555
+ }
43556
+ } catch (error) {
43557
+ lastError = error instanceof Error ? error.message : String(error);
43558
+ recordAbortAttempt(results, `error=${lastError}`);
43559
+ }
43560
+ if (disposed()) {
43561
+ outcome = "call_settled";
43562
+ break;
43563
+ }
43564
+ if (Date.now() >= deadline)
43565
+ break;
43566
+ await sleep(Math.min(ABORT_REGISTRATION_POLL_MS, deadline - Date.now()));
43567
+ }
43568
+ sessionLog(runtime.sessionID, `[bash] foreground abort ${outcome}`, {
43569
+ attempts,
43570
+ elapsed_ms: Date.now() - startedAt,
43571
+ results,
43572
+ ...results.length < attempts ? { results_omitted: attempts - results.length } : {},
43573
+ last_response: lastResponse,
43574
+ last_error: lastError
43575
+ });
43576
+ flushLog();
43577
+ }
43578
+ var ABORT_ATTEMPT_RESULTS_KEPT = 20;
43579
+ function recordAbortAttempt(results, result) {
43580
+ if (results.length < ABORT_ATTEMPT_RESULTS_KEPT)
43581
+ results.push(result);
43582
+ }
43277
43583
  function listenForForegroundAbort(ctx, runtime, enabled) {
43278
43584
  if (!enabled)
43279
43585
  return () => {};
43280
43586
  let fired = false;
43587
+ let disposed = false;
43281
43588
  const onAbort = () => {
43282
43589
  if (fired)
43283
43590
  return;
43284
43591
  fired = true;
43285
- callBashBridge(ctx, runtime, "bash_abort_inflight").catch(() => {});
43592
+ abortForegroundWhenRegistered(ctx, runtime, () => disposed);
43286
43593
  };
43287
43594
  runtime.abort.addEventListener("abort", onAbort, { once: true });
43288
43595
  if (runtime.abort.aborted)
43289
43596
  onAbort();
43290
- return () => runtime.abort.removeEventListener("abort", onAbort);
43597
+ return () => {
43598
+ disposed = true;
43599
+ runtime.abort.removeEventListener("abort", onAbort);
43600
+ };
43291
43601
  }
43292
43602
  function userMessageDetachDescription(detachOnUserMessage) {
43293
43603
  return detachOnUserMessage ? "A new user message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`." : "Because `bash.detach_on_user_message` is false, a new user message leaves this wait blocking; include the literal `&detach` anywhere to force detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`.";