@cortexkit/aft 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -394,6 +394,15 @@ var init_bridge = __esm(() => {
394
394
  async send(command, params = {}, options) {
395
395
  return this.sendWithVersionMismatchRetry(command, params, options, true);
396
396
  }
397
+ async toolCall(sessionId, name, rawArgs = {}, options) {
398
+ const params = { name, arguments: rawArgs };
399
+ if (sessionId)
400
+ params.session_id = sessionId;
401
+ const { preview, ...sendOptions } = options ?? {};
402
+ if (preview === true)
403
+ params.preview = true;
404
+ return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
405
+ }
397
406
  async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
398
407
  try {
399
408
  if (this._shuttingDown) {
@@ -1078,6 +1087,9 @@ function configHome() {
1078
1087
  function resolveCortexKitUserConfigPath() {
1079
1088
  return join2(configHome(), "cortexkit", "aft.jsonc");
1080
1089
  }
1090
+ function resolveCortexKitProjectConfigPath(projectDirectory) {
1091
+ return join2(projectDirectory, ".cortexkit", "aft.jsonc");
1092
+ }
1081
1093
  var init_paths = () => {};
1082
1094
 
1083
1095
  // ../aft-bridge/dist/resolver.js
@@ -1377,124 +1389,2147 @@ class BridgePool {
1377
1389
  let projectOverrides = {};
1378
1390
  if (this.projectConfigLoader) {
1379
1391
  try {
1380
- projectOverrides = this.projectConfigLoader(key) ?? {};
1381
- } catch (err) {
1382
- const message = err instanceof Error ? err.message : String(err);
1383
- this.error(`projectConfigLoader failed; using global overrides only: ${message}`);
1384
- }
1392
+ projectOverrides = this.projectConfigLoader(key) ?? {};
1393
+ } catch (err) {
1394
+ const message = err instanceof Error ? err.message : String(err);
1395
+ this.error(`projectConfigLoader failed; using global overrides only: ${message}`);
1396
+ }
1397
+ }
1398
+ const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
1399
+ const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
1400
+ this.bridges.set(key, { bridge, lastUsed: Date.now() });
1401
+ return bridge;
1402
+ }
1403
+ async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
1404
+ return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
1405
+ }
1406
+ cleanup() {
1407
+ const now = Date.now();
1408
+ for (const [dir, entry] of this.bridges) {
1409
+ if (entry.bridge.hasPendingRequests() || entry.bridge.hasOutstandingBackgroundTasks())
1410
+ continue;
1411
+ if (now - entry.lastUsed > this.idleTimeoutMs) {
1412
+ entry.bridge.shutdown().catch((err) => this.error("cleanup shutdown failed:", err));
1413
+ this.bridges.delete(dir);
1414
+ }
1415
+ }
1416
+ for (const bridge of this.staleBridges) {
1417
+ if (bridge.hasPendingRequests() || bridge.hasOutstandingBackgroundTasks())
1418
+ continue;
1419
+ bridge.shutdown().catch((err) => this.error("stale cleanup shutdown failed:", err));
1420
+ this.staleBridges.delete(bridge);
1421
+ }
1422
+ }
1423
+ evictLRU() {
1424
+ let oldestDir = null;
1425
+ let oldestTime = Infinity;
1426
+ for (const [dir, entry] of this.bridges) {
1427
+ if (entry.bridge.hasPendingRequests() || entry.bridge.hasOutstandingBackgroundTasks())
1428
+ continue;
1429
+ if (entry.lastUsed < oldestTime) {
1430
+ oldestTime = entry.lastUsed;
1431
+ oldestDir = dir;
1432
+ }
1433
+ }
1434
+ if (oldestDir) {
1435
+ const entry = this.bridges.get(oldestDir);
1436
+ entry?.bridge.shutdown().catch((err) => this.error("eviction shutdown failed:", err));
1437
+ this.bridges.delete(oldestDir);
1438
+ }
1439
+ }
1440
+ async closeSession(_projectRoot, _session) {}
1441
+ async shutdown() {
1442
+ if (this.cleanupTimer) {
1443
+ clearInterval(this.cleanupTimer);
1444
+ this.cleanupTimer = null;
1445
+ }
1446
+ const shutdowns = [
1447
+ ...Array.from(this.bridges.values(), (e) => e.bridge.shutdown()),
1448
+ ...Array.from(this.staleBridges.values(), (bridge) => bridge.shutdown())
1449
+ ];
1450
+ this.bridges.clear();
1451
+ this.staleBridges.clear();
1452
+ await Promise.allSettled(shutdowns);
1453
+ }
1454
+ async replaceBinary(newPath) {
1455
+ this.binaryPath = newPath;
1456
+ for (const entry of this.bridges.values()) {
1457
+ this.staleBridges.add(entry.bridge);
1458
+ }
1459
+ this.bridges.clear();
1460
+ this.log(`Binary path updated to ${newPath}. Active bridges marked stale — next calls will use the new binary.`);
1461
+ return newPath;
1462
+ }
1463
+ log(message, meta) {
1464
+ const logger = this.logger ?? getActiveLogger();
1465
+ if (logger) {
1466
+ try {
1467
+ logger.log(message, meta);
1468
+ } catch (err) {
1469
+ console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
1470
+ console.error(`[aft-bridge] ${message}`);
1471
+ }
1472
+ } else
1473
+ log(message, meta);
1474
+ }
1475
+ error(message, meta) {
1476
+ const logger = this.logger ?? getActiveLogger();
1477
+ if (logger) {
1478
+ try {
1479
+ logger.error(message, meta);
1480
+ } catch (err) {
1481
+ console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
1482
+ console.error(`[aft-bridge] ERROR: ${message}`);
1483
+ }
1484
+ } else
1485
+ error(message, meta);
1486
+ }
1487
+ setConfigureOverride(key, value) {
1488
+ if (value === undefined) {
1489
+ delete this.configOverrides[key];
1490
+ } else {
1491
+ this.configOverrides[key] = value;
1492
+ }
1493
+ }
1494
+ get size() {
1495
+ return this.bridges.size;
1496
+ }
1497
+ _testGetConfigOverrides() {
1498
+ return { ...this.configOverrides };
1499
+ }
1500
+ _testGetBridgeOptions() {
1501
+ return { ...this.bridgeOptions };
1502
+ }
1503
+ }
1504
+ function normalizeKey(projectRoot) {
1505
+ return canonicalizeProjectRoot(projectRoot);
1506
+ }
1507
+ var DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_MAX_POOL_SIZE = 8, CLEANUP_INTERVAL_MS;
1508
+ var init_pool = __esm(() => {
1509
+ init_active_logger();
1510
+ init_bridge();
1511
+ init_project_identity();
1512
+ DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
1513
+ CLEANUP_INTERVAL_MS = 60 * 1000;
1514
+ });
1515
+
1516
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/auth.ts
1517
+ import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
1518
+ function computeProof(key, domain, clientNonce, serverNonce, daemonId) {
1519
+ const mac = createHmac("sha256", Buffer.from(key));
1520
+ mac.update(Buffer.from(domain, "utf8"));
1521
+ mac.update(Buffer.from(clientNonce));
1522
+ mac.update(Buffer.from(serverNonce));
1523
+ mac.update(Buffer.from(daemonId));
1524
+ return new Uint8Array(mac.digest());
1525
+ }
1526
+ function constantTimeEq(a, b) {
1527
+ if (a.length !== b.length)
1528
+ return false;
1529
+ return timingSafeEqual(Buffer.from(a), Buffer.from(b));
1530
+ }
1531
+ async function writeMessage(sock, value, deadlineMs) {
1532
+ const json = Buffer.from(JSON.stringify(value), "utf8");
1533
+ if (json.length > MAX_AUTH_MESSAGE_LEN) {
1534
+ throw new AuthError(`auth message too large: ${json.length} > ${MAX_AUTH_MESSAGE_LEN}`);
1535
+ }
1536
+ const lenPrefix = new Uint8Array(4);
1537
+ new DataView(lenPrefix.buffer).setUint32(0, json.length, true);
1538
+ await sock.write(lenPrefix, deadlineMs);
1539
+ await sock.write(json, deadlineMs);
1540
+ }
1541
+ async function readMessage(sock, deadlineMs) {
1542
+ const lenBytes = await sock.readExact(4, deadlineMs);
1543
+ const len = new DataView(lenBytes.buffer, lenBytes.byteOffset, 4).getUint32(0, true);
1544
+ if (len > MAX_AUTH_MESSAGE_LEN) {
1545
+ throw new AuthError(`auth message too large: ${len} > ${MAX_AUTH_MESSAGE_LEN}`);
1546
+ }
1547
+ const body = len === 0 ? new Uint8Array(0) : await sock.readExact(len, deadlineMs);
1548
+ try {
1549
+ return JSON.parse(Buffer.from(body).toString("utf8"));
1550
+ } catch (err) {
1551
+ throw new AuthError(`auth message JSON decode failed: ${String(err)}`);
1552
+ }
1553
+ }
1554
+ async function authenticateClient(sock, conn, deadlineMs) {
1555
+ const clientNonce = new Uint8Array(randomBytes(NONCE_LEN));
1556
+ await writeMessage(sock, { client_nonce: Array.from(clientNonce), role: DEFAULT_CLIENT_ROLE }, deadlineMs);
1557
+ const proof = await readMessage(sock, deadlineMs);
1558
+ const serverNonce = Uint8Array.from(proof.server_nonce);
1559
+ const daemonId = Uint8Array.from(proof.daemon_id);
1560
+ const serverProof = Uint8Array.from(proof.server_proof);
1561
+ const expected = computeProof(conn.key, SERVER_PROOF_DOMAIN, clientNonce, serverNonce, daemonId);
1562
+ if (!constantTimeEq(expected, serverProof)) {
1563
+ throw new AuthError("server proof mismatch — wrong key or impostor daemon");
1564
+ }
1565
+ if (!constantTimeEq(daemonId, conn.daemonId)) {
1566
+ throw new AuthError("daemon id mismatch — connection file points at a different daemon");
1567
+ }
1568
+ const clientAuth = computeProof(conn.key, CLIENT_AUTH_DOMAIN, clientNonce, serverNonce, daemonId);
1569
+ await writeMessage(sock, { client_auth: Array.from(clientAuth) }, deadlineMs);
1570
+ }
1571
+ var NONCE_LEN = 32, MAX_AUTH_MESSAGE_LEN = 4096, SERVER_PROOF_DOMAIN = "subc-server-v1", CLIENT_AUTH_DOMAIN = "subc-client-v1", DEFAULT_CLIENT_ROLE = "client", AuthError;
1572
+ var init_auth = __esm(() => {
1573
+ AuthError = class AuthError extends Error {
1574
+ };
1575
+ });
1576
+
1577
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/connection-file.ts
1578
+ import { promises as fs } from "node:fs";
1579
+ function toBytes(value, field) {
1580
+ if (!Array.isArray(value) || value.some((n) => typeof n !== "number")) {
1581
+ throw new ConnectionFileError(`connection file field '${field}' must be a JSON array of bytes`);
1582
+ }
1583
+ return Uint8Array.from(value);
1584
+ }
1585
+ function validate(info) {
1586
+ if (info.schema !== SCHEMA_VERSION) {
1587
+ throw new ConnectionFileError(`unsupported connection file schema ${info.schema}; expected ${SCHEMA_VERSION}`);
1588
+ }
1589
+ if (info.endpoints.length === 0) {
1590
+ throw new ConnectionFileError("connection file must include at least one endpoint");
1591
+ }
1592
+ if (info.key.length < MIN_KEY_LEN) {
1593
+ throw new ConnectionFileError(`connection file key is too short: ${info.key.length} bytes, need at least ${MIN_KEY_LEN}`);
1594
+ }
1595
+ if (info.daemonId.length !== DAEMON_ID_LEN) {
1596
+ throw new ConnectionFileError(`connection file daemon_id must be ${DAEMON_ID_LEN} bytes, got ${info.daemonId.length}`);
1597
+ }
1598
+ }
1599
+ async function verifyOwnerOnly(path) {
1600
+ if (process.platform === "win32")
1601
+ return;
1602
+ const stat = await fs.stat(path);
1603
+ const mode = stat.mode & 511;
1604
+ if ((mode & 63) !== 0) {
1605
+ throw new ConnectionFileError(`connection file ${path} has insecure permissions 0o${mode.toString(8)}; expected owner-only 0600`);
1606
+ }
1607
+ }
1608
+ async function readConnectionFile(path) {
1609
+ await verifyOwnerOnly(path);
1610
+ const raw = await fs.readFile(path, "utf8");
1611
+ let parsed;
1612
+ try {
1613
+ parsed = JSON.parse(raw);
1614
+ } catch (err) {
1615
+ throw new ConnectionFileError(`connection file JSON read failed for ${path}: ${String(err)}`);
1616
+ }
1617
+ const endpointsRaw = parsed.endpoints;
1618
+ if (!Array.isArray(endpointsRaw)) {
1619
+ throw new ConnectionFileError("connection file 'endpoints' must be an array");
1620
+ }
1621
+ const endpoints = endpointsRaw.map((e) => {
1622
+ const ep = e;
1623
+ if (typeof ep.host !== "string" || typeof ep.port !== "number") {
1624
+ throw new ConnectionFileError("connection file endpoint must be { host: string, port: number }");
1625
+ }
1626
+ return { host: ep.host, port: ep.port };
1627
+ });
1628
+ const info = {
1629
+ schema: parsed.schema,
1630
+ endpoints,
1631
+ key: toBytes(parsed.key, "key"),
1632
+ daemonId: toBytes(parsed.daemon_id, "daemon_id"),
1633
+ pid: parsed.pid,
1634
+ daemonVer: parsed.daemon_ver ?? ""
1635
+ };
1636
+ validate(info);
1637
+ return info;
1638
+ }
1639
+ var SCHEMA_VERSION = 1, MIN_KEY_LEN = 32, DAEMON_ID_LEN = 16, ConnectionFileError;
1640
+ var init_connection_file = __esm(() => {
1641
+ ConnectionFileError = class ConnectionFileError extends Error {
1642
+ };
1643
+ });
1644
+
1645
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/envelope.ts
1646
+ function isPureHeader(ty) {
1647
+ return ty === 6 /* Cancel */ || ty === 7 /* Ping */ || ty === 8 /* Pong */ || ty === 11 /* Goodbye */;
1648
+ }
1649
+ function buildFlags(binary, priority, last) {
1650
+ let b = 0;
1651
+ if (binary)
1652
+ b |= FLAG_BINARY;
1653
+ b |= priority << FLAG_PRIORITY_SHIFT;
1654
+ if (last)
1655
+ b |= FLAG_LAST;
1656
+ return b;
1657
+ }
1658
+ function encodeHeader(h) {
1659
+ const buf = new Uint8Array(HEADER_LEN);
1660
+ const view = new DataView(buf.buffer);
1661
+ view.setUint32(0, h.len, true);
1662
+ buf[4] = h.ver;
1663
+ buf[5] = h.ty;
1664
+ buf[6] = h.flags;
1665
+ view.setUint16(7, h.channel, true);
1666
+ view.setBigUint64(9, h.corr, true);
1667
+ return buf;
1668
+ }
1669
+ function decodeHeader(bytes) {
1670
+ if (bytes.length < FROZEN_PREFIX_LEN) {
1671
+ throw new DecodeError(`header shorter than frozen prefix: have ${bytes.length} bytes`);
1672
+ }
1673
+ const ver = bytes[4];
1674
+ if (ver !== PROTOCOL_VERSION) {
1675
+ throw new DecodeError(`unsupported envelope version ${ver}`);
1676
+ }
1677
+ if (bytes.length < HEADER_LEN) {
1678
+ throw new DecodeError(`header too short for version: have ${bytes.length} bytes, need ${HEADER_LEN}`);
1679
+ }
1680
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1681
+ const len = view.getUint32(0, true);
1682
+ const tyByte = bytes[5];
1683
+ if (tyByte > FRAME_TYPE_MAX) {
1684
+ throw new DecodeError(`unknown frame type byte ${tyByte}`);
1685
+ }
1686
+ const ty = tyByte;
1687
+ const flags = bytes[6];
1688
+ if ((flags & FLAG_RESERVED_MASK) !== 0) {
1689
+ throw new DecodeError(`reserved flag bits set in flags 0b${flags.toString(2)}`);
1690
+ }
1691
+ if ((flags & FLAG_PRIORITY_MASK) >> FLAG_PRIORITY_SHIFT === 3) {
1692
+ throw new DecodeError(`reserved priority bits set in flags 0b${flags.toString(2)}`);
1693
+ }
1694
+ if (isPureHeader(ty) && len !== 0) {
1695
+ throw new DecodeError(`pure-header frame ${FrameType[ty]} declared non-zero body length ${len}`);
1696
+ }
1697
+ const channel = view.getUint16(7, true);
1698
+ const corr = view.getBigUint64(9, true);
1699
+ return { len, ver, ty, flags, channel, corr };
1700
+ }
1701
+ function buildFrame(ty, flags, channel, corr, body) {
1702
+ return buildFrameWithVersion(PROTOCOL_VERSION, ty, flags, channel, corr, body);
1703
+ }
1704
+ function buildFrameWithVersion(ver, ty, flags, channel, corr, body) {
1705
+ if (body.length > MAX_FRAME_BODY_LEN) {
1706
+ throw new DecodeError(`frame body ${body.length} exceeds max ${MAX_FRAME_BODY_LEN}`);
1707
+ }
1708
+ if (isPureHeader(ty) && body.length !== 0) {
1709
+ throw new DecodeError(`pure-header frame ${FrameType[ty]} cannot carry a body`);
1710
+ }
1711
+ return {
1712
+ header: { len: body.length, ver, ty, flags, channel, corr },
1713
+ body
1714
+ };
1715
+ }
1716
+ function encodeFrame(frame) {
1717
+ const header = encodeHeader(frame.header);
1718
+ const out = new Uint8Array(header.length + frame.body.length);
1719
+ out.set(header, 0);
1720
+ out.set(frame.body, header.length);
1721
+ return out;
1722
+ }
1723
+ var PROTOCOL_VERSION = 1, HEADER_LEN = 17, FROZEN_PREFIX_LEN = 5, MAX_FRAME_BODY_LEN, FrameType, FRAME_TYPE_MAX = 11 /* Goodbye */, FLAG_BINARY = 1, FLAG_PRIORITY_MASK = 6, FLAG_PRIORITY_SHIFT = 1, FLAG_LAST = 8, FLAG_RESERVED_MASK = 240, DecodeError;
1724
+ var init_envelope = __esm(() => {
1725
+ MAX_FRAME_BODY_LEN = 64 * 1024 * 1024;
1726
+ ((FrameType2) => {
1727
+ FrameType2[FrameType2["Request"] = 0] = "Request";
1728
+ FrameType2[FrameType2["Response"] = 1] = "Response";
1729
+ FrameType2[FrameType2["Push"] = 2] = "Push";
1730
+ FrameType2[FrameType2["StreamData"] = 3] = "StreamData";
1731
+ FrameType2[FrameType2["StreamEnd"] = 4] = "StreamEnd";
1732
+ FrameType2[FrameType2["Error"] = 5] = "Error";
1733
+ FrameType2[FrameType2["Cancel"] = 6] = "Cancel";
1734
+ FrameType2[FrameType2["Ping"] = 7] = "Ping";
1735
+ FrameType2[FrameType2["Pong"] = 8] = "Pong";
1736
+ FrameType2[FrameType2["Hello"] = 9] = "Hello";
1737
+ FrameType2[FrameType2["HelloAck"] = 10] = "HelloAck";
1738
+ FrameType2[FrameType2["Goodbye"] = 11] = "Goodbye";
1739
+ })(FrameType ||= {});
1740
+ DecodeError = class DecodeError extends Error {
1741
+ };
1742
+ });
1743
+
1744
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/socket.ts
1745
+ import net from "node:net";
1746
+
1747
+ class SubcSocket {
1748
+ sock;
1749
+ chunks = [];
1750
+ buffered = 0;
1751
+ waiter = null;
1752
+ closedErr = null;
1753
+ constructor(sock) {
1754
+ this.sock = sock;
1755
+ sock.on("data", (chunk) => {
1756
+ this.chunks.push(chunk);
1757
+ this.buffered += chunk.length;
1758
+ this.tryServe();
1759
+ });
1760
+ const fail = (err) => {
1761
+ if (!this.closedErr)
1762
+ this.closedErr = err;
1763
+ this.tryServe();
1764
+ };
1765
+ sock.on("error", (err) => fail(err instanceof Error ? err : new Error(String(err))));
1766
+ sock.on("end", () => fail(new SocketClosedError("subc closed the connection")));
1767
+ sock.on("close", () => fail(new SocketClosedError("subc connection closed")));
1768
+ }
1769
+ static connect(host, port, deadlineMs) {
1770
+ return new Promise((resolve4, reject) => {
1771
+ const sock = net.connect({ host, port });
1772
+ sock.setNoDelay(true);
1773
+ const timer = setTimeout(() => {
1774
+ sock.destroy();
1775
+ reject(new SocketTimeoutError(`timed out connecting to ${host}:${port}`));
1776
+ }, Math.max(0, deadlineMs - Date.now()));
1777
+ sock.once("connect", () => {
1778
+ clearTimeout(timer);
1779
+ resolve4(new SubcSocket(sock));
1780
+ });
1781
+ sock.once("error", (err) => {
1782
+ clearTimeout(timer);
1783
+ reject(err);
1784
+ });
1785
+ });
1786
+ }
1787
+ readExact(n, deadlineMs) {
1788
+ if (this.waiter) {
1789
+ return Promise.reject(new Error("concurrent readExact is not supported"));
1790
+ }
1791
+ if (n === 0)
1792
+ return Promise.resolve(new Uint8Array(0));
1793
+ return new Promise((resolve4, reject) => {
1794
+ let timer = null;
1795
+ if (Number.isFinite(deadlineMs)) {
1796
+ const remaining = deadlineMs - Date.now();
1797
+ if (remaining <= 0) {
1798
+ reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
1799
+ return;
1800
+ }
1801
+ timer = setTimeout(() => {
1802
+ this.waiter = null;
1803
+ reject(new SocketTimeoutError(`timed out waiting for ${n} bytes`));
1804
+ }, remaining);
1805
+ }
1806
+ this.waiter = { need: n, resolve: resolve4, reject, timer };
1807
+ this.tryServe();
1808
+ });
1809
+ }
1810
+ async write(bytes, deadlineMs) {
1811
+ try {
1812
+ await this.writeTracked(bytes, deadlineMs).completed;
1813
+ } catch (err) {
1814
+ if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError) {
1815
+ throw err.cause ?? err;
1816
+ }
1817
+ throw err;
1818
+ }
1819
+ }
1820
+ writeTracked(bytes, deadlineMs) {
1821
+ if (this.closedErr) {
1822
+ return {
1823
+ queued: false,
1824
+ completed: Promise.reject(new SocketWriteNotQueuedError("subc socket was closed before bytes could be queued", this.closedErr))
1825
+ };
1826
+ }
1827
+ let queued = false;
1828
+ let settled = false;
1829
+ let timer = null;
1830
+ const completed = new Promise((resolve4, reject) => {
1831
+ const settle = (run) => {
1832
+ if (settled)
1833
+ return;
1834
+ settled = true;
1835
+ if (timer)
1836
+ clearTimeout(timer);
1837
+ run();
1838
+ };
1839
+ const remaining = deadlineMs - Date.now();
1840
+ if (remaining <= 0) {
1841
+ settle(() => reject(new SocketWriteNotQueuedError("timed out before bytes could be queued to subc", new SocketTimeoutError("timed out writing to subc"))));
1842
+ return;
1843
+ }
1844
+ timer = setTimeout(() => {
1845
+ const timeout = new SocketTimeoutError("timed out writing to subc");
1846
+ settle(() => reject(queued ? new SocketWriteQueuedError("timed out after bytes were handed to the subc socket", timeout) : new SocketWriteNotQueuedError("timed out before bytes could be queued to subc", timeout)));
1847
+ }, remaining);
1848
+ try {
1849
+ this.sock.write(Buffer.from(bytes), (err) => {
1850
+ settle(() => {
1851
+ if (err) {
1852
+ reject(new SocketWriteQueuedError("subc socket reported a write error after bytes were handed to the socket", err instanceof Error ? err : new Error(String(err))));
1853
+ } else {
1854
+ resolve4();
1855
+ }
1856
+ });
1857
+ });
1858
+ queued = true;
1859
+ } catch (err) {
1860
+ settle(() => reject(new SocketWriteNotQueuedError("subc socket write threw before bytes could be queued", err instanceof Error ? err : new Error(String(err)))));
1861
+ }
1862
+ });
1863
+ return { queued, completed };
1864
+ }
1865
+ close() {
1866
+ this.sock.destroy();
1867
+ }
1868
+ tryServe() {
1869
+ const w = this.waiter;
1870
+ if (!w)
1871
+ return;
1872
+ if (this.buffered >= w.need) {
1873
+ const out = this.take(w.need);
1874
+ this.waiter = null;
1875
+ if (w.timer)
1876
+ clearTimeout(w.timer);
1877
+ w.resolve(out);
1878
+ return;
1879
+ }
1880
+ if (this.closedErr) {
1881
+ this.waiter = null;
1882
+ if (w.timer)
1883
+ clearTimeout(w.timer);
1884
+ w.reject(this.closedErr);
1885
+ }
1886
+ }
1887
+ take(n) {
1888
+ const out = Buffer.allocUnsafe(n);
1889
+ let off = 0;
1890
+ while (off < n) {
1891
+ const head = this.chunks[0];
1892
+ const want = n - off;
1893
+ if (head.length <= want) {
1894
+ head.copy(out, off);
1895
+ off += head.length;
1896
+ this.chunks.shift();
1897
+ } else {
1898
+ head.copy(out, off, 0, want);
1899
+ this.chunks[0] = head.subarray(want);
1900
+ off += want;
1901
+ }
1902
+ }
1903
+ this.buffered -= n;
1904
+ return out;
1905
+ }
1906
+ }
1907
+ var SocketClosedError, SocketTimeoutError, SocketWriteNotQueuedError, SocketWriteQueuedError;
1908
+ var init_socket = __esm(() => {
1909
+ SocketClosedError = class SocketClosedError extends Error {
1910
+ };
1911
+ SocketTimeoutError = class SocketTimeoutError extends Error {
1912
+ };
1913
+ SocketWriteNotQueuedError = class SocketWriteNotQueuedError extends Error {
1914
+ cause;
1915
+ constructor(message, cause) {
1916
+ super(message);
1917
+ this.cause = cause;
1918
+ }
1919
+ };
1920
+ SocketWriteQueuedError = class SocketWriteQueuedError extends Error {
1921
+ cause;
1922
+ constructor(message, cause) {
1923
+ super(message);
1924
+ this.cause = cause;
1925
+ }
1926
+ };
1927
+ });
1928
+
1929
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/client.ts
1930
+ import { promises as fs2 } from "node:fs";
1931
+
1932
+ class SubcClient {
1933
+ sock;
1934
+ currentConn;
1935
+ opts;
1936
+ nextCorr = 1n;
1937
+ pending = new Map;
1938
+ routes = new Map;
1939
+ closedErr = null;
1940
+ closeStarted = false;
1941
+ reconnecting = null;
1942
+ generation = 1;
1943
+ constructor(sock, currentConn, opts) {
1944
+ this.sock = sock;
1945
+ this.currentConn = currentConn;
1946
+ this.opts = opts;
1947
+ this.readLoop(sock, this.generation);
1948
+ }
1949
+ get conn() {
1950
+ return this.currentConn;
1951
+ }
1952
+ static async connect(opts) {
1953
+ const normalized = normalizeConnectOptions(opts);
1954
+ const opened = await SubcClient.openConnection(normalized);
1955
+ return new SubcClient(opened.sock, opened.conn, normalized);
1956
+ }
1957
+ async catalogList(moduleId) {
1958
+ const body = this.encode(moduleId === undefined ? { op: "catalog.list" } : { op: "catalog.list", module_id: moduleId });
1959
+ const reply = await this.controlRpc(body);
1960
+ const parsed = this.parseJson(reply);
1961
+ return parsed.modules ?? [];
1962
+ }
1963
+ async routeOpen(target, identity, opts = {}) {
1964
+ const consumerIdentity = routeOpenConsumerIdentity(opts);
1965
+ const body = this.encode({
1966
+ op: "route.open",
1967
+ target,
1968
+ identity,
1969
+ ...consumerIdentity ? { consumer_identity: consumerIdentity } : {}
1970
+ });
1971
+ const reply = await this.controlRpc(body);
1972
+ const parsed = this.parseJson(reply);
1973
+ if (typeof parsed.route_channel !== "number") {
1974
+ throw new SubcError(`route.open returned no route_channel: ${JSON.stringify(parsed)}`);
1975
+ }
1976
+ return parsed.route_channel;
1977
+ }
1978
+ async request(routeChannel, body, opts = {}) {
1979
+ const bytes = body instanceof Uint8Array ? body : this.encode(body);
1980
+ const priority = opts.priority ?? 1 /* Interactive */;
1981
+ const reply = await this.send(routeChannel, bytes, priority, opts.timeoutMs, opts.onProgress);
1982
+ return this.parseJson(reply);
1983
+ }
1984
+ async call(moduleId, method, params, opts = {}) {
1985
+ const body = params === undefined ? { method } : { method, params };
1986
+ for (;; ) {
1987
+ const routeChannel = await this.cachedRouteChannel(moduleId, opts);
1988
+ try {
1989
+ return await this.managedRequest(routeChannel, body, opts);
1990
+ } catch (err) {
1991
+ if (!(err instanceof SubcCallError))
1992
+ throw this.terminalCallError("managed call failed", err);
1993
+ if (err.kind === "not_sent") {
1994
+ try {
1995
+ await this.reconnectAfterDrop(err);
1996
+ } catch (reconnectErr) {
1997
+ throw this.notSentRecoveryError("managed call was not sent", reconnectErr);
1998
+ }
1999
+ continue;
2000
+ }
2001
+ if (err.kind === "outcome_unknown") {
2002
+ this.scheduleReconnectAfterDrop(err);
2003
+ }
2004
+ throw err;
2005
+ }
2006
+ }
2007
+ }
2008
+ subscribe(routeChannel, body, onEvent, opts = {}) {
2009
+ const bytes = body instanceof Uint8Array ? body : this.encode(body);
2010
+ const priority = opts.priority ?? 1 /* Interactive */;
2011
+ const corr = this.nextCorr++;
2012
+ const key = `${routeChannel}:${corr}`;
2013
+ const closed = new Promise((resolve4, reject) => {
2014
+ if (this.closedErr) {
2015
+ reject(this.closedErr);
2016
+ return;
2017
+ }
2018
+ this.pending.set(key, {
2019
+ channel: routeChannel,
2020
+ resolve: () => resolve4(),
2021
+ reject,
2022
+ onProgress: onEvent,
2023
+ timer: null,
2024
+ subscription: true
2025
+ });
2026
+ const frame = buildFrame(0 /* Request */, buildFlags(false, priority, false), routeChannel, corr, bytes);
2027
+ this.sock.write(encodeFrame(frame), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch((err) => {
2028
+ const p = this.pending.get(key);
2029
+ if (p)
2030
+ this.rejectPending(key, p, err instanceof Error ? err : new SubcError(String(err)));
2031
+ });
2032
+ });
2033
+ let cancelled = false;
2034
+ const unsubscribe = () => {
2035
+ if (cancelled)
2036
+ return;
2037
+ cancelled = true;
2038
+ const cancel = buildFrame(6 /* Cancel */, buildFlags(false, priority, false), routeChannel, corr, EMPTY_BODY);
2039
+ this.sock.write(encodeFrame(cancel), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {});
2040
+ };
2041
+ return { unsubscribe, closed };
2042
+ }
2043
+ async closeRoute(target, identity, opts = {}) {
2044
+ const key = routeCacheKey(target, identity, routeOpenConsumerIdentity(opts));
2045
+ const cached = this.routes.get(key);
2046
+ if (!cached)
2047
+ return;
2048
+ cached.closed = true;
2049
+ this.routes.delete(key);
2050
+ const channel = cached.channel;
2051
+ cached.channel = null;
2052
+ if (channel !== null)
2053
+ await this.closeRouteChannel(channel, opts);
2054
+ }
2055
+ async closeRouteChannel(channel, opts = {}) {
2056
+ if (channel === 0)
2057
+ return;
2058
+ if (opts.drain) {
2059
+ await this.drainUnaryOnChannel(channel);
2060
+ }
2061
+ this.failChannel(channel, new SubcError("route closed by closeRoute", "route_closed"));
2062
+ this.sendRouteGoodbye(channel);
2063
+ }
2064
+ close() {
2065
+ this.closeStarted = true;
2066
+ this.fail(new SubcError("client closed"));
2067
+ this.sock.close();
2068
+ }
2069
+ drainUnaryOnChannel(channel) {
2070
+ const waiters = [];
2071
+ for (const pending of this.pending.values()) {
2072
+ if (pending.channel === channel && !pending.subscription) {
2073
+ waiters.push(new Promise((resolve4) => {
2074
+ const prev = pending.onSettle;
2075
+ pending.onSettle = () => {
2076
+ prev?.();
2077
+ resolve4();
2078
+ };
2079
+ }));
2080
+ }
2081
+ }
2082
+ return Promise.all(waiters).then(() => {
2083
+ return;
2084
+ });
2085
+ }
2086
+ sendRouteGoodbye(channel) {
2087
+ if (this.closedErr)
2088
+ return;
2089
+ const goodbye = buildFrame(11 /* Goodbye */, buildFlags(false, 1 /* Interactive */, false), channel, 0n, EMPTY_BODY);
2090
+ this.sock.write(encodeFrame(goodbye), Date.now() + DEFAULT_REQUEST_TIMEOUT_MS).catch(() => {});
2091
+ }
2092
+ static async openConnection(opts) {
2093
+ const conn = await readConnectionFile(opts.connectionFile);
2094
+ const deadline = Date.now() + (opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS);
2095
+ const endpoint = conn.endpoints[0];
2096
+ const sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
2097
+ try {
2098
+ await authenticateClient(sock, conn, deadline);
2099
+ } catch (err) {
2100
+ sock.close();
2101
+ throw err;
2102
+ }
2103
+ return { sock, conn };
2104
+ }
2105
+ async controlRpc(body) {
2106
+ return this.send(0, body, 1 /* Interactive */, undefined, undefined);
2107
+ }
2108
+ send(channel, body, priority, timeoutMs, onProgress) {
2109
+ if (this.closedErr)
2110
+ return Promise.reject(this.closedErr);
2111
+ const corr = this.nextCorr++;
2112
+ const key = `${channel}:${corr}`;
2113
+ const frame = buildFrame(0 /* Request */, buildFlags(false, priority, false), channel, corr, body);
2114
+ return new Promise((resolve4, reject) => {
2115
+ const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
2116
+ const pending = {
2117
+ channel,
2118
+ resolve: resolve4,
2119
+ reject,
2120
+ onProgress,
2121
+ timer: null
2122
+ };
2123
+ pending.timer = setTimeout(() => {
2124
+ this.rejectPending(key, pending, new SubcError(`request on channel ${channel} timed out after ${ms}ms`));
2125
+ }, ms);
2126
+ this.pending.set(key, pending);
2127
+ this.sock.write(encodeFrame(frame), Date.now() + ms).catch((err) => {
2128
+ const p = this.pending.get(key);
2129
+ if (p)
2130
+ this.rejectPending(key, p, err instanceof Error ? err : new SubcError(String(err)));
2131
+ });
2132
+ });
2133
+ }
2134
+ async managedRequest(routeChannel, body, opts) {
2135
+ const bytes = body instanceof Uint8Array ? body : this.encode(body);
2136
+ const priority = opts.priority ?? 1 /* Interactive */;
2137
+ try {
2138
+ const reply = await this.sendManaged(routeChannel, bytes, priority, opts.timeoutMs, opts.onProgress);
2139
+ return this.parseJson(reply);
2140
+ } catch (err) {
2141
+ if (err instanceof SubcCallError)
2142
+ throw err;
2143
+ throw this.terminalCallError("managed call failed", err);
2144
+ }
2145
+ }
2146
+ sendManaged(channel, body, priority, timeoutMs, onProgress) {
2147
+ if (this.closedErr) {
2148
+ return Promise.reject(this.notSentCallError("request was not sent because the subc connection was already closed", this.closedErr));
2149
+ }
2150
+ const corr = this.nextCorr++;
2151
+ const key = `${channel}:${corr}`;
2152
+ const frame = buildFrame(0 /* Request */, buildFlags(false, priority, false), channel, corr, body);
2153
+ let handedToSocket = false;
2154
+ const classifyFailure = (err) => {
2155
+ if (!handedToSocket) {
2156
+ return this.notSentCallError("request bytes were not queued to the subc socket", err);
2157
+ }
2158
+ return this.outcomeUnknownCallError("connection dropped before the managed call returned a response", err);
2159
+ };
2160
+ return new Promise((resolve4, reject) => {
2161
+ const ms = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
2162
+ const pending = {
2163
+ channel,
2164
+ resolve: resolve4,
2165
+ reject,
2166
+ onProgress,
2167
+ timer: null,
2168
+ classifyFailure
2169
+ };
2170
+ pending.timer = setTimeout(() => {
2171
+ this.rejectPending(key, pending, new SubcError(`request on channel ${channel} timed out after ${ms}ms`));
2172
+ }, ms);
2173
+ this.pending.set(key, pending);
2174
+ const write = this.sock.writeTracked(encodeFrame(frame), Date.now() + ms);
2175
+ handedToSocket = write.queued;
2176
+ write.completed.catch((err) => {
2177
+ const p = this.pending.get(key);
2178
+ if (p)
2179
+ this.rejectPending(key, p, err instanceof Error ? err : new SubcError(String(err)));
2180
+ });
2181
+ });
2182
+ }
2183
+ async cachedRouteChannel(moduleId, opts) {
2184
+ const identity = opts.identity ?? this.opts.identity;
2185
+ if (!identity) {
2186
+ throw new SubcCallError("terminal", "managed call requires a BindIdentity in SubcClient.connect({ identity }) or call(..., { identity })", "missing_identity");
2187
+ }
2188
+ const target = { kind: opts.targetKind ?? this.opts.targetKind, module_id: moduleId };
2189
+ const consumerIdentity = routeOpenConsumerIdentity(opts);
2190
+ const key = routeCacheKey(target, identity, consumerIdentity);
2191
+ let cached = this.routes.get(key);
2192
+ if (!cached) {
2193
+ cached = {
2194
+ key,
2195
+ moduleId,
2196
+ target,
2197
+ identity,
2198
+ consumerIdentity,
2199
+ channel: null,
2200
+ generation: 0,
2201
+ opening: null
2202
+ };
2203
+ this.routes.set(key, cached);
2204
+ }
2205
+ if (cached.channel !== null && cached.generation === this.generation && !this.closedErr) {
2206
+ return cached.channel;
2207
+ }
2208
+ if (!cached.opening) {
2209
+ cached.opening = this.openCachedRoute(cached).finally(() => {
2210
+ cached.opening = null;
2211
+ });
2212
+ }
2213
+ return cached.opening;
2214
+ }
2215
+ async openCachedRoute(cached) {
2216
+ for (;; ) {
2217
+ if (cached.closed)
2218
+ throw this.routeClosedDuringOpen();
2219
+ try {
2220
+ await this.ensureConnectedForManaged();
2221
+ } catch (err) {
2222
+ throw this.notSentRecoveryError("route.open could not run because reconnect failed", err);
2223
+ }
2224
+ if (cached.channel !== null && cached.generation === this.generation && !this.closedErr) {
2225
+ return cached.channel;
2226
+ }
2227
+ try {
2228
+ const channel = await this.routeOpen(cached.target, cached.identity, {
2229
+ consumerIdentity: cached.consumerIdentity ?? null
2230
+ });
2231
+ if (cached.closed) {
2232
+ this.sendRouteGoodbye(channel);
2233
+ throw this.routeClosedDuringOpen();
2234
+ }
2235
+ cached.channel = channel;
2236
+ cached.generation = this.generation;
2237
+ return channel;
2238
+ } catch (err) {
2239
+ if (err instanceof SubcCallError && err.code === "route_closed")
2240
+ throw err;
2241
+ if (!this.closeStarted && isConsumerReconnectTransient(err)) {
2242
+ try {
2243
+ await this.reconnectAfterDrop(err);
2244
+ } catch (reconnectErr) {
2245
+ throw this.notSentRecoveryError("route.open was not sent and reconnect failed", reconnectErr);
2246
+ }
2247
+ continue;
2248
+ }
2249
+ throw this.terminalCallError(`route.open failed for module ${cached.moduleId}`, err);
2250
+ }
2251
+ }
2252
+ }
2253
+ async ensureConnectedForManaged() {
2254
+ if (this.closeStarted)
2255
+ throw new SubcError("client closed");
2256
+ if (this.reconnecting)
2257
+ await this.reconnecting;
2258
+ if (this.closedErr)
2259
+ await this.reconnectAfterDrop(this.closedErr);
2260
+ }
2261
+ scheduleReconnectAfterDrop(err) {
2262
+ if (this.closeStarted || this.reconnecting)
2263
+ return;
2264
+ this.reconnectAfterDrop(err).catch(() => {});
2265
+ }
2266
+ reconnectAfterDrop(trigger) {
2267
+ if (this.closeStarted)
2268
+ return Promise.reject(new SubcError("client closed"));
2269
+ if (this.reconnecting)
2270
+ return this.reconnecting;
2271
+ const promise = this.reconnectWithRetry(trigger).finally(() => {
2272
+ if (this.reconnecting === promise)
2273
+ this.reconnecting = null;
2274
+ });
2275
+ this.reconnecting = promise;
2276
+ return promise;
2277
+ }
2278
+ async reconnectWithRetry(_trigger) {
2279
+ let attempt = 0;
2280
+ let delay = this.opts.reconnectBackoff.baseMs;
2281
+ for (;; ) {
2282
+ if (this.closeStarted)
2283
+ throw new SubcError("client closed");
2284
+ attempt += 1;
2285
+ try {
2286
+ const opened = await SubcClient.openConnection(this.opts);
2287
+ if (this.closeStarted) {
2288
+ opened.sock.close();
2289
+ throw new SubcError("client closed");
2290
+ }
2291
+ this.replaceConnection(opened);
2292
+ await this.reopenCachedRoutes();
2293
+ return;
2294
+ } catch (err) {
2295
+ if (!isConsumerReconnectTransient(err) || attempt >= this.opts.reconnectBackoff.maxAttempts) {
2296
+ throw err;
2297
+ }
2298
+ await this.opts.sleep(delay);
2299
+ delay = Math.min(delay * 2, this.opts.reconnectBackoff.capMs);
2300
+ }
2301
+ }
2302
+ }
2303
+ replaceConnection(opened) {
2304
+ this.sock.close();
2305
+ this.sock = opened.sock;
2306
+ this.currentConn = opened.conn;
2307
+ this.closedErr = null;
2308
+ this.generation += 1;
2309
+ this.readLoop(opened.sock, this.generation);
2310
+ }
2311
+ async reopenCachedRoutes() {
2312
+ for (const cached of this.routes.values()) {
2313
+ cached.channel = null;
2314
+ cached.generation = 0;
2315
+ }
2316
+ for (const cached of this.routes.values()) {
2317
+ if (cached.closed)
2318
+ continue;
2319
+ const channel = await this.routeOpen(cached.target, cached.identity);
2320
+ if (cached.closed) {
2321
+ this.sendRouteGoodbye(channel);
2322
+ continue;
2323
+ }
2324
+ cached.channel = channel;
2325
+ cached.generation = this.generation;
2326
+ }
2327
+ }
2328
+ routeClosedDuringOpen() {
2329
+ return new SubcCallError("not_sent", "route was closed before route.open completed", "route_closed");
2330
+ }
2331
+ async readLoop(sock, generation) {
2332
+ try {
2333
+ for (;; ) {
2334
+ const headerBytes = await sock.readExact(HEADER_LEN, Number.POSITIVE_INFINITY);
2335
+ const header = decodeHeader(headerBytes);
2336
+ const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, Date.now() + BODY_READ_TIMEOUT_MS);
2337
+ this.dispatch({ header, body });
2338
+ }
2339
+ } catch (err) {
2340
+ if (this.sock === sock && this.generation === generation) {
2341
+ this.fail(err instanceof Error ? err : new SubcError(String(err)));
2342
+ }
2343
+ }
2344
+ }
2345
+ dispatch(frame) {
2346
+ const key = `${frame.header.channel}:${frame.header.corr}`;
2347
+ const pending = this.pending.get(key);
2348
+ if (pending) {
2349
+ switch (frame.header.ty) {
2350
+ case 2 /* Push */:
2351
+ case 3 /* StreamData */:
2352
+ pending.onProgress?.(frame.body);
2353
+ return;
2354
+ case 1 /* Response */:
2355
+ case 4 /* StreamEnd */:
2356
+ this.settle(key, pending, () => pending.resolve(frame));
2357
+ return;
2358
+ case 5 /* Error */:
2359
+ this.settle(key, pending, () => pending.reject(this.errorFromFrame(frame)));
2360
+ return;
2361
+ default:
2362
+ return;
2363
+ }
2364
+ }
2365
+ if (frame.header.ty === 11 /* Goodbye */) {
2366
+ this.failChannel(frame.header.channel, new SubcError("route closed by subc (GOODBYE)"));
2367
+ return;
2368
+ }
2369
+ }
2370
+ settle(key, pending, run) {
2371
+ this.pending.delete(key);
2372
+ if (pending.timer)
2373
+ clearTimeout(pending.timer);
2374
+ run();
2375
+ pending.onSettle?.();
2376
+ }
2377
+ rejectPending(key, pending, err) {
2378
+ this.settle(key, pending, () => pending.reject(pending.classifyFailure?.(err) ?? err));
2379
+ }
2380
+ errorFromFrame(frame) {
2381
+ try {
2382
+ const parsed = JSON.parse(Buffer.from(frame.body).toString("utf8"));
2383
+ return new SubcError(parsed.message ?? "subc error", parsed.code);
2384
+ } catch {
2385
+ return new SubcError(Buffer.from(frame.body).toString("utf8") || "subc error");
2386
+ }
2387
+ }
2388
+ failChannel(channel, err) {
2389
+ for (const [key, pending] of this.pending) {
2390
+ if (pending.channel === channel) {
2391
+ this.rejectPending(key, pending, err);
2392
+ }
2393
+ }
2394
+ }
2395
+ fail(err) {
2396
+ if (!this.closedErr)
2397
+ this.closedErr = err;
2398
+ for (const [key, pending] of this.pending) {
2399
+ this.rejectPending(key, pending, err);
2400
+ }
2401
+ }
2402
+ notSentCallError(message, cause) {
2403
+ return new SubcCallError("not_sent", `${message}${causeMessage(cause)}`, errorCode(cause), cause);
2404
+ }
2405
+ outcomeUnknownCallError(message, cause) {
2406
+ return new SubcCallError("outcome_unknown", `${message}${causeMessage(cause)}`, errorCode(cause), cause);
2407
+ }
2408
+ terminalCallError(message, cause) {
2409
+ if (cause instanceof SubcCallError)
2410
+ return cause;
2411
+ return new SubcCallError("terminal", `${message}${causeMessage(cause)}`, errorCode(cause), cause);
2412
+ }
2413
+ notSentRecoveryError(message, cause) {
2414
+ if (cause instanceof SubcCallError)
2415
+ return cause;
2416
+ if (isConsumerReconnectTransient(cause))
2417
+ return this.notSentCallError(message, cause);
2418
+ return this.terminalCallError(message, cause);
2419
+ }
2420
+ encode(value) {
2421
+ return new Uint8Array(Buffer.from(JSON.stringify(value), "utf8"));
2422
+ }
2423
+ parseJson(frame) {
2424
+ return JSON.parse(Buffer.from(frame.body).toString("utf8"));
2425
+ }
2426
+ }
2427
+ function isConsumerReconnectTransient(err) {
2428
+ if (err instanceof SocketClosedError || err instanceof SocketTimeoutError)
2429
+ return true;
2430
+ if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError)
2431
+ return true;
2432
+ if (err instanceof SubcCallError)
2433
+ return err.kind === "not_sent" || err.kind === "outcome_unknown";
2434
+ if (err instanceof SubcError || err instanceof ConnectionFileError || err instanceof AuthError)
2435
+ return false;
2436
+ const code = errorCode(err);
2437
+ return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
2438
+ }
2439
+ async function connectionFileExists(path) {
2440
+ try {
2441
+ await fs2.access(path);
2442
+ return true;
2443
+ } catch {
2444
+ return false;
2445
+ }
2446
+ }
2447
+ function normalizeConnectOptions(opts) {
2448
+ return {
2449
+ connectionFile: opts.connectionFile,
2450
+ handshakeTimeoutMs: opts.handshakeTimeoutMs,
2451
+ identity: opts.identity,
2452
+ targetKind: opts.targetKind ?? DEFAULT_MANAGED_TARGET_KIND,
2453
+ reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
2454
+ sleep: opts.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)))
2455
+ };
2456
+ }
2457
+ function routeCacheKey(target, identity, consumerIdentity) {
2458
+ const consumerPart = consumerIdentity ? `${consumerIdentity.module_id}\x00${consumerIdentity.launch_nonce}` : "";
2459
+ return `${target.kind}\x00${target.module_id}\x00${identity.project_root}\x00${identity.harness}\x00${identity.session}\x00${consumerPart}`;
2460
+ }
2461
+ function routeOpenConsumerIdentity(opts = {}) {
2462
+ if (opts.consumerIdentity !== undefined)
2463
+ return opts.consumerIdentity ?? undefined;
2464
+ const moduleId = process.env[SUBC_MODULE_ID_ENV];
2465
+ const launchNonce = process.env[SUBC_LAUNCH_NONCE_ENV];
2466
+ if (!moduleId || !launchNonce)
2467
+ return;
2468
+ return { module_id: moduleId, launch_nonce: launchNonce };
2469
+ }
2470
+ function errorCode(err) {
2471
+ if (typeof err === "object" && err !== null && "code" in err) {
2472
+ const code = err.code;
2473
+ if (typeof code === "string")
2474
+ return code;
2475
+ }
2476
+ return;
2477
+ }
2478
+ function causeMessage(cause) {
2479
+ if (cause === undefined)
2480
+ return "";
2481
+ return `: ${cause instanceof Error ? cause.message : String(cause)}`;
2482
+ }
2483
+ var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4, DEFAULT_REQUEST_TIMEOUT_MS = 30000, BODY_READ_TIMEOUT_MS = 30000, EMPTY_BODY, DEFAULT_MANAGED_TARGET_KIND = "management_surface", SUBC_MODULE_ID_ENV = "SUBC_MODULE_ID", SUBC_LAUNCH_NONCE_ENV = "SUBC_LAUNCH_NONCE", DEFAULT_RECONNECT_BACKOFF, SubcCallError, SubcError;
2484
+ var init_client = __esm(() => {
2485
+ init_auth();
2486
+ init_connection_file();
2487
+ init_envelope();
2488
+ init_socket();
2489
+ EMPTY_BODY = new Uint8Array(0);
2490
+ DEFAULT_RECONNECT_BACKOFF = {
2491
+ baseMs: 100,
2492
+ capMs: 2000,
2493
+ maxAttempts: 6
2494
+ };
2495
+ SubcCallError = class SubcCallError extends Error {
2496
+ kind;
2497
+ code;
2498
+ cause;
2499
+ constructor(kind, message, code, cause) {
2500
+ super(message);
2501
+ this.kind = kind;
2502
+ this.code = code;
2503
+ this.cause = cause;
2504
+ this.name = "SubcCallError";
2505
+ }
2506
+ };
2507
+ SubcError = class SubcError extends Error {
2508
+ code;
2509
+ constructor(message, code) {
2510
+ super(message);
2511
+ this.code = code;
2512
+ }
2513
+ };
2514
+ });
2515
+
2516
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/provider.ts
2517
+ import { Buffer as Buffer2 } from "node:buffer";
2518
+
2519
+ class SubcProvider {
2520
+ sock;
2521
+ currentConn;
2522
+ opts;
2523
+ closed;
2524
+ resolveClosed = () => {
2525
+ return;
2526
+ };
2527
+ closeStarted = false;
2528
+ closedErr = null;
2529
+ inflight = new Map;
2530
+ reconnecting = null;
2531
+ generation = 1;
2532
+ connectionEpoch = 1;
2533
+ stateQueue = [];
2534
+ drainingStateQueue = false;
2535
+ restoredDebounceToken = 0;
2536
+ storage;
2537
+ constructor(sock, currentConn, opts, storage) {
2538
+ this.sock = sock;
2539
+ this.currentConn = currentConn;
2540
+ this.opts = opts;
2541
+ this.storage = storage;
2542
+ this.closed = new Promise((resolve4) => {
2543
+ this.resolveClosed = resolve4;
2544
+ });
2545
+ this.readLoop(sock, this.generation);
2546
+ this.enqueueConnectionState({ state: "connected", epoch: this.connectionEpoch });
2547
+ }
2548
+ get conn() {
2549
+ return this.currentConn;
2550
+ }
2551
+ currentEpoch() {
2552
+ return this.connectionEpoch;
2553
+ }
2554
+ static async connect(opts) {
2555
+ if (opts.manifest.protocol_ver !== PROTOCOL_VERSION) {
2556
+ throw new SubcProviderError(`manifest protocol_ver ${opts.manifest.protocol_ver} does not match client protocol ${PROTOCOL_VERSION}`, "invalid_manifest");
2557
+ }
2558
+ const normalized = normalizeProviderConnectOptions(opts);
2559
+ const opened = await SubcProvider.openConnection(normalized);
2560
+ return new SubcProvider(opened.sock, opened.conn, normalized, opened.ack.storage);
2561
+ }
2562
+ async close() {
2563
+ if (!this.closeStarted) {
2564
+ this.closeStarted = true;
2565
+ this.cancelRestoredDebounce();
2566
+ const sock = this.sock;
2567
+ try {
2568
+ await sendFrame(sock, buildFrame(11 /* Goodbye */, controlFlags(), 0, 0n, new Uint8Array(0)));
2569
+ } catch {} finally {
2570
+ sock.close();
2571
+ this.finishClosed();
2572
+ }
2573
+ }
2574
+ await this.closed;
2575
+ }
2576
+ static async openConnection(opts) {
2577
+ const conn = await readConnectionFile(opts.connectionFile);
2578
+ const deadline = Date.now() + (opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS2);
2579
+ const endpoint = conn.endpoints[0];
2580
+ const sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
2581
+ try {
2582
+ await authenticateClient(sock, conn, deadline);
2583
+ await sendFrame(sock, buildHelloFrame(opts));
2584
+ const ack = await expectHelloAck(sock, deadline);
2585
+ return { sock, conn, ack };
2586
+ } catch (err) {
2587
+ sock.close();
2588
+ throw err;
2589
+ }
2590
+ }
2591
+ async readLoop(sock, generation) {
2592
+ try {
2593
+ for (;; ) {
2594
+ const headerBytes = await sock.readExact(HEADER_LEN, Number.POSITIVE_INFINITY);
2595
+ const header = decodeHeader(headerBytes);
2596
+ const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, Date.now() + BODY_READ_TIMEOUT_MS2);
2597
+ const keepGoing = await this.dispatch({ header, body }, sock, generation);
2598
+ if (!keepGoing) {
2599
+ if (this.sock === sock && this.generation === generation)
2600
+ this.closeStarted = true;
2601
+ break;
2602
+ }
2603
+ }
2604
+ } catch (err) {
2605
+ if (this.sock === sock && this.generation === generation && !this.closeStarted) {
2606
+ this.handleUnexpectedDrop(sock, generation, err instanceof Error ? err : new SubcProviderError(String(err)));
2607
+ return;
2608
+ }
2609
+ } finally {
2610
+ if (this.sock === sock && this.generation === generation) {
2611
+ sock.close();
2612
+ if (this.closeStarted)
2613
+ this.finishClosed();
2614
+ }
2615
+ }
2616
+ }
2617
+ async dispatch(frame, sock, generation) {
2618
+ switch (frame.header.ty) {
2619
+ case 7 /* Ping */:
2620
+ if (frame.header.channel === 0) {
2621
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, 8 /* Pong */, frame.header.flags, 0, frame.header.corr, new Uint8Array(0)));
2622
+ }
2623
+ return true;
2624
+ case 11 /* Goodbye */:
2625
+ if (frame.header.channel === 0)
2626
+ return false;
2627
+ this.abortChannel(generation, frame.header.channel);
2628
+ await this.opts.onRouteGone?.(frame.header.channel);
2629
+ return true;
2630
+ case 6 /* Cancel */:
2631
+ this.inflight.get(routeKey(generation, frame.header.channel, frame.header.corr))?.abort();
2632
+ return true;
2633
+ case 0 /* Request */:
2634
+ if (frame.header.channel === 0) {
2635
+ await this.handleControlRequest(frame, sock, generation);
2636
+ } else {
2637
+ this.handleDataRequest(frame, sock, generation).catch((err) => {
2638
+ if (!this.closeStarted && this.sock === sock && this.generation === generation) {
2639
+ console.warn("SubcProvider handler failed after its request was dispatched", err);
2640
+ }
2641
+ });
2642
+ }
2643
+ return true;
2644
+ default:
2645
+ return true;
2646
+ }
2647
+ }
2648
+ abortChannel(generation, channel) {
2649
+ const prefix = `${generation}:${channel}:`;
2650
+ for (const [key, controller] of this.inflight) {
2651
+ if (key.startsWith(prefix))
2652
+ controller.abort();
2653
+ }
2654
+ }
2655
+ abortGeneration(generation) {
2656
+ const prefix = `${generation}:`;
2657
+ for (const [key, controller] of this.inflight) {
2658
+ if (key.startsWith(prefix))
2659
+ controller.abort();
2660
+ }
2661
+ }
2662
+ abortAllInflight() {
2663
+ for (const controller of this.inflight.values())
2664
+ controller.abort();
2665
+ }
2666
+ async handleControlRequest(frame, sock, generation) {
2667
+ const request = parseJson(frame.body);
2668
+ if (request.op !== "route.bind") {
2669
+ throw new SubcProviderError(`unsupported module control request ${request.op ?? "<missing op>"}`);
2670
+ }
2671
+ const bindRequest = {
2672
+ route_channel: numberField(request.route_channel, "route_channel"),
2673
+ target: request.target,
2674
+ identity: request.identity,
2675
+ principal: request.principal
2676
+ };
2677
+ const decision = await this.opts.onBind?.(bindRequest);
2678
+ const rejection = bindRejection(decision);
2679
+ if (rejection) {
2680
+ await this.sendError(frame, rejection.code, rejection.message, controlFlags(), sock, generation);
2681
+ return;
2682
+ }
2683
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, 1 /* Response */, controlFlags(), 0, frame.header.corr, encodeJson({ op: "route.bind" })));
2684
+ }
2685
+ async handleDataRequest(frame, sock, generation) {
2686
+ const { channel, corr, ver } = frame.header;
2687
+ const key = routeKey(generation, channel, corr);
2688
+ const controller = new AbortController;
2689
+ this.inflight.set(key, controller);
2690
+ const dataFlags = buildFlags(false, 1 /* Interactive */, false);
2691
+ const ctx = {
2692
+ signal: controller.signal,
2693
+ currentEpoch: () => this.connectionEpoch,
2694
+ emit: async (eventBody) => {
2695
+ if (controller.signal.aborted)
2696
+ return;
2697
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, 3 /* StreamData */, dataFlags, channel, corr, eventBody));
2698
+ }
2699
+ };
2700
+ try {
2701
+ const body = await this.opts.handler(channel, frame.body, ctx);
2702
+ if (body === undefined) {
2703
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, 4 /* StreamEnd */, dataFlags, channel, corr, new Uint8Array(0)));
2704
+ } else if (body instanceof Uint8Array) {
2705
+ await this.sendOn(sock, generation, buildFrameWithVersion(ver, 1 /* Response */, dataFlags, channel, corr, body));
2706
+ } else {
2707
+ throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
2708
+ }
2709
+ } catch (err) {
2710
+ await this.sendError(frame, err instanceof SubcProviderError && err.code ? err.code : "handler_error", err instanceof Error ? err.message : String(err), dataFlags, sock, generation);
2711
+ } finally {
2712
+ if (this.inflight.get(key) === controller)
2713
+ this.inflight.delete(key);
2714
+ }
2715
+ }
2716
+ async sendError(frame, code, message, flags, sock, generation) {
2717
+ await this.sendOn(sock, generation, buildFrameWithVersion(frame.header.ver, 5 /* Error */, flags, frame.header.channel, frame.header.corr, encodeJson({ code, message })));
2718
+ }
2719
+ async sendOn(sock, generation, frame) {
2720
+ if (this.sock !== sock || this.generation !== generation || this.closeStarted || this.closedErr)
2721
+ return;
2722
+ await sendFrame(sock, frame);
2723
+ }
2724
+ handleUnexpectedDrop(sock, generation, cause) {
2725
+ this.cancelRestoredDebounce();
2726
+ this.abortGeneration(generation);
2727
+ this.generation += 1;
2728
+ sock.close();
2729
+ this.enqueueConnectionState({ state: "down", cause });
2730
+ this.scheduleReconnectAfterDrop(cause);
2731
+ }
2732
+ scheduleReconnectAfterDrop(trigger) {
2733
+ if (this.closeStarted || this.reconnecting)
2734
+ return;
2735
+ const promise = this.reconnectWithRetry(trigger).catch((err) => {
2736
+ if (!this.closeStarted)
2737
+ this.failFatal(err instanceof Error ? err : new SubcProviderError(String(err)));
2738
+ }).finally(() => {
2739
+ if (this.reconnecting === promise)
2740
+ this.reconnecting = null;
2741
+ });
2742
+ this.reconnecting = promise;
2743
+ }
2744
+ async reconnectWithRetry(_trigger) {
2745
+ let attempt = 0;
2746
+ let delay = this.opts.reconnectBackoff.baseMs;
2747
+ for (;; ) {
2748
+ if (this.closeStarted)
2749
+ throw new SubcProviderError("provider closed");
2750
+ attempt += 1;
2751
+ this.enqueueConnectionState({ state: "reconnecting", attempt });
2752
+ try {
2753
+ const opened = await SubcProvider.openConnection(this.opts);
2754
+ if (this.closeStarted) {
2755
+ opened.sock.close();
2756
+ throw new SubcProviderError("provider closed");
2757
+ }
2758
+ this.replaceConnection(opened);
2759
+ return;
2760
+ } catch (err) {
2761
+ if (this.closeStarted)
2762
+ throw err;
2763
+ if (!isProviderReconnectTransient(err))
2764
+ throw err;
2765
+ await this.opts.sleep(delay);
2766
+ delay = Math.min(delay * 2, this.opts.reconnectBackoff.capMs);
2767
+ }
2768
+ }
2769
+ }
2770
+ replaceConnection(opened) {
2771
+ this.sock.close();
2772
+ this.sock = opened.sock;
2773
+ this.currentConn = opened.conn;
2774
+ this.storage = opened.ack.storage;
2775
+ this.closedErr = null;
2776
+ this.connectionEpoch += 1;
2777
+ const generation = this.generation;
2778
+ this.readLoop(opened.sock, generation);
2779
+ this.scheduleRestored(generation, this.connectionEpoch);
2780
+ }
2781
+ scheduleRestored(generation, epoch) {
2782
+ if (!this.opts.onConnectionState)
2783
+ return;
2784
+ const token = ++this.restoredDebounceToken;
2785
+ this.opts.sleep(this.opts.restoredDebounceMs).then(() => {
2786
+ if (token === this.restoredDebounceToken && !this.closeStarted && this.sock && this.generation === generation && this.connectionEpoch === epoch) {
2787
+ this.enqueueConnectionState({ state: "restored", epoch });
2788
+ }
2789
+ }).catch((err) => {
2790
+ if (token === this.restoredDebounceToken && !this.closeStarted) {
2791
+ console.warn("SubcProvider restored debounce timer failed", err);
2792
+ }
2793
+ });
2794
+ }
2795
+ cancelRestoredDebounce() {
2796
+ this.restoredDebounceToken += 1;
2797
+ }
2798
+ enqueueConnectionState(event) {
2799
+ if (!this.opts.onConnectionState)
2800
+ return;
2801
+ this.stateQueue.push(event);
2802
+ if (!this.drainingStateQueue)
2803
+ this.drainConnectionStateQueue();
2804
+ }
2805
+ async drainConnectionStateQueue() {
2806
+ if (this.drainingStateQueue)
2807
+ return;
2808
+ this.drainingStateQueue = true;
2809
+ try {
2810
+ while (this.stateQueue.length > 0) {
2811
+ const event = this.stateQueue[0];
2812
+ try {
2813
+ await this.opts.onConnectionState?.(event);
2814
+ this.stateQueue.shift();
2815
+ } catch (err) {
2816
+ if (event.state === "restored") {
2817
+ console.warn("SubcProvider restored callback failed; retrying delivery", err);
2818
+ await pauseBeforeStateRetry();
2819
+ continue;
2820
+ }
2821
+ console.warn("SubcProvider connection-state callback failed", err);
2822
+ this.stateQueue.shift();
2823
+ }
2824
+ }
2825
+ } finally {
2826
+ this.drainingStateQueue = false;
2827
+ if (this.stateQueue.length > 0)
2828
+ this.drainConnectionStateQueue();
2829
+ }
2830
+ }
2831
+ failFatal(err) {
2832
+ if (!this.closedErr)
2833
+ this.closedErr = err;
2834
+ this.closeStarted = true;
2835
+ this.cancelRestoredDebounce();
2836
+ this.abortAllInflight();
2837
+ this.sock.close();
2838
+ this.finishClosed();
2839
+ }
2840
+ finishClosed() {
2841
+ this.resolveClosed();
2842
+ }
2843
+ }
2844
+ function routeKey(generation, channel, corr) {
2845
+ return `${generation}:${channel}:${corr}`;
2846
+ }
2847
+ function launchNonce(opts) {
2848
+ const nonce = opts.launchNonce ?? process.env[SUBC_LAUNCH_NONCE_ENV2];
2849
+ return nonce && nonce.length > 0 ? nonce : undefined;
2850
+ }
2851
+ function normalizeProviderConnectOptions(opts) {
2852
+ return {
2853
+ connectionFile: opts.connectionFile,
2854
+ manifest: opts.manifest,
2855
+ handler: opts.handler,
2856
+ handshakeTimeoutMs: opts.handshakeTimeoutMs,
2857
+ controlOps: opts.controlOps,
2858
+ onBind: opts.onBind,
2859
+ onRouteGone: opts.onRouteGone,
2860
+ reconnectBackoff: opts.reconnectBackoff ?? DEFAULT_RECONNECT_BACKOFF,
2861
+ sleep: opts.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms))),
2862
+ restoredDebounceMs: opts.restoredDebounceMs ?? DEFAULT_RESTORED_DEBOUNCE_MS,
2863
+ onConnectionState: opts.onConnectionState,
2864
+ launchNonce: opts.launchNonce
2865
+ };
2866
+ }
2867
+ function buildHelloFrame(opts) {
2868
+ const nonce = launchNonce(opts);
2869
+ return buildFrame(9 /* Hello */, controlFlags(), 0, HELLO_CORR, encodeJson({
2870
+ manifest: normalizeManifest(opts.manifest),
2871
+ protocol_ver: PROTOCOL_VERSION,
2872
+ control_ops: opts.controlOps === undefined ? null : opts.controlOps,
2873
+ ...nonce ? { launch_nonce: nonce } : {}
2874
+ }));
2875
+ }
2876
+ function isProviderReconnectTransient(err) {
2877
+ if (err instanceof SubcProviderError)
2878
+ return err.code === "duplicate_module_id";
2879
+ if (err instanceof SocketClosedError || err instanceof SocketTimeoutError)
2880
+ return true;
2881
+ if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError)
2882
+ return true;
2883
+ if (err instanceof ConnectionFileError || err instanceof AuthError)
2884
+ return false;
2885
+ const code = errorCode2(err);
2886
+ return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "ETIMEDOUT" || code === "ENOENT";
2887
+ }
2888
+ function errorCode2(err) {
2889
+ if (typeof err === "object" && err !== null && "code" in err) {
2890
+ const code = err.code;
2891
+ if (typeof code === "string")
2892
+ return code;
2893
+ }
2894
+ return;
2895
+ }
2896
+ async function pauseBeforeStateRetry() {
2897
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
2898
+ }
2899
+ function controlFlags() {
2900
+ return buildFlags(false, 0 /* Passive */, false);
2901
+ }
2902
+ async function sendFrame(sock, frame) {
2903
+ await sock.write(encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
2904
+ }
2905
+ async function expectHelloAck(sock, deadline) {
2906
+ const header = decodeHeader(await sock.readExact(HEADER_LEN, deadline));
2907
+ const body = header.len === 0 ? new Uint8Array(0) : await sock.readExact(header.len, deadline);
2908
+ const frame = { header, body };
2909
+ switch (header.ty) {
2910
+ case 10 /* HelloAck */:
2911
+ return parseJson(body);
2912
+ case 5 /* Error */: {
2913
+ const error2 = parseJson(body);
2914
+ throw new SubcProviderError(`subc rejected HELLO: ${error2.code ?? "unknown"} — ${error2.message ?? "subc error"}`, error2.code);
2915
+ }
2916
+ default:
2917
+ throw new SubcProviderError(`unexpected frame ${FrameType[frame.header.ty]} awaiting HELLO_ACK`);
2918
+ }
2919
+ }
2920
+ function encodeJson(value) {
2921
+ return new Uint8Array(Buffer2.from(JSON.stringify(value), "utf8"));
2922
+ }
2923
+ function parseJson(bytes) {
2924
+ return JSON.parse(Buffer2.from(bytes).toString("utf8"));
2925
+ }
2926
+ function numberField(value, field) {
2927
+ if (typeof value !== "number" || !Number.isInteger(value)) {
2928
+ throw new SubcProviderError(`route.bind ${field} must be an integer`);
2929
+ }
2930
+ return value;
2931
+ }
2932
+ function bindRejection(decision) {
2933
+ if (decision === undefined || decision === true)
2934
+ return null;
2935
+ if (decision === false) {
2936
+ return { code: "route_rejected", message: "route.bind rejected by provider" };
2937
+ }
2938
+ if (decision.accept)
2939
+ return null;
2940
+ return {
2941
+ code: decision.code ?? "route_rejected",
2942
+ message: decision.message ?? "route.bind rejected by provider"
2943
+ };
2944
+ }
2945
+ function normalizeManifest(manifest) {
2946
+ return {
2947
+ module_id: manifest.module_id,
2948
+ module_version: manifest.module_version,
2949
+ protocol_ver: manifest.protocol_ver,
2950
+ trust_tier: manifest.trust_tier,
2951
+ provides: manifest.provides.map(normalizeProviderRole),
2952
+ consumes: manifest.consumes.map(normalizeConsumerRole),
2953
+ scheduled_tasks: manifest.scheduled_tasks.map(normalizeScheduledTask),
2954
+ bindings: {
2955
+ storage: {
2956
+ kind: manifest.bindings.storage.kind,
2957
+ scope: manifest.bindings.storage.scope,
2958
+ owns_schema: manifest.bindings.storage.owns_schema
2959
+ },
2960
+ vault_grants: manifest.bindings.vault_grants.map((grant) => ({
2961
+ secret: grant.secret,
2962
+ reason: grant.reason
2963
+ })),
2964
+ identity: {
2965
+ requires: [...manifest.bindings.identity.requires],
2966
+ optional: [...manifest.bindings.identity.optional]
2967
+ }
2968
+ }
2969
+ };
2970
+ }
2971
+ function normalizeProviderRole(role) {
2972
+ switch (role.role) {
2973
+ case "tool_provider":
2974
+ return {
2975
+ role: "tool_provider",
2976
+ tools: role.tools.map((tool) => ({
2977
+ name: tool.name,
2978
+ execution_mode: tool.execution_mode,
2979
+ schema: tool.schema
2980
+ })),
2981
+ identity_scope: [...role.identity_scope],
2982
+ concurrency: role.concurrency,
2983
+ emits_push: role.emits_push,
2984
+ sub_supervises: role.sub_supervises
2985
+ };
2986
+ case "pipeline_stage":
2987
+ return {
2988
+ role: "pipeline_stage",
2989
+ stage: role.stage,
2990
+ applies_to: {
2991
+ provider: role.applies_to.provider,
2992
+ model: role.applies_to.model
2993
+ },
2994
+ interface: role.interface,
2995
+ declares_frozen_floor: role.declares_frozen_floor,
2996
+ needs_signals: [...role.needs_signals],
2997
+ conformance_class: role.conformance_class
2998
+ };
2999
+ case "management_surface":
3000
+ return {
3001
+ role: "management_surface",
3002
+ operations: role.operations.map((operation) => ({
3003
+ name: operation.name,
3004
+ kind: operation.kind
3005
+ })),
3006
+ config_schema: role.config_schema,
3007
+ observability: role.observability.map((surface) => ({
3008
+ name: surface.name,
3009
+ kind: surface.kind
3010
+ })),
3011
+ identity_scope: [...role.identity_scope]
3012
+ };
3013
+ case "internal_service":
3014
+ return {
3015
+ role: "internal_service",
3016
+ service_id: role.service_id,
3017
+ transport: role.transport,
3018
+ agent_facing: role.agent_facing,
3019
+ operations: [...role.operations]
3020
+ };
3021
+ }
3022
+ }
3023
+ function normalizeConsumerRole(role) {
3024
+ switch (role.role) {
3025
+ case "tool_client":
3026
+ return { role: "tool_client", of: [...role.of] };
3027
+ case "llm_client":
3028
+ return { role: "llm_client", via: role.via, auth: role.auth };
3029
+ case "service_client":
3030
+ return { role: "service_client", of: [...role.of] };
3031
+ }
3032
+ }
3033
+ function normalizeScheduledTask(task) {
3034
+ return {
3035
+ task_id: task.task_id,
3036
+ eligibility: {
3037
+ cooldown: task.eligibility.cooldown,
3038
+ window: task.eligibility.window
3039
+ },
3040
+ lease_scope: task.lease_scope,
3041
+ renews_during_calls: task.renews_during_calls,
3042
+ toolset: [...task.toolset],
3043
+ model_policy: {
3044
+ tier: task.model_policy.tier,
3045
+ fallback_chain: [...task.model_policy.fallback_chain]
3046
+ },
3047
+ step_cap: task.step_cap,
3048
+ circuit_breaker: {
3049
+ identical_failures: task.circuit_breaker.identical_failures
3050
+ }
3051
+ };
3052
+ }
3053
+ var DEFAULT_HANDSHAKE_TIMEOUT_MS2 = 1e4, BODY_READ_TIMEOUT_MS2 = 30000, WRITE_TIMEOUT_MS = 30000, DEFAULT_RESTORED_DEBOUNCE_MS = 250, HELLO_CORR = 1n, SubcProviderError, SUBC_LAUNCH_NONCE_ENV2 = "SUBC_LAUNCH_NONCE";
3054
+ var init_provider = __esm(() => {
3055
+ init_auth();
3056
+ init_client();
3057
+ init_connection_file();
3058
+ init_envelope();
3059
+ init_socket();
3060
+ SubcProviderError = class SubcProviderError extends Error {
3061
+ code;
3062
+ constructor(message, code) {
3063
+ super(message);
3064
+ this.code = code;
3065
+ }
3066
+ };
3067
+ });
3068
+
3069
+ // ../../node_modules/.bun/@cortexkit+subc-client@0.2.0/node_modules/@cortexkit/subc-client/src/index.ts
3070
+ var init_src = __esm(() => {
3071
+ init_client();
3072
+ init_connection_file();
3073
+ init_envelope();
3074
+ init_auth();
3075
+ init_socket();
3076
+ init_provider();
3077
+ });
3078
+
3079
+ // ../aft-bridge/dist/subc-transport.js
3080
+ function identityKey(identity) {
3081
+ return `${identity.project_root}\x00${identity.harness}\x00${identity.session}`;
3082
+ }
3083
+
3084
+ class BgSubscription {
3085
+ identity;
3086
+ acquireClient;
3087
+ dropClient;
3088
+ onNudge;
3089
+ sleep;
3090
+ stopped = false;
3091
+ current = null;
3092
+ loop;
3093
+ constructor(identity, acquireClient, dropClient, onNudge, sleep) {
3094
+ this.identity = identity;
3095
+ this.acquireClient = acquireClient;
3096
+ this.dropClient = dropClient;
3097
+ this.onNudge = onNudge;
3098
+ this.sleep = sleep;
3099
+ this.loop = this.run();
3100
+ }
3101
+ async stop() {
3102
+ this.stopped = true;
3103
+ const sub = this.current;
3104
+ if (sub) {
3105
+ try {
3106
+ sub.unsubscribe();
3107
+ } catch {}
1385
3108
  }
1386
- const mergedOverrides = { ...this.configOverrides, ...projectOverrides };
1387
- const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, mergedOverrides);
1388
- this.bridges.set(key, { bridge, lastUsed: Date.now() });
1389
- return bridge;
3109
+ await this.loop.catch(() => {
3110
+ return;
3111
+ });
1390
3112
  }
1391
- cleanup() {
1392
- const now = Date.now();
1393
- for (const [dir, entry] of this.bridges) {
1394
- if (entry.bridge.hasPendingRequests() || entry.bridge.hasOutstandingBackgroundTasks())
3113
+ async run() {
3114
+ let attempt = 0;
3115
+ while (!this.stopped) {
3116
+ let client;
3117
+ try {
3118
+ client = await this.acquireClient();
3119
+ } catch {
3120
+ await this.backoff(attempt++);
1395
3121
  continue;
1396
- if (now - entry.lastUsed > this.idleTimeoutMs) {
1397
- entry.bridge.shutdown().catch((err) => this.error("cleanup shutdown failed:", err));
1398
- this.bridges.delete(dir);
1399
3122
  }
1400
- }
1401
- for (const bridge of this.staleBridges) {
1402
- if (bridge.hasPendingRequests() || bridge.hasOutstandingBackgroundTasks())
1403
- continue;
1404
- bridge.shutdown().catch((err) => this.error("stale cleanup shutdown failed:", err));
1405
- this.staleBridges.delete(bridge);
1406
- }
1407
- }
1408
- evictLRU() {
1409
- let oldestDir = null;
1410
- let oldestTime = Infinity;
1411
- for (const [dir, entry] of this.bridges) {
1412
- if (entry.bridge.hasPendingRequests() || entry.bridge.hasOutstandingBackgroundTasks())
3123
+ if (this.stopped)
3124
+ return;
3125
+ let channel;
3126
+ try {
3127
+ channel = await client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, this.identity);
3128
+ } catch (err) {
3129
+ if (isConsumerReconnectTransient(err))
3130
+ this.dropClient(client);
3131
+ await this.backoff(attempt++);
1413
3132
  continue;
1414
- if (entry.lastUsed < oldestTime) {
1415
- oldestTime = entry.lastUsed;
1416
- oldestDir = dir;
1417
3133
  }
3134
+ if (this.stopped) {
3135
+ safeCloseRoute(client, channel);
3136
+ return;
3137
+ }
3138
+ const subscribedAt = Date.now();
3139
+ try {
3140
+ const sub = client.subscribe(channel, { op: "bg_events" }, () => {
3141
+ if (!this.stopped)
3142
+ this.onNudge();
3143
+ });
3144
+ this.current = sub;
3145
+ if (this.stopped)
3146
+ sub.unsubscribe();
3147
+ if (!this.stopped)
3148
+ this.onNudge();
3149
+ await sub.closed;
3150
+ return;
3151
+ } catch (err) {
3152
+ if (this.stopped)
3153
+ return;
3154
+ if (isConsumerReconnectTransient(err))
3155
+ this.dropClient(client);
3156
+ if (Date.now() - subscribedAt >= BG_STABLE_MS)
3157
+ attempt = 0;
3158
+ } finally {
3159
+ this.current = null;
3160
+ safeCloseRoute(client, channel);
3161
+ }
3162
+ await this.backoff(attempt++);
1418
3163
  }
1419
- if (oldestDir) {
1420
- const entry = this.bridges.get(oldestDir);
1421
- entry?.bridge.shutdown().catch((err) => this.error("eviction shutdown failed:", err));
1422
- this.bridges.delete(oldestDir);
3164
+ }
3165
+ async backoff(attempt) {
3166
+ const ms = Math.min(100 * 2 ** Math.min(attempt, 6), 2000);
3167
+ await this.sleep(ms);
3168
+ }
3169
+ }
3170
+ function isRecord(value) {
3171
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3172
+ }
3173
+ function safeCloseRoute(client, channel) {
3174
+ try {
3175
+ client.closeRouteChannel(channel).catch(() => {
3176
+ return;
3177
+ });
3178
+ } catch {}
3179
+ }
3180
+ function reliftReply(reply) {
3181
+ if (!isRecord(reply) || !isRecord(reply.structuredContent)) {
3182
+ throw new Error("subc tool reply is missing the structuredContent envelope (protocol violation)");
3183
+ }
3184
+ const flat = reply.structuredContent;
3185
+ if (typeof flat.success !== "boolean" || typeof flat.text !== "string") {
3186
+ throw new Error("subc tool reply structuredContent lacks a boolean `success` / string `text` (protocol violation)");
3187
+ }
3188
+ return flat;
3189
+ }
3190
+
3191
+ class SubcTransport {
3192
+ pool;
3193
+ projectRoot;
3194
+ lastStatusBar;
3195
+ cachedStatus = null;
3196
+ constructor(pool, projectRoot) {
3197
+ this.pool = pool;
3198
+ this.projectRoot = projectRoot;
3199
+ }
3200
+ getCwd() {
3201
+ return this.projectRoot;
3202
+ }
3203
+ getStatusBar() {
3204
+ return this.lastStatusBar;
3205
+ }
3206
+ getCachedStatus() {
3207
+ return this.cachedStatus;
3208
+ }
3209
+ cacheStatusSnapshot(snapshot) {
3210
+ this.cachedStatus = snapshot;
3211
+ }
3212
+ captureStatusBar(response) {
3213
+ const parsed = parseStatusBarCounts(response.status_bar);
3214
+ if (parsed)
3215
+ this.lastStatusBar = parsed;
3216
+ }
3217
+ identityFor(session) {
3218
+ return {
3219
+ project_root: this.projectRoot,
3220
+ harness: this.pool.harness,
3221
+ session: session && session.length > 0 ? session : DEFAULT_SESSION_ID
3222
+ };
3223
+ }
3224
+ async toolCall(sessionId, name, rawArgs = {}, options) {
3225
+ const { preview, timeoutMs, onProgress } = this.splitOptions(options);
3226
+ const body = { name, arguments: rawArgs };
3227
+ if (preview === true)
3228
+ body.preview = true;
3229
+ const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress);
3230
+ const result = reliftReply(reply);
3231
+ this.captureStatusBar(result);
3232
+ return result;
3233
+ }
3234
+ async send(command, params = {}, options) {
3235
+ if (LOCALLY_SATISFIED_COMMANDS.has(command)) {
3236
+ return { success: true, command, subc_local: true };
3237
+ }
3238
+ const { timeoutMs, onProgress } = this.splitOptions(options);
3239
+ const session = typeof params.session_id === "string" ? params.session_id : undefined;
3240
+ const reply = await this.pool.routeRequest(this.identityFor(session), { name: command, arguments: params }, timeoutMs, onProgress);
3241
+ const response = reliftReply(reply);
3242
+ this.captureStatusBar(response);
3243
+ return response;
3244
+ }
3245
+ splitOptions(options) {
3246
+ if (!options)
3247
+ return {};
3248
+ const preview = options.preview;
3249
+ const timeoutMs = options.timeoutMs;
3250
+ const onProgress = options.onProgress;
3251
+ return { preview, timeoutMs, onProgress };
3252
+ }
3253
+ }
3254
+
3255
+ class SubcTransportPool {
3256
+ harness;
3257
+ connectionFile;
3258
+ handshakeTimeoutMs;
3259
+ connectFn;
3260
+ onBgEventsNudge;
3261
+ bgBackoffSleep;
3262
+ client = null;
3263
+ connecting = null;
3264
+ sessions = new Map;
3265
+ transportFailures = 0;
3266
+ transports = new Map;
3267
+ shuttingDown = false;
3268
+ constructor(options) {
3269
+ this.connectionFile = options.connectionFile;
3270
+ this.harness = options.harness;
3271
+ this.handshakeTimeoutMs = options.handshakeTimeoutMs;
3272
+ this.connectFn = options.connect ?? ((opts) => SubcClient.connect(opts));
3273
+ this.onBgEventsNudge = options.onBgEventsNudge;
3274
+ this.bgBackoffSleep = options.bgBackoffSleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
3275
+ }
3276
+ static async connectionAvailable(connectionFile) {
3277
+ return connectionFileExists(connectionFile);
3278
+ }
3279
+ getBridge(projectRoot) {
3280
+ const key = canonicalizeProjectRoot(projectRoot);
3281
+ let transport = this.transports.get(key);
3282
+ if (!transport) {
3283
+ transport = new SubcTransport(this, key);
3284
+ this.transports.set(key, transport);
1423
3285
  }
3286
+ return transport;
1424
3287
  }
1425
- async shutdown() {
1426
- if (this.cleanupTimer) {
1427
- clearInterval(this.cleanupTimer);
1428
- this.cleanupTimer = null;
3288
+ getActiveBridgeForRoot(projectRoot) {
3289
+ const key = canonicalizeProjectRoot(projectRoot);
3290
+ if (!this.client)
3291
+ return null;
3292
+ return this.transports.get(key) ?? null;
3293
+ }
3294
+ async toolCall(projectRoot, runtime, name, rawArgs = {}, options) {
3295
+ return this.getBridge(projectRoot).toolCall(runtime.sessionID, name, rawArgs, options);
3296
+ }
3297
+ getOrCreateSession(key) {
3298
+ let record = this.sessions.get(key);
3299
+ if (!record || record.closed) {
3300
+ record = { routeEntry: null, bgSub: null, closed: false, inflight: 0 };
3301
+ this.sessions.set(key, record);
1429
3302
  }
1430
- const shutdowns = [
1431
- ...Array.from(this.bridges.values(), (e) => e.bridge.shutdown()),
1432
- ...Array.from(this.staleBridges.values(), (bridge) => bridge.shutdown())
1433
- ];
1434
- this.bridges.clear();
1435
- this.staleBridges.clear();
1436
- await Promise.allSettled(shutdowns);
3303
+ return record;
1437
3304
  }
1438
- async replaceBinary(newPath) {
1439
- this.binaryPath = newPath;
1440
- for (const entry of this.bridges.values()) {
1441
- this.staleBridges.add(entry.bridge);
3305
+ isCurrentSession(key, record) {
3306
+ return this.sessions.get(key) === record && !record.closed;
3307
+ }
3308
+ deleteSessionIfEmpty(key, record) {
3309
+ if (this.sessions.get(key) === record && !record.closed && record.inflight === 0 && record.routeEntry === null && record.bgSub === null) {
3310
+ this.sessions.delete(key);
1442
3311
  }
1443
- this.bridges.clear();
1444
- this.log(`Binary path updated to ${newPath}. Active bridges marked stale — next calls will use the new binary.`);
1445
- return newPath;
1446
3312
  }
1447
- log(message, meta) {
1448
- const logger = this.logger ?? getActiveLogger();
1449
- if (logger) {
3313
+ async routeRequest(identity, body, timeoutMs, onProgress) {
3314
+ const key = identityKey(identity);
3315
+ const record = this.getOrCreateSession(key);
3316
+ record.inflight += 1;
3317
+ try {
3318
+ const client = await this.ensureClient();
3319
+ if (!this.isCurrentSession(key, record)) {
3320
+ throw new RouteTornDownError("subc session closed");
3321
+ }
3322
+ let channel;
3323
+ let entry;
1450
3324
  try {
1451
- logger.log(message, meta);
3325
+ ({ channel, entry } = await this.routeChannel(client, identity, record));
3326
+ if (!this.isCurrentSession(key, record)) {
3327
+ throw new RouteTornDownError("subc session closed");
3328
+ }
1452
3329
  } catch (err) {
1453
- console.error(`[aft-bridge] ERROR: pool logger log threw: ${err instanceof Error ? err.message : String(err)}`);
1454
- console.error(`[aft-bridge] ${message}`);
3330
+ if (err instanceof RouteTornDownError)
3331
+ throw err;
3332
+ if (isConsumerReconnectTransient(err) && this.isCurrentSession(key, record) && this.client === client) {
3333
+ this.dropClient(client);
3334
+ }
3335
+ throw err;
1455
3336
  }
1456
- } else
1457
- log(message, meta);
1458
- }
1459
- error(message, meta) {
1460
- const logger = this.logger ?? getActiveLogger();
1461
- if (logger) {
1462
3337
  try {
1463
- logger.error(message, meta);
3338
+ const reply = await client.request(channel, body, { timeoutMs, onProgress });
3339
+ if (this.isCurrentSession(key, record) && this.client === client) {
3340
+ this.transportFailures = 0;
3341
+ }
3342
+ this.ensureBgSubscription(identity, record);
3343
+ return reply;
1464
3344
  } catch (err) {
1465
- console.error(`[aft-bridge] ERROR: pool logger error threw: ${err instanceof Error ? err.message : String(err)}`);
1466
- console.error(`[aft-bridge] ERROR: ${message}`);
3345
+ if (record.routeEntry === entry) {
3346
+ entry.closed = true;
3347
+ record.routeEntry = null;
3348
+ }
3349
+ if (this.isCurrentSession(key, record) && this.client === client) {
3350
+ if (isConsumerReconnectTransient(err)) {
3351
+ this.transportFailures = 0;
3352
+ this.dropClient(client);
3353
+ } else if (++this.transportFailures >= MAX_CONSECUTIVE_TRANSPORT_FAILURES) {
3354
+ this.transportFailures = 0;
3355
+ this.dropClient(client);
3356
+ }
3357
+ }
3358
+ throw err;
1467
3359
  }
1468
- } else
1469
- error(message, meta);
3360
+ } finally {
3361
+ record.inflight -= 1;
3362
+ this.deleteSessionIfEmpty(key, record);
3363
+ }
3364
+ }
3365
+ async ensureClient() {
3366
+ if (this.shuttingDown) {
3367
+ throw new SubcCallError("terminal", "subc transport is shutting down");
3368
+ }
3369
+ if (this.client)
3370
+ return this.client;
3371
+ if (this.connecting)
3372
+ return this.connecting;
3373
+ this.connecting = this.connectFn({
3374
+ connectionFile: this.connectionFile,
3375
+ handshakeTimeoutMs: this.handshakeTimeoutMs
3376
+ }).then((client) => {
3377
+ this.connecting = null;
3378
+ if (this.shuttingDown) {
3379
+ try {
3380
+ client.close();
3381
+ } catch {}
3382
+ throw new SubcCallError("terminal", "subc transport is shutting down");
3383
+ }
3384
+ this.client = client;
3385
+ this.transportFailures = 0;
3386
+ return client;
3387
+ }).catch((err) => {
3388
+ this.connecting = null;
3389
+ throw err;
3390
+ });
3391
+ return this.connecting;
3392
+ }
3393
+ async routeChannel(client, identity, record) {
3394
+ const key = identityKey(identity);
3395
+ const existing = record.routeEntry;
3396
+ if (existing?.channel != null)
3397
+ return { channel: existing.channel, entry: existing };
3398
+ if (existing?.opening)
3399
+ return { channel: await existing.opening, entry: existing };
3400
+ const entry = { client, opening: null, channel: null, closed: false };
3401
+ const opening = client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity).then((channel) => {
3402
+ if (!this.isCurrentSession(key, record) || record.routeEntry !== entry || entry.closed || this.client !== client) {
3403
+ safeCloseRoute(client, channel);
3404
+ if (record.routeEntry === entry)
3405
+ record.routeEntry = null;
3406
+ throw new RouteTornDownError("subc route opened after teardown");
3407
+ }
3408
+ entry.channel = channel;
3409
+ entry.opening = null;
3410
+ return channel;
3411
+ }).catch((err) => {
3412
+ const current = this.isCurrentSession(key, record);
3413
+ if (record.routeEntry === entry) {
3414
+ entry.closed = true;
3415
+ record.routeEntry = null;
3416
+ }
3417
+ if (!current && !(err instanceof RouteTornDownError)) {
3418
+ throw new RouteTornDownError("subc route opened after session closed");
3419
+ }
3420
+ throw err;
3421
+ });
3422
+ entry.opening = opening;
3423
+ record.routeEntry = entry;
3424
+ return { channel: await opening, entry };
1470
3425
  }
1471
- setConfigureOverride(key, value) {
1472
- if (value === undefined) {
1473
- delete this.configOverrides[key];
1474
- } else {
1475
- this.configOverrides[key] = value;
3426
+ ensureBgSubscription(identity, record) {
3427
+ if (this.shuttingDown || !this.onBgEventsNudge)
3428
+ return;
3429
+ const key = identityKey(identity);
3430
+ if (!this.isCurrentSession(key, record))
3431
+ return;
3432
+ if (record.bgSub)
3433
+ return;
3434
+ const onNudge = () => this.onBgEventsNudge?.(identity.project_root, identity.session);
3435
+ const sub = new BgSubscription(identity, () => this.ensureClient(), (client) => this.dropClient(client), onNudge, this.bgBackoffSleep);
3436
+ record.bgSub = sub;
3437
+ }
3438
+ dropClient(client) {
3439
+ if (this.client === client) {
3440
+ this.client = null;
3441
+ for (const [key, record] of this.sessions) {
3442
+ const entry = record.routeEntry;
3443
+ if (entry?.client === client) {
3444
+ entry.closed = true;
3445
+ record.routeEntry = null;
3446
+ this.deleteSessionIfEmpty(key, record);
3447
+ }
3448
+ }
3449
+ this.transportFailures = 0;
3450
+ try {
3451
+ client.close();
3452
+ } catch {}
1476
3453
  }
1477
3454
  }
1478
- get size() {
1479
- return this.bridges.size;
3455
+ setConfigureOverride(_key, _value) {}
3456
+ async replaceBinary(path) {
3457
+ return path;
1480
3458
  }
1481
- _testGetConfigOverrides() {
1482
- return { ...this.configOverrides };
3459
+ async shutdown() {
3460
+ this.shuttingDown = true;
3461
+ const subs = [];
3462
+ const entries = [];
3463
+ for (const record of this.sessions.values()) {
3464
+ record.closed = true;
3465
+ const sub = record.bgSub;
3466
+ record.bgSub = null;
3467
+ if (sub)
3468
+ subs.push(sub);
3469
+ const entry = record.routeEntry;
3470
+ record.routeEntry = null;
3471
+ if (entry) {
3472
+ entry.closed = true;
3473
+ entries.push(entry);
3474
+ }
3475
+ }
3476
+ this.sessions.clear();
3477
+ const client = this.client;
3478
+ this.client = null;
3479
+ this.transports.clear();
3480
+ await Promise.allSettled(subs.map((sub) => sub.stop()));
3481
+ await Promise.allSettled(entries.map(async (entry) => {
3482
+ if (entry.channel == null)
3483
+ return;
3484
+ try {
3485
+ await entry.client.closeRouteChannel(entry.channel);
3486
+ } catch {}
3487
+ }));
3488
+ if (client) {
3489
+ try {
3490
+ client.close();
3491
+ } catch {}
3492
+ }
1483
3493
  }
1484
- _testGetBridgeOptions() {
1485
- return { ...this.bridgeOptions };
3494
+ async closeSession(projectRoot, session) {
3495
+ const identity = {
3496
+ project_root: canonicalizeProjectRoot(projectRoot),
3497
+ harness: this.harness,
3498
+ session: session && session.length > 0 ? session : DEFAULT_SESSION_ID
3499
+ };
3500
+ const key = identityKey(identity);
3501
+ const record = this.sessions.get(key);
3502
+ if (!record)
3503
+ return;
3504
+ record.closed = true;
3505
+ this.sessions.delete(key);
3506
+ const sub = record.bgSub;
3507
+ record.bgSub = null;
3508
+ const entry = record.routeEntry;
3509
+ record.routeEntry = null;
3510
+ if (entry)
3511
+ entry.closed = true;
3512
+ if (sub)
3513
+ await sub.stop();
3514
+ if (entry?.channel != null) {
3515
+ try {
3516
+ await entry.client.closeRouteChannel(entry.channel);
3517
+ } catch {}
3518
+ }
1486
3519
  }
1487
3520
  }
1488
- function normalizeKey(projectRoot) {
1489
- return canonicalizeProjectRoot(projectRoot);
1490
- }
1491
- var DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_MAX_POOL_SIZE = 8, CLEANUP_INTERVAL_MS;
1492
- var init_pool = __esm(() => {
1493
- init_active_logger();
1494
- init_bridge();
3521
+ var AFT_MODULE_ID = "aft", MAX_CONSECUTIVE_TRANSPORT_FAILURES = 3, BG_STABLE_MS = 5000, DEFAULT_SESSION_ID = "__default__", LOCALLY_SATISFIED_COMMANDS, RouteTornDownError;
3522
+ var init_subc_transport = __esm(() => {
3523
+ init_src();
1495
3524
  init_project_identity();
1496
- DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
1497
- CLEANUP_INTERVAL_MS = 60 * 1000;
3525
+ LOCALLY_SATISFIED_COMMANDS = new Set(["configure"]);
3526
+ RouteTornDownError = class RouteTornDownError extends Error {
3527
+ };
3528
+ });
3529
+ // ../aft-bridge/dist/transport-factory.js
3530
+ var init_transport_factory = __esm(() => {
3531
+ init_pool();
3532
+ init_subc_transport();
1498
3533
  });
1499
3534
  // ../aft-bridge/dist/index.js
1500
3535
  var init_dist = __esm(() => {
@@ -1513,6 +3548,8 @@ var init_dist = __esm(() => {
1513
3548
  init_pool();
1514
3549
  init_project_identity();
1515
3550
  init_resolver();
3551
+ init_subc_transport();
3552
+ init_transport_factory();
1516
3553
  });
1517
3554
 
1518
3555
  // src/lib/paths.ts
@@ -9803,6 +11840,63 @@ class OpenCodeAdapter {
9803
11840
  configPath
9804
11841
  };
9805
11842
  }
11843
+ hasTuiPluginEntry() {
11844
+ const paths = this.detectConfigPaths();
11845
+ if (!paths.tuiConfig)
11846
+ return false;
11847
+ const { value } = readJsoncFile(paths.tuiConfig);
11848
+ const plugins = Array.isArray(value?.plugin) ? value.plugin : [];
11849
+ return plugins.some((entry) => typeof entry === "string" && matchesPluginEntry(entry));
11850
+ }
11851
+ async ensureTuiPluginEntry() {
11852
+ const paths = this.detectConfigPaths();
11853
+ const configPath = paths.tuiConfig;
11854
+ if (!configPath) {
11855
+ return {
11856
+ ok: false,
11857
+ action: "error",
11858
+ message: "No TUI config path detected",
11859
+ configPath: paths.configDir
11860
+ };
11861
+ }
11862
+ if (paths.tuiConfigFormat === "none") {
11863
+ writeJsoncFile(configPath, { plugin: [PLUGIN_ENTRY] }, "json");
11864
+ return {
11865
+ ok: true,
11866
+ action: "added",
11867
+ message: `Created ${configPath} and added ${PLUGIN_ENTRY} (TUI sidebar)`,
11868
+ configPath
11869
+ };
11870
+ }
11871
+ const { value, error: error2 } = readJsoncFile(configPath);
11872
+ if (error2 || !value) {
11873
+ return {
11874
+ ok: false,
11875
+ action: "error",
11876
+ message: `Could not parse ${configPath}: ${error2 ?? "unknown error"}`,
11877
+ configPath
11878
+ };
11879
+ }
11880
+ const plugins = Array.isArray(value.plugin) ? value.plugin : [];
11881
+ const already = plugins.some((entry) => typeof entry === "string" && matchesPluginEntry(entry));
11882
+ if (already) {
11883
+ return {
11884
+ ok: true,
11885
+ action: "already_present",
11886
+ message: `${PLUGIN_NAME} is already registered in ${configPath}`,
11887
+ configPath
11888
+ };
11889
+ }
11890
+ plugins.push(PLUGIN_ENTRY);
11891
+ value.plugin = plugins;
11892
+ writeJsoncFile(configPath, value, paths.tuiConfigFormat);
11893
+ return {
11894
+ ok: true,
11895
+ action: "added",
11896
+ message: `Added ${PLUGIN_ENTRY} to ${configPath} (TUI sidebar)`,
11897
+ configPath
11898
+ };
11899
+ }
9806
11900
  getPluginCacheInfo() {
9807
11901
  const path = join7(getOpenCodeCacheDir(), "packages", PLUGIN_ENTRY);
9808
11902
  let cached;
@@ -11954,6 +14048,14 @@ async function runSetup(argv) {
11954
14048
  default:
11955
14049
  log2.info(`${adapter.displayName}: ${result.message}`);
11956
14050
  }
14051
+ if (adapter.ensureTuiPluginEntry) {
14052
+ const tuiResult = await adapter.ensureTuiPluginEntry();
14053
+ if (!tuiResult.ok) {
14054
+ log2.warn(`${adapter.displayName}: ${tuiResult.message}`);
14055
+ } else if (tuiResult.action === "added" || tuiResult.action === "updated") {
14056
+ log2.success(`${adapter.displayName}: ${tuiResult.message}`);
14057
+ }
14058
+ }
11957
14059
  try {
11958
14060
  const { aftConfig, aftConfigFormat } = adapter.detectConfigPaths();
11959
14061
  const schemaResult = ensureAftSchemaUrl(aftConfig, aftConfigFormat);
@@ -13141,6 +15243,12 @@ async function diagnoseHarness(adapter) {
13141
15243
  const configPaths = adapter.detectConfigPaths();
13142
15244
  const aftConfigRead = readJsoncFile(configPaths.aftConfig);
13143
15245
  const aftFlags = sanitizeValue(aftConfigRead.value ?? {}) ?? {};
15246
+ const projectConfigPath = resolveCortexKitProjectConfigPath(process.cwd());
15247
+ const projectConfigRead = readJsoncFile(projectConfigPath);
15248
+ const userEnabled = typeof aftFlags.enabled === "boolean" ? aftFlags.enabled : undefined;
15249
+ const projectEnabled = typeof projectConfigRead.value?.enabled === "boolean" ? projectConfigRead.value.enabled : undefined;
15250
+ const aftEnabled = projectEnabled ?? userEnabled ?? true;
15251
+ const aftEnabledSource = projectEnabled !== undefined ? projectConfigPath : userEnabled !== undefined ? configPaths.aftConfig : undefined;
13144
15252
  const storage = adapter.getStorageDir();
13145
15253
  const logPath = adapter.getLogFile();
13146
15254
  const pluginCache = adapter.getPluginCacheInfo();
@@ -13155,7 +15263,7 @@ async function diagnoseHarness(adapter) {
13155
15263
  }
13156
15264
  })();
13157
15265
  const describeStorage = "describeStorageSubtrees" in adapter && typeof adapter.describeStorageSubtrees === "function" ? adapter.describeStorageSubtrees() : {};
13158
- const semanticEnabled = aftConfigRead.value?.semantic_search === true || aftConfigRead.value?.experimental_semantic_search === true;
15266
+ const semanticEnabled = aftEnabled && (aftConfigRead.value?.semantic_search === true || aftConfigRead.value?.experimental_semantic_search === true);
13159
15267
  const systemOrtDir = findSystemOnnxRuntime();
13160
15268
  const cachedOrtDir = findCachedOnnxRuntime(storage);
13161
15269
  const systemVersion = systemOrtDir ? detectOrtVersion(systemOrtDir) : null;
@@ -13170,6 +15278,8 @@ async function diagnoseHarness(adapter) {
13170
15278
  aftConfig: {
13171
15279
  exists: existsSync14(configPaths.aftConfig),
13172
15280
  ...aftConfigRead.error ? { parseError: aftConfigRead.error } : {},
15281
+ enabled: aftEnabled,
15282
+ ...aftEnabledSource ? { enabledSource: aftEnabledSource } : {},
13173
15283
  flags: aftFlags
13174
15284
  },
13175
15285
  pluginCache,
@@ -13220,6 +15330,7 @@ function renderDiagnosticsMarkdown(report) {
13220
15330
  lines.push(`- Host version: ${h2.hostVersion ?? "unknown"}`);
13221
15331
  lines.push(`- Plugin registered: ${h2.pluginRegistered}`);
13222
15332
  lines.push(`- Plugin version: ${h2.pluginCache.cached ?? "not installed"}`);
15333
+ lines.push(`- AFT enabled: ${h2.aftConfig.enabled}${h2.aftConfig.enabledSource ? ` (from ${h2.aftConfig.enabledSource})` : ""}`);
13223
15334
  lines.push(`- AFT config parse error: ${h2.aftConfig.parseError ?? "none"}`);
13224
15335
  lines.push("");
13225
15336
  lines.push("#### Config paths");
@@ -13300,7 +15411,8 @@ function pluginVersionSkewIssue(harness, cliVersion) {
13300
15411
  }
13301
15412
  function collectDiagnosticIssues(report) {
13302
15413
  const issues = [];
13303
- if (!report.binaryVersion) {
15414
+ const hasEnabledRegisteredHarness = report.harnesses.some((h2) => h2.pluginRegistered && h2.aftConfig.enabled);
15415
+ if (!report.binaryVersion && hasEnabledRegisteredHarness) {
13304
15416
  issues.push({
13305
15417
  code: "binary_missing",
13306
15418
  severity: "high",
@@ -13339,9 +15451,9 @@ function collectDiagnosticIssues(report) {
13339
15451
  });
13340
15452
  }
13341
15453
  const skewIssue = pluginVersionSkewIssue(h2, report.cliVersion);
13342
- if (skewIssue)
15454
+ if (h2.aftConfig.enabled && skewIssue)
13343
15455
  issues.push(skewIssue);
13344
- if (h2.onnxRuntime.required) {
15456
+ if (h2.aftConfig.enabled && h2.onnxRuntime.required) {
13345
15457
  if (!h2.onnxRuntime.cachedPath && !h2.onnxRuntime.systemPath) {
13346
15458
  issues.push({
13347
15459
  code: "onnx_missing",
@@ -13425,6 +15537,7 @@ function tailLogFile(path, lines) {
13425
15537
  }
13426
15538
  }
13427
15539
  var init_diagnostics = __esm(async () => {
15540
+ init_dist();
13428
15541
  init_binary_cache();
13429
15542
  init_jsonc();
13430
15543
  init_lsp_cache();
@@ -13887,7 +16000,15 @@ async function runDoctor(options) {
13887
16000
  const report = await collectDiagnostics(adapters);
13888
16001
  log2.info(`AFT CLI v${report.cliVersion}, AFT binary ${report.binaryVersion ?? "unknown"}`);
13889
16002
  if (!report.binaryVersion) {
13890
- log2.warn(" no matching aft binary detected run `aft doctor --fix` to download, or it will install automatically when an AFT-enabled session makes its first tool call");
16003
+ const hasEnabledRegisteredHarness = report.harnesses.some((h2) => h2.pluginRegistered && h2.aftConfig.enabled);
16004
+ const hasRegisteredHarness = report.harnesses.some((h2) => h2.pluginRegistered);
16005
+ if (hasEnabledRegisteredHarness) {
16006
+ log2.warn(" no matching aft binary detected — run `aft doctor --fix` to download, or it will install automatically when an AFT-enabled session makes its first tool call");
16007
+ } else if (hasRegisteredHarness) {
16008
+ log2.info(" no matching aft binary detected; all registered AFT harnesses are disabled by config");
16009
+ } else {
16010
+ log2.warn(" no matching aft binary detected — run `aft doctor --fix` to download");
16011
+ }
13891
16012
  logUnmatchedBinaryCandidates(report.cliVersion);
13892
16013
  }
13893
16014
  log2.info(`Binary cache: ${report.binaryCache.versions.length} version(s), ${formatBytes(report.binaryCache.totalSize)} at ${report.binaryCache.path}`);
@@ -13906,6 +16027,9 @@ async function runDoctor(options) {
13906
16027
  log2.info(` host: ${h2.hostVersion ?? "unknown version"}`);
13907
16028
  log2.info(` plugin registered: ${h2.pluginRegistered ? "yes" : "no"}`);
13908
16029
  log2.info(` plugin version: ${h2.pluginCache.cached ?? "not installed"}`);
16030
+ if (!h2.aftConfig.enabled) {
16031
+ log2.info(` AFT disabled by config${h2.aftConfig.enabledSource ? ` (${h2.aftConfig.enabledSource})` : ""}; plugin will stay inert`);
16032
+ }
13909
16033
  if (!h2.pluginRegistered) {
13910
16034
  log2.warn(" plugin registration can be fixed with `aft setup` or `aft doctor --fix`");
13911
16035
  }
@@ -14061,6 +16185,8 @@ function findPluginUpdateTargets(adapters, report) {
14061
16185
  for (const harness of report.harnesses) {
14062
16186
  if (harness.kind !== "opencode")
14063
16187
  continue;
16188
+ if (!harness.aftConfig.enabled)
16189
+ continue;
14064
16190
  if (!harness.hostInstalled || !harness.pluginRegistered)
14065
16191
  continue;
14066
16192
  const cache = harness.pluginCache;
@@ -14164,13 +16290,29 @@ function buildDoctorFixPlan(adapters, report) {
14164
16290
  });
14165
16291
  }
14166
16292
  }
16293
+ for (const harness of report.harnesses) {
16294
+ const adapter = adaptersByKind.get(harness.kind);
16295
+ if (!adapter || !harness.hostInstalled)
16296
+ continue;
16297
+ if (!adapter.ensureTuiPluginEntry || !adapter.hasTuiPluginEntry)
16298
+ continue;
16299
+ if (adapter.hasTuiPluginEntry())
16300
+ continue;
16301
+ items.push({
16302
+ kind: "plugin",
16303
+ message: `Will add ${adapter.pluginEntryWithVersion} to ${harness.configPaths.tuiConfig} (TUI sidebar)`
16304
+ });
16305
+ }
14167
16306
  for (const target of findPluginUpdateTargets(adapters, report)) {
14168
16307
  items.push({
14169
16308
  kind: "plugin-update",
14170
16309
  message: `Will update ${target.adapter.displayName} plugin ${target.cached} → ${target.latest} via npm (the plugin's own auto-update could not run, often no npm on PATH)`
14171
16310
  });
14172
16311
  }
14173
- if (!report.binaryVersion) {
16312
+ const hasEnabledHarness = report.harnesses.some((harness) => {
16313
+ return harness.hostInstalled && harness.aftConfig.enabled;
16314
+ });
16315
+ if (!report.binaryVersion && hasEnabledHarness) {
14174
16316
  const skews = findPluginCliVersionSkews(report);
14175
16317
  items.push({
14176
16318
  kind: "binary",
@@ -14178,7 +16320,9 @@ function buildDoctorFixPlan(adapters, report) {
14178
16320
  });
14179
16321
  }
14180
16322
  for (const harness of report.harnesses) {
14181
- if (!harness.hostInstalled || !harness.pluginRegistered || harness.storageDir.exists)
16323
+ if (!harness.hostInstalled || !harness.pluginRegistered || !harness.aftConfig.enabled)
16324
+ continue;
16325
+ if (harness.storageDir.exists)
14182
16326
  continue;
14183
16327
  items.push({
14184
16328
  kind: "storage",
@@ -14408,7 +16552,9 @@ async function fixPluginEntries(adapters) {
14408
16552
  }
14409
16553
  }
14410
16554
  async function maybeFixPlugin(adapter) {
14411
- if (!adapter.hasPluginEntry() && adapter.isInstalled()) {
16555
+ if (!adapter.isInstalled())
16556
+ return;
16557
+ if (!adapter.hasPluginEntry()) {
14412
16558
  log2.info(`${adapter.displayName}: attempting to register plugin…`);
14413
16559
  const r2 = await adapter.ensurePluginEntry();
14414
16560
  if (r2.ok) {
@@ -14417,6 +16563,14 @@ async function maybeFixPlugin(adapter) {
14417
16563
  log2.error(`${adapter.displayName}: ${r2.message}`);
14418
16564
  }
14419
16565
  }
16566
+ if (adapter.ensureTuiPluginEntry && adapter.hasTuiPluginEntry && !adapter.hasTuiPluginEntry()) {
16567
+ const r2 = await adapter.ensureTuiPluginEntry();
16568
+ if (r2.ok && (r2.action === "added" || r2.action === "updated")) {
16569
+ log2.success(`${adapter.displayName}: ${r2.message}`);
16570
+ } else if (!r2.ok) {
16571
+ log2.error(`${adapter.displayName}: ${r2.message}`);
16572
+ }
16573
+ }
14420
16574
  }
14421
16575
  function describeAdapterInstallHint(kind) {
14422
16576
  if (kind === "opencode")