@cortexkit/aft-pi 0.57.0 → 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.
Files changed (2) hide show
  1. package/dist/index.js +335 -90
  2. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -10642,17 +10642,17 @@ ${reflowText}`;
10642
10642
  }
10643
10643
  return " Auto-formatted.";
10644
10644
  }
10645
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/client.js
10645
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/client.js
10646
10646
  import { promises as fs3 } from "node:fs";
10647
10647
  import { debuglog } from "node:util";
10648
10648
 
10649
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/auth.js
10649
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/auth.js
10650
10650
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
10651
10651
 
10652
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/socket.js
10652
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/socket.js
10653
10653
  import net from "node:net";
10654
10654
 
10655
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/envelope.js
10655
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/envelope.js
10656
10656
  var PROTOCOL_VERSION = 2;
10657
10657
  var HEADER_LEN = 21;
10658
10658
  var FROZEN_PREFIX_LEN = 5;
@@ -10694,7 +10694,7 @@ var FLAG_PRIORITY_SHIFT = 1;
10694
10694
  var FLAG_LAST = 8;
10695
10695
  var FLAG_ADMISSION_MASK = 48;
10696
10696
  var FLAG_ADMISSION_SHIFT = 4;
10697
- var FLAG_RESERVED_MASK = 192;
10697
+ var FLAG_RESERVED_MASK = 0;
10698
10698
  function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Normal) {
10699
10699
  let flags = 0;
10700
10700
  if (binary)
@@ -10705,6 +10705,9 @@ function buildFlags(binary, priority, last, admissionClass = AdmissionClass.Norm
10705
10705
  flags |= admissionClass << FLAG_ADMISSION_SHIFT;
10706
10706
  return flags;
10707
10707
  }
10708
+ function hasBinary(flags) {
10709
+ return (flags & FLAG_BINARY) !== 0;
10710
+ }
10708
10711
  function encodeHeader(header) {
10709
10712
  const buffer = new Uint8Array(HEADER_LEN);
10710
10713
  const view = new DataView(buffer.buffer);
@@ -10821,7 +10824,7 @@ function encodeFrame(frame) {
10821
10824
  return output;
10822
10825
  }
10823
10826
 
10824
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/socket.js
10827
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/socket.js
10825
10828
  class SocketClosedError extends Error {
10826
10829
  }
10827
10830
 
@@ -11054,7 +11057,7 @@ class SubcSocket {
11054
11057
  }
11055
11058
  }
11056
11059
 
11057
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/auth.js
11060
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/auth.js
11058
11061
  var NONCE_LEN = 32;
11059
11062
  var MAX_AUTH_MESSAGE_LEN = 4096;
11060
11063
  var SERVER_PROOF_DOMAIN = "subc-server-v1";
@@ -11128,7 +11131,7 @@ async function authenticateClient(sock, conn, deadlineMs) {
11128
11131
  await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
11129
11132
  }
11130
11133
 
11131
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/connection-file.js
11134
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/connection-file.js
11132
11135
  import { promises as fs2 } from "node:fs";
11133
11136
  var SCHEMA_VERSION = 1;
11134
11137
  var MIN_KEY_LEN = 32;
@@ -11206,13 +11209,49 @@ async function readConnectionFile(path) {
11206
11209
  return info;
11207
11210
  }
11208
11211
 
11209
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/route-handle.js
11212
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/route-handle.js
11210
11213
  var connectionToken = new WeakMap;
11214
+ var reverseRequests = new WeakMap;
11215
+
11216
+ class ReverseRequestRegistrationError extends Error {
11217
+ methodFamily;
11218
+ code = "reverse_request_capability_not_declared";
11219
+ constructor(methodFamily) {
11220
+ super(`reverse-request capability ${JSON.stringify(methodFamily)} was not registered before route.open`);
11221
+ this.methodFamily = methodFamily;
11222
+ this.name = "ReverseRequestRegistrationError";
11223
+ }
11224
+ }
11225
+
11226
+ class ReverseRequestRegistry {
11227
+ handlers = new Map;
11228
+ declaredFamilies = null;
11229
+ onRequest(methodFamily, handler) {
11230
+ if (!isMethodFamily(methodFamily)) {
11231
+ throw new TypeError(`reverse-request method family must be a capability identifier, got ${JSON.stringify(methodFamily)}`);
11232
+ }
11233
+ if (typeof handler !== "function")
11234
+ throw new TypeError("reverse-request handler must be a function");
11235
+ if (this.declaredFamilies && !this.declaredFamilies.has(methodFamily)) {
11236
+ throw new ReverseRequestRegistrationError(methodFamily);
11237
+ }
11238
+ this.handlers.set(methodFamily, handler);
11239
+ }
11240
+ capabilities() {
11241
+ return [...this.handlers.keys()].sort();
11242
+ }
11243
+ handler(methodFamily) {
11244
+ return this.handlers.get(methodFamily);
11245
+ }
11246
+ seal() {
11247
+ this.declaredFamilies ??= new Set(this.handlers.keys());
11248
+ }
11249
+ }
11211
11250
 
11212
11251
  class RouteHandle {
11213
11252
  channel;
11214
11253
  epoch;
11215
- constructor(channel, epoch, token) {
11254
+ constructor(channel, epoch, token, registry) {
11216
11255
  if (!Number.isInteger(channel) || channel <= 0 || channel > 65535) {
11217
11256
  throw new RangeError(`route channel must be an integer in 1..65535, got ${channel}`);
11218
11257
  }
@@ -11221,16 +11260,27 @@ class RouteHandle {
11221
11260
  }
11222
11261
  this.channel = channel;
11223
11262
  this.epoch = epoch;
11263
+ registry.seal();
11224
11264
  connectionToken.set(this, token);
11265
+ reverseRequests.set(this, registry);
11225
11266
  Object.freeze(this);
11226
11267
  }
11227
- static create(channel, epoch, token) {
11228
- return new RouteHandle(channel, epoch, token);
11268
+ onRequest(methodFamily, handler) {
11269
+ reverseRequests.get(this).onRequest(methodFamily, handler);
11270
+ }
11271
+ static create(channel, epoch, token, registry) {
11272
+ return new RouteHandle(channel, epoch, token, registry);
11229
11273
  }
11230
11274
  }
11231
- function createRouteHandle(channel, epoch, token) {
11275
+ function createRouteHandle(channel, epoch, token, registry = new ReverseRequestRegistry) {
11232
11276
  const factory = RouteHandle;
11233
- return factory.create(channel, epoch, token);
11277
+ return factory.create(channel, epoch, token, registry);
11278
+ }
11279
+ function reverseRequestHandler(handle, methodFamily) {
11280
+ return reverseRequests.get(handle)?.handler(methodFamily);
11281
+ }
11282
+ function isMethodFamily(value) {
11283
+ return /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(value);
11234
11284
  }
11235
11285
 
11236
11286
  class StaleRouteHandleError extends Error {
@@ -11252,7 +11302,7 @@ function sameRouteHandle(left, right) {
11252
11302
  return left === right;
11253
11303
  }
11254
11304
 
11255
- // ../../node_modules/.bun/@cortexkit+subc-client@0.8.1/node_modules/@cortexkit/subc-client/dist/client.js
11305
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.13.1/node_modules/@cortexkit/subc-client/dist/client.js
11256
11306
  var debug = debuglog("subc-client");
11257
11307
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
11258
11308
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
@@ -11283,16 +11333,26 @@ class SubcCallError extends Error {
11283
11333
  this.cause = cause;
11284
11334
  this.name = "SubcCallError";
11285
11335
  }
11336
+ get detail() {
11337
+ return this.cause instanceof SubcError ? this.cause.detail : undefined;
11338
+ }
11286
11339
  }
11287
11340
 
11288
11341
  class SubcError extends Error {
11289
11342
  code;
11290
- constructor(message, code) {
11343
+ detail;
11344
+ constructor(message, code, detail) {
11291
11345
  super(message);
11292
11346
  this.code = code;
11347
+ this.detail = detail;
11293
11348
  }
11294
11349
  }
11295
-
11350
+ function requireBinaryBody(body) {
11351
+ if (body instanceof Uint8Array)
11352
+ return body;
11353
+ const type = body === null ? "null" : Array.isArray(body) ? "array" : typeof body;
11354
+ throw new SubcError(`binary request body must be a Uint8Array; got ${type}`, "binary_body_required");
11355
+ }
11296
11356
  class SubcClient {
11297
11357
  sock;
11298
11358
  currentConn;
@@ -11329,15 +11389,34 @@ class SubcClient {
11329
11389
  const parsed = this.parseJson(reply);
11330
11390
  return parsed.modules ?? [];
11331
11391
  }
11392
+ async resolveProvider(capability) {
11393
+ const claimants = await this.resolveProviders(capability);
11394
+ if (claimants.length === 0) {
11395
+ throw new SubcError(`no catalog claimant for capability ${capability}`, "capability_unprovided");
11396
+ }
11397
+ if (claimants.length > 1) {
11398
+ throw new SubcError(`multiple catalog claimants for capability ${capability}: ${claimants.join(", ")}`, "capability_ambiguous");
11399
+ }
11400
+ return claimants[0];
11401
+ }
11402
+ async resolveProviders(capability) {
11403
+ if (!isValidCapabilityIdentifier(capability)) {
11404
+ throw new SubcError(`malformed capability identifier ${JSON.stringify(capability)}`, "invalid_capability_identifier");
11405
+ }
11406
+ const modules = await this.catalogList();
11407
+ return modules.filter((module) => module.capabilities?.provides.includes(capability) ?? false).map((module) => module.module_id).sort();
11408
+ }
11332
11409
  async routeOpen(target, identity, opts = {}) {
11333
11410
  const consumerIdentity = routeOpenConsumerIdentity(opts);
11334
- const consumerCapabilities = opts.consumerCapabilities;
11411
+ const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
11412
+ reverseRequests.seal();
11413
+ const consumerCapabilities = reverseRequests.capabilities();
11335
11414
  const body = this.encode({
11336
11415
  op: "route.open",
11337
11416
  target,
11338
11417
  identity,
11339
11418
  ...consumerIdentity ? { consumer_identity: consumerIdentity } : {},
11340
- ...consumerCapabilities !== undefined ? { consumer_capabilities: consumerCapabilities } : {}
11419
+ ...consumerCapabilities.length > 0 ? { consumer_capabilities: consumerCapabilities } : {}
11341
11420
  });
11342
11421
  let installed = null;
11343
11422
  const install = (frame) => {
@@ -11347,7 +11426,7 @@ class SubcClient {
11347
11426
  if (typeof parsed.route_channel !== "number" || typeof parsed.route_epoch !== "number") {
11348
11427
  throw new SubcError(`route.open returned no route handle: ${JSON.stringify(parsed)}`);
11349
11428
  }
11350
- installed = this.installRoute(parsed.route_channel, parsed.route_epoch);
11429
+ installed = this.installRoute(parsed.route_channel, parsed.route_epoch, reverseRequests);
11351
11430
  return true;
11352
11431
  };
11353
11432
  const closeLateRoute = (frame) => {
@@ -11372,14 +11451,25 @@ class SubcClient {
11372
11451
  }
11373
11452
  async request(handle, body, opts = {}) {
11374
11453
  this.assertLiveHandle(handle);
11375
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
11454
+ const binary = opts.binary ?? false;
11455
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
11376
11456
  const priority = opts.priority ?? Priority.Interactive;
11377
11457
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
11378
- const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
11379
- return this.parseJson(reply);
11458
+ const reply = await this.send(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, undefined, undefined, binary, opts.signal);
11459
+ return this.decodeReply(reply);
11380
11460
  }
11381
11461
  async call(moduleId, method, params, opts = {}) {
11462
+ if (opts.binary) {
11463
+ 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");
11464
+ }
11382
11465
  const body = params === undefined ? { method } : { method, params };
11466
+ return await this.managedCall(moduleId, body, opts);
11467
+ }
11468
+ async callBinary(moduleId, body, opts = {}) {
11469
+ const bytes = requireBinaryBody(body);
11470
+ return this.managedCall(moduleId, bytes, { ...opts, binary: true });
11471
+ }
11472
+ async managedCall(moduleId, body, opts) {
11383
11473
  let retriedUnknownChannel = false;
11384
11474
  for (;; ) {
11385
11475
  const routeHandle = await this.cachedRouteHandle(moduleId, opts);
@@ -11457,6 +11547,22 @@ class SubcClient {
11457
11547
  };
11458
11548
  return { unsubscribe, closed };
11459
11549
  }
11550
+ attachCancelSignal(signal, key, handle, corr, priority) {
11551
+ if (!handle)
11552
+ return;
11553
+ const emit = () => {
11554
+ if (!this.pending.has(key))
11555
+ return;
11556
+ try {
11557
+ this.cancel(handle, corr, priority);
11558
+ } catch {}
11559
+ };
11560
+ if (signal.aborted) {
11561
+ emit();
11562
+ return;
11563
+ }
11564
+ signal.addEventListener("abort", emit, { once: true });
11565
+ }
11460
11566
  cancel(handle, corr, priority = Priority.Interactive) {
11461
11567
  this.assertLiveHandle(handle);
11462
11568
  const cancel = buildFrame(FrameType.Cancel, buildFlags(false, priority, false), handle.channel, handle.epoch, corr, EMPTY_BODY);
@@ -11497,7 +11603,7 @@ class SubcClient {
11497
11603
  this.sendRouteGoodbye(handle);
11498
11604
  }
11499
11605
  async closeManagedRoute(target, identity, opts = {}) {
11500
- const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts));
11606
+ const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts), (opts.reverseRequests ?? new ReverseRequestRegistry).capabilities());
11501
11607
  const cached = this.routes.get(key);
11502
11608
  if (!cached)
11503
11609
  return;
@@ -11565,7 +11671,7 @@ class SubcClient {
11565
11671
  async controlRpc(body, acceptFrame, onLateResponse) {
11566
11672
  return this.send(null, body, Priority.Interactive, AdmissionClass.Normal, undefined, undefined, acceptFrame, onLateResponse);
11567
11673
  }
11568
- send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse) {
11674
+ send(handle, body, priority, admission, timeoutMs, onProgress, acceptFrame, onLateResponse, binary = false, signal) {
11569
11675
  if (handle)
11570
11676
  this.assertLiveHandle(handle);
11571
11677
  if (this.closedErr)
@@ -11579,7 +11685,7 @@ class SubcClient {
11579
11685
  const key = pendingKey(handle, corr);
11580
11686
  const channel = handle?.channel ?? 0;
11581
11687
  const epoch = handle?.epoch ?? 0;
11582
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), channel, epoch, corr, body);
11688
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), channel, epoch, corr, body);
11583
11689
  return new Promise((resolve, reject) => {
11584
11690
  const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
11585
11691
  const pending = {
@@ -11593,6 +11699,8 @@ class SubcClient {
11593
11699
  };
11594
11700
  pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, channel, corr, ms), ms);
11595
11701
  this.pending.set(key, pending);
11702
+ if (signal)
11703
+ this.attachCancelSignal(signal, key, handle, corr, priority);
11596
11704
  writeBorrowed(this.sock, encodeFrame(frame), Date.now() + ms).catch((error) => {
11597
11705
  const current = this.pending.get(key);
11598
11706
  if (current)
@@ -11620,19 +11728,20 @@ class SubcClient {
11620
11728
  setImmediate(arbitrate);
11621
11729
  }
11622
11730
  async managedRequest(handle, body, opts) {
11623
- const bytes = body instanceof Uint8Array ? body : this.encode(body);
11731
+ const binary = opts.binary ?? false;
11732
+ const bytes = binary ? requireBinaryBody(body) : body instanceof Uint8Array ? body : this.encode(body);
11624
11733
  const priority = opts.priority ?? Priority.Interactive;
11625
11734
  const admission = opts.admissionClass ?? AdmissionClass.Normal;
11626
11735
  try {
11627
- const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress);
11628
- return this.parseJson(reply);
11736
+ const reply = await this.sendManaged(handle, bytes, priority, admission, opts.timeoutMs, opts.onProgress, binary, opts.signal);
11737
+ return this.decodeReply(reply);
11629
11738
  } catch (error) {
11630
11739
  if (error instanceof SubcCallError)
11631
11740
  throw error;
11632
11741
  throw this.terminalCallError("managed call failed", error);
11633
11742
  }
11634
11743
  }
11635
- sendManaged(handle, body, priority, admission, timeoutMs, onProgress) {
11744
+ sendManaged(handle, body, priority, admission, timeoutMs, onProgress, binary = false, signal) {
11636
11745
  try {
11637
11746
  this.assertLiveHandle(handle);
11638
11747
  } catch (error) {
@@ -11648,7 +11757,7 @@ class SubcClient {
11648
11757
  return Promise.reject(this.notSentCallError("request correlation allocator was exhausted", error));
11649
11758
  }
11650
11759
  const key = pendingKey(handle, corr);
11651
- const frame = buildFrame(FrameType.Request, buildFlags(false, priority, false, admission), handle.channel, handle.epoch, corr, body);
11760
+ const frame = buildFrame(FrameType.Request, buildFlags(binary, priority, false, admission), handle.channel, handle.epoch, corr, body);
11652
11761
  let handedToSocket = false;
11653
11762
  const classifyFailure = (error) => {
11654
11763
  if (!handedToSocket)
@@ -11670,6 +11779,8 @@ class SubcClient {
11670
11779
  };
11671
11780
  pending.timer = setTimeout(() => this.arbitrateTimeout(key, pending, handle.channel, corr, ms), ms);
11672
11781
  this.pending.set(key, pending);
11782
+ if (signal)
11783
+ this.attachCancelSignal(signal, key, handle, corr, priority);
11673
11784
  const write = writeTrackedBorrowed(this.sock, encodeFrame(frame), Date.now() + ms);
11674
11785
  handedToSocket = write.queued;
11675
11786
  write.completed.catch((error) => {
@@ -11686,7 +11797,9 @@ class SubcClient {
11686
11797
  }
11687
11798
  const target = { kind: opts.targetKind ?? this.opts.targetKind, module_id: moduleId };
11688
11799
  const consumerIdentity = routeOpenConsumerIdentity(opts);
11689
- const key = routeCacheKey(target, identity, consumerIdentity);
11800
+ const reverseRequests = opts.reverseRequests ?? new ReverseRequestRegistry;
11801
+ reverseRequests.seal();
11802
+ const key = routeCacheKey(target, identity, consumerIdentity, reverseRequests.capabilities());
11690
11803
  let cached = this.routes.get(key);
11691
11804
  if (!cached) {
11692
11805
  cached = {
@@ -11695,6 +11808,7 @@ class SubcClient {
11695
11808
  target,
11696
11809
  identity,
11697
11810
  consumerIdentity,
11811
+ reverseRequests,
11698
11812
  handle: null,
11699
11813
  opening: null
11700
11814
  };
@@ -11710,7 +11824,7 @@ class SubcClient {
11710
11824
  return cached.opening;
11711
11825
  }
11712
11826
  async openCachedRoute(cached) {
11713
- const routeRetryDeadline = Date.now() + ROUTE_OPEN_RETRY_DEADLINE_MS;
11827
+ const routeRetryDeadline = Date.now() + this.opts.routeOpenRetryDeadlineMs;
11714
11828
  let routeRetryDelay = this.opts.reconnectBackoff.baseMs;
11715
11829
  let routeRetryAttempt = 0;
11716
11830
  for (;; ) {
@@ -11725,7 +11839,8 @@ class SubcClient {
11725
11839
  return cached.handle;
11726
11840
  try {
11727
11841
  const handle = await this.routeOpen(cached.target, cached.identity, {
11728
- consumerIdentity: cached.consumerIdentity ?? null
11842
+ consumerIdentity: cached.consumerIdentity ?? null,
11843
+ reverseRequests: cached.reverseRequests
11729
11844
  });
11730
11845
  if (cached.closed) {
11731
11846
  this.liveRoutes.delete(handle.channel);
@@ -11747,12 +11862,12 @@ class SubcClient {
11747
11862
  }
11748
11863
  if (!this.closeStarted && error instanceof SubcError && isRetryableRouteOpenCode(error.code)) {
11749
11864
  routeRetryAttempt += 1;
11750
- if (routeRetryAttempt < this.opts.reconnectBackoff.maxAttempts && Date.now() < routeRetryDeadline) {
11865
+ if (Date.now() < routeRetryDeadline) {
11751
11866
  await this.opts.sleep(routeRetryDelay);
11752
11867
  routeRetryDelay = Math.min(routeRetryDelay * 2, this.opts.reconnectBackoff.capMs);
11753
11868
  continue;
11754
11869
  }
11755
- throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry budget exhausted)`, error);
11870
+ throw this.notSentCallError(`route.open failed for module ${cached.moduleId}: ${error.code} (retry deadline exhausted after ${routeRetryAttempt} attempts)`, error);
11756
11871
  }
11757
11872
  throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, error);
11758
11873
  }
@@ -11864,20 +11979,34 @@ class SubcClient {
11864
11979
  this.readLoop(opened.sock, this.generation);
11865
11980
  }
11866
11981
  async reopenCachedRoutes() {
11867
- for (const cached of this.routes.values())
11868
- cached.handle = null;
11869
- for (const cached of this.routes.values()) {
11870
- if (cached.closed)
11871
- continue;
11872
- const handle = await this.routeOpen(cached.target, cached.identity, {
11873
- consumerIdentity: cached.consumerIdentity ?? null
11874
- });
11875
- if (cached.closed) {
11876
- this.liveRoutes.delete(handle.channel);
11877
- this.sendRouteGoodbye(handle);
11982
+ const routeKeys = [...this.routes.keys()];
11983
+ for (const key of routeKeys) {
11984
+ const cached = this.routes.get(key);
11985
+ if (cached)
11986
+ cached.handle = null;
11987
+ }
11988
+ for (const key of routeKeys) {
11989
+ const cached = this.routes.get(key);
11990
+ if (!cached || cached.closed)
11878
11991
  continue;
11992
+ try {
11993
+ const handle = await this.routeOpen(cached.target, cached.identity, {
11994
+ consumerIdentity: cached.consumerIdentity ?? null,
11995
+ reverseRequests: cached.reverseRequests
11996
+ });
11997
+ if (cached.closed) {
11998
+ this.liveRoutes.delete(handle.channel);
11999
+ this.sendRouteGoodbye(handle);
12000
+ continue;
12001
+ }
12002
+ cached.handle = handle;
12003
+ } catch (error) {
12004
+ if (isRouteOpenRefusal(error)) {
12005
+ this.routes.delete(key);
12006
+ continue;
12007
+ }
12008
+ throw error;
11879
12009
  }
11880
- cached.handle = handle;
11881
12010
  }
11882
12011
  }
11883
12012
  timeoutMessage(channel, corr, ms) {
@@ -11935,6 +12064,10 @@ class SubcClient {
11935
12064
  return;
11936
12065
  }
11937
12066
  }
12067
+ if (frame.header.ty === FrameType.Request && handle) {
12068
+ this.dispatchReverseRequest(frame, handle);
12069
+ return;
12070
+ }
11938
12071
  const key = pendingKey(handle, frame.header.corr);
11939
12072
  const pending = this.pending.get(key);
11940
12073
  if (pending) {
@@ -11988,10 +12121,49 @@ class SubcClient {
11988
12121
  rejectPending(key, pending, err) {
11989
12122
  this.settle(key, pending, () => pending.reject(pending.classifyFailure?.(err) ?? err));
11990
12123
  }
12124
+ dispatchReverseRequest(frame, handle) {
12125
+ const method = reverseRequestMethod(frame.body);
12126
+ const methodFamily = method?.split("/", 1)[0];
12127
+ const handler = methodFamily ? reverseRequestHandler(handle, methodFamily) : undefined;
12128
+ const sock = this.sock;
12129
+ const generation = this.generation;
12130
+ const reply = async () => {
12131
+ if (!handler || !method) {
12132
+ 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);
12133
+ return;
12134
+ }
12135
+ let body;
12136
+ try {
12137
+ body = await handler(frame.body, { corr: frame.header.corr, method });
12138
+ if (!(body instanceof Uint8Array)) {
12139
+ throw new TypeError("reverse-request handler must return a Uint8Array");
12140
+ }
12141
+ } catch (error) {
12142
+ await this.sendReverseError(frame, handle, "reverse_request_unhandled", error instanceof Error ? error.message : String(error), sock, generation);
12143
+ return;
12144
+ }
12145
+ await this.sendReverseReply(frame, handle, FrameType.Response, body, sock, generation);
12146
+ };
12147
+ reply().catch((error) => {
12148
+ if (this.sock === sock && this.generation === generation && !this.closeStarted) {
12149
+ debug("failed to answer reverse request: %s", causeMessage(error));
12150
+ }
12151
+ });
12152
+ }
12153
+ async sendReverseError(request, handle, code, message, sock, generation) {
12154
+ await this.sendReverseReply(request, handle, FrameType.Error, this.encode({ code, message }), sock, generation);
12155
+ }
12156
+ async sendReverseReply(request, handle, type, body, sock, generation) {
12157
+ if (this.sock !== sock || this.generation !== generation || !this.isLiveHandle(handle) || this.closeStarted || this.closedErr) {
12158
+ return;
12159
+ }
12160
+ const frame = buildFrame(type, buildFlags(false, Priority.Interactive, false), handle.channel, handle.epoch, request.header.corr, body);
12161
+ await writeBorrowed(sock, encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS);
12162
+ }
11991
12163
  errorFromFrame(frame) {
11992
12164
  try {
11993
12165
  const parsed = JSON.parse(Buffer.from(frame.body).toString("utf8"));
11994
- return new SubcError(parsed.message ?? "subc error", parsed.code);
12166
+ return new SubcError(parsed.message ?? "subc error", parsed.code, parsed.detail);
11995
12167
  } catch {
11996
12168
  return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
11997
12169
  }
@@ -12036,8 +12208,8 @@ class SubcClient {
12036
12208
  get droppedIngressFrames() {
12037
12209
  return this.ingressEpochDropCount;
12038
12210
  }
12039
- installRoute(channel, epoch) {
12040
- const handle = createRouteHandle(channel, epoch, this.connectionToken);
12211
+ installRoute(channel, epoch, reverseRequests = new ReverseRequestRegistry) {
12212
+ const handle = createRouteHandle(channel, epoch, this.connectionToken, reverseRequests);
12041
12213
  this.liveRoutes.set(channel, handle);
12042
12214
  return handle;
12043
12215
  }
@@ -12074,6 +12246,9 @@ class SubcClient {
12074
12246
  encode(value) {
12075
12247
  return Buffer.from(JSON.stringify(value), "utf8");
12076
12248
  }
12249
+ decodeReply(frame) {
12250
+ return hasBinary(frame.header.flags) ? frame.body : this.parseJson(frame);
12251
+ }
12077
12252
  parseJson(frame) {
12078
12253
  const b = frame.body;
12079
12254
  return JSON.parse(Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString("utf8"));
@@ -12093,7 +12268,12 @@ function isConsumerReconnectTransient(err) {
12093
12268
  const code = errorCode(err);
12094
12269
  return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
12095
12270
  }
12271
+ function isRouteOpenRefusal(err) {
12272
+ return err instanceof SubcError && typeof err.code === "string";
12273
+ }
12096
12274
  function isRetryableRouteOpenCode(code) {
12275
+ if (code === "capability_forbidden")
12276
+ return false;
12097
12277
  return code === "unknown_module" || code === "module_reloading" || code === "module_warming" || code === "target_unavailable" || code === "module_timeout";
12098
12278
  }
12099
12279
  async function connectionFileExists(path) {
@@ -12112,14 +12292,35 @@ function normalizeConnectOptions(opts) {
12112
12292
  targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
12113
12293
  reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
12114
12294
  sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
12295
+ routeOpenRetryDeadlineMs: opts.routeOpenRetryDeadlineMs ?? ROUTE_OPEN_RETRY_DEADLINE_MS,
12115
12296
  timeoutArbitrationGraceMs: opts.timeoutArbitrationGraceMs ?? TIMEOUT_ARBITRATION_GRACE_MS,
12116
12297
  livenessProbeWindowMs: opts.livenessProbeWindowMs ?? LIVENESS_PROBE_WINDOW_MS,
12117
12298
  onControlPush: opts.onControlPush
12118
12299
  };
12119
12300
  }
12120
- function routeCacheKey(target, identity, consumerIdentity) {
12301
+ function isValidCapabilityIdentifier(identifier) {
12302
+ if (/\s/u.test(identifier))
12303
+ return false;
12304
+ const separator = identifier.indexOf("/v");
12305
+ if (separator <= 0 || identifier.indexOf("/v", separator + 1) !== -1)
12306
+ return false;
12307
+ const name = identifier.slice(0, separator);
12308
+ const version = identifier.slice(separator + 2);
12309
+ if (new TextEncoder().encode(name).byteLength > 64 || version.length === 0)
12310
+ return false;
12311
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/u.test(name) && !/^[a-z]$/u.test(name))
12312
+ return false;
12313
+ if (name.includes("--"))
12314
+ return false;
12315
+ if (!/^\d+$/u.test(version) || version.length > 1 && version.startsWith("0"))
12316
+ return false;
12317
+ const numericVersion = Number(version);
12318
+ return Number.isSafeInteger(numericVersion) && numericVersion >= 1 && numericVersion <= 4294967295;
12319
+ }
12320
+ function routeCacheKey(target, identity, consumerIdentity, consumerCapabilities) {
12121
12321
  const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
12122
- return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}`;
12322
+ const capabilitiesPart = [...consumerCapabilities].sort().join("\x00");
12323
+ return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}\x00${capabilitiesPart}`;
12123
12324
  }
12124
12325
  function routeOpenConsumerIdentity(opts = {}) {
12125
12326
  if (opts.consumerIdentity !== undefined)
@@ -12146,6 +12347,14 @@ function causeMessage(cause) {
12146
12347
  function pendingKey(handle, corr) {
12147
12348
  return handle ? `${handle.channel}:${handle.epoch}:${corr}` : `0:0:${corr}`;
12148
12349
  }
12350
+ function reverseRequestMethod(body) {
12351
+ try {
12352
+ const parsed = JSON.parse(Buffer.from(body).toString("utf8"));
12353
+ return typeof parsed.method === "string" && parsed.method.length > 0 ? parsed.method : undefined;
12354
+ } catch {
12355
+ return;
12356
+ }
12357
+ }
12149
12358
  // ../aft-bridge/dist/subc-transport.js
12150
12359
  import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
12151
12360
 
@@ -12629,8 +12838,16 @@ var AFT_MODULE_ID = "aft";
12629
12838
  var MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3;
12630
12839
  var RECONNECT_RETRY_FLOOR_MS = 100;
12631
12840
  var RECONNECT_RETRY_CAP_MS = 2000;
12632
- var ROUTE_OPEN_RELOAD_WAIT_CAP_MS = 15000;
12633
- var ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX = " The AFT daemon module did not return within the 15s reload window.";
12841
+ var SUBC_DEFAULT_REQUEST_TIMEOUT_MS = 30000;
12842
+ var ROUTE_OPEN_RELOAD_WAIT_CEILING_MS = 45000;
12843
+ function reloadWaitExhaustedSuffix(callDeadlineMs) {
12844
+ if (callDeadlineMs > ROUTE_OPEN_RELOAD_WAIT_CEILING_MS) {
12845
+ const ceilingSeconds = ROUTE_OPEN_RELOAD_WAIT_CEILING_MS / 1000;
12846
+ return ` The AFT daemon module did not return within the ${ceilingSeconds}s reload-wait ceiling.`;
12847
+ }
12848
+ const seconds = Math.round(callDeadlineMs / 100) / 10;
12849
+ return ` The AFT daemon module did not return within this call's ${seconds}s deadline.`;
12850
+ }
12634
12851
  var BG_STABLE_MS = 5000;
12635
12852
  function reconnectBackoffMs(attempt) {
12636
12853
  return Math.min(RECONNECT_RETRY_FLOOR_MS * 2 ** Math.min(attempt, 6), RECONNECT_RETRY_CAP_MS);
@@ -12939,14 +13156,15 @@ function isAbsentRootRouteError(err) {
12939
13156
  function absentRootError(root) {
12940
13157
  return new SubcError(`invalid route project root: project root does not exist: ${root}`, "config_divergence");
12941
13158
  }
12942
- function reloadWindowExhaustedError(error) {
13159
+ function reloadWindowExhaustedError(error, callDeadlineMs) {
13160
+ const suffix = reloadWaitExhaustedSuffix(callDeadlineMs);
12943
13161
  if (error instanceof Error) {
12944
13162
  try {
12945
- error.message += ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX;
13163
+ error.message += suffix;
12946
13164
  return error;
12947
13165
  } catch {}
12948
13166
  }
12949
- return new Error(`${String(error)}${ROUTE_OPEN_RELOAD_WAIT_EXHAUSTED_SUFFIX}`);
13167
+ return new Error(`${String(error)}${suffix}`);
12950
13168
  }
12951
13169
  function safeCloseRoute(client, route) {
12952
13170
  try {
@@ -13551,18 +13769,20 @@ class SubcTransportPool {
13551
13769
  throw error;
13552
13770
  }
13553
13771
  };
13772
+ const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
13773
+ const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
13774
+ let reloadWaitedMs = 0;
13554
13775
  const openRouteAfterReloadWindow = async () => {
13555
- let reloadWaitedMs = 0;
13556
13776
  while (true) {
13557
13777
  try {
13558
13778
  return await openRoute();
13559
13779
  } catch (error) {
13560
13780
  if (!isRouteOpenReloadWindowError(error))
13561
13781
  throw error;
13562
- const { delayMs, wait } = this.waitForRouteReopenBackoff();
13563
- if (reloadWaitedMs + delayMs > ROUTE_OPEN_RELOAD_WAIT_CAP_MS) {
13564
- throw reloadWindowExhaustedError(error);
13782
+ if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
13783
+ throw reloadWindowExhaustedError(error, callDeadlineMs);
13565
13784
  }
13785
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
13566
13786
  reloadWaitedMs += delayMs;
13567
13787
  await wait;
13568
13788
  }
@@ -13608,7 +13828,8 @@ class SubcTransportPool {
13608
13828
  };
13609
13829
  abortSignal?.addEventListener("abort", onAbort, { once: true });
13610
13830
  try {
13611
- const request = client.request(route, body, { timeoutMs, onProgress });
13831
+ const requestTimeoutMs = reloadWaitedMs > 0 ? callDeadlineMs - reloadWaitedMs : timeoutMs;
13832
+ const request = client.request(route, body, { timeoutMs: requestTimeoutMs, onProgress });
13612
13833
  const reply = abortSignal ? await Promise.race([request, aborted]) : await request;
13613
13834
  if (!this.isCurrentSession(key, record)) {
13614
13835
  if (this.isReapInduced(record))
@@ -13625,28 +13846,41 @@ class SubcTransportPool {
13625
13846
  }
13626
13847
  };
13627
13848
  let routeAndEntry = await openRouteAfterReloadWindow();
13628
- try {
13629
- return await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
13630
- } catch (error) {
13631
- if (this.isReapInduced(record))
13632
- throw this.annotateReapError(error, record);
13633
- if (isRouteProvenAbsentError(error) && this.isCurrentSession(key, record) && this.client === client) {
13634
- clearRouteEntry(routeAndEntry.entry);
13635
- await this.waitForRouteReopenBackoff().wait;
13636
- routeAndEntry = await openRouteAfterReloadWindow();
13637
- try {
13638
- const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
13849
+ let reopened = false;
13850
+ let retriedAbsentRoute = false;
13851
+ while (true) {
13852
+ try {
13853
+ const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
13854
+ if (reopened)
13639
13855
  this.resetRouteReopenBackoff();
13640
- return reply;
13641
- } catch (retryError) {
13642
- if (this.isReapInduced(record))
13643
- throw this.annotateReapError(retryError, record);
13644
- handleRequestFailure(retryError, routeAndEntry.entry);
13645
- throw retryError;
13856
+ return reply;
13857
+ } catch (error) {
13858
+ if (this.isReapInduced(record))
13859
+ throw this.annotateReapError(error, record);
13860
+ const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
13861
+ if (ownsRoute && isRouteRequestReloadRefusal(error)) {
13862
+ clearRouteEntry(routeAndEntry.entry);
13863
+ if (reloadWaitedMs + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
13864
+ throw reloadWindowExhaustedError(error, callDeadlineMs);
13865
+ }
13866
+ const { delayMs, wait } = this.waitForRouteReopenBackoff();
13867
+ reloadWaitedMs += delayMs;
13868
+ await wait;
13869
+ routeAndEntry = await openRouteAfterReloadWindow();
13870
+ reopened = true;
13871
+ continue;
13646
13872
  }
13873
+ if (ownsRoute && !retriedAbsentRoute && isRouteProvenAbsentError(error)) {
13874
+ retriedAbsentRoute = true;
13875
+ clearRouteEntry(routeAndEntry.entry);
13876
+ await this.waitForRouteReopenBackoff().wait;
13877
+ routeAndEntry = await openRouteAfterReloadWindow();
13878
+ reopened = true;
13879
+ continue;
13880
+ }
13881
+ handleRequestFailure(error, routeAndEntry.entry);
13882
+ throw error;
13647
13883
  }
13648
- handleRequestFailure(error, routeAndEntry.entry);
13649
- throw error;
13650
13884
  }
13651
13885
  } catch (error) {
13652
13886
  if (this.isReapInduced(record))
@@ -13675,6 +13909,11 @@ class SubcTransportPool {
13675
13909
  this.routeReopenRetryMs = delayMs;
13676
13910
  return { delayMs, wait: retry };
13677
13911
  }
13912
+ nextRouteReopenDelayMs() {
13913
+ if (this.routeReopenRetry && this.routeReopenRetryMs !== null)
13914
+ return this.routeReopenRetryMs;
13915
+ return this.routeReopenRetryDelayMs;
13916
+ }
13678
13917
  resetRouteReopenBackoff() {
13679
13918
  this.routeReopenRetryDelayMs = RECONNECT_RETRY_FLOOR_MS;
13680
13919
  }
@@ -13966,6 +14205,12 @@ function isRouteOpenReloadWindowError(error) {
13966
14205
  const code = error.code;
13967
14206
  return code === "module_reloading" || code === "module_warming" || code === "target_unavailable";
13968
14207
  }
14208
+ function isRouteRequestReloadRefusal(error) {
14209
+ if (error instanceof SubcCallError) {
14210
+ return error.kind === "not_sent" && error.code === "module_reloading";
14211
+ }
14212
+ return error instanceof SubcError && error.code === "module_reloading";
14213
+ }
13969
14214
  function hasEngineResponse(error) {
13970
14215
  const response = error.response;
13971
14216
  if (response !== null && typeof response === "object")
@@ -18154,7 +18399,7 @@ import {
18154
18399
  // package.json
18155
18400
  var package_default = {
18156
18401
  name: "@cortexkit/aft-pi",
18157
- version: "0.57.0",
18402
+ version: "0.57.2",
18158
18403
  type: "module",
18159
18404
  description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
18160
18405
  main: "dist/index.js",
@@ -18177,19 +18422,19 @@ var package_default = {
18177
18422
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
18178
18423
  },
18179
18424
  dependencies: {
18180
- "@cortexkit/aft-bridge": "0.57.0",
18425
+ "@cortexkit/aft-bridge": "0.57.2",
18181
18426
  "comment-json": "^5.0.0",
18182
18427
  diff: "^8.0.4",
18183
18428
  typebox: "1.1.38",
18184
18429
  zod: "^4.4.3"
18185
18430
  },
18186
18431
  optionalDependencies: {
18187
- "@cortexkit/aft-darwin-arm64": "0.57.0",
18188
- "@cortexkit/aft-darwin-x64": "0.57.0",
18189
- "@cortexkit/aft-linux-arm64": "0.57.0",
18190
- "@cortexkit/aft-linux-x64": "0.57.0",
18191
- "@cortexkit/aft-win32-arm64": "0.57.0",
18192
- "@cortexkit/aft-win32-x64": "0.57.0"
18432
+ "@cortexkit/aft-darwin-arm64": "0.57.2",
18433
+ "@cortexkit/aft-darwin-x64": "0.57.2",
18434
+ "@cortexkit/aft-linux-arm64": "0.57.2",
18435
+ "@cortexkit/aft-linux-x64": "0.57.2",
18436
+ "@cortexkit/aft-win32-arm64": "0.57.2",
18437
+ "@cortexkit/aft-win32-x64": "0.57.2"
18193
18438
  },
18194
18439
  devDependencies: {
18195
18440
  "@earendil-works/pi-coding-agent": "^0.80.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-pi",
3
- "version": "0.57.0",
3
+ "version": "0.57.2",
4
4
  "type": "module",
5
5
  "description": "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
6
6
  "main": "dist/index.js",
@@ -23,19 +23,19 @@
23
23
  "test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
24
24
  },
25
25
  "dependencies": {
26
- "@cortexkit/aft-bridge": "0.57.0",
26
+ "@cortexkit/aft-bridge": "0.57.2",
27
27
  "comment-json": "^5.0.0",
28
28
  "diff": "^8.0.4",
29
29
  "typebox": "1.1.38",
30
30
  "zod": "^4.4.3"
31
31
  },
32
32
  "optionalDependencies": {
33
- "@cortexkit/aft-darwin-arm64": "0.57.0",
34
- "@cortexkit/aft-darwin-x64": "0.57.0",
35
- "@cortexkit/aft-linux-arm64": "0.57.0",
36
- "@cortexkit/aft-linux-x64": "0.57.0",
37
- "@cortexkit/aft-win32-arm64": "0.57.0",
38
- "@cortexkit/aft-win32-x64": "0.57.0"
33
+ "@cortexkit/aft-darwin-arm64": "0.57.2",
34
+ "@cortexkit/aft-darwin-x64": "0.57.2",
35
+ "@cortexkit/aft-linux-arm64": "0.57.2",
36
+ "@cortexkit/aft-linux-x64": "0.57.2",
37
+ "@cortexkit/aft-win32-arm64": "0.57.2",
38
+ "@cortexkit/aft-win32-x64": "0.57.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@earendil-works/pi-coding-agent": "^0.80.2",