@multi-agent-protocol/sdk 0.0.3 → 0.0.5

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
@@ -1,4 +1,5 @@
1
1
  export { monotonicFactory, ulid } from 'ulid';
2
+ import { EventEmitter } from 'events';
2
3
  import { z } from 'zod';
3
4
 
4
5
  // src/types/index.ts
@@ -225,9 +226,6 @@ function isDirectAddress(address) {
225
226
  function isFederatedAddress(address) {
226
227
  return typeof address === "object" && "system" in address && "agent" in address;
227
228
  }
228
- function isScopeAddress(address) {
229
- return typeof address === "object" && "scope" in address;
230
- }
231
229
  function isBroadcastAddress(address) {
232
230
  return typeof address === "object" && "broadcast" in address;
233
231
  }
@@ -717,6 +715,30 @@ function websocketStream(ws) {
717
715
  });
718
716
  return { readable, writable };
719
717
  }
718
+ function waitForOpen(ws, timeoutMs = 1e4) {
719
+ return new Promise((resolve, reject) => {
720
+ if (ws.readyState === WebSocket.OPEN) {
721
+ resolve();
722
+ return;
723
+ }
724
+ const timeout = setTimeout(() => {
725
+ ws.close();
726
+ reject(new Error(`WebSocket connection timeout after ${timeoutMs}ms`));
727
+ }, timeoutMs);
728
+ const onOpen = () => {
729
+ clearTimeout(timeout);
730
+ ws.removeEventListener("error", onError);
731
+ resolve();
732
+ };
733
+ const onError = () => {
734
+ clearTimeout(timeout);
735
+ ws.removeEventListener("open", onOpen);
736
+ reject(new Error("WebSocket connection failed"));
737
+ };
738
+ ws.addEventListener("open", onOpen, { once: true });
739
+ ws.addEventListener("error", onError, { once: true });
740
+ });
741
+ }
720
742
  function createStreamPair() {
721
743
  const clientToServer = [];
722
744
  const serverToClient = [];
@@ -1523,9 +1545,16 @@ var CausalEventBuffer = class {
1523
1545
  this.#options = {
1524
1546
  maxWaitTime: options.maxWaitTime ?? 5e3,
1525
1547
  maxBufferSize: options.maxBufferSize ?? 1e3,
1548
+ multiCauseMode: options.multiCauseMode ?? "all",
1526
1549
  onForcedRelease: options.onForcedRelease
1527
1550
  };
1528
1551
  }
1552
+ /**
1553
+ * Get the current multi-cause mode.
1554
+ */
1555
+ get multiCauseMode() {
1556
+ return this.#options.multiCauseMode;
1557
+ }
1529
1558
  /**
1530
1559
  * Push an event into the buffer.
1531
1560
  *
@@ -1541,13 +1570,14 @@ var CausalEventBuffer = class {
1541
1570
  if (!event.receivedAt) {
1542
1571
  event = { ...event, receivedAt: Date.now() };
1543
1572
  }
1544
- const missingPredecessors = this.#getMissingPredecessors(event);
1545
- if (missingPredecessors.length === 0) {
1573
+ const shouldRelease = this.#shouldReleaseEvent(event);
1574
+ if (shouldRelease) {
1546
1575
  ready.push(event);
1547
1576
  this.#releaseWaiting(event.eventId, ready);
1548
1577
  } else {
1549
1578
  this.#pending.set(event.eventId, event);
1550
- for (const predecessorId of missingPredecessors) {
1579
+ const predecessors = event.causedBy ?? [];
1580
+ for (const predecessorId of predecessors) {
1551
1581
  if (!this.#waitingFor.has(predecessorId)) {
1552
1582
  this.#waitingFor.set(predecessorId, /* @__PURE__ */ new Set());
1553
1583
  }
@@ -1606,6 +1636,20 @@ var CausalEventBuffer = class {
1606
1636
  this.#pending.clear();
1607
1637
  this.#waitingFor.clear();
1608
1638
  }
1639
+ /**
1640
+ * Check if an event should be released based on its predecessors and the multi-cause mode.
1641
+ */
1642
+ #shouldReleaseEvent(event) {
1643
+ if (!event.causedBy || event.causedBy.length === 0) {
1644
+ return true;
1645
+ }
1646
+ const missingPredecessors = this.#getMissingPredecessors(event);
1647
+ if (this.#options.multiCauseMode === "any") {
1648
+ return missingPredecessors.length < event.causedBy.length;
1649
+ } else {
1650
+ return missingPredecessors.length === 0;
1651
+ }
1652
+ }
1609
1653
  /**
1610
1654
  * Get missing predecessors for an event.
1611
1655
  * A predecessor is considered "missing" if it hasn't been released yet
@@ -1629,9 +1673,21 @@ var CausalEventBuffer = class {
1629
1673
  for (const waitingEventId of waitingEventIds) {
1630
1674
  const waitingEvent = this.#pending.get(waitingEventId);
1631
1675
  if (!waitingEvent) continue;
1632
- const stillMissing = this.#getMissingPredecessors(waitingEvent);
1633
- if (stillMissing.length === 0) {
1676
+ if (this.#shouldReleaseEvent(waitingEvent)) {
1634
1677
  this.#pending.delete(waitingEventId);
1678
+ if (waitingEvent.causedBy) {
1679
+ for (const otherPredId of waitingEvent.causedBy) {
1680
+ if (otherPredId !== predecessorId) {
1681
+ const waiting = this.#waitingFor.get(otherPredId);
1682
+ if (waiting) {
1683
+ waiting.delete(waitingEventId);
1684
+ if (waiting.size === 0) {
1685
+ this.#waitingFor.delete(otherPredId);
1686
+ }
1687
+ }
1688
+ }
1689
+ }
1690
+ }
1635
1691
  ready.push(waitingEvent);
1636
1692
  this.#releaseWaiting(waitingEventId, ready);
1637
1693
  }
@@ -1744,12 +1800,633 @@ function sortCausalOrder(events) {
1744
1800
  return result;
1745
1801
  }
1746
1802
 
1803
+ // src/acp/types.ts
1804
+ var ACP_ERROR_CODES = {
1805
+ PARSE_ERROR: -32700,
1806
+ INVALID_REQUEST: -32600,
1807
+ METHOD_NOT_FOUND: -32601,
1808
+ INVALID_PARAMS: -32602,
1809
+ INTERNAL_ERROR: -32603,
1810
+ REQUEST_CANCELLED: -32800,
1811
+ AUTH_REQUIRED: -32e3,
1812
+ SESSION_NOT_FOUND: -32002
1813
+ };
1814
+ var ACPError = class _ACPError extends Error {
1815
+ code;
1816
+ data;
1817
+ constructor(code, message, data) {
1818
+ super(message);
1819
+ this.name = "ACPError";
1820
+ this.code = code;
1821
+ this.data = data;
1822
+ }
1823
+ /**
1824
+ * Create an ACPError from an error response.
1825
+ */
1826
+ static fromResponse(error) {
1827
+ return new _ACPError(error.code, error.message, error.data);
1828
+ }
1829
+ /**
1830
+ * Convert to JSON-RPC error object.
1831
+ */
1832
+ toErrorObject() {
1833
+ return {
1834
+ code: this.code,
1835
+ message: this.message,
1836
+ ...this.data !== void 0 && { data: this.data }
1837
+ };
1838
+ }
1839
+ };
1840
+ var ACP_PROTOCOL_VERSION = 20241007;
1841
+ var ACP_METHODS = {
1842
+ // Lifecycle
1843
+ INITIALIZE: "initialize",
1844
+ AUTHENTICATE: "authenticate",
1845
+ // Session management
1846
+ SESSION_NEW: "session/new",
1847
+ SESSION_LOAD: "session/load",
1848
+ SESSION_SET_MODE: "session/set_mode",
1849
+ // Prompt
1850
+ SESSION_PROMPT: "session/prompt",
1851
+ SESSION_CANCEL: "session/cancel",
1852
+ // Notifications
1853
+ SESSION_UPDATE: "session/update",
1854
+ // Agent→Client requests
1855
+ REQUEST_PERMISSION: "request_permission",
1856
+ FS_READ_TEXT_FILE: "fs/read_text_file",
1857
+ FS_WRITE_TEXT_FILE: "fs/write_text_file",
1858
+ TERMINAL_CREATE: "terminal/create",
1859
+ TERMINAL_OUTPUT: "terminal/output",
1860
+ TERMINAL_RELEASE: "terminal/release",
1861
+ TERMINAL_WAIT_FOR_EXIT: "terminal/wait_for_exit",
1862
+ TERMINAL_KILL: "terminal/kill"
1863
+ };
1864
+ function isACPRequest(msg) {
1865
+ if (typeof msg !== "object" || msg === null) {
1866
+ return false;
1867
+ }
1868
+ return "method" in msg && "id" in msg && msg.id !== void 0;
1869
+ }
1870
+ function isACPNotification(msg) {
1871
+ if (typeof msg !== "object" || msg === null) {
1872
+ return false;
1873
+ }
1874
+ return "method" in msg && !("id" in msg);
1875
+ }
1876
+ function isACPResponse(msg) {
1877
+ if (typeof msg !== "object" || msg === null) {
1878
+ return false;
1879
+ }
1880
+ return "id" in msg && !("method" in msg);
1881
+ }
1882
+ function isACPErrorResponse(response) {
1883
+ if (typeof response !== "object" || response === null) {
1884
+ return false;
1885
+ }
1886
+ return "error" in response;
1887
+ }
1888
+ function isACPSuccessResponse(response) {
1889
+ if (typeof response !== "object" || response === null) {
1890
+ return false;
1891
+ }
1892
+ return "result" in response;
1893
+ }
1894
+ function isACPEnvelope(payload) {
1895
+ if (typeof payload !== "object" || payload === null) {
1896
+ return false;
1897
+ }
1898
+ const envelope = payload;
1899
+ if (typeof envelope.acp !== "object" || envelope.acp === null || typeof envelope.acpContext !== "object" || envelope.acpContext === null) {
1900
+ return false;
1901
+ }
1902
+ const acpContext = envelope.acpContext;
1903
+ return typeof acpContext.streamId === "string";
1904
+ }
1905
+
1906
+ // src/acp/stream.ts
1907
+ var ACPStreamConnection = class extends EventEmitter {
1908
+ #mapClient;
1909
+ #options;
1910
+ #streamId;
1911
+ #pendingRequests = /* @__PURE__ */ new Map();
1912
+ #subscription = null;
1913
+ #sessionId = null;
1914
+ #initialized = false;
1915
+ #capabilities = null;
1916
+ #closed = false;
1917
+ #lastEventId = null;
1918
+ #isReconnecting = false;
1919
+ #unsubscribeReconnection = null;
1920
+ /**
1921
+ * Create a new ACP stream connection.
1922
+ *
1923
+ * @param mapClient - The underlying MAP client connection
1924
+ * @param options - Stream configuration options
1925
+ */
1926
+ constructor(mapClient, options) {
1927
+ super();
1928
+ this.#mapClient = mapClient;
1929
+ this.#options = options;
1930
+ this.#streamId = `acp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1931
+ this.#unsubscribeReconnection = mapClient.onReconnection((event) => {
1932
+ void this.#handleReconnectionEvent(event);
1933
+ });
1934
+ }
1935
+ // ===========================================================================
1936
+ // Public Properties
1937
+ // ===========================================================================
1938
+ /** Unique identifier for this ACP stream */
1939
+ get streamId() {
1940
+ return this.#streamId;
1941
+ }
1942
+ /** Target agent this stream connects to */
1943
+ get targetAgent() {
1944
+ return this.#options.targetAgent;
1945
+ }
1946
+ /** Current ACP session ID (null until newSession called) */
1947
+ get sessionId() {
1948
+ return this.#sessionId;
1949
+ }
1950
+ /** Whether initialize() has been called */
1951
+ get initialized() {
1952
+ return this.#initialized;
1953
+ }
1954
+ /** Agent capabilities from initialize response */
1955
+ get capabilities() {
1956
+ return this.#capabilities;
1957
+ }
1958
+ /** Whether the stream is closed */
1959
+ get isClosed() {
1960
+ return this.#closed;
1961
+ }
1962
+ /** Last processed event ID (for reconnection support) */
1963
+ get lastEventId() {
1964
+ return this.#lastEventId;
1965
+ }
1966
+ /** Whether the stream is currently reconnecting */
1967
+ get isReconnecting() {
1968
+ return this.#isReconnecting;
1969
+ }
1970
+ // ===========================================================================
1971
+ // Reconnection Handling
1972
+ // ===========================================================================
1973
+ /**
1974
+ * Handle MAP reconnection events.
1975
+ */
1976
+ async #handleReconnectionEvent(event) {
1977
+ if (this.#closed) return;
1978
+ switch (event.type) {
1979
+ case "disconnected":
1980
+ this.#isReconnecting = true;
1981
+ this.emit("reconnecting");
1982
+ for (const [id, pending] of this.#pendingRequests) {
1983
+ clearTimeout(pending.timeout);
1984
+ pending.reject(new Error("Connection lost during ACP request"));
1985
+ this.#pendingRequests.delete(id);
1986
+ }
1987
+ break;
1988
+ case "reconnected":
1989
+ await this.#handleReconnected();
1990
+ break;
1991
+ case "reconnectFailed":
1992
+ this.#isReconnecting = false;
1993
+ this.emit("error", event.error ?? new Error("MAP reconnection failed"));
1994
+ break;
1995
+ }
1996
+ }
1997
+ /**
1998
+ * Handle successful MAP reconnection.
1999
+ */
2000
+ async #handleReconnected() {
2001
+ this.#subscription = null;
2002
+ try {
2003
+ await this.#setupSubscription();
2004
+ if (this.#sessionId) {
2005
+ const sessionValid = await this.#verifySessionValid();
2006
+ if (!sessionValid) {
2007
+ const lostSessionId = this.#sessionId;
2008
+ this.#sessionId = null;
2009
+ this.emit("sessionLost", {
2010
+ sessionId: lostSessionId,
2011
+ reason: "Session no longer valid after reconnection"
2012
+ });
2013
+ }
2014
+ }
2015
+ this.#isReconnecting = false;
2016
+ this.emit("reconnected");
2017
+ } catch (error) {
2018
+ this.#isReconnecting = false;
2019
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
2020
+ }
2021
+ }
2022
+ /**
2023
+ * Verify that the current ACP session is still valid.
2024
+ *
2025
+ * Since ACP doesn't have a dedicated status check method, we attempt
2026
+ * a lightweight operation (cancel with no effect) to verify the session.
2027
+ */
2028
+ async #verifySessionValid() {
2029
+ if (!this.#sessionId) return false;
2030
+ try {
2031
+ await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
2032
+ sessionId: this.#sessionId,
2033
+ reason: "session_verification"
2034
+ });
2035
+ return true;
2036
+ } catch {
2037
+ return false;
2038
+ }
2039
+ }
2040
+ // ===========================================================================
2041
+ // Internal Methods
2042
+ // ===========================================================================
2043
+ /**
2044
+ * Set up the subscription for receiving messages from the target agent.
2045
+ */
2046
+ async #setupSubscription() {
2047
+ if (this.#subscription) return;
2048
+ this.#subscription = await this.#mapClient.subscribe({
2049
+ fromAgents: [this.#options.targetAgent]
2050
+ });
2051
+ void this.#processEvents();
2052
+ }
2053
+ /**
2054
+ * Process incoming events from the subscription.
2055
+ */
2056
+ async #processEvents() {
2057
+ if (!this.#subscription) return;
2058
+ try {
2059
+ for await (const event of this.#subscription) {
2060
+ if (this.#closed) break;
2061
+ if (event.id) {
2062
+ this.#lastEventId = event.id;
2063
+ }
2064
+ if (event.type === "message_delivered" && event.data) {
2065
+ const message = event.data.message;
2066
+ if (message?.payload) {
2067
+ await this.#handleIncomingMessage(message);
2068
+ }
2069
+ }
2070
+ }
2071
+ } catch (error) {
2072
+ if (!this.#closed) {
2073
+ this.emit("error", error);
2074
+ }
2075
+ }
2076
+ }
2077
+ /**
2078
+ * Handle an incoming message from the target agent.
2079
+ */
2080
+ async #handleIncomingMessage(message) {
2081
+ const payload = message.payload;
2082
+ if (!isACPEnvelope(payload)) return;
2083
+ const envelope = payload;
2084
+ const { acp, acpContext } = envelope;
2085
+ if (acpContext.streamId !== this.#streamId) return;
2086
+ if (acp.id !== void 0 && !acp.method) {
2087
+ const requestId = String(acp.id);
2088
+ const pending = this.#pendingRequests.get(requestId);
2089
+ if (pending) {
2090
+ clearTimeout(pending.timeout);
2091
+ this.#pendingRequests.delete(requestId);
2092
+ if (acp.error) {
2093
+ pending.reject(ACPError.fromResponse(acp.error));
2094
+ } else {
2095
+ pending.resolve(acp.result);
2096
+ }
2097
+ }
2098
+ return;
2099
+ }
2100
+ if (acp.method && acp.id === void 0) {
2101
+ await this.#handleNotification(acp.method, acp.params, acpContext);
2102
+ return;
2103
+ }
2104
+ if (acp.method && acp.id !== void 0) {
2105
+ await this.#handleAgentRequest(acp.id, acp.method, acp.params, acpContext, message);
2106
+ }
2107
+ }
2108
+ /**
2109
+ * Handle an ACP notification from the agent.
2110
+ */
2111
+ async #handleNotification(method, params, _acpContext) {
2112
+ if (method === ACP_METHODS.SESSION_UPDATE) {
2113
+ await this.#options.client.sessionUpdate(params);
2114
+ }
2115
+ }
2116
+ /**
2117
+ * Handle an agent→client request.
2118
+ */
2119
+ async #handleAgentRequest(requestId, method, params, _ctx, originalMessage) {
2120
+ let result;
2121
+ let error;
2122
+ try {
2123
+ switch (method) {
2124
+ case ACP_METHODS.REQUEST_PERMISSION:
2125
+ result = await this.#options.client.requestPermission(
2126
+ params
2127
+ );
2128
+ break;
2129
+ case ACP_METHODS.FS_READ_TEXT_FILE:
2130
+ if (!this.#options.client.readTextFile) {
2131
+ throw new ACPError(-32601, "Method not supported: fs/read_text_file");
2132
+ }
2133
+ result = await this.#options.client.readTextFile(
2134
+ params
2135
+ );
2136
+ break;
2137
+ case ACP_METHODS.FS_WRITE_TEXT_FILE:
2138
+ if (!this.#options.client.writeTextFile) {
2139
+ throw new ACPError(-32601, "Method not supported: fs/write_text_file");
2140
+ }
2141
+ result = await this.#options.client.writeTextFile(
2142
+ params
2143
+ );
2144
+ break;
2145
+ case ACP_METHODS.TERMINAL_CREATE:
2146
+ if (!this.#options.client.createTerminal) {
2147
+ throw new ACPError(-32601, "Method not supported: terminal/create");
2148
+ }
2149
+ result = await this.#options.client.createTerminal(
2150
+ params
2151
+ );
2152
+ break;
2153
+ case ACP_METHODS.TERMINAL_OUTPUT:
2154
+ if (!this.#options.client.terminalOutput) {
2155
+ throw new ACPError(-32601, "Method not supported: terminal/output");
2156
+ }
2157
+ result = await this.#options.client.terminalOutput(
2158
+ params
2159
+ );
2160
+ break;
2161
+ case ACP_METHODS.TERMINAL_RELEASE:
2162
+ if (!this.#options.client.releaseTerminal) {
2163
+ throw new ACPError(-32601, "Method not supported: terminal/release");
2164
+ }
2165
+ result = await this.#options.client.releaseTerminal(
2166
+ params
2167
+ );
2168
+ break;
2169
+ case ACP_METHODS.TERMINAL_WAIT_FOR_EXIT:
2170
+ if (!this.#options.client.waitForTerminalExit) {
2171
+ throw new ACPError(-32601, "Method not supported: terminal/wait_for_exit");
2172
+ }
2173
+ result = await this.#options.client.waitForTerminalExit(
2174
+ params
2175
+ );
2176
+ break;
2177
+ case ACP_METHODS.TERMINAL_KILL:
2178
+ if (!this.#options.client.killTerminal) {
2179
+ throw new ACPError(-32601, "Method not supported: terminal/kill");
2180
+ }
2181
+ result = await this.#options.client.killTerminal(
2182
+ params
2183
+ );
2184
+ break;
2185
+ default:
2186
+ throw new ACPError(-32601, `Unknown method: ${method}`);
2187
+ }
2188
+ } catch (e) {
2189
+ if (e instanceof ACPError) {
2190
+ error = e;
2191
+ } else {
2192
+ error = new ACPError(-32603, e.message);
2193
+ }
2194
+ }
2195
+ const responseEnvelope = {
2196
+ acp: {
2197
+ jsonrpc: "2.0",
2198
+ id: requestId,
2199
+ ...error ? { error: error.toErrorObject() } : { result }
2200
+ },
2201
+ acpContext: {
2202
+ streamId: this.#streamId,
2203
+ sessionId: this.#sessionId,
2204
+ direction: "client-to-agent"
2205
+ }
2206
+ };
2207
+ await this.#mapClient.send(
2208
+ { agent: this.#options.targetAgent },
2209
+ responseEnvelope,
2210
+ {
2211
+ protocol: "acp",
2212
+ correlationId: originalMessage.id
2213
+ }
2214
+ );
2215
+ }
2216
+ /**
2217
+ * Send an ACP request and wait for response.
2218
+ */
2219
+ async #sendRequest(method, params) {
2220
+ if (this.#closed) {
2221
+ throw new Error("ACP stream is closed");
2222
+ }
2223
+ await this.#setupSubscription();
2224
+ if (this.#closed) {
2225
+ throw new Error("ACP stream closed");
2226
+ }
2227
+ const correlationId = `${this.#streamId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
2228
+ const timeout = this.#options.timeout ?? 3e4;
2229
+ const envelope = {
2230
+ acp: {
2231
+ jsonrpc: "2.0",
2232
+ id: correlationId,
2233
+ method,
2234
+ ...params !== void 0 && { params }
2235
+ },
2236
+ acpContext: {
2237
+ streamId: this.#streamId,
2238
+ sessionId: this.#sessionId,
2239
+ direction: "client-to-agent"
2240
+ }
2241
+ };
2242
+ const resultPromise = new Promise((resolve, reject) => {
2243
+ const timeoutHandle = setTimeout(() => {
2244
+ this.#pendingRequests.delete(correlationId);
2245
+ reject(new Error(`ACP request timed out after ${timeout}ms: ${method}`));
2246
+ }, timeout);
2247
+ this.#pendingRequests.set(correlationId, {
2248
+ resolve,
2249
+ reject,
2250
+ timeout: timeoutHandle,
2251
+ method
2252
+ });
2253
+ });
2254
+ try {
2255
+ await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
2256
+ protocol: "acp",
2257
+ correlationId
2258
+ });
2259
+ } catch (err) {
2260
+ const pending = this.#pendingRequests.get(correlationId);
2261
+ if (pending) {
2262
+ clearTimeout(pending.timeout);
2263
+ this.#pendingRequests.delete(correlationId);
2264
+ }
2265
+ throw err;
2266
+ }
2267
+ if (this.#closed && !this.#pendingRequests.has(correlationId)) {
2268
+ throw new Error("ACP stream closed");
2269
+ }
2270
+ return resultPromise;
2271
+ }
2272
+ /**
2273
+ * Send an ACP notification (no response expected).
2274
+ */
2275
+ async #sendNotification(method, params) {
2276
+ if (this.#closed) {
2277
+ throw new Error("ACP stream is closed");
2278
+ }
2279
+ await this.#setupSubscription();
2280
+ const envelope = {
2281
+ acp: {
2282
+ jsonrpc: "2.0",
2283
+ method,
2284
+ ...params !== void 0 && { params }
2285
+ },
2286
+ acpContext: {
2287
+ streamId: this.#streamId,
2288
+ sessionId: this.#sessionId,
2289
+ direction: "client-to-agent"
2290
+ }
2291
+ };
2292
+ await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
2293
+ protocol: "acp"
2294
+ });
2295
+ }
2296
+ // ===========================================================================
2297
+ // ACP Lifecycle Methods
2298
+ // ===========================================================================
2299
+ /**
2300
+ * Initialize the ACP connection with the target agent.
2301
+ */
2302
+ async initialize(params) {
2303
+ if (this.#initialized) {
2304
+ throw new Error("ACP stream already initialized");
2305
+ }
2306
+ const result = await this.#sendRequest(
2307
+ ACP_METHODS.INITIALIZE,
2308
+ params
2309
+ );
2310
+ this.#initialized = true;
2311
+ this.#capabilities = result.agentCapabilities ?? null;
2312
+ return result;
2313
+ }
2314
+ /**
2315
+ * Authenticate with the agent.
2316
+ */
2317
+ async authenticate(params) {
2318
+ if (!this.#initialized) {
2319
+ throw new Error("Must call initialize() before authenticate()");
2320
+ }
2321
+ return this.#sendRequest(
2322
+ ACP_METHODS.AUTHENTICATE,
2323
+ params
2324
+ );
2325
+ }
2326
+ // ===========================================================================
2327
+ // ACP Session Methods
2328
+ // ===========================================================================
2329
+ /**
2330
+ * Create a new ACP session.
2331
+ */
2332
+ async newSession(params) {
2333
+ if (!this.#initialized) {
2334
+ throw new Error("Must call initialize() before newSession()");
2335
+ }
2336
+ const result = await this.#sendRequest(
2337
+ ACP_METHODS.SESSION_NEW,
2338
+ params
2339
+ );
2340
+ this.#sessionId = result.sessionId;
2341
+ return result;
2342
+ }
2343
+ /**
2344
+ * Load an existing ACP session.
2345
+ */
2346
+ async loadSession(params) {
2347
+ if (!this.#initialized) {
2348
+ throw new Error("Must call initialize() before loadSession()");
2349
+ }
2350
+ const result = await this.#sendRequest(
2351
+ ACP_METHODS.SESSION_LOAD,
2352
+ params
2353
+ );
2354
+ this.#sessionId = params.sessionId;
2355
+ return result;
2356
+ }
2357
+ /**
2358
+ * Set the session mode.
2359
+ */
2360
+ async setSessionMode(params) {
2361
+ return this.#sendRequest(
2362
+ ACP_METHODS.SESSION_SET_MODE,
2363
+ params
2364
+ );
2365
+ }
2366
+ // ===========================================================================
2367
+ // ACP Prompt Methods
2368
+ // ===========================================================================
2369
+ /**
2370
+ * Send a prompt to the agent.
2371
+ * Updates are received via the sessionUpdate handler.
2372
+ */
2373
+ async prompt(params) {
2374
+ if (!this.#sessionId) {
2375
+ throw new Error("Must call newSession() or loadSession() before prompt()");
2376
+ }
2377
+ return this.#sendRequest(
2378
+ ACP_METHODS.SESSION_PROMPT,
2379
+ params
2380
+ );
2381
+ }
2382
+ /**
2383
+ * Cancel ongoing operations for the current session.
2384
+ */
2385
+ async cancel(params) {
2386
+ if (!this.#sessionId) {
2387
+ throw new Error("No active session to cancel");
2388
+ }
2389
+ await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
2390
+ sessionId: this.#sessionId,
2391
+ ...params
2392
+ });
2393
+ }
2394
+ // ===========================================================================
2395
+ // Lifecycle
2396
+ // ===========================================================================
2397
+ /**
2398
+ * Close this ACP stream and clean up resources.
2399
+ */
2400
+ async close() {
2401
+ if (this.#closed) return;
2402
+ this.#closed = true;
2403
+ if (this.#unsubscribeReconnection) {
2404
+ this.#unsubscribeReconnection();
2405
+ this.#unsubscribeReconnection = null;
2406
+ }
2407
+ for (const [id, pending] of this.#pendingRequests) {
2408
+ clearTimeout(pending.timeout);
2409
+ pending.reject(new Error("ACP stream closed"));
2410
+ this.#pendingRequests.delete(id);
2411
+ }
2412
+ if (this.#subscription) {
2413
+ await this.#subscription.unsubscribe();
2414
+ this.#subscription = null;
2415
+ }
2416
+ this.emit("close");
2417
+ }
2418
+ };
2419
+ function createACPStream(mapClient, options) {
2420
+ return new ACPStreamConnection(mapClient, options);
2421
+ }
2422
+
1747
2423
  // src/connection/client.ts
1748
- var ClientConnection = class {
2424
+ var ClientConnection = class _ClientConnection {
1749
2425
  #connection;
1750
2426
  #subscriptions = /* @__PURE__ */ new Map();
1751
2427
  #subscriptionStates = /* @__PURE__ */ new Map();
1752
2428
  #reconnectionHandlers = /* @__PURE__ */ new Set();
2429
+ #acpStreams = /* @__PURE__ */ new Map();
1753
2430
  #options;
1754
2431
  #sessionId = null;
1755
2432
  #serverCapabilities = null;
@@ -1769,6 +2446,59 @@ var ClientConnection = class {
1769
2446
  }
1770
2447
  }
1771
2448
  // ===========================================================================
2449
+ // Static Factory Methods
2450
+ // ===========================================================================
2451
+ /**
2452
+ * Connect to a MAP server via WebSocket URL.
2453
+ *
2454
+ * Handles:
2455
+ * - WebSocket creation and connection
2456
+ * - Stream wrapping
2457
+ * - Auto-configuration of createStream for reconnection
2458
+ * - Initial MAP protocol connect handshake
2459
+ *
2460
+ * @param url - WebSocket URL (ws:// or wss://)
2461
+ * @param options - Connection options
2462
+ * @returns Connected ClientConnection instance
2463
+ *
2464
+ * @example
2465
+ * ```typescript
2466
+ * const client = await ClientConnection.connect('ws://localhost:8080', {
2467
+ * name: 'MyClient',
2468
+ * reconnection: true
2469
+ * });
2470
+ *
2471
+ * // Already connected, ready to use
2472
+ * const agents = await client.listAgents();
2473
+ * ```
2474
+ */
2475
+ static async connect(url, options) {
2476
+ const parsedUrl = new URL(url);
2477
+ if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
2478
+ throw new Error(
2479
+ `Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
2480
+ );
2481
+ }
2482
+ const timeout = options?.connectTimeout ?? 1e4;
2483
+ const ws = new WebSocket(url);
2484
+ await waitForOpen(ws, timeout);
2485
+ const stream = websocketStream(ws);
2486
+ const createStream = async () => {
2487
+ const newWs = new WebSocket(url);
2488
+ await waitForOpen(newWs, timeout);
2489
+ return websocketStream(newWs);
2490
+ };
2491
+ const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
2492
+ const client = new _ClientConnection(stream, {
2493
+ name: options?.name,
2494
+ capabilities: options?.capabilities,
2495
+ createStream,
2496
+ reconnection
2497
+ });
2498
+ await client.connect({ auth: options?.auth });
2499
+ return client;
2500
+ }
2501
+ // ===========================================================================
1772
2502
  // Connection Lifecycle
1773
2503
  // ===========================================================================
1774
2504
  /**
@@ -1781,6 +2511,7 @@ var ClientConnection = class {
1781
2511
  name: this.#options.name,
1782
2512
  capabilities: this.#options.capabilities,
1783
2513
  sessionId: options?.sessionId,
2514
+ resumeToken: options?.resumeToken,
1784
2515
  auth: options?.auth
1785
2516
  };
1786
2517
  const result = await this.#connection.sendRequest(CORE_METHODS.CONNECT, params);
@@ -1793,15 +2524,23 @@ var ClientConnection = class {
1793
2524
  }
1794
2525
  /**
1795
2526
  * Disconnect from the MAP system
2527
+ * @param reason - Optional reason for disconnecting
2528
+ * @returns Resume token that can be used to resume this session later
1796
2529
  */
1797
2530
  async disconnect(reason) {
1798
- if (!this.#connected) return;
2531
+ if (!this.#connected) return void 0;
2532
+ let resumeToken;
1799
2533
  try {
1800
- await this.#connection.sendRequest(
2534
+ const result = await this.#connection.sendRequest(
1801
2535
  CORE_METHODS.DISCONNECT,
1802
2536
  reason ? { reason } : void 0
1803
2537
  );
2538
+ resumeToken = result.resumeToken;
1804
2539
  } finally {
2540
+ for (const stream of this.#acpStreams.values()) {
2541
+ await stream.close();
2542
+ }
2543
+ this.#acpStreams.clear();
1805
2544
  for (const subscription of this.#subscriptions.values()) {
1806
2545
  subscription._close();
1807
2546
  }
@@ -1809,6 +2548,7 @@ var ClientConnection = class {
1809
2548
  await this.#connection.close();
1810
2549
  this.#connected = false;
1811
2550
  }
2551
+ return resumeToken;
1812
2552
  }
1813
2553
  /**
1814
2554
  * Whether the client is connected
@@ -1982,6 +2722,65 @@ var ClientConnection = class {
1982
2722
  }
1983
2723
  }
1984
2724
  // ===========================================================================
2725
+ // ACP Streams
2726
+ // ===========================================================================
2727
+ /**
2728
+ * Create a virtual ACP stream connection to an agent.
2729
+ *
2730
+ * This allows clients to interact with ACP-compatible agents using the
2731
+ * familiar ACP interface while routing all messages through MAP.
2732
+ *
2733
+ * @param options - Stream configuration options
2734
+ * @returns ACPStreamConnection instance ready for initialize()
2735
+ *
2736
+ * @example
2737
+ * ```typescript
2738
+ * const acp = client.createACPStream({
2739
+ * targetAgent: 'coding-agent-1',
2740
+ * client: {
2741
+ * requestPermission: async (req) => ({
2742
+ * outcome: { outcome: 'selected', optionId: 'allow' }
2743
+ * }),
2744
+ * sessionUpdate: async (update) => {
2745
+ * console.log('Agent update:', update);
2746
+ * }
2747
+ * }
2748
+ * });
2749
+ *
2750
+ * await acp.initialize({
2751
+ * protocolVersion: 20241007,
2752
+ * clientInfo: { name: 'IDE', version: '1.0' }
2753
+ * });
2754
+ * const { sessionId } = await acp.newSession({ cwd: '/project', mcpServers: [] });
2755
+ * const result = await acp.prompt({
2756
+ * sessionId,
2757
+ * prompt: [{ type: 'text', text: 'Hello' }]
2758
+ * });
2759
+ *
2760
+ * await acp.close();
2761
+ * ```
2762
+ */
2763
+ createACPStream(options) {
2764
+ const stream = new ACPStreamConnection(this, options);
2765
+ this.#acpStreams.set(stream.streamId, stream);
2766
+ stream.on("close", () => {
2767
+ this.#acpStreams.delete(stream.streamId);
2768
+ });
2769
+ return stream;
2770
+ }
2771
+ /**
2772
+ * Get an active ACP stream by ID.
2773
+ */
2774
+ getACPStream(streamId) {
2775
+ return this.#acpStreams.get(streamId);
2776
+ }
2777
+ /**
2778
+ * Get all active ACP streams.
2779
+ */
2780
+ get acpStreams() {
2781
+ return this.#acpStreams;
2782
+ }
2783
+ // ===========================================================================
1985
2784
  // Subscriptions
1986
2785
  // ===========================================================================
1987
2786
  /**
@@ -2344,7 +3143,7 @@ var ClientConnection = class {
2344
3143
  };
2345
3144
 
2346
3145
  // src/connection/agent.ts
2347
- var AgentConnection = class {
3146
+ var AgentConnection = class _AgentConnection {
2348
3147
  #connection;
2349
3148
  #subscriptions = /* @__PURE__ */ new Map();
2350
3149
  #options;
@@ -2371,6 +3170,66 @@ var AgentConnection = class {
2371
3170
  }
2372
3171
  }
2373
3172
  // ===========================================================================
3173
+ // Static Factory Methods
3174
+ // ===========================================================================
3175
+ /**
3176
+ * Connect and register an agent via WebSocket URL.
3177
+ *
3178
+ * Handles:
3179
+ * - WebSocket creation and connection
3180
+ * - Stream wrapping
3181
+ * - Auto-configuration of createStream for reconnection
3182
+ * - Initial MAP protocol connect handshake
3183
+ * - Agent registration
3184
+ *
3185
+ * @param url - WebSocket URL (ws:// or wss://)
3186
+ * @param options - Connection and agent options
3187
+ * @returns Connected and registered AgentConnection instance
3188
+ *
3189
+ * @example
3190
+ * ```typescript
3191
+ * const agent = await AgentConnection.connect('ws://localhost:8080', {
3192
+ * name: 'Worker',
3193
+ * role: 'processor',
3194
+ * reconnection: true
3195
+ * });
3196
+ *
3197
+ * // Already registered, ready to work
3198
+ * agent.onMessage(handleMessage);
3199
+ * await agent.busy();
3200
+ * ```
3201
+ */
3202
+ static async connect(url, options) {
3203
+ const parsedUrl = new URL(url);
3204
+ if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
3205
+ throw new Error(
3206
+ `Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
3207
+ );
3208
+ }
3209
+ const timeout = options?.connectTimeout ?? 1e4;
3210
+ const ws = new WebSocket(url);
3211
+ await waitForOpen(ws, timeout);
3212
+ const stream = websocketStream(ws);
3213
+ const createStream = async () => {
3214
+ const newWs = new WebSocket(url);
3215
+ await waitForOpen(newWs, timeout);
3216
+ return websocketStream(newWs);
3217
+ };
3218
+ const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
3219
+ const agent = new _AgentConnection(stream, {
3220
+ name: options?.name,
3221
+ role: options?.role,
3222
+ capabilities: options?.capabilities,
3223
+ visibility: options?.visibility,
3224
+ parent: options?.parent,
3225
+ scopes: options?.scopes,
3226
+ createStream,
3227
+ reconnection
3228
+ });
3229
+ await agent.connect({ auth: options?.auth });
3230
+ return agent;
3231
+ }
3232
+ // ===========================================================================
2374
3233
  // Connection Lifecycle
2375
3234
  // ===========================================================================
2376
3235
  /**
@@ -2383,6 +3242,7 @@ var AgentConnection = class {
2383
3242
  participantId: options?.agentId,
2384
3243
  name: this.#options.name,
2385
3244
  capabilities: this.#options.capabilities,
3245
+ resumeToken: options?.resumeToken,
2386
3246
  auth: options?.auth
2387
3247
  };
2388
3248
  const connectResult = await this.#connection.sendRequest(CORE_METHODS.CONNECT, connectParams);
@@ -2407,9 +3267,12 @@ var AgentConnection = class {
2407
3267
  }
2408
3268
  /**
2409
3269
  * Disconnect from the MAP system
3270
+ * @param reason - Optional reason for disconnecting
3271
+ * @returns Resume token that can be used to resume this session later
2410
3272
  */
2411
3273
  async disconnect(reason) {
2412
- if (!this.#connected) return;
3274
+ if (!this.#connected) return void 0;
3275
+ let resumeToken;
2413
3276
  try {
2414
3277
  if (this.#agentId) {
2415
3278
  await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_UNREGISTER, {
@@ -2417,10 +3280,11 @@ var AgentConnection = class {
2417
3280
  reason
2418
3281
  });
2419
3282
  }
2420
- await this.#connection.sendRequest(
3283
+ const result = await this.#connection.sendRequest(
2421
3284
  CORE_METHODS.DISCONNECT,
2422
3285
  reason ? { reason } : void 0
2423
3286
  );
3287
+ resumeToken = result.resumeToken;
2424
3288
  } finally {
2425
3289
  for (const subscription of this.#subscriptions.values()) {
2426
3290
  subscription._close();
@@ -2429,6 +3293,7 @@ var AgentConnection = class {
2429
3293
  await this.#connection.close();
2430
3294
  this.#connected = false;
2431
3295
  }
3296
+ return resumeToken;
2432
3297
  }
2433
3298
  /**
2434
3299
  * Whether the agent is connected
@@ -4348,6 +5213,423 @@ function canAgentMessageAgent(senderAgent, targetAgentId, context, config = DEFA
4348
5213
  return false;
4349
5214
  }
4350
5215
 
4351
- export { AGENT_ERROR_CODES, AUTH_ERROR_CODES, AUTH_METHODS, AddressSchema, AgentConnection, AgentIdSchema, AgentLifecycleSchema, AgentRelationshipSchema, AgentSchema, AgentStateSchema, AgentVisibilitySchema, BaseConnection, BroadcastAddressSchema, CAPABILITY_REQUIREMENTS, CORE_METHODS, CausalEventBuffer, ClientConnection, CorrelationIdSchema, DEFAULT_AGENT_PERMISSION_CONFIG, DEFAULT_RETRY_POLICY, DeliverySemanticsSchema, DirectAddressSchema, ERROR_CODES, EVENT_TYPES, EXTENSION_METHODS, ErrorCategorySchema, EventSchema, EventTypeSchema, FEDERATION_ERROR_CODES, FEDERATION_METHODS, FederatedAddressSchema, FederationOutageBuffer, GatewayConnection, HierarchicalAddressSchema, JSONRPC_VERSION, JsonRpcVersionSchema, LIFECYCLE_METHODS, MAPConnectionError, MAPErrorDataSchema, MAPErrorSchema, MAPNotificationSchema, MAPRequestError, MAPRequestSchema, MAPResponseErrorSchema, MAPResponseSchema, MAPResponseSuccessSchema, MAPTimeoutError, MAP_METHODS, METHOD_REGISTRY, MessageIdSchema, MessageMetaSchema, MessagePrioritySchema, MessageRelationshipSchema, MessageSchema, MessageVisibilitySchema, MetaSchema, MultiAddressSchema, NOTIFICATION_METHODS, OBSERVATION_METHODS, PERMISSION_METHODS, PROTOCOL_ERROR_CODES, PROTOCOL_VERSION, ParticipantAddressSchema, ParticipantCapabilitiesSchema, ParticipantIdSchema, ParticipantTypeSchema, ProtocolVersionSchema, RESOURCE_ERROR_CODES, ROUTING_ERROR_CODES, RequestIdSchema, RoleAddressSchema, SCOPE_METHODS, SESSION_METHODS, STATE_METHODS, STEERING_METHODS, STRUCTURE_METHODS, ScopeAddressSchema, ScopeIdSchema, ScopeJoinPolicySchema, ScopeSchema, ScopeSendPolicySchema, ScopeVisibilitySchema, SessionIdSchema, Subscription, SubscriptionFilterSchema, SubscriptionIdSchema, SystemAddressSchema, TimestampSchema, TransportTypeSchema, buildAgentsGetResponse, buildAgentsListResponse, buildAgentsRegisterResponse, buildAgentsSpawnResponse, buildAgentsUnregisterResponse, buildAgentsUpdateResponse, buildConnectResponse, buildDisconnectResponse, buildScopesCreateResponse, buildScopesJoinResponse, buildScopesLeaveResponse, buildScopesListResponse, buildSendResponse, buildSubscribeResponse, buildUnsubscribeResponse, calculateDelay, canAgentAcceptMessage, canAgentMessageAgent, canAgentSeeAgent, canControlAgent, canJoinScope, canMessageAgent, canPerformAction, canPerformMethod, canSeeAgent, canSeeScope, canSendToScope, compareUlid, createErrorResponse, createEvent, createFederationEnvelope, createNotification, createRequest, createRetryPolicy, createStreamPair, createSubscription, createSuccessResponse, deepMergePermissions, filterVisibleAgents, filterVisibleEvents, filterVisibleScopes, getEnvelopeRoutingInfo, getMethodInfo, getMethodsByCategory, getRequiredCapabilities, hasCapability, hasRequiredCapabilities, isAgentExposed, isBroadcastAddress, isDirectAddress, isEnvelopeAtDestination, isErrorResponse, isEventTypeExposed, isFederatedAddress, isHierarchicalAddress, isNotification, isOrphanedAgent, isRequest, isResponse, isScopeAddress, isScopeExposed, isSuccessResponse, isValidEnvelope, isValidUlid, mapVisibilityToRule, ndJsonStream, processFederationEnvelope, resolveAgentPermissions, retryable, sleep, sortCausalOrder, ulidTimestamp, unwrapEnvelope, validateCausalOrder, websocketStream, withPayload, withRetry };
5216
+ // src/server/messages/address.ts
5217
+ var SEPARATOR = ":";
5218
+ var VALID_ADDRESS_TYPES = ["agent", "scope"];
5219
+ var InvalidAddressError = class extends Error {
5220
+ constructor(address, reason) {
5221
+ super(`Invalid address "${address}": ${reason}`);
5222
+ this.name = "InvalidAddressError";
5223
+ }
5224
+ };
5225
+ function formatAddress(type, id) {
5226
+ if (!id) {
5227
+ throw new InvalidAddressError("", "ID cannot be empty");
5228
+ }
5229
+ if (id.includes(SEPARATOR)) {
5230
+ throw new InvalidAddressError(id, "ID cannot contain colon separator");
5231
+ }
5232
+ return `${type}${SEPARATOR}${id}`;
5233
+ }
5234
+ function parseAddress(address) {
5235
+ const separatorIndex = address.indexOf(SEPARATOR);
5236
+ if (separatorIndex === -1) {
5237
+ throw new InvalidAddressError(address, "missing type prefix");
5238
+ }
5239
+ const type = address.slice(0, separatorIndex);
5240
+ const id = address.slice(separatorIndex + 1);
5241
+ if (!VALID_ADDRESS_TYPES.includes(type)) {
5242
+ throw new InvalidAddressError(address, `invalid type "${type}", must be agent or scope`);
5243
+ }
5244
+ if (!id) {
5245
+ throw new InvalidAddressError(address, "ID cannot be empty");
5246
+ }
5247
+ return {
5248
+ type,
5249
+ id
5250
+ };
5251
+ }
5252
+ function isAddress(address) {
5253
+ try {
5254
+ parseAddress(address);
5255
+ return true;
5256
+ } catch {
5257
+ return false;
5258
+ }
5259
+ }
5260
+ function isAgentAddress(address) {
5261
+ try {
5262
+ const parsed = parseAddress(address);
5263
+ return parsed.type === "agent";
5264
+ } catch {
5265
+ return false;
5266
+ }
5267
+ }
5268
+ function isScopeAddress(address) {
5269
+ try {
5270
+ const parsed = parseAddress(address);
5271
+ return parsed.type === "scope";
5272
+ } catch {
5273
+ return false;
5274
+ }
5275
+ }
5276
+ function extractId(address) {
5277
+ try {
5278
+ const parsed = parseAddress(address);
5279
+ return parsed.id;
5280
+ } catch {
5281
+ return address;
5282
+ }
5283
+ }
5284
+ function extractType(address) {
5285
+ try {
5286
+ const parsed = parseAddress(address);
5287
+ return parsed.type;
5288
+ } catch {
5289
+ return void 0;
5290
+ }
5291
+ }
5292
+ function toAgent(agentId) {
5293
+ return formatAddress("agent", agentId);
5294
+ }
5295
+ function toScope(scopeId) {
5296
+ return formatAddress("scope", scopeId);
5297
+ }
5298
+
5299
+ // src/acp/adapter.ts
5300
+ var ACPAgentAdapter = class {
5301
+ #mapAgent;
5302
+ #handler;
5303
+ #streamContexts = /* @__PURE__ */ new Map();
5304
+ #pendingClientRequests = /* @__PURE__ */ new Map();
5305
+ #clientRequestTimeout;
5306
+ /**
5307
+ * Create a new ACP agent adapter.
5308
+ *
5309
+ * @param mapAgent - The underlying MAP agent connection
5310
+ * @param handler - Handler implementing ACP agent methods
5311
+ * @param options - Optional configuration
5312
+ */
5313
+ constructor(mapAgent, handler, options) {
5314
+ this.#mapAgent = mapAgent;
5315
+ this.#handler = handler;
5316
+ this.#clientRequestTimeout = options?.clientRequestTimeout ?? 3e4;
5317
+ mapAgent.onMessage((message) => {
5318
+ if (!message.payload || !isACPEnvelope(message.payload)) {
5319
+ return;
5320
+ }
5321
+ const envelope = message.payload;
5322
+ const { acp, acpContext } = envelope;
5323
+ if (acp.id !== void 0 && !acp.method) {
5324
+ void this.#handleMessage(message);
5325
+ return;
5326
+ }
5327
+ if (acp.method) {
5328
+ this.#trackStreamContext(acpContext, message.from);
5329
+ }
5330
+ queueMicrotask(() => void this.#handleMessage(message));
5331
+ });
5332
+ }
5333
+ /**
5334
+ * Track stream context for a client request.
5335
+ * This is called synchronously so that hasStream() works immediately.
5336
+ */
5337
+ #trackStreamContext(acpCtx, clientParticipantId) {
5338
+ if (!this.#streamContexts.has(acpCtx.streamId)) {
5339
+ this.#streamContexts.set(acpCtx.streamId, {
5340
+ clientParticipantId,
5341
+ sessionId: acpCtx.sessionId
5342
+ });
5343
+ } else if (acpCtx.sessionId) {
5344
+ const streamCtx = this.#streamContexts.get(acpCtx.streamId);
5345
+ streamCtx.sessionId = acpCtx.sessionId;
5346
+ }
5347
+ }
5348
+ // ===========================================================================
5349
+ // Message Handling
5350
+ // ===========================================================================
5351
+ /**
5352
+ * Handle incoming messages from MAP.
5353
+ */
5354
+ async #handleMessage(message) {
5355
+ if (!message.payload || !isACPEnvelope(message.payload)) {
5356
+ return;
5357
+ }
5358
+ const envelope = message.payload;
5359
+ const { acp, acpContext } = envelope;
5360
+ if (acp.id !== void 0 && !acp.method) {
5361
+ const requestId = String(acp.id);
5362
+ const pending = this.#pendingClientRequests.get(requestId);
5363
+ if (pending) {
5364
+ clearTimeout(pending.timeout);
5365
+ this.#pendingClientRequests.delete(requestId);
5366
+ if (acp.error) {
5367
+ pending.reject(ACPError.fromResponse(acp.error));
5368
+ } else {
5369
+ pending.resolve(acp.result);
5370
+ }
5371
+ }
5372
+ return;
5373
+ }
5374
+ if (acp.method) {
5375
+ await this.#handleClientRequest(
5376
+ acp.id,
5377
+ acp.method,
5378
+ acp.params,
5379
+ acpContext,
5380
+ message
5381
+ );
5382
+ }
5383
+ }
5384
+ /**
5385
+ * Handle a client→agent request.
5386
+ */
5387
+ async #handleClientRequest(requestId, method, params, acpCtx, originalMessage) {
5388
+ const ctx = {
5389
+ streamId: acpCtx.streamId,
5390
+ sessionId: acpCtx.sessionId,
5391
+ clientParticipantId: originalMessage.from
5392
+ };
5393
+ let result;
5394
+ let error;
5395
+ try {
5396
+ switch (method) {
5397
+ case ACP_METHODS.INITIALIZE:
5398
+ result = await this.#handler.initialize(
5399
+ params,
5400
+ ctx
5401
+ );
5402
+ break;
5403
+ case ACP_METHODS.AUTHENTICATE:
5404
+ if (!this.#handler.authenticate) {
5405
+ throw new ACPError(-32601, "Method not implemented: authenticate");
5406
+ }
5407
+ result = await this.#handler.authenticate(
5408
+ params,
5409
+ ctx
5410
+ );
5411
+ break;
5412
+ case ACP_METHODS.SESSION_NEW:
5413
+ result = await this.#handler.newSession(
5414
+ params,
5415
+ ctx
5416
+ );
5417
+ const newSessionResult = result;
5418
+ const streamContext = this.#streamContexts.get(acpCtx.streamId);
5419
+ if (streamContext) {
5420
+ streamContext.sessionId = newSessionResult.sessionId;
5421
+ }
5422
+ break;
5423
+ case ACP_METHODS.SESSION_LOAD:
5424
+ if (!this.#handler.loadSession) {
5425
+ throw new ACPError(-32601, "Method not implemented: session/load");
5426
+ }
5427
+ result = await this.#handler.loadSession(
5428
+ params,
5429
+ ctx
5430
+ );
5431
+ break;
5432
+ case ACP_METHODS.SESSION_SET_MODE:
5433
+ if (!this.#handler.setSessionMode) {
5434
+ throw new ACPError(-32601, "Method not implemented: session/set_mode");
5435
+ }
5436
+ result = await this.#handler.setSessionMode(
5437
+ params,
5438
+ ctx
5439
+ );
5440
+ break;
5441
+ case ACP_METHODS.SESSION_PROMPT:
5442
+ result = await this.#handler.prompt(params, ctx);
5443
+ break;
5444
+ case ACP_METHODS.SESSION_CANCEL:
5445
+ await this.#handler.cancel(params, ctx);
5446
+ return;
5447
+ default:
5448
+ throw new ACPError(-32601, `Unknown method: ${method}`);
5449
+ }
5450
+ } catch (e) {
5451
+ if (e instanceof ACPError) {
5452
+ error = e;
5453
+ } else {
5454
+ error = new ACPError(-32603, e.message);
5455
+ }
5456
+ }
5457
+ if (requestId !== void 0) {
5458
+ const responseEnvelope = {
5459
+ acp: {
5460
+ jsonrpc: "2.0",
5461
+ id: requestId,
5462
+ ...error ? { error: error.toErrorObject() } : { result }
5463
+ },
5464
+ acpContext: {
5465
+ streamId: acpCtx.streamId,
5466
+ sessionId: this.#streamContexts.get(acpCtx.streamId)?.sessionId ?? null,
5467
+ direction: "agent-to-client"
5468
+ }
5469
+ };
5470
+ this.#mapAgent.send(
5471
+ { participant: originalMessage.from },
5472
+ responseEnvelope,
5473
+ {
5474
+ protocol: "acp",
5475
+ correlationId: originalMessage.id
5476
+ }
5477
+ ).catch((err) => {
5478
+ console.error("ACP: Failed to send response:", err);
5479
+ });
5480
+ }
5481
+ }
5482
+ // ===========================================================================
5483
+ // Agent→Client Communication
5484
+ // ===========================================================================
5485
+ /**
5486
+ * Send a session update notification to the client.
5487
+ */
5488
+ async sendSessionUpdate(streamId, notification) {
5489
+ const streamCtx = this.#streamContexts.get(streamId);
5490
+ if (!streamCtx) {
5491
+ throw new Error(`Unknown stream: ${streamId}`);
5492
+ }
5493
+ const envelope = {
5494
+ acp: {
5495
+ jsonrpc: "2.0",
5496
+ method: ACP_METHODS.SESSION_UPDATE,
5497
+ params: notification
5498
+ },
5499
+ acpContext: {
5500
+ streamId,
5501
+ sessionId: streamCtx.sessionId,
5502
+ direction: "agent-to-client"
5503
+ }
5504
+ };
5505
+ await this.#mapAgent.send(
5506
+ { participant: streamCtx.clientParticipantId },
5507
+ envelope,
5508
+ { protocol: "acp" }
5509
+ );
5510
+ }
5511
+ /**
5512
+ * Send an agent→client request and wait for response.
5513
+ */
5514
+ async #sendClientRequest(streamId, method, params) {
5515
+ const streamCtx = this.#streamContexts.get(streamId);
5516
+ if (!streamCtx) {
5517
+ throw new Error(`Unknown stream: ${streamId}`);
5518
+ }
5519
+ const correlationId = `agent-${Date.now()}-${Math.random().toString(36).slice(2)}`;
5520
+ const envelope = {
5521
+ acp: {
5522
+ jsonrpc: "2.0",
5523
+ id: correlationId,
5524
+ method,
5525
+ params
5526
+ },
5527
+ acpContext: {
5528
+ streamId,
5529
+ sessionId: streamCtx.sessionId,
5530
+ direction: "agent-to-client",
5531
+ pendingClientRequest: {
5532
+ requestId: correlationId,
5533
+ method,
5534
+ timeout: this.#clientRequestTimeout
5535
+ }
5536
+ }
5537
+ };
5538
+ await this.#mapAgent.send(
5539
+ { participant: streamCtx.clientParticipantId },
5540
+ envelope,
5541
+ { protocol: "acp", correlationId }
5542
+ );
5543
+ return new Promise((resolve, reject) => {
5544
+ const timeoutHandle = setTimeout(() => {
5545
+ this.#pendingClientRequests.delete(correlationId);
5546
+ reject(new Error(`Client request timed out: ${method}`));
5547
+ }, this.#clientRequestTimeout);
5548
+ this.#pendingClientRequests.set(correlationId, {
5549
+ resolve,
5550
+ reject,
5551
+ timeout: timeoutHandle,
5552
+ method
5553
+ });
5554
+ });
5555
+ }
5556
+ /**
5557
+ * Request permission from the client.
5558
+ */
5559
+ async requestPermission(streamId, request) {
5560
+ return this.#sendClientRequest(streamId, ACP_METHODS.REQUEST_PERMISSION, request);
5561
+ }
5562
+ /**
5563
+ * Read a text file from the client.
5564
+ */
5565
+ async readTextFile(streamId, request) {
5566
+ return this.#sendClientRequest(streamId, ACP_METHODS.FS_READ_TEXT_FILE, request);
5567
+ }
5568
+ /**
5569
+ * Write a text file on the client.
5570
+ */
5571
+ async writeTextFile(streamId, request) {
5572
+ return this.#sendClientRequest(streamId, ACP_METHODS.FS_WRITE_TEXT_FILE, request);
5573
+ }
5574
+ /**
5575
+ * Create a terminal on the client.
5576
+ */
5577
+ async createTerminal(streamId, request) {
5578
+ return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_CREATE, request);
5579
+ }
5580
+ /**
5581
+ * Get terminal output from the client.
5582
+ */
5583
+ async terminalOutput(streamId, request) {
5584
+ return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_OUTPUT, request);
5585
+ }
5586
+ /**
5587
+ * Release a terminal on the client.
5588
+ */
5589
+ async releaseTerminal(streamId, request) {
5590
+ return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_RELEASE, request);
5591
+ }
5592
+ /**
5593
+ * Wait for a terminal to exit on the client.
5594
+ */
5595
+ async waitForTerminalExit(streamId, request) {
5596
+ return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_WAIT_FOR_EXIT, request);
5597
+ }
5598
+ /**
5599
+ * Kill a terminal command on the client.
5600
+ */
5601
+ async killTerminal(streamId, request) {
5602
+ return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_KILL, request);
5603
+ }
5604
+ // ===========================================================================
5605
+ // Utility Methods
5606
+ // ===========================================================================
5607
+ /**
5608
+ * Get the current session ID for a stream.
5609
+ */
5610
+ getSessionId(streamId) {
5611
+ return this.#streamContexts.get(streamId)?.sessionId ?? null;
5612
+ }
5613
+ /**
5614
+ * Get the client participant ID for a stream.
5615
+ */
5616
+ getClientParticipantId(streamId) {
5617
+ return this.#streamContexts.get(streamId)?.clientParticipantId;
5618
+ }
5619
+ /**
5620
+ * Check if a stream is active.
5621
+ */
5622
+ hasStream(streamId) {
5623
+ return this.#streamContexts.has(streamId);
5624
+ }
5625
+ /**
5626
+ * Remove a stream context (e.g., on disconnect).
5627
+ */
5628
+ removeStream(streamId) {
5629
+ return this.#streamContexts.delete(streamId);
5630
+ }
5631
+ };
5632
+
5633
+ export { ACPAgentAdapter, ACPError, ACPStreamConnection, ACP_ERROR_CODES, ACP_METHODS, ACP_PROTOCOL_VERSION, AGENT_ERROR_CODES, AUTH_ERROR_CODES, AUTH_METHODS, AddressSchema, AgentConnection, AgentIdSchema, AgentLifecycleSchema, AgentRelationshipSchema, AgentSchema, AgentStateSchema, AgentVisibilitySchema, BaseConnection, BroadcastAddressSchema, CAPABILITY_REQUIREMENTS, CORE_METHODS, CausalEventBuffer, ClientConnection, CorrelationIdSchema, DEFAULT_AGENT_PERMISSION_CONFIG, DEFAULT_RETRY_POLICY, DeliverySemanticsSchema, DirectAddressSchema, ERROR_CODES, EVENT_TYPES, EXTENSION_METHODS, ErrorCategorySchema, EventSchema, EventTypeSchema, FEDERATION_ERROR_CODES, FEDERATION_METHODS, FederatedAddressSchema, FederationOutageBuffer, GatewayConnection, HierarchicalAddressSchema, InvalidAddressError, JSONRPC_VERSION, JsonRpcVersionSchema, LIFECYCLE_METHODS, MAPConnectionError, MAPErrorDataSchema, MAPErrorSchema, MAPNotificationSchema, MAPRequestError, MAPRequestSchema, MAPResponseErrorSchema, MAPResponseSchema, MAPResponseSuccessSchema, MAPTimeoutError, MAP_METHODS, METHOD_REGISTRY, MessageIdSchema, MessageMetaSchema, MessagePrioritySchema, MessageRelationshipSchema, MessageSchema, MessageVisibilitySchema, MetaSchema, MultiAddressSchema, NOTIFICATION_METHODS, OBSERVATION_METHODS, PERMISSION_METHODS, PROTOCOL_ERROR_CODES, PROTOCOL_VERSION, ParticipantAddressSchema, ParticipantCapabilitiesSchema, ParticipantIdSchema, ParticipantTypeSchema, ProtocolVersionSchema, RESOURCE_ERROR_CODES, ROUTING_ERROR_CODES, RequestIdSchema, RoleAddressSchema, SCOPE_METHODS, SESSION_METHODS, STATE_METHODS, STEERING_METHODS, STRUCTURE_METHODS, ScopeAddressSchema, ScopeIdSchema, ScopeJoinPolicySchema, ScopeSchema, ScopeSendPolicySchema, ScopeVisibilitySchema, SessionIdSchema, Subscription, SubscriptionFilterSchema, SubscriptionIdSchema, SystemAddressSchema, TimestampSchema, TransportTypeSchema, buildAgentsGetResponse, buildAgentsListResponse, buildAgentsRegisterResponse, buildAgentsSpawnResponse, buildAgentsUnregisterResponse, buildAgentsUpdateResponse, buildConnectResponse, buildDisconnectResponse, buildScopesCreateResponse, buildScopesJoinResponse, buildScopesLeaveResponse, buildScopesListResponse, buildSendResponse, buildSubscribeResponse, buildUnsubscribeResponse, calculateDelay, canAgentAcceptMessage, canAgentMessageAgent, canAgentSeeAgent, canControlAgent, canJoinScope, canMessageAgent, canPerformAction, canPerformMethod, canSeeAgent, canSeeScope, canSendToScope, compareUlid, createACPStream, createErrorResponse, createEvent, createFederationEnvelope, createNotification, createRequest, createRetryPolicy, createStreamPair, createSubscription, createSuccessResponse, deepMergePermissions, extractId, extractType, filterVisibleAgents, filterVisibleEvents, filterVisibleScopes, formatAddress, getEnvelopeRoutingInfo, getMethodInfo, getMethodsByCategory, getRequiredCapabilities, hasCapability, hasRequiredCapabilities, isACPEnvelope, isACPErrorResponse, isACPNotification, isACPRequest, isACPResponse, isACPSuccessResponse, isAddress, isAgentAddress, isAgentExposed, isBroadcastAddress, isDirectAddress, isEnvelopeAtDestination, isErrorResponse, isEventTypeExposed, isFederatedAddress, isHierarchicalAddress, isNotification, isOrphanedAgent, isRequest, isResponse, isScopeAddress, isScopeExposed, isSuccessResponse, isValidEnvelope, isValidUlid, mapVisibilityToRule, ndJsonStream, parseAddress, processFederationEnvelope, resolveAgentPermissions, retryable, sleep, sortCausalOrder, toAgent, toScope, ulidTimestamp, unwrapEnvelope, validateCausalOrder, waitForOpen, websocketStream, withPayload, withRetry };
4352
5634
  //# sourceMappingURL=index.js.map
4353
5635
  //# sourceMappingURL=index.js.map