@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.
package/dist/index.js CHANGED
@@ -10270,17 +10270,17 @@ async function renameIfPresent(from, to) {
10270
10270
  throw error;
10271
10271
  }
10272
10272
  }
10273
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/client.js
10273
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/client.js
10274
10274
  import { promises as fs3 } from "node:fs";
10275
10275
  import { debuglog } from "node:util";
10276
10276
 
10277
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/auth.js
10277
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/auth.js
10278
10278
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
10279
10279
 
10280
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/socket.js
10280
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/socket.js
10281
10281
  import net from "node:net";
10282
10282
 
10283
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/envelope.js
10283
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/envelope.js
10284
10284
  var PROTOCOL_VERSION = 2;
10285
10285
  var HEADER_LEN = 21;
10286
10286
  var FROZEN_PREFIX_LEN = 5;
@@ -10322,7 +10322,7 @@ var FLAG_PRIORITY_SHIFT = 1;
10322
10322
  var FLAG_LAST = 8;
10323
10323
  var FLAG_ADMISSION_MASK = 48;
10324
10324
  var FLAG_ADMISSION_SHIFT = 4;
10325
- var FLAG_RESERVED_MASK = 192;
10325
+ var FLAG_RESERVED_MASK = 0;
10326
10326
  function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Normal) {
10327
10327
  let flags = 0;
10328
10328
  if (binary)
@@ -10333,6 +10333,9 @@ function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Norm
10333
10333
  flags |= admissionClass << FLAG_ADMISSION_SHIFT;
10334
10334
  return flags;
10335
10335
  }
10336
+ function hasBinary(flags) {
10337
+ return (flags & FLAG_BINARY) !== 0;
10338
+ }
10336
10339
  function encodeHeader(header) {
10337
10340
  const buffer = new Uint8Array(HEADER_LEN);
10338
10341
  const view = new DataView(buffer.buffer);
@@ -10449,7 +10452,7 @@ function encodeFrame(frame) {
10449
10452
  return output;
10450
10453
  }
10451
10454
 
10452
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/socket.js
10455
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/socket.js
10453
10456
  class SocketClosedError extends Error {
10454
10457
  }
10455
10458
 
@@ -10682,7 +10685,7 @@ class SubcSocket {
10682
10685
  }
10683
10686
  }
10684
10687
 
10685
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/auth.js
10688
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/auth.js
10686
10689
  var NONCE_LEN = 32;
10687
10690
  var MAX_AUTH_MESSAGE_LEN = 4096;
10688
10691
  var SERVER_PROOF_DOMAIN = "subc-server-v1";
@@ -10756,7 +10759,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
10756
10759
  await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
10757
10760
  }
10758
10761
 
10759
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/connection-file.js
10762
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/connection-file.js
10760
10763
  import { promises as fs2 } from "node:fs";
10761
10764
  var SCHEMA_VERSION = 1;
10762
10765
  var MIN_KEY_LEN = 32;
@@ -10834,13 +10837,49 @@ async function readConnectionFile(path) {
10834
10837
  return info;
10835
10838
  }
10836
10839
 
10837
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/route-handle.js
10840
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/route-handle.js
10838
10841
  var connectionToken = new WeakMap;
10842
+ var reverseRequests = new WeakMap;
10843
+
10844
+ class ReverseRequestRegistrationError extends Error {
10845
+ methodFamily;
10846
+ code = "reverse_request_capability_not_declared";
10847
+ constructor(methodFamily) {
10848
+ super(`reverse-request capability ${JSON.stringify(methodFamily)} was not registered before route.open`);
10849
+ this.methodFamily = methodFamily;
10850
+ this.name = "ReverseRequestRegistrationError";
10851
+ }
10852
+ }
10853
+
10854
+ class ReverseRequestRegistry {
10855
+ handlers = new Map;
10856
+ declaredFamilies = null;
10857
+ onRequest(methodFamily, handler) {
10858
+ if (!isMethodFamily(methodFamily)) {
10859
+ throw new TypeError(`reverse-request method family must be a capability identifier, got ${JSON.stringify(methodFamily)}`);
10860
+ }
10861
+ if (typeof handler !== "function")
10862
+ throw new TypeError("reverse-request handler must be a function");
10863
+ if (this.declaredFamilies && !this.declaredFamilies.has(methodFamily)) {
10864
+ throw new ReverseRequestRegistrationError(methodFamily);
10865
+ }
10866
+ this.handlers.set(methodFamily, handler);
10867
+ }
10868
+ capabilities() {
10869
+ return [...this.handlers.keys()].sort();
10870
+ }
10871
+ handler(methodFamily) {
10872
+ return this.handlers.get(methodFamily);
10873
+ }
10874
+ seal() {
10875
+ this.declaredFamilies ??= new Set(this.handlers.keys());
10876
+ }
10877
+ }
10839
10878
 
10840
10879
  class RouteHandle {
10841
10880
  channel;
10842
10881
  epoch;
10843
- constructor(channel, epoch, token) {
10882
+ constructor(channel, epoch, token, registry) {
10844
10883
  if (!Number.isInteger(channel) || channel <= 0 || channel > 65535) {
10845
10884
  throw new RangeError(`route channel must be an integer in 1..65535, got ${channel}`);
10846
10885
  }
@@ -10849,16 +10888,27 @@ class RouteHandle {
10849
10888
  }
10850
10889
  this.channel = channel;
10851
10890
  this.epoch = epoch;
10891
+ registry.seal();
10852
10892
  connectionToken.set(this, token);
10893
+ reverseRequests.set(this, registry);
10853
10894
  Object.freeze(this);
10854
10895
  }
10855
- static create(channel, epoch, token) {
10856
- return new RouteHandle(channel, epoch, token);
10896
+ onRequest(methodFamily, handler) {
10897
+ reverseRequests.get(this).onRequest(methodFamily, handler);
10898
+ }
10899
+ static create(channel, epoch, token, registry) {
10900
+ return new RouteHandle(channel, epoch, token, registry);
10857
10901
  }
10858
10902
  }
10859
- function createRouteHandle(channel, epoch, token) {
10903
+ function createRouteHandle(channel, epoch, token, registry = new ReverseRequestRegistry) {
10860
10904
  const factory = RouteHandle;
10861
- return factory.create(channel, epoch, token);
10905
+ return factory.create(channel, epoch, token, registry);
10906
+ }
10907
+ function reverseRequestHandler(handle, methodFamily) {
10908
+ return reverseRequests.get(handle)?.handler(methodFamily);
10909
+ }
10910
+ function isMethodFamily(value) {
10911
+ return /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(value);
10862
10912
  }
10863
10913
 
10864
10914
  class StaleRouteHandleError extends Error {
@@ -10880,7 +10930,7 @@ function sameRouteHandle(left, right) {
10880
10930
  return left === right;
10881
10931
  }
10882
10932
 
10883
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/client.js
10933
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/client.js
10884
10934
  var debug = debuglog("subc-client");
10885
10935
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
10886
10936
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
@@ -10911,16 +10961,26 @@ class SubcCallError extends Error {
10911
10961
  this.cause = cause;
10912
10962
  this.name = "SubcCallError";
10913
10963
  }
10964
+ get detail() {
10965
+ return this.cause instanceof SubcError ? this.cause.detail : undefined;
10966
+ }
10914
10967
  }
10915
10968
 
10916
10969
  class SubcError extends Error {
10917
10970
  code;
10918
- constructor(message, code) {
10971
+ detail;
10972
+ constructor(message, code, detail) {
10919
10973
  super(message);
10920
10974
  this.code = code;
10975
+ this.detail = detail;
10921
10976
  }
10922
10977
  }
10923
-
10978
+ function requireBinaryBody(body) {
10979
+ if (body instanceof Uint8Array)
10980
+ return body;
10981
+ const type = body === null ? "null" : Array.isArray(body) ? "array" : typeof body;
10982
+ throw new SubcError(`binary request body must be a Uint8Array; got ${type}`, "binary_body_required");
10983
+ }
10924
10984
  class SubcClient {
10925
10985
  sock;
10926
10986
  currentConn;
@@ -10957,15 +11017,34 @@ class SubcClient {
10957
11017
  const parsed = this.parseJson(reply);
10958
11018
  return parsed.modules ?? [];
10959
11019
  }
11020
+ async resolveProvider(capability) {
11021
+ const claimants = await this.resolveProviders(capability);
11022
+ if (claimants.length === 0) {
11023
+ throw new SubcError(`no catalog claimant for capability ${capability}`, "capability_unprovided");
11024
+ }
11025
+ if (claimants.length > 1) {
11026
+ throw new SubcError(`multiple catalog claimants for capability ${capability}: ${claimants.join(", ")}`, "capability_ambiguous");
11027
+ }
11028
+ return claimants[0];
11029
+ }
11030
+ async resolveProviders(capability) {
11031
+ if (!isValidCapabilityIdentifier(capability)) {
11032
+ throw new SubcError(`malformed capability identifier ${JSON.stringify(capability)}`, "invalid_capability_identifier");
11033
+ }
11034
+ const modules = await this.catalogList();
11035
+ return modules.filter((module) => module.capabilities?.provides.includes(capability) ?? false).map((module) => module.module_id).sort();
11036
+ }
10960
11037
  async routeOpen(target, identity, opts = {}) {
10961
11038
  const consumerIdentity = routeOpenConsumerIdentity(opts);
10962
- const consumerCapabilities = opts.consumerCapabilities;
11039
+ const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
11040
+ reverseRequests.seal();
11041
+ const consumerCapabilities = reverseRequests.capabilities();
10963
11042
  const body = this.encode({
10964
11043
  op: "route.open",
10965
11044
  target,
10966
11045
  identity,
10967
11046
  ...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
10968
- ...consumerCapabilities !== undefined ? { consumer_capabilities: consumerCapabilities } : {}
11047
+ ...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {}
10969
11048
  });
10970
11049
  let installed = null;
10971
11050
  const install = (frame) => {
@@ -10975,7 +11054,7 @@ class SubcClient {
10975
11054
  if (typeof parsed.route_channel !== "number" || typeof parsed.route_epoch !== "number") {
10976
11055
  throw new SubcError(`route.open returned no route handle: ${JSON.stringify(parsed)}`);
10977
11056
  }
10978
- installed = this.installRoute(parsed.route_channel, parsed.route_epoch);
11057
+ installed = this.installRoute(parsed.route_channel, parsed.route_epoch, reverseRequests);
10979
11058
  return true;
10980
11059
  };
10981
11060
  const closeLateRoute = (frame) => {
@@ -11000,14 +11079,25 @@ class SubcClient {
11000
11079
  }
11001
11080
  async request(handle, body, opts = {}) {
11002
11081
  this.assertLiveHandle(handle);
11003
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
11082
+ const binary = opts.binary ?? false;
11083
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
11004
11084
  const priority = opts.priority ?? Priority.Interactive;
11005
11085
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
11006
- const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
11007
- return this.parseJson(reply);
11086
+ const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, undefined, undefined, binary, opts.signal);
11087
+ return this.decodeReply(reply);
11008
11088
  }
11009
11089
  async call(moduleId, method, params, opts = {}) {
11090
+ if (opts.binary) {
11091
+ 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");
11092
+ }
11010
11093
  const body = params === undefined ? { method } : { method, params };
11094
+ return await this.managedCall(moduleId, body, opts);
11095
+ }
11096
+ async callBinary(moduleId, body, opts = {}) {
11097
+ const bytes = requireBinaryBody(body);
11098
+ return this.managedCall(moduleId, bytes, { ...opts, binary: true });
11099
+ }
11100
+ async managedCall(moduleId, body, opts) {
11011
11101
  let retriedUnknownChannel = false;
11012
11102
  for (;; ) {
11013
11103
  const routeHandle = await this.cachedRouteHandle(moduleId, opts);
@@ -11085,6 +11175,22 @@ class SubcClient {
11085
11175
  };
11086
11176
  return { unsubscribe, closed };
11087
11177
  }
11178
+ attachCancelSignal(signal, key, handle, corr, priority) {
11179
+ if (!handle)
11180
+ return;
11181
+ const emit = () => {
11182
+ if (!this.pending.has(key))
11183
+ return;
11184
+ try {
11185
+ this.cancel(handle, corr, priority);
11186
+ } catch {}
11187
+ };
11188
+ if (signal.aborted) {
11189
+ emit();
11190
+ return;
11191
+ }
11192
+ signal.addEventListener("abort", emit, { once: true });
11193
+ }
11088
11194
  cancel(handle, corr, priority = Priority.Interactive) {
11089
11195
  this.assertLiveHandle(handle);
11090
11196
  const cancel = buildFrame(FrameType.Cancel, buildFlags(false, priority, false), handle.channel, handle.epoch, corr, EMPTY_BODY);
@@ -11125,7 +11231,7 @@ class SubcClient {
11125
11231
  this.sendRouteGoodbye(handle);
11126
11232
  }
11127
11233
  async closeManagedRoute(target, identity, opts = {}) {
11128
- const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts));
11234
+ const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities());
11129
11235
  const cached = this.routes.get(key);
11130
11236
  if (!cached)
11131
11237
  return;
@@ -11193,7 +11299,7 @@ class SubcClient {
11193
11299
  async controlRpc(body, acceptFrame, onLateResponse) {
11194
11300
  return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
11195
11301
  }
11196
- send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse) {
11302
+ send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary = false, signal) {
11197
11303
  if (handle)
11198
11304
  this.assertLiveHandle(handle);
11199
11305
  if (this.closedErr)
@@ -11207,7 +11313,7 @@ class SubcClient {
11207
11313
  const key = pendingKey(handle, corr);
11208
11314
  const channel = handle?.channel ?? 0;
11209
11315
  const epoch = handle?.epoch ?? 0;
11210
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), channel, epoch, corr, body);
11316
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), channel, epoch, corr, body);
11211
11317
  return new Promise((resolve, reject) => {
11212
11318
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
11213
11319
  const pending = {
@@ -11221,6 +11327,8 @@ class SubcClient {
11221
11327
  };
11222
11328
  pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
11223
11329
  this.pending.set(key, pending);
11330
+ if (signal)
11331
+ this.attachCancelSignal(signal, key, handle, corr, priority);
11224
11332
  writeBorrowed(this.sock, encodeFrame(frame), Date.now() + ms).catch((error) => {
11225
11333
  const current = this.pending.get(key);
11226
11334
  if (current)
@@ -11248,19 +11356,20 @@ class SubcClient {
11248
11356
  setImmediate(arbitrate);
11249
11357
  }
11250
11358
  async managedRequest(handle, body, opts) {
11251
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
11359
+ const binary = opts.binary ?? false;
11360
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
11252
11361
  const priority = opts.priority ?? Priority.Interactive;
11253
11362
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
11254
11363
  try {
11255
- const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
11256
- return this.parseJson(reply);
11364
+ const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, binary, opts.signal);
11365
+ return this.decodeReply(reply);
11257
11366
  } catch (error) {
11258
11367
  if (error instanceof SubcCallError)
11259
11368
  throw error;
11260
11369
  throw this.terminalCallError("managed call failed", error);
11261
11370
  }
11262
11371
  }
11263
- sendManaged(handle, body, priority, admission, timeoutMs, onProgress) {
11372
+ sendManaged(handle, body, priority, admission, timeoutMs, onProgress, binary = false, signal) {
11264
11373
  try {
11265
11374
  this.assertLiveHandle(handle);
11266
11375
  } catch (error) {
@@ -11276,7 +11385,7 @@ class SubcClient {
11276
11385
  return Promise.reject(this.notSentCallError("request correlation allocator was exhausted", error));
11277
11386
  }
11278
11387
  const key = pendingKey(handle, corr);
11279
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, body);
11388
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), handle.channel, handle.epoch, corr, body);
11280
11389
  let handedToSocket = false;
11281
11390
  const classifyFailure = (error) => {
11282
11391
  if (!handedToSocket)
@@ -11298,6 +11407,8 @@ class SubcClient {
11298
11407
  };
11299
11408
  pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
11300
11409
  this.pending.set(key, pending);
11410
+ if (signal)
11411
+ this.attachCancelSignal(signal, key, handle, corr, priority);
11301
11412
  const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
11302
11413
  handedToSocket = write.queued;
11303
11414
  write.completed.catch((error) => {
@@ -11314,7 +11425,9 @@ class SubcClient {
11314
11425
  }
11315
11426
  const target = { kind: opts.targetKind ?? this.opts.targetKind, module_id: moduleId };
11316
11427
  const consumerIdentity = routeOpenConsumerIdentity(opts);
11317
- const key = routeCacheKey(target, identity, consumerIdentity);
11428
+ const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
11429
+ reverseRequests.seal();
11430
+ const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities());
11318
11431
  let cached = this.routes.get(key);
11319
11432
  if (!cached) {
11320
11433
  cached = {
@@ -11323,6 +11436,7 @@ class SubcClient {
11323
11436
  target,
11324
11437
  identity,
11325
11438
  consumerIdentity,
11439
+ reverseRequests,
11326
11440
  handle: null,
11327
11441
  opening: null
11328
11442
  };
@@ -11338,7 +11452,7 @@ class SubcClient {
11338
11452
  return cached.opening;
11339
11453
  }
11340
11454
  async openCachedRoute(cached) {
11341
- const routeRetryDeadline = Date.now() + ROUTE_OPEN_RETRY_DEADLINE_MS;
11455
+ const routeRetryDeadline = Date.now() + this.opts.routeOpenRetryDeadlineMs;
11342
11456
  let routeRetryDelay = this.opts.reconnectBackoff.baseMs;
11343
11457
  let routeRetryAttempt = 0;
11344
11458
  for (;; ) {
@@ -11353,7 +11467,8 @@ class SubcClient {
11353
11467
  return cached.handle;
11354
11468
  try {
11355
11469
  const handle = await this.routeOpen(cached.target, cached.identity, {
11356
- consumerIdentity: cached.consumerIdentity ?? null
11470
+ consumerIdentity: cached.consumerIdentity ?? null,
11471
+ reverseRequests: cached.reverseRequests
11357
11472
  });
11358
11473
  if (cached.closed) {
11359
11474
  this.liveRoutes.delete(handle.channel);
@@ -11375,12 +11490,12 @@ class SubcClient {
11375
11490
  }
11376
11491
  if (!this.closeStarted && error instanceof SubcError && isRetryableRouteOpenCode(error.code)) {
11377
11492
  routeRetryAttempt += 1;
11378
- if (routeRetryAttempt < this.opts.reconnectBackoff.maxAttempts && Date.now() < routeRetryDeadline) {
11493
+ if (Date.now() < routeRetryDeadline) {
11379
11494
  await this.opts.sleep(routeRetryDelay);
11380
11495
  routeRetryDelay = Math.min(routeRetryDelay * 2, this.opts.reconnectBackoff.capMs);
11381
11496
  continue;
11382
11497
  }
11383
- throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry budget exhausted)`, error);
11498
+ throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry deadline exhausted after ${routeRetryAttempt} attempts)`, error);
11384
11499
  }
11385
11500
  throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, error);
11386
11501
  }
@@ -11492,20 +11607,34 @@ class SubcClient {
11492
11607
  this.readLoop(opened.sock, this.generation);
11493
11608
  }
11494
11609
  async reopenCachedRoutes() {
11495
- for (const cached of this.routes.values())
11496
- cached.handle = null;
11497
- for (const cached of this.routes.values()) {
11498
- if (cached.closed)
11499
- continue;
11500
- const handle = await this.routeOpen(cached.target, cached.identity, {
11501
- consumerIdentity: cached.consumerIdentity ?? null
11502
- });
11503
- if (cached.closed) {
11504
- this.liveRoutes.delete(handle.channel);
11505
- this.sendRouteGoodbye(handle);
11610
+ const routeKeys = [...this.routes.keys()];
11611
+ for (const key of routeKeys) {
11612
+ const cached = this.routes.get(key);
11613
+ if (cached)
11614
+ cached.handle = null;
11615
+ }
11616
+ for (const key of routeKeys) {
11617
+ const cached = this.routes.get(key);
11618
+ if (!cached || cached.closed)
11506
11619
  continue;
11620
+ try {
11621
+ const handle = await this.routeOpen(cached.target, cached.identity, {
11622
+ consumerIdentity: cached.consumerIdentity ?? null,
11623
+ reverseRequests: cached.reverseRequests
11624
+ });
11625
+ if (cached.closed) {
11626
+ this.liveRoutes.delete(handle.channel);
11627
+ this.sendRouteGoodbye(handle);
11628
+ continue;
11629
+ }
11630
+ cached.handle = handle;
11631
+ } catch (error) {
11632
+ if (isRouteOpenRefusal(error)) {
11633
+ this.routes.delete(key);
11634
+ continue;
11635
+ }
11636
+ throw error;
11507
11637
  }
11508
- cached.handle = handle;
11509
11638
  }
11510
11639
  }
11511
11640
  timeoutMessage(channel, corr, ms) {
@@ -11563,6 +11692,10 @@ class SubcClient {
11563
11692
  return;
11564
11693
  }
11565
11694
  }
11695
+ if (frame.header.ty === FrameType.Request && handle) {
11696
+ this.dispatchReverseRequest(frame, handle);
11697
+ return;
11698
+ }
11566
11699
  const key = pendingKey(handle, frame.header.corr);
11567
11700
  const pending = this.pending.get(key);
11568
11701
  if (pending) {
@@ -11616,10 +11749,49 @@ class SubcClient {
11616
11749
  rejectPending(key, pending, err) {
11617
11750
  this.settle(key, pending, () => pending.reject(pending.classifyFailure?.(err) ?? err));
11618
11751
  }
11752
+ dispatchReverseRequest(frame, handle) {
11753
+ const method = reverseRequestMethod(frame.body);
11754
+ const methodFamily = method?.split("/", 1)[0];
11755
+ const handler = methodFamily ? reverseRequestHandler(handle, methodFamily) : undefined;
11756
+ const sock = this.sock;
11757
+ const generation = this.generation;
11758
+ const reply = async () => {
11759
+ if (!handler || !method) {
11760
+ 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);
11761
+ return;
11762
+ }
11763
+ let body;
11764
+ try {
11765
+ body = await handler(frame.body, { corr: frame.header.corr, method });
11766
+ if (!(body instanceof Uint8Array)) {
11767
+ throw new TypeError("reverse-request handler must return a Uint8Array");
11768
+ }
11769
+ } catch (error) {
11770
+ await this.sendReverseError(frame, handle, "reverse_request_unhandled", error instanceof Error ? error.message : String(error), sock, generation);
11771
+ return;
11772
+ }
11773
+ await this.sendReverseReply(frame, handle, FrameType.Response, body, sock, generation);
11774
+ };
11775
+ reply().catch((error) => {
11776
+ if (this.sock === sock && this.generation === generation && !this.closeStarted) {
11777
+ debug("failed to answer reverse request: %s", causeMessage(error));
11778
+ }
11779
+ });
11780
+ }
11781
+ async sendReverseError(request, handle, code, message, sock, generation) {
11782
+ await this.sendReverseReply(request, handle, FrameType.Error, this.encode({ code, message }), sock, generation);
11783
+ }
11784
+ async sendReverseReply(request, handle, type, body, sock, generation) {
11785
+ if (this.sock !== sock || this.generation !== generation || !this.isLiveHandle(handle) || this.closeStarted || this.closedErr) {
11786
+ return;
11787
+ }
11788
+ const frame = buildFrame(type, buildFlags(false, Priority.Interactive, false), handle.channel, handle.epoch, request.header.corr, body);
11789
+ await writeBorrowed(sock, encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS);
11790
+ }
11619
11791
  errorFromFrame(frame) {
11620
11792
  try {
11621
11793
  const parsed = JSON.parse(Buffer.from(frame.body).toString("utf8"));
11622
- return new SubcError(parsed.message ?? "subc error", parsed.code);
11794
+ return new SubcError(parsed.message ?? "subc error", parsed.code, parsed.detail);
11623
11795
  } catch {
11624
11796
  return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
11625
11797
  }
@@ -11664,8 +11836,8 @@ class SubcClient {
11664
11836
  get droppedIngressFrames() {
11665
11837
  return this.ingressEpochDropCount;
11666
11838
  }
11667
- installRoute(channel, epoch) {
11668
- const handle = createRouteHandle(channel, epoch, this.connectionToken);
11839
+ installRoute(channel, epoch, reverseRequests = new ReverseRequestRegistry) {
11840
+ const handle = createRouteHandle(channel, epoch, this.connectionToken, reverseRequests);
11669
11841
  this.liveRoutes.set(channel, handle);
11670
11842
  return handle;
11671
11843
  }
@@ -11702,6 +11874,9 @@ class SubcClient {
11702
11874
  encode(value) {
11703
11875
  return Buffer.from(JSON.stringify(value), "utf8");
11704
11876
  }
11877
+ decodeReply(frame) {
11878
+ return hasBinary(frame.header.flags) ? frame.body : this.parseJson(frame);
11879
+ }
11705
11880
  parseJson(frame) {
11706
11881
  const b = frame.body;
11707
11882
  return JSON.parse(Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString("utf8"));
@@ -11721,7 +11896,12 @@ function isConsumerReconnectTransient(err) {
11721
11896
  const code = errorCode(err);
11722
11897
  return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
11723
11898
  }
11899
+ function isRouteOpenRefusal(err) {
11900
+ return err instanceof SubcError && typeof err.code === "string";
11901
+ }
11724
11902
  function isRetryableRouteOpenCode(code) {
11903
+ if (code === "capability_forbidden")
11904
+ return false;
11725
11905
  return code === "unknown_module" || code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout";
11726
11906
  }
11727
11907
  async function connectionFileExists(path) {
@@ -11740,14 +11920,35 @@ function normalizeConnectOptions(opts) {
11740
11920
  targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
11741
11921
  reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
11742
11922
  sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
11923
+ routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
11743
11924
  timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
11744
11925
  livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
11745
11926
  onControlPush: opts.onControlPush
11746
11927
  };
11747
11928
  }
11748
- function routeCacheKey(target, identity, consumerIdentity) {
11929
+ function isValidCapabilityIdentifier(identifier) {
11930
+ if (/\s/u.test(identifier))
11931
+ return false;
11932
+ const separator = identifier.indexOf("/v");
11933
+ if (separator <= 0 || identifier.indexOf("/v", separator + 1) !== -1)
11934
+ return false;
11935
+ const name = identifier.slice(0, separator);
11936
+ const version = identifier.slice(separator + 2);
11937
+ if (new TextEncoder().encode(name).byteLength > 64 || version.length === 0)
11938
+ return false;
11939
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/u.test(name) && !/^[a-z]$/u.test(name))
11940
+ return false;
11941
+ if (name.includes("--"))
11942
+ return false;
11943
+ if (!/^\d+$/u.test(version) || version.length > 1 && version.startsWith("0"))
11944
+ return false;
11945
+ const numericVersion = Number(version);
11946
+ return Number.isSafeInteger(numericVersion) && numericVersion >= 1 && numericVersion <= 4294967295;
11947
+ }
11948
+ function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities) {
11749
11949
  const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
11750
- return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}`;
11950
+ const capabilitiesPart = [...consumerCapabilities].sort().join("\x00");
11951
+ return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}\x00${capabilitiesPart}`;
11751
11952
  }
11752
11953
  function routeOpenConsumerIdentity(opts = {}) {
11753
11954
  if (opts.consumerIdentity !== undefined)
@@ -11774,6 +11975,14 @@ function causeMessage(cause) {
11774
11975
  function pendingKey(handle, corr) {
11775
11976
  return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
11776
11977
  }
11978
+ function reverseRequestMethod(body) {
11979
+ try {
11980
+ const parsed = JSON.parse(Buffer.from(body).toString("utf8"));
11981
+ return typeof parsed.method === "string" && parsed.method.length > 0 ? parsed.method : undefined;
11982
+ } catch {
11983
+ return;
11984
+ }
11985
+ }
11777
11986
  // ../aft-bridge/dist/subc-transport.js
11778
11987
  import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
11779
11988
 
@@ -12261,8 +12470,16 @@ var AFT_MODULE_ID = "aft";
12261
12470
  var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
12262
12471
  var RECONNECT_RETRY_FLOOR_MS = 100;
12263
12472
  var RECONNECT_RETRY_CAP_MS = 2000;
12264
- var ROUTE_OPEN_RELOAD_WAIT_CAP_MS = 15000;
12265
- var ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX = " The AFT daemon module did not return within the 15s reload window.";
12473
+ var SUBC_DEFAULT_REQUEST_TIMEOUT_MS = 30000;
12474
+ var ROUTE_OPEN_RELOAD_WAIT_CEILING_MS = 45000;
12475
+ function reloadWaitExhaustedSuffix(callDeadlineMs) {
12476
+ if (callDeadlineMs > ROUTE_OPEN_RELOAD_WAIT_CEILING_MS) {
12477
+ const ceilingSeconds = ROUTE_OPEN_RELOAD_WAIT_CEILING_MS / 1000;
12478
+ return ` The AFT daemon module did not return within the ${ceilingSeconds}s reload-wait ceiling.`;
12479
+ }
12480
+ const seconds = Math.round(callDeadlineMs / 100) / 10;
12481
+ return ` The AFT daemon module did not return within this call's ${seconds}s deadline.`;
12482
+ }
12266
12483
  var BG_STABLE_MS = 5000;
12267
12484
  function reconnectBackoffMs(attempt) {
12268
12485
  return Math.min(RECONNECT_RETRY_FLOOR_MS * 2 ** Math.min(attempt, 6), RECONNECT_RETRY_CAP_MS);
@@ -12571,14 +12788,15 @@ function isAbsentRootRouteError(err) {
12571
12788
  function absentRootError(root) {
12572
12789
  return new SubcError(`invalid route project root: project root does not exist: ${root}`, "config_divergence");
12573
12790
  }
12574
- function reloadWindowExhaustedError(error) {
12791
+ function reloadWindowExhaustedError(error, callDeadlineMs) {
12792
+ const suffix = reloadWaitExhaustedSuffix(callDeadlineMs);
12575
12793
  if (error instanceof Error) {
12576
12794
  try {
12577
- error.message += ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX;
12795
+ error.message += suffix;
12578
12796
  return error;
12579
12797
  } catch {}
12580
12798
  }
12581
- return new Error(`${String(error)}${ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX}`);
12799
+ return new Error(`${String(error)}${suffix}`);
12582
12800
  }
12583
12801
  function safeCloseRoute(client, route) {
12584
12802
  try {
@@ -13183,18 +13401,20 @@ class SubcTransportPool {
13183
13401
  throw error;
13184
13402
  }
13185
13403
  };
13404
+ const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
13405
+ const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
13406
+ let reloadWaitedMs = 0;
13186
13407
  const openRouteAfterReloadWindow = async () => {
13187
- let reloadWaitedMs = 0;
13188
13408
  while (true) {
13189
13409
  try {
13190
13410
  return await openRoute();
13191
13411
  } catch (error) {
13192
13412
  if (!isRouteOpenReloadWindowError(error))
13193
13413
  throw error;
13194
- const { delayMs, wait } = this.waitForRouteReopenBackoff();
13195
- if (reloadWaitedMs + delayMs > ROUTE_OPEN_RELOAD_WAIT_CAP_MS) {
13196
- throw reloadWindowExhaustedError(error);
13414
+ if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
13415
+ throw reloadWindowExhaustedError(error, callDeadlineMs);
13197
13416
  }
13417
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
13198
13418
  reloadWaitedMs += delayMs;
13199
13419
  await wait;
13200
13420
  }
@@ -13240,7 +13460,8 @@ class SubcTransportPool {
13240
13460
  };
13241
13461
  abortSignal?.addEventListener("abort", onAbort, { once: true });
13242
13462
  try {
13243
- const request = client.request(route, body, { timeoutMs, onProgress });
13463
+ const requestTimeoutMs = reloadWaitedMs > 0 ? callDeadlineMs - reloadWaitedMs : timeoutMs;
13464
+ const request = client.request(route, body, { timeoutMs: requestTimeoutMs, onProgress });
13244
13465
  const reply = abortSignal ? await Promise.race([request, aborted]) : await request;
13245
13466
  if (!this.isCurrentSession(key, record)) {
13246
13467
  if (this.isReapInduced(record))
@@ -13257,28 +13478,41 @@ class SubcTransportPool {
13257
13478
  }
13258
13479
  };
13259
13480
  let routeAndEntry = await openRouteAfterReloadWindow();
13260
- try {
13261
- return await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
13262
- } catch (error) {
13263
- if (this.isReapInduced(record))
13264
- throw this.annotateReapError(error, record);
13265
- if (isRouteProvenAbsentError(error) && this.isCurrentSession(key, record) && this.client === client) {
13266
- clearRouteEntry(routeAndEntry.entry);
13267
- await this.waitForRouteReopenBackoff().wait;
13268
- routeAndEntry = await openRouteAfterReloadWindow();
13269
- try {
13270
- const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
13481
+ let reopened = false;
13482
+ let retriedAbsentRoute = false;
13483
+ while (true) {
13484
+ try {
13485
+ const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
13486
+ if (reopened)
13271
13487
  this.resetRouteReopenBackoff();
13272
- return reply;
13273
- } catch (retryError) {
13274
- if (this.isReapInduced(record))
13275
- throw this.annotateReapError(retryError, record);
13276
- handleRequestFailure(retryError, routeAndEntry.entry);
13277
- throw retryError;
13488
+ return reply;
13489
+ } catch (error) {
13490
+ if (this.isReapInduced(record))
13491
+ throw this.annotateReapError(error, record);
13492
+ const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
13493
+ if (ownsRoute && isRouteRequestReloadRefusal(error)) {
13494
+ clearRouteEntry(routeAndEntry.entry);
13495
+ if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
13496
+ throw reloadWindowExhaustedError(error, callDeadlineMs);
13497
+ }
13498
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
13499
+ reloadWaitedMs += delayMs;
13500
+ await wait;
13501
+ routeAndEntry = await openRouteAfterReloadWindow();
13502
+ reopened = true;
13503
+ continue;
13504
+ }
13505
+ if (ownsRoute && !retriedAbsentRoute && isRouteProvenAbsentError(error)) {
13506
+ retriedAbsentRoute = true;
13507
+ clearRouteEntry(routeAndEntry.entry);
13508
+ await this.waitForRouteReopenBackoff().wait;
13509
+ routeAndEntry = await openRouteAfterReloadWindow();
13510
+ reopened = true;
13511
+ continue;
13278
13512
  }
13513
+ handleRequestFailure(error, routeAndEntry.entry);
13514
+ throw error;
13279
13515
  }
13280
- handleRequestFailure(error, routeAndEntry.entry);
13281
- throw error;
13282
13516
  }
13283
13517
  } catch (error) {
13284
13518
  if (this.isReapInduced(record))
@@ -13307,6 +13541,11 @@ class SubcTransportPool {
13307
13541
  this.routeReopenRetryMs = delayMs;
13308
13542
  return { delayMs, wait: retry };
13309
13543
  }
13544
+ nextRouteReopenDelayMs() {
13545
+ if (this.routeReopenRetry && this.routeReopenRetryMs !== null)
13546
+ return this.routeReopenRetryMs;
13547
+ return this.routeReopenRetryDelayMs;
13548
+ }
13310
13549
  resetRouteReopenBackoff() {
13311
13550
  this.routeReopenRetryDelayMs = RECONNECT_RETRY_FLOOR_MS;
13312
13551
  }
@@ -13598,6 +13837,12 @@ function isRouteOpenReloadWindowError(error) {
13598
13837
  const code = error.code;
13599
13838
  return code === "module_reloading" || code === "module_warming" || code === "target_unavailable";
13600
13839
  }
13840
+ function isRouteRequestReloadRefusal(error) {
13841
+ if (error instanceof SubcCallError) {
13842
+ return error.kind === "not_sent" && error.code === "module_reloading";
13843
+ }
13844
+ return error instanceof SubcError && error.code === "module_reloading";
13845
+ }
13601
13846
  function hasEngineResponse(error) {
13602
13847
  const response = error.response;
13603
13848
  if (response !== null && typeof response === "object")
@@ -21626,6 +21871,13 @@ function sessionWarn(sessionId, message, data) {
21626
21871
  function sessionError(sessionId, message, data) {
21627
21872
  write("ERROR", message, data, sessionId);
21628
21873
  }
21874
+ function flushLog() {
21875
+ if (flushTimer) {
21876
+ clearTimeout(flushTimer);
21877
+ flushTimer = null;
21878
+ }
21879
+ flush();
21880
+ }
21629
21881
  var bridgeLogger = {
21630
21882
  log(message, meta) {
21631
21883
  sessionLog(meta?.sessionId, message);
@@ -40576,6 +40828,7 @@ function coerceAftStatus(response) {
40576
40828
  },
40577
40829
  compression: readCompression(response.compression),
40578
40830
  status_bar: readStatusBar(response.status_bar),
40831
+ status_bar_values: readStatusBar(response.status_bar_values),
40579
40832
  message: readString(response.message, "")
40580
40833
  };
40581
40834
  }
@@ -40919,14 +41172,13 @@ function isSystemTempPath(target) {
40919
41172
  const normalizedTarget = normalizePath(target);
40920
41173
  return systemTempRoots().some((root) => containsPath(normalizePath(root), normalizedTarget));
40921
41174
  }
40922
- function isBashTaskArtifactPath(target) {
40923
- const storageRoot = normalizePath(resolveAftStorageRoot());
40924
- const normalizedTarget = normalizePath(target);
40925
- if (!containsPath(storageRoot, normalizedTarget))
41175
+ async function isSessionOwnedBashArtifact(ctx, context, target) {
41176
+ try {
41177
+ const response = await callBridge(ctx, context, "bash_artifact_owned", { path: target });
41178
+ return response.success !== false && response.owned === true;
41179
+ } catch {
40926
41180
  return false;
40927
- const relative = normalizedTarget.slice(storageRoot.length).replace(/^[/\\]+/, "");
40928
- const segments = relative.split(/[/\\]+/);
40929
- return segments.length === 4 && segments[1] === "bash-tasks";
41181
+ }
40930
41182
  }
40931
41183
  function windowsPath(p) {
40932
41184
  if (process.platform !== "win32")
@@ -40990,7 +41242,7 @@ async function assertExternalDirectoryPermission(ctx, context, target, options)
40990
41242
  }
40991
41243
  if (isSystemTempPath(absoluteTarget))
40992
41244
  return;
40993
- if (options?.serverValidatedRead === true && isBashTaskArtifactPath(absoluteTarget)) {
41245
+ if (options?.serverValidatedRead === true && await isSessionOwnedBashArtifact(ctx, context, absoluteTarget)) {
40994
41246
  return;
40995
41247
  }
40996
41248
  if (typeof context.ask !== "function")
@@ -41565,6 +41817,9 @@ var z4 = tool.schema;
41565
41817
  var METADATA_PREVIEW_LIMIT = 30 * 1024;
41566
41818
  var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
41567
41819
  var BASH_TRANSPORT_MARGIN_MS = 1e4;
41820
+ var ABORT_REGISTRATION_WAIT_MS = 5000;
41821
+ var ABORT_REGISTRATION_POLL_MS = 25;
41822
+ var ABORT_ATTEMPT_TIMEOUT_MS = 1000;
41568
41823
  function resolveForegroundWaitMs(configured) {
41569
41824
  const override = process.env.AFT_TEST_FOREGROUND_WAIT_MS;
41570
41825
  if (override !== undefined) {
@@ -41578,20 +41833,75 @@ function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeou
41578
41833
  const waitBudget = blockToCompletion || wait ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : foregroundWaitMs;
41579
41834
  return waitBudget + BASH_TRANSPORT_MARGIN_MS;
41580
41835
  }
41836
+ async function abortForegroundWhenRegistered(ctx, runtime, disposed) {
41837
+ const startedAt = Date.now();
41838
+ const deadline = startedAt + ABORT_REGISTRATION_WAIT_MS;
41839
+ const results = [];
41840
+ let attempts = 0;
41841
+ let lastResponse;
41842
+ let lastError;
41843
+ let outcome = "timed_out";
41844
+ while (Date.now() < deadline) {
41845
+ if (disposed()) {
41846
+ outcome = "call_settled";
41847
+ break;
41848
+ }
41849
+ attempts += 1;
41850
+ try {
41851
+ lastResponse = await callBashBridge(ctx, runtime, "bash_abort_inflight", {}, {
41852
+ transportTimeoutMs: Math.max(1, Math.min(ABORT_ATTEMPT_TIMEOUT_MS, deadline - Date.now()))
41853
+ });
41854
+ lastError = undefined;
41855
+ recordAbortAttempt(results, `killed=${String(lastResponse.killed)}`);
41856
+ if (typeof lastResponse.killed === "number" && lastResponse.killed > 0) {
41857
+ outcome = "killed";
41858
+ break;
41859
+ }
41860
+ } catch (error) {
41861
+ lastError = error instanceof Error ? error.message : String(error);
41862
+ recordAbortAttempt(results, `error=${lastError}`);
41863
+ }
41864
+ if (disposed()) {
41865
+ outcome = "call_settled";
41866
+ break;
41867
+ }
41868
+ if (Date.now() >= deadline)
41869
+ break;
41870
+ await sleep(Math.min(ABORT_REGISTRATION_POLL_MS, deadline - Date.now()));
41871
+ }
41872
+ sessionLog(runtime.sessionID, `[bash] foreground abort ${outcome}`, {
41873
+ attempts,
41874
+ elapsed_ms: Date.now() - startedAt,
41875
+ results,
41876
+ ...results.length < attempts ? { results_omitted: attempts - results.length } : {},
41877
+ last_response: lastResponse,
41878
+ last_error: lastError
41879
+ });
41880
+ flushLog();
41881
+ }
41882
+ var ABORT_ATTEMPT_RESULTS_KEPT = 20;
41883
+ function recordAbortAttempt(results, result) {
41884
+ if (results.length < ABORT_ATTEMPT_RESULTS_KEPT)
41885
+ results.push(result);
41886
+ }
41581
41887
  function listenForForegroundAbort(ctx, runtime, enabled) {
41582
41888
  if (!enabled)
41583
41889
  return () => {};
41584
41890
  let fired = false;
41891
+ let disposed = false;
41585
41892
  const onAbort = () => {
41586
41893
  if (fired)
41587
41894
  return;
41588
41895
  fired = true;
41589
- callBashBridge(ctx, runtime, "bash_abort_inflight").catch(() => {});
41896
+ abortForegroundWhenRegistered(ctx, runtime, () => disposed);
41590
41897
  };
41591
41898
  runtime.abort.addEventListener("abort", onAbort, { once: true });
41592
41899
  if (runtime.abort.aborted)
41593
41900
  onAbort();
41594
- return () => runtime.abort.removeEventListener("abort", onAbort);
41901
+ return () => {
41902
+ disposed = true;
41903
+ runtime.abort.removeEventListener("abort", onAbort);
41904
+ };
41595
41905
  }
41596
41906
  function userMessageDetachDescription(detachOnUserMessage) {
41597
41907
  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)`.";