@multi-agent-protocol/sdk 0.0.4 → 0.0.6
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/README.md +164 -5
- package/dist/{index-Z76qC_Us.d.cts → index-BQXp4_rd.d.cts} +1898 -142
- package/dist/{index-Z76qC_Us.d.ts → index-BQXp4_rd.d.ts} +1898 -142
- package/dist/index.cjs +2906 -1104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +641 -24
- package/dist/index.d.ts +641 -24
- package/dist/index.js +2889 -1102
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +1092 -8
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -1
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +1092 -8
- package/dist/testing.js.map +1 -1
- package/package.json +8 -1
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,89 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var agenticMesh = require('agentic-mesh');
|
|
3
4
|
var ulid = require('ulid');
|
|
5
|
+
var events = require('events');
|
|
4
6
|
var zod = require('zod');
|
|
5
7
|
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __esm = (fn, res) => function __init() {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/stream/agentic-mesh.ts
|
|
19
|
+
var agentic_mesh_exports = {};
|
|
20
|
+
__export(agentic_mesh_exports, {
|
|
21
|
+
agenticMeshStream: () => agenticMeshStream
|
|
22
|
+
});
|
|
23
|
+
async function agenticMeshStream(config) {
|
|
24
|
+
if (!config.transport.active) {
|
|
25
|
+
await config.transport.start();
|
|
26
|
+
}
|
|
27
|
+
const connected = await config.transport.connect(config.peer);
|
|
28
|
+
if (!connected) {
|
|
29
|
+
throw new Error(`Failed to connect to peer: ${config.peer.peerId}`);
|
|
30
|
+
}
|
|
31
|
+
const streamId = `map-${config.localPeerId}-${Date.now()}`;
|
|
32
|
+
const tunnelStream = new agenticMesh.TunnelStream({
|
|
33
|
+
transport: config.transport,
|
|
34
|
+
peerId: config.peer.peerId,
|
|
35
|
+
streamId
|
|
36
|
+
});
|
|
37
|
+
await tunnelStream.open();
|
|
38
|
+
return tunnelStreamToMapStream(tunnelStream);
|
|
39
|
+
}
|
|
40
|
+
function tunnelStreamToMapStream(tunnel) {
|
|
41
|
+
let readingAborted = false;
|
|
42
|
+
const readable = new ReadableStream({
|
|
43
|
+
async start(controller) {
|
|
44
|
+
try {
|
|
45
|
+
for await (const frame of tunnel) {
|
|
46
|
+
if (readingAborted) break;
|
|
47
|
+
controller.enqueue(frame);
|
|
48
|
+
}
|
|
49
|
+
if (!readingAborted) {
|
|
50
|
+
controller.close();
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (!readingAborted) {
|
|
54
|
+
controller.error(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
cancel() {
|
|
59
|
+
readingAborted = true;
|
|
60
|
+
tunnel.close().catch(() => {
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const writable = new WritableStream({
|
|
65
|
+
async write(message) {
|
|
66
|
+
if (!tunnel.isOpen) {
|
|
67
|
+
throw new Error("Stream is not open");
|
|
68
|
+
}
|
|
69
|
+
await tunnel.write(message);
|
|
70
|
+
},
|
|
71
|
+
async close() {
|
|
72
|
+
await tunnel.close();
|
|
73
|
+
},
|
|
74
|
+
abort() {
|
|
75
|
+
readingAborted = true;
|
|
76
|
+
tunnel.close().catch(() => {
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return { readable, writable };
|
|
81
|
+
}
|
|
82
|
+
var init_agentic_mesh = __esm({
|
|
83
|
+
"src/stream/agentic-mesh.ts"() {
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
6
87
|
// src/types/index.ts
|
|
7
88
|
function isOrphanedAgent(agent) {
|
|
8
89
|
return agent.ownerId === null;
|
|
@@ -88,6 +169,7 @@ var SESSION_METHODS = {
|
|
|
88
169
|
SESSION_CLOSE: "map/session/close"
|
|
89
170
|
};
|
|
90
171
|
var AUTH_METHODS = {
|
|
172
|
+
AUTHENTICATE: "map/authenticate",
|
|
91
173
|
AUTH_REFRESH: "map/auth/refresh"
|
|
92
174
|
};
|
|
93
175
|
var PERMISSION_METHODS = {
|
|
@@ -101,7 +183,9 @@ var NOTIFICATION_METHODS = {
|
|
|
101
183
|
EVENT: "map/event",
|
|
102
184
|
MESSAGE: "map/message",
|
|
103
185
|
/** Client acknowledges received events (for backpressure) */
|
|
104
|
-
SUBSCRIBE_ACK: "map/subscribe.ack"
|
|
186
|
+
SUBSCRIBE_ACK: "map/subscribe.ack",
|
|
187
|
+
/** Server notifies client that auth is about to expire */
|
|
188
|
+
AUTH_EXPIRING: "map/auth/expiring"
|
|
105
189
|
};
|
|
106
190
|
var MAP_METHODS = {
|
|
107
191
|
...CORE_METHODS,
|
|
@@ -136,7 +220,10 @@ var AUTH_ERROR_CODES = {
|
|
|
136
220
|
AUTH_REQUIRED: 1e3,
|
|
137
221
|
AUTH_FAILED: 1001,
|
|
138
222
|
TOKEN_EXPIRED: 1002,
|
|
139
|
-
PERMISSION_DENIED: 1003
|
|
223
|
+
PERMISSION_DENIED: 1003,
|
|
224
|
+
INSUFFICIENT_SCOPE: 1004,
|
|
225
|
+
METHOD_NOT_SUPPORTED: 1005,
|
|
226
|
+
INVALID_CREDENTIALS: 1006
|
|
140
227
|
};
|
|
141
228
|
var ROUTING_ERROR_CODES = {
|
|
142
229
|
ADDRESS_NOT_FOUND: 2e3,
|
|
@@ -210,7 +297,8 @@ var CAPABILITY_REQUIREMENTS = {
|
|
|
210
297
|
[SESSION_METHODS.SESSION_LIST]: [],
|
|
211
298
|
[SESSION_METHODS.SESSION_LOAD]: [],
|
|
212
299
|
[SESSION_METHODS.SESSION_CLOSE]: [],
|
|
213
|
-
// Auth
|
|
300
|
+
// Auth (no capability required - anyone can authenticate)
|
|
301
|
+
[AUTH_METHODS.AUTHENTICATE]: [],
|
|
214
302
|
[AUTH_METHODS.AUTH_REFRESH]: [],
|
|
215
303
|
// Permissions (system-only, no capability check - enforced by participant type)
|
|
216
304
|
[PERMISSION_METHODS.PERMISSIONS_UPDATE]: [],
|
|
@@ -381,6 +469,27 @@ var MAPRequestError = class _MAPRequestError extends Error {
|
|
|
381
469
|
{ category: "auth" }
|
|
382
470
|
);
|
|
383
471
|
}
|
|
472
|
+
static insufficientScope(required) {
|
|
473
|
+
return new _MAPRequestError(
|
|
474
|
+
AUTH_ERROR_CODES.INSUFFICIENT_SCOPE,
|
|
475
|
+
required ? `Insufficient scope: ${required}` : "Insufficient scope",
|
|
476
|
+
{ category: "auth" }
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
static methodNotSupported(method) {
|
|
480
|
+
return new _MAPRequestError(
|
|
481
|
+
AUTH_ERROR_CODES.METHOD_NOT_SUPPORTED,
|
|
482
|
+
`Authentication method not supported: ${method}`,
|
|
483
|
+
{ category: "auth" }
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
static invalidCredentials(details) {
|
|
487
|
+
return new _MAPRequestError(
|
|
488
|
+
AUTH_ERROR_CODES.INVALID_CREDENTIALS,
|
|
489
|
+
details ?? "Invalid credentials",
|
|
490
|
+
{ category: "auth" }
|
|
491
|
+
);
|
|
492
|
+
}
|
|
384
493
|
// ==========================================================================
|
|
385
494
|
// Routing Errors (2xxx)
|
|
386
495
|
// ==========================================================================
|
|
@@ -562,6 +671,7 @@ var MAPTimeoutError = class extends Error {
|
|
|
562
671
|
};
|
|
563
672
|
|
|
564
673
|
// src/stream/index.ts
|
|
674
|
+
init_agentic_mesh();
|
|
565
675
|
function ndJsonStream(readable, writable) {
|
|
566
676
|
const encoder = new TextEncoder();
|
|
567
677
|
const decoder = new TextDecoder();
|
|
@@ -1801,948 +1911,1071 @@ function sortCausalOrder(events) {
|
|
|
1801
1911
|
return result;
|
|
1802
1912
|
}
|
|
1803
1913
|
|
|
1804
|
-
// src/
|
|
1805
|
-
var
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1914
|
+
// src/acp/types.ts
|
|
1915
|
+
var ACP_ERROR_CODES = {
|
|
1916
|
+
PARSE_ERROR: -32700,
|
|
1917
|
+
INVALID_REQUEST: -32600,
|
|
1918
|
+
METHOD_NOT_FOUND: -32601,
|
|
1919
|
+
INVALID_PARAMS: -32602,
|
|
1920
|
+
INTERNAL_ERROR: -32603,
|
|
1921
|
+
REQUEST_CANCELLED: -32800,
|
|
1922
|
+
AUTH_REQUIRED: -32e3,
|
|
1923
|
+
SESSION_NOT_FOUND: -32002
|
|
1924
|
+
};
|
|
1925
|
+
var ACPError = class _ACPError extends Error {
|
|
1926
|
+
code;
|
|
1927
|
+
data;
|
|
1928
|
+
constructor(code, message, data) {
|
|
1929
|
+
super(message);
|
|
1930
|
+
this.name = "ACPError";
|
|
1931
|
+
this.code = code;
|
|
1932
|
+
this.data = data;
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Create an ACPError from an error response.
|
|
1936
|
+
*/
|
|
1937
|
+
static fromResponse(error) {
|
|
1938
|
+
return new _ACPError(error.code, error.message, error.data);
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Convert to JSON-RPC error object.
|
|
1942
|
+
*/
|
|
1943
|
+
toErrorObject() {
|
|
1944
|
+
return {
|
|
1945
|
+
code: this.code,
|
|
1946
|
+
message: this.message,
|
|
1947
|
+
...this.data !== void 0 && { data: this.data }
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
var ACP_PROTOCOL_VERSION = 20241007;
|
|
1952
|
+
var ACP_METHODS = {
|
|
1953
|
+
// Lifecycle
|
|
1954
|
+
INITIALIZE: "initialize",
|
|
1955
|
+
AUTHENTICATE: "authenticate",
|
|
1956
|
+
// Session management
|
|
1957
|
+
SESSION_NEW: "session/new",
|
|
1958
|
+
SESSION_LOAD: "session/load",
|
|
1959
|
+
SESSION_SET_MODE: "session/set_mode",
|
|
1960
|
+
// Prompt
|
|
1961
|
+
SESSION_PROMPT: "session/prompt",
|
|
1962
|
+
SESSION_CANCEL: "session/cancel",
|
|
1963
|
+
// Notifications
|
|
1964
|
+
SESSION_UPDATE: "session/update",
|
|
1965
|
+
// Agent→Client requests
|
|
1966
|
+
REQUEST_PERMISSION: "request_permission",
|
|
1967
|
+
FS_READ_TEXT_FILE: "fs/read_text_file",
|
|
1968
|
+
FS_WRITE_TEXT_FILE: "fs/write_text_file",
|
|
1969
|
+
TERMINAL_CREATE: "terminal/create",
|
|
1970
|
+
TERMINAL_OUTPUT: "terminal/output",
|
|
1971
|
+
TERMINAL_RELEASE: "terminal/release",
|
|
1972
|
+
TERMINAL_WAIT_FOR_EXIT: "terminal/wait_for_exit",
|
|
1973
|
+
TERMINAL_KILL: "terminal/kill"
|
|
1974
|
+
};
|
|
1975
|
+
function isACPRequest(msg) {
|
|
1976
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1977
|
+
return false;
|
|
1978
|
+
}
|
|
1979
|
+
return "method" in msg && "id" in msg && msg.id !== void 0;
|
|
1980
|
+
}
|
|
1981
|
+
function isACPNotification(msg) {
|
|
1982
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
return "method" in msg && !("id" in msg);
|
|
1986
|
+
}
|
|
1987
|
+
function isACPResponse(msg) {
|
|
1988
|
+
if (typeof msg !== "object" || msg === null) {
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
return "id" in msg && !("method" in msg);
|
|
1992
|
+
}
|
|
1993
|
+
function isACPErrorResponse(response) {
|
|
1994
|
+
if (typeof response !== "object" || response === null) {
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
return "error" in response;
|
|
1998
|
+
}
|
|
1999
|
+
function isACPSuccessResponse(response) {
|
|
2000
|
+
if (typeof response !== "object" || response === null) {
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
|
2003
|
+
return "result" in response;
|
|
2004
|
+
}
|
|
2005
|
+
function isACPEnvelope(payload) {
|
|
2006
|
+
if (typeof payload !== "object" || payload === null) {
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
const envelope = payload;
|
|
2010
|
+
if (typeof envelope.acp !== "object" || envelope.acp === null || typeof envelope.acpContext !== "object" || envelope.acpContext === null) {
|
|
2011
|
+
return false;
|
|
2012
|
+
}
|
|
2013
|
+
const acpContext = envelope.acpContext;
|
|
2014
|
+
return typeof acpContext.streamId === "string";
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/acp/stream.ts
|
|
2018
|
+
var ACPStreamConnection = class extends events.EventEmitter {
|
|
2019
|
+
#mapClient;
|
|
1810
2020
|
#options;
|
|
2021
|
+
#streamId;
|
|
2022
|
+
#pendingRequests = /* @__PURE__ */ new Map();
|
|
2023
|
+
#subscription = null;
|
|
1811
2024
|
#sessionId = null;
|
|
1812
|
-
#
|
|
1813
|
-
#
|
|
1814
|
-
#
|
|
2025
|
+
#initialized = false;
|
|
2026
|
+
#capabilities = null;
|
|
2027
|
+
#closed = false;
|
|
2028
|
+
#lastEventId = null;
|
|
1815
2029
|
#isReconnecting = false;
|
|
1816
|
-
|
|
1817
|
-
this.#connection = new BaseConnection(stream, options);
|
|
1818
|
-
this.#options = options;
|
|
1819
|
-
this.#connection.setNotificationHandler(this.#handleNotification.bind(this));
|
|
1820
|
-
if (options.reconnection?.enabled && options.createStream) {
|
|
1821
|
-
this.#connection.onStateChange((newState) => {
|
|
1822
|
-
if (newState === "closed" && this.#connected && !this.#isReconnecting) {
|
|
1823
|
-
void this.#handleDisconnect();
|
|
1824
|
-
}
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
// ===========================================================================
|
|
1829
|
-
// Static Factory Methods
|
|
1830
|
-
// ===========================================================================
|
|
2030
|
+
#unsubscribeReconnection = null;
|
|
1831
2031
|
/**
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
* Handles:
|
|
1835
|
-
* - WebSocket creation and connection
|
|
1836
|
-
* - Stream wrapping
|
|
1837
|
-
* - Auto-configuration of createStream for reconnection
|
|
1838
|
-
* - Initial MAP protocol connect handshake
|
|
1839
|
-
*
|
|
1840
|
-
* @param url - WebSocket URL (ws:// or wss://)
|
|
1841
|
-
* @param options - Connection options
|
|
1842
|
-
* @returns Connected ClientConnection instance
|
|
1843
|
-
*
|
|
1844
|
-
* @example
|
|
1845
|
-
* ```typescript
|
|
1846
|
-
* const client = await ClientConnection.connect('ws://localhost:8080', {
|
|
1847
|
-
* name: 'MyClient',
|
|
1848
|
-
* reconnection: true
|
|
1849
|
-
* });
|
|
2032
|
+
* Create a new ACP stream connection.
|
|
1850
2033
|
*
|
|
1851
|
-
*
|
|
1852
|
-
*
|
|
1853
|
-
* ```
|
|
2034
|
+
* @param mapClient - The underlying MAP client connection
|
|
2035
|
+
* @param options - Stream configuration options
|
|
1854
2036
|
*/
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
const timeout = options?.connectTimeout ?? 1e4;
|
|
1863
|
-
const ws = new WebSocket(url);
|
|
1864
|
-
await waitForOpen(ws, timeout);
|
|
1865
|
-
const stream = websocketStream(ws);
|
|
1866
|
-
const createStream = async () => {
|
|
1867
|
-
const newWs = new WebSocket(url);
|
|
1868
|
-
await waitForOpen(newWs, timeout);
|
|
1869
|
-
return websocketStream(newWs);
|
|
1870
|
-
};
|
|
1871
|
-
const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
|
|
1872
|
-
const client = new _ClientConnection(stream, {
|
|
1873
|
-
name: options?.name,
|
|
1874
|
-
capabilities: options?.capabilities,
|
|
1875
|
-
createStream,
|
|
1876
|
-
reconnection
|
|
2037
|
+
constructor(mapClient, options) {
|
|
2038
|
+
super();
|
|
2039
|
+
this.#mapClient = mapClient;
|
|
2040
|
+
this.#options = options;
|
|
2041
|
+
this.#streamId = `acp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2042
|
+
this.#unsubscribeReconnection = mapClient.onReconnection((event) => {
|
|
2043
|
+
void this.#handleReconnectionEvent(event);
|
|
1877
2044
|
});
|
|
1878
|
-
await client.connect({ auth: options?.auth });
|
|
1879
|
-
return client;
|
|
1880
2045
|
}
|
|
1881
2046
|
// ===========================================================================
|
|
1882
|
-
//
|
|
2047
|
+
// Public Properties
|
|
2048
|
+
// ===========================================================================
|
|
2049
|
+
/** Unique identifier for this ACP stream */
|
|
2050
|
+
get streamId() {
|
|
2051
|
+
return this.#streamId;
|
|
2052
|
+
}
|
|
2053
|
+
/** Target agent this stream connects to */
|
|
2054
|
+
get targetAgent() {
|
|
2055
|
+
return this.#options.targetAgent;
|
|
2056
|
+
}
|
|
2057
|
+
/** Current ACP session ID (null until newSession called) */
|
|
2058
|
+
get sessionId() {
|
|
2059
|
+
return this.#sessionId;
|
|
2060
|
+
}
|
|
2061
|
+
/** Whether initialize() has been called */
|
|
2062
|
+
get initialized() {
|
|
2063
|
+
return this.#initialized;
|
|
2064
|
+
}
|
|
2065
|
+
/** Agent capabilities from initialize response */
|
|
2066
|
+
get capabilities() {
|
|
2067
|
+
return this.#capabilities;
|
|
2068
|
+
}
|
|
2069
|
+
/** Whether the stream is closed */
|
|
2070
|
+
get isClosed() {
|
|
2071
|
+
return this.#closed;
|
|
2072
|
+
}
|
|
2073
|
+
/** Last processed event ID (for reconnection support) */
|
|
2074
|
+
get lastEventId() {
|
|
2075
|
+
return this.#lastEventId;
|
|
2076
|
+
}
|
|
2077
|
+
/** Whether the stream is currently reconnecting */
|
|
2078
|
+
get isReconnecting() {
|
|
2079
|
+
return this.#isReconnecting;
|
|
2080
|
+
}
|
|
2081
|
+
// ===========================================================================
|
|
2082
|
+
// Reconnection Handling
|
|
1883
2083
|
// ===========================================================================
|
|
1884
2084
|
/**
|
|
1885
|
-
*
|
|
2085
|
+
* Handle MAP reconnection events.
|
|
1886
2086
|
*/
|
|
1887
|
-
async
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2087
|
+
async #handleReconnectionEvent(event) {
|
|
2088
|
+
if (this.#closed) return;
|
|
2089
|
+
switch (event.type) {
|
|
2090
|
+
case "disconnected":
|
|
2091
|
+
this.#isReconnecting = true;
|
|
2092
|
+
this.emit("reconnecting");
|
|
2093
|
+
for (const [id, pending] of this.#pendingRequests) {
|
|
2094
|
+
clearTimeout(pending.timeout);
|
|
2095
|
+
pending.reject(new Error("Connection lost during ACP request"));
|
|
2096
|
+
this.#pendingRequests.delete(id);
|
|
2097
|
+
}
|
|
2098
|
+
break;
|
|
2099
|
+
case "reconnected":
|
|
2100
|
+
await this.#handleReconnected();
|
|
2101
|
+
break;
|
|
2102
|
+
case "reconnectFailed":
|
|
2103
|
+
this.#isReconnecting = false;
|
|
2104
|
+
this.emit("error", event.error ?? new Error("MAP reconnection failed"));
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
1904
2107
|
}
|
|
1905
2108
|
/**
|
|
1906
|
-
*
|
|
1907
|
-
* @param reason - Optional reason for disconnecting
|
|
1908
|
-
* @returns Resume token that can be used to resume this session later
|
|
2109
|
+
* Handle successful MAP reconnection.
|
|
1909
2110
|
*/
|
|
1910
|
-
async
|
|
1911
|
-
|
|
1912
|
-
let resumeToken;
|
|
2111
|
+
async #handleReconnected() {
|
|
2112
|
+
this.#subscription = null;
|
|
1913
2113
|
try {
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2114
|
+
await this.#setupSubscription();
|
|
2115
|
+
if (this.#sessionId) {
|
|
2116
|
+
const sessionValid = await this.#verifySessionValid();
|
|
2117
|
+
if (!sessionValid) {
|
|
2118
|
+
const lostSessionId = this.#sessionId;
|
|
2119
|
+
this.#sessionId = null;
|
|
2120
|
+
this.emit("sessionLost", {
|
|
2121
|
+
sessionId: lostSessionId,
|
|
2122
|
+
reason: "Session no longer valid after reconnection"
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
1922
2125
|
}
|
|
1923
|
-
this.#
|
|
1924
|
-
|
|
1925
|
-
|
|
2126
|
+
this.#isReconnecting = false;
|
|
2127
|
+
this.emit("reconnected");
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
this.#isReconnecting = false;
|
|
2130
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
1926
2131
|
}
|
|
1927
|
-
return resumeToken;
|
|
1928
|
-
}
|
|
1929
|
-
/**
|
|
1930
|
-
* Whether the client is connected
|
|
1931
|
-
*/
|
|
1932
|
-
get isConnected() {
|
|
1933
|
-
return this.#connected && !this.#connection.isClosed;
|
|
1934
2132
|
}
|
|
1935
2133
|
/**
|
|
1936
|
-
*
|
|
2134
|
+
* Verify that the current ACP session is still valid.
|
|
2135
|
+
*
|
|
2136
|
+
* Since ACP doesn't have a dedicated status check method, we attempt
|
|
2137
|
+
* a lightweight operation (cancel with no effect) to verify the session.
|
|
1937
2138
|
*/
|
|
1938
|
-
|
|
1939
|
-
|
|
2139
|
+
async #verifySessionValid() {
|
|
2140
|
+
if (!this.#sessionId) return false;
|
|
2141
|
+
try {
|
|
2142
|
+
await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
|
|
2143
|
+
sessionId: this.#sessionId,
|
|
2144
|
+
reason: "session_verification"
|
|
2145
|
+
});
|
|
2146
|
+
return true;
|
|
2147
|
+
} catch {
|
|
2148
|
+
return false;
|
|
2149
|
+
}
|
|
1940
2150
|
}
|
|
2151
|
+
// ===========================================================================
|
|
2152
|
+
// Internal Methods
|
|
2153
|
+
// ===========================================================================
|
|
1941
2154
|
/**
|
|
1942
|
-
*
|
|
2155
|
+
* Set up the subscription for receiving messages from the target agent.
|
|
1943
2156
|
*/
|
|
1944
|
-
|
|
1945
|
-
|
|
2157
|
+
async #setupSubscription() {
|
|
2158
|
+
if (this.#subscription) return;
|
|
2159
|
+
this.#subscription = await this.#mapClient.subscribe({
|
|
2160
|
+
fromAgents: [this.#options.targetAgent]
|
|
2161
|
+
});
|
|
2162
|
+
void this.#processEvents();
|
|
1946
2163
|
}
|
|
1947
2164
|
/**
|
|
1948
|
-
*
|
|
2165
|
+
* Process incoming events from the subscription.
|
|
1949
2166
|
*/
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2167
|
+
async #processEvents() {
|
|
2168
|
+
if (!this.#subscription) return;
|
|
2169
|
+
try {
|
|
2170
|
+
for await (const event of this.#subscription) {
|
|
2171
|
+
if (this.#closed) break;
|
|
2172
|
+
if (event.id) {
|
|
2173
|
+
this.#lastEventId = event.id;
|
|
2174
|
+
}
|
|
2175
|
+
if (event.type === "message_delivered" && event.data) {
|
|
2176
|
+
const message = event.data.message;
|
|
2177
|
+
if (message?.payload) {
|
|
2178
|
+
await this.#handleIncomingMessage(message);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
} catch (error) {
|
|
2183
|
+
if (!this.#closed) {
|
|
2184
|
+
this.emit("error", error);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
1953
2188
|
/**
|
|
1954
|
-
*
|
|
1955
|
-
*/
|
|
1956
|
-
|
|
1957
|
-
|
|
2189
|
+
* Handle an incoming message from the target agent.
|
|
2190
|
+
*/
|
|
2191
|
+
async #handleIncomingMessage(message) {
|
|
2192
|
+
const payload = message.payload;
|
|
2193
|
+
if (!isACPEnvelope(payload)) return;
|
|
2194
|
+
const envelope = payload;
|
|
2195
|
+
const { acp, acpContext } = envelope;
|
|
2196
|
+
if (acpContext.streamId !== this.#streamId) return;
|
|
2197
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
2198
|
+
const requestId = String(acp.id);
|
|
2199
|
+
const pending = this.#pendingRequests.get(requestId);
|
|
2200
|
+
if (pending) {
|
|
2201
|
+
clearTimeout(pending.timeout);
|
|
2202
|
+
this.#pendingRequests.delete(requestId);
|
|
2203
|
+
if (acp.error) {
|
|
2204
|
+
pending.reject(ACPError.fromResponse(acp.error));
|
|
2205
|
+
} else {
|
|
2206
|
+
pending.resolve(acp.result);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
if (acp.method && acp.id === void 0) {
|
|
2212
|
+
await this.#handleNotification(acp.method, acp.params, acpContext);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
if (acp.method && acp.id !== void 0) {
|
|
2216
|
+
await this.#handleAgentRequest(acp.id, acp.method, acp.params, acpContext, message);
|
|
2217
|
+
}
|
|
1958
2218
|
}
|
|
1959
|
-
// ===========================================================================
|
|
1960
|
-
// Session Management
|
|
1961
|
-
// ===========================================================================
|
|
1962
2219
|
/**
|
|
1963
|
-
*
|
|
2220
|
+
* Handle an ACP notification from the agent.
|
|
1964
2221
|
*/
|
|
1965
|
-
async
|
|
1966
|
-
|
|
2222
|
+
async #handleNotification(method, params, _acpContext) {
|
|
2223
|
+
if (method === ACP_METHODS.SESSION_UPDATE) {
|
|
2224
|
+
await this.#options.client.sessionUpdate(params);
|
|
2225
|
+
}
|
|
1967
2226
|
}
|
|
1968
2227
|
/**
|
|
1969
|
-
*
|
|
2228
|
+
* Handle an agent→client request.
|
|
1970
2229
|
*/
|
|
1971
|
-
async
|
|
1972
|
-
|
|
2230
|
+
async #handleAgentRequest(requestId, method, params, _ctx, originalMessage) {
|
|
2231
|
+
let result;
|
|
2232
|
+
let error;
|
|
2233
|
+
try {
|
|
2234
|
+
switch (method) {
|
|
2235
|
+
case ACP_METHODS.REQUEST_PERMISSION:
|
|
2236
|
+
result = await this.#options.client.requestPermission(
|
|
2237
|
+
params
|
|
2238
|
+
);
|
|
2239
|
+
break;
|
|
2240
|
+
case ACP_METHODS.FS_READ_TEXT_FILE:
|
|
2241
|
+
if (!this.#options.client.readTextFile) {
|
|
2242
|
+
throw new ACPError(-32601, "Method not supported: fs/read_text_file");
|
|
2243
|
+
}
|
|
2244
|
+
result = await this.#options.client.readTextFile(
|
|
2245
|
+
params
|
|
2246
|
+
);
|
|
2247
|
+
break;
|
|
2248
|
+
case ACP_METHODS.FS_WRITE_TEXT_FILE:
|
|
2249
|
+
if (!this.#options.client.writeTextFile) {
|
|
2250
|
+
throw new ACPError(-32601, "Method not supported: fs/write_text_file");
|
|
2251
|
+
}
|
|
2252
|
+
result = await this.#options.client.writeTextFile(
|
|
2253
|
+
params
|
|
2254
|
+
);
|
|
2255
|
+
break;
|
|
2256
|
+
case ACP_METHODS.TERMINAL_CREATE:
|
|
2257
|
+
if (!this.#options.client.createTerminal) {
|
|
2258
|
+
throw new ACPError(-32601, "Method not supported: terminal/create");
|
|
2259
|
+
}
|
|
2260
|
+
result = await this.#options.client.createTerminal(
|
|
2261
|
+
params
|
|
2262
|
+
);
|
|
2263
|
+
break;
|
|
2264
|
+
case ACP_METHODS.TERMINAL_OUTPUT:
|
|
2265
|
+
if (!this.#options.client.terminalOutput) {
|
|
2266
|
+
throw new ACPError(-32601, "Method not supported: terminal/output");
|
|
2267
|
+
}
|
|
2268
|
+
result = await this.#options.client.terminalOutput(
|
|
2269
|
+
params
|
|
2270
|
+
);
|
|
2271
|
+
break;
|
|
2272
|
+
case ACP_METHODS.TERMINAL_RELEASE:
|
|
2273
|
+
if (!this.#options.client.releaseTerminal) {
|
|
2274
|
+
throw new ACPError(-32601, "Method not supported: terminal/release");
|
|
2275
|
+
}
|
|
2276
|
+
result = await this.#options.client.releaseTerminal(
|
|
2277
|
+
params
|
|
2278
|
+
);
|
|
2279
|
+
break;
|
|
2280
|
+
case ACP_METHODS.TERMINAL_WAIT_FOR_EXIT:
|
|
2281
|
+
if (!this.#options.client.waitForTerminalExit) {
|
|
2282
|
+
throw new ACPError(-32601, "Method not supported: terminal/wait_for_exit");
|
|
2283
|
+
}
|
|
2284
|
+
result = await this.#options.client.waitForTerminalExit(
|
|
2285
|
+
params
|
|
2286
|
+
);
|
|
2287
|
+
break;
|
|
2288
|
+
case ACP_METHODS.TERMINAL_KILL:
|
|
2289
|
+
if (!this.#options.client.killTerminal) {
|
|
2290
|
+
throw new ACPError(-32601, "Method not supported: terminal/kill");
|
|
2291
|
+
}
|
|
2292
|
+
result = await this.#options.client.killTerminal(
|
|
2293
|
+
params
|
|
2294
|
+
);
|
|
2295
|
+
break;
|
|
2296
|
+
default:
|
|
2297
|
+
throw new ACPError(-32601, `Unknown method: ${method}`);
|
|
2298
|
+
}
|
|
2299
|
+
} catch (e) {
|
|
2300
|
+
if (e instanceof ACPError) {
|
|
2301
|
+
error = e;
|
|
2302
|
+
} else {
|
|
2303
|
+
error = new ACPError(-32603, e.message);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
const responseEnvelope = {
|
|
2307
|
+
acp: {
|
|
2308
|
+
jsonrpc: "2.0",
|
|
2309
|
+
id: requestId,
|
|
2310
|
+
...error ? { error: error.toErrorObject() } : { result }
|
|
2311
|
+
},
|
|
2312
|
+
acpContext: {
|
|
2313
|
+
streamId: this.#streamId,
|
|
2314
|
+
sessionId: this.#sessionId,
|
|
2315
|
+
direction: "client-to-agent"
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
await this.#mapClient.send(
|
|
2319
|
+
{ agent: this.#options.targetAgent },
|
|
2320
|
+
responseEnvelope,
|
|
2321
|
+
{
|
|
2322
|
+
protocol: "acp",
|
|
2323
|
+
correlationId: originalMessage.id
|
|
2324
|
+
}
|
|
2325
|
+
);
|
|
1973
2326
|
}
|
|
1974
2327
|
/**
|
|
1975
|
-
*
|
|
2328
|
+
* Send an ACP request and wait for response.
|
|
1976
2329
|
*/
|
|
1977
|
-
async
|
|
1978
|
-
|
|
2330
|
+
async #sendRequest(method, params) {
|
|
2331
|
+
if (this.#closed) {
|
|
2332
|
+
throw new Error("ACP stream is closed");
|
|
2333
|
+
}
|
|
2334
|
+
await this.#setupSubscription();
|
|
2335
|
+
if (this.#closed) {
|
|
2336
|
+
throw new Error("ACP stream closed");
|
|
2337
|
+
}
|
|
2338
|
+
const correlationId = `${this.#streamId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2339
|
+
const timeout = this.#options.timeout ?? 3e4;
|
|
2340
|
+
const envelope = {
|
|
2341
|
+
acp: {
|
|
2342
|
+
jsonrpc: "2.0",
|
|
2343
|
+
id: correlationId,
|
|
2344
|
+
method,
|
|
2345
|
+
...params !== void 0 && { params }
|
|
2346
|
+
},
|
|
2347
|
+
acpContext: {
|
|
2348
|
+
streamId: this.#streamId,
|
|
2349
|
+
sessionId: this.#sessionId,
|
|
2350
|
+
direction: "client-to-agent"
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
2354
|
+
const timeoutHandle = setTimeout(() => {
|
|
2355
|
+
this.#pendingRequests.delete(correlationId);
|
|
2356
|
+
reject(new Error(`ACP request timed out after ${timeout}ms: ${method}`));
|
|
2357
|
+
}, timeout);
|
|
2358
|
+
this.#pendingRequests.set(correlationId, {
|
|
2359
|
+
resolve,
|
|
2360
|
+
reject,
|
|
2361
|
+
timeout: timeoutHandle,
|
|
2362
|
+
method
|
|
2363
|
+
});
|
|
2364
|
+
});
|
|
2365
|
+
try {
|
|
2366
|
+
await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
|
|
2367
|
+
protocol: "acp",
|
|
2368
|
+
correlationId
|
|
2369
|
+
});
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
const pending = this.#pendingRequests.get(correlationId);
|
|
2372
|
+
if (pending) {
|
|
2373
|
+
clearTimeout(pending.timeout);
|
|
2374
|
+
this.#pendingRequests.delete(correlationId);
|
|
2375
|
+
}
|
|
2376
|
+
throw err;
|
|
2377
|
+
}
|
|
2378
|
+
if (this.#closed && !this.#pendingRequests.has(correlationId)) {
|
|
2379
|
+
throw new Error("ACP stream closed");
|
|
2380
|
+
}
|
|
2381
|
+
return resultPromise;
|
|
1979
2382
|
}
|
|
1980
|
-
// ===========================================================================
|
|
1981
|
-
// Agent Queries
|
|
1982
|
-
// ===========================================================================
|
|
1983
2383
|
/**
|
|
1984
|
-
*
|
|
2384
|
+
* Send an ACP notification (no response expected).
|
|
1985
2385
|
*/
|
|
1986
|
-
async
|
|
1987
|
-
|
|
2386
|
+
async #sendNotification(method, params) {
|
|
2387
|
+
if (this.#closed) {
|
|
2388
|
+
throw new Error("ACP stream is closed");
|
|
2389
|
+
}
|
|
2390
|
+
await this.#setupSubscription();
|
|
2391
|
+
const envelope = {
|
|
2392
|
+
acp: {
|
|
2393
|
+
jsonrpc: "2.0",
|
|
2394
|
+
method,
|
|
2395
|
+
...params !== void 0 && { params }
|
|
2396
|
+
},
|
|
2397
|
+
acpContext: {
|
|
2398
|
+
streamId: this.#streamId,
|
|
2399
|
+
sessionId: this.#sessionId,
|
|
2400
|
+
direction: "client-to-agent"
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
await this.#mapClient.send({ agent: this.#options.targetAgent }, envelope, {
|
|
2404
|
+
protocol: "acp"
|
|
2405
|
+
});
|
|
1988
2406
|
}
|
|
2407
|
+
// ===========================================================================
|
|
2408
|
+
// ACP Lifecycle Methods
|
|
2409
|
+
// ===========================================================================
|
|
1989
2410
|
/**
|
|
1990
|
-
*
|
|
2411
|
+
* Initialize the ACP connection with the target agent.
|
|
1991
2412
|
*/
|
|
1992
|
-
async
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2413
|
+
async initialize(params) {
|
|
2414
|
+
if (this.#initialized) {
|
|
2415
|
+
throw new Error("ACP stream already initialized");
|
|
2416
|
+
}
|
|
2417
|
+
const result = await this.#sendRequest(
|
|
2418
|
+
ACP_METHODS.INITIALIZE,
|
|
1996
2419
|
params
|
|
1997
2420
|
);
|
|
2421
|
+
this.#initialized = true;
|
|
2422
|
+
this.#capabilities = result.agentCapabilities ?? null;
|
|
2423
|
+
return result;
|
|
1998
2424
|
}
|
|
1999
2425
|
/**
|
|
2000
|
-
*
|
|
2426
|
+
* Authenticate with the agent.
|
|
2001
2427
|
*/
|
|
2002
|
-
async
|
|
2003
|
-
|
|
2428
|
+
async authenticate(params) {
|
|
2429
|
+
if (!this.#initialized) {
|
|
2430
|
+
throw new Error("Must call initialize() before authenticate()");
|
|
2431
|
+
}
|
|
2432
|
+
return this.#sendRequest(
|
|
2433
|
+
ACP_METHODS.AUTHENTICATE,
|
|
2434
|
+
params
|
|
2435
|
+
);
|
|
2004
2436
|
}
|
|
2005
2437
|
// ===========================================================================
|
|
2006
|
-
//
|
|
2438
|
+
// ACP Session Methods
|
|
2007
2439
|
// ===========================================================================
|
|
2008
2440
|
/**
|
|
2009
|
-
*
|
|
2441
|
+
* Create a new ACP session.
|
|
2010
2442
|
*/
|
|
2011
|
-
async
|
|
2012
|
-
|
|
2443
|
+
async newSession(params) {
|
|
2444
|
+
if (!this.#initialized) {
|
|
2445
|
+
throw new Error("Must call initialize() before newSession()");
|
|
2446
|
+
}
|
|
2447
|
+
const result = await this.#sendRequest(
|
|
2448
|
+
ACP_METHODS.SESSION_NEW,
|
|
2449
|
+
params
|
|
2450
|
+
);
|
|
2451
|
+
this.#sessionId = result.sessionId;
|
|
2452
|
+
return result;
|
|
2013
2453
|
}
|
|
2014
2454
|
/**
|
|
2015
|
-
*
|
|
2455
|
+
* Load an existing ACP session.
|
|
2016
2456
|
*/
|
|
2017
|
-
async
|
|
2018
|
-
|
|
2019
|
-
|
|
2457
|
+
async loadSession(params) {
|
|
2458
|
+
if (!this.#initialized) {
|
|
2459
|
+
throw new Error("Must call initialize() before loadSession()");
|
|
2460
|
+
}
|
|
2461
|
+
const result = await this.#sendRequest(
|
|
2462
|
+
ACP_METHODS.SESSION_LOAD,
|
|
2463
|
+
params
|
|
2464
|
+
);
|
|
2465
|
+
this.#sessionId = params.sessionId;
|
|
2466
|
+
return result;
|
|
2020
2467
|
}
|
|
2021
2468
|
/**
|
|
2022
|
-
*
|
|
2469
|
+
* Set the session mode.
|
|
2023
2470
|
*/
|
|
2024
|
-
async
|
|
2025
|
-
return this.#
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2471
|
+
async setSessionMode(params) {
|
|
2472
|
+
return this.#sendRequest(
|
|
2473
|
+
ACP_METHODS.SESSION_SET_MODE,
|
|
2474
|
+
params
|
|
2475
|
+
);
|
|
2029
2476
|
}
|
|
2030
2477
|
// ===========================================================================
|
|
2031
|
-
//
|
|
2478
|
+
// ACP Prompt Methods
|
|
2032
2479
|
// ===========================================================================
|
|
2033
2480
|
/**
|
|
2034
|
-
* Send a
|
|
2035
|
-
|
|
2036
|
-
async send(to, payload, meta) {
|
|
2037
|
-
const params = { to };
|
|
2038
|
-
if (payload !== void 0) params.payload = payload;
|
|
2039
|
-
if (meta) params.meta = meta;
|
|
2040
|
-
return this.#connection.sendRequest(CORE_METHODS.SEND, params);
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Send a message to a specific agent
|
|
2044
|
-
*/
|
|
2045
|
-
async sendToAgent(agentId, payload, meta) {
|
|
2046
|
-
return this.send({ agent: agentId }, payload, meta);
|
|
2047
|
-
}
|
|
2048
|
-
/**
|
|
2049
|
-
* Send a message to all agents in a scope
|
|
2050
|
-
*/
|
|
2051
|
-
async sendToScope(scopeId, payload, meta) {
|
|
2052
|
-
return this.send({ scope: scopeId }, payload, meta);
|
|
2053
|
-
}
|
|
2054
|
-
/**
|
|
2055
|
-
* Send a message to agents with a specific role
|
|
2481
|
+
* Send a prompt to the agent.
|
|
2482
|
+
* Updates are received via the sessionUpdate handler.
|
|
2056
2483
|
*/
|
|
2057
|
-
async
|
|
2058
|
-
|
|
2484
|
+
async prompt(params) {
|
|
2485
|
+
if (!this.#sessionId) {
|
|
2486
|
+
throw new Error("Must call newSession() or loadSession() before prompt()");
|
|
2487
|
+
}
|
|
2488
|
+
return this.#sendRequest(
|
|
2489
|
+
ACP_METHODS.SESSION_PROMPT,
|
|
2490
|
+
params
|
|
2491
|
+
);
|
|
2059
2492
|
}
|
|
2060
2493
|
/**
|
|
2061
|
-
*
|
|
2494
|
+
* Cancel ongoing operations for the current session.
|
|
2062
2495
|
*/
|
|
2063
|
-
async
|
|
2064
|
-
|
|
2496
|
+
async cancel(params) {
|
|
2497
|
+
if (!this.#sessionId) {
|
|
2498
|
+
throw new Error("No active session to cancel");
|
|
2499
|
+
}
|
|
2500
|
+
await this.#sendNotification(ACP_METHODS.SESSION_CANCEL, {
|
|
2501
|
+
sessionId: this.#sessionId,
|
|
2502
|
+
...params
|
|
2503
|
+
});
|
|
2065
2504
|
}
|
|
2505
|
+
// ===========================================================================
|
|
2506
|
+
// Extension Methods
|
|
2507
|
+
// ===========================================================================
|
|
2066
2508
|
/**
|
|
2067
|
-
*
|
|
2509
|
+
* Call an ACP extension method on the target agent.
|
|
2068
2510
|
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2511
|
+
* Extension methods are prefixed with "_" (e.g., "_macro/spawnAgent").
|
|
2512
|
+
* The agent must support the extension for this to succeed.
|
|
2513
|
+
*
|
|
2514
|
+
* @param method - The extension method name (e.g., "_macro/spawnAgent")
|
|
2515
|
+
* @param params - Parameters to pass to the extension method
|
|
2516
|
+
* @returns The result from the extension method
|
|
2517
|
+
*
|
|
2518
|
+
* @example
|
|
2519
|
+
* ```typescript
|
|
2520
|
+
* const result = await acp.callExtension("_macro/spawnAgent", {
|
|
2521
|
+
* task: "Implement feature X",
|
|
2522
|
+
* cwd: "/project"
|
|
2523
|
+
* });
|
|
2524
|
+
* ```
|
|
2071
2525
|
*/
|
|
2072
|
-
async
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
// We'll filter in the handler since subscription filters don't support correlationId
|
|
2076
|
-
});
|
|
2077
|
-
try {
|
|
2078
|
-
await this.send(to, payload, {
|
|
2079
|
-
...options?.meta,
|
|
2080
|
-
expectsResponse: true,
|
|
2081
|
-
correlationId
|
|
2082
|
-
});
|
|
2083
|
-
const timeout = options?.timeout ?? 3e4;
|
|
2084
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2085
|
-
setTimeout(() => reject(new Error(`Request timed out after ${timeout}ms`)), timeout);
|
|
2086
|
-
});
|
|
2087
|
-
const responsePromise = (async () => {
|
|
2088
|
-
for await (const event of responseSub) {
|
|
2089
|
-
if (event.type === "message_delivered" && event.data && event.data.correlationId === correlationId) {
|
|
2090
|
-
return event.data.message;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
throw new Error("Subscription closed before response received");
|
|
2094
|
-
})();
|
|
2095
|
-
return await Promise.race([responsePromise, timeoutPromise]);
|
|
2096
|
-
} finally {
|
|
2097
|
-
await responseSub.unsubscribe();
|
|
2526
|
+
async callExtension(method, params) {
|
|
2527
|
+
if (!this.#initialized) {
|
|
2528
|
+
throw new Error("Must call initialize() before callExtension()");
|
|
2098
2529
|
}
|
|
2530
|
+
return this.#sendRequest(method, params);
|
|
2099
2531
|
}
|
|
2100
2532
|
// ===========================================================================
|
|
2101
|
-
//
|
|
2533
|
+
// Lifecycle
|
|
2102
2534
|
// ===========================================================================
|
|
2103
2535
|
/**
|
|
2104
|
-
*
|
|
2536
|
+
* Close this ACP stream and clean up resources.
|
|
2105
2537
|
*/
|
|
2106
|
-
async
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
this.#connection.sendNotification(NOTIFICATION_METHODS.SUBSCRIBE_ACK, ackParams);
|
|
2113
|
-
} : void 0;
|
|
2114
|
-
const subscription = createSubscription(
|
|
2115
|
-
result.subscriptionId,
|
|
2116
|
-
() => this.unsubscribe(result.subscriptionId),
|
|
2117
|
-
{ filter },
|
|
2118
|
-
sendAck
|
|
2119
|
-
);
|
|
2120
|
-
if (serverSupportsAck) {
|
|
2121
|
-
subscription._setServerSupportsAck(true);
|
|
2538
|
+
async close() {
|
|
2539
|
+
if (this.#closed) return;
|
|
2540
|
+
this.#closed = true;
|
|
2541
|
+
if (this.#unsubscribeReconnection) {
|
|
2542
|
+
this.#unsubscribeReconnection();
|
|
2543
|
+
this.#unsubscribeReconnection = null;
|
|
2122
2544
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
handlers: /* @__PURE__ */ new Set()
|
|
2128
|
-
});
|
|
2129
|
-
const originalPushEvent = subscription._pushEvent.bind(subscription);
|
|
2130
|
-
subscription._pushEvent = (event) => {
|
|
2131
|
-
const state = this.#subscriptionStates.get(result.subscriptionId);
|
|
2132
|
-
if (state && event.eventId) {
|
|
2133
|
-
state.lastEventId = event.eventId;
|
|
2134
|
-
}
|
|
2135
|
-
originalPushEvent(event);
|
|
2136
|
-
};
|
|
2545
|
+
for (const [id, pending] of this.#pendingRequests) {
|
|
2546
|
+
clearTimeout(pending.timeout);
|
|
2547
|
+
pending.reject(new Error("ACP stream closed"));
|
|
2548
|
+
this.#pendingRequests.delete(id);
|
|
2137
2549
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
* Unsubscribe from events
|
|
2142
|
-
*/
|
|
2143
|
-
async unsubscribe(subscriptionId) {
|
|
2144
|
-
const subscription = this.#subscriptions.get(subscriptionId);
|
|
2145
|
-
if (subscription) {
|
|
2146
|
-
subscription._close();
|
|
2147
|
-
this.#subscriptions.delete(subscriptionId);
|
|
2550
|
+
if (this.#subscription) {
|
|
2551
|
+
await this.#subscription.unsubscribe();
|
|
2552
|
+
this.#subscription = null;
|
|
2148
2553
|
}
|
|
2149
|
-
this
|
|
2150
|
-
await this.#connection.sendRequest(CORE_METHODS.UNSUBSCRIBE, { subscriptionId });
|
|
2554
|
+
this.emit("close");
|
|
2151
2555
|
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2556
|
+
};
|
|
2557
|
+
function createACPStream(mapClient, options) {
|
|
2558
|
+
return new ACPStreamConnection(mapClient, options);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
// src/connection/client.ts
|
|
2562
|
+
var ClientConnection = class _ClientConnection {
|
|
2563
|
+
#connection;
|
|
2564
|
+
#subscriptions = /* @__PURE__ */ new Map();
|
|
2565
|
+
#subscriptionStates = /* @__PURE__ */ new Map();
|
|
2566
|
+
#reconnectionHandlers = /* @__PURE__ */ new Set();
|
|
2567
|
+
#acpStreams = /* @__PURE__ */ new Map();
|
|
2568
|
+
#options;
|
|
2569
|
+
#sessionId = null;
|
|
2570
|
+
#serverCapabilities = null;
|
|
2571
|
+
#connected = false;
|
|
2572
|
+
#lastConnectOptions;
|
|
2573
|
+
#isReconnecting = false;
|
|
2574
|
+
#lastResumeToken;
|
|
2575
|
+
#onTokenExpiring;
|
|
2576
|
+
constructor(stream, options = {}) {
|
|
2577
|
+
this.#connection = new BaseConnection(stream, options);
|
|
2578
|
+
this.#options = options;
|
|
2579
|
+
this.#connection.setNotificationHandler(this.#handleNotification.bind(this));
|
|
2580
|
+
if (options.reconnection?.enabled && options.createStream) {
|
|
2581
|
+
this.#connection.onStateChange((newState) => {
|
|
2582
|
+
if (newState === "closed" && this.#connected && !this.#isReconnecting) {
|
|
2583
|
+
void this.#handleDisconnect();
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
// ===========================================================================
|
|
2589
|
+
// Static Factory Methods
|
|
2590
|
+
// ===========================================================================
|
|
2591
|
+
/**
|
|
2592
|
+
* Connect to a MAP server via WebSocket URL.
|
|
2593
|
+
*
|
|
2594
|
+
* Handles:
|
|
2595
|
+
* - WebSocket creation and connection
|
|
2596
|
+
* - Stream wrapping
|
|
2597
|
+
* - Auto-configuration of createStream for reconnection
|
|
2598
|
+
* - Initial MAP protocol connect handshake
|
|
2599
|
+
*
|
|
2600
|
+
* @param url - WebSocket URL (ws:// or wss://)
|
|
2601
|
+
* @param options - Connection options
|
|
2602
|
+
* @returns Connected ClientConnection instance
|
|
2603
|
+
*
|
|
2604
|
+
* @example
|
|
2162
2605
|
* ```typescript
|
|
2163
|
-
*
|
|
2164
|
-
*
|
|
2165
|
-
*
|
|
2166
|
-
* filter: { eventTypes: ['agent.registered'] },
|
|
2167
|
-
* limit: 100
|
|
2606
|
+
* const client = await ClientConnection.connect('ws://localhost:8080', {
|
|
2607
|
+
* name: 'MyClient',
|
|
2608
|
+
* reconnection: true
|
|
2168
2609
|
* });
|
|
2169
2610
|
*
|
|
2170
|
-
* //
|
|
2171
|
-
*
|
|
2172
|
-
* do {
|
|
2173
|
-
* const page = await client.replay({ afterEventId, limit: 100 });
|
|
2174
|
-
* for (const item of page.events) {
|
|
2175
|
-
* console.log(item.eventId, item.event);
|
|
2176
|
-
* }
|
|
2177
|
-
* afterEventId = page.events.at(-1)?.eventId;
|
|
2178
|
-
* } while (page.hasMore);
|
|
2611
|
+
* // Already connected, ready to use
|
|
2612
|
+
* const agents = await client.listAgents();
|
|
2179
2613
|
* ```
|
|
2180
2614
|
*/
|
|
2181
|
-
async
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2615
|
+
static async connect(url, options) {
|
|
2616
|
+
const parsedUrl = new URL(url);
|
|
2617
|
+
if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
|
|
2618
|
+
throw new Error(
|
|
2619
|
+
`Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
const timeout = options?.connectTimeout ?? 1e4;
|
|
2623
|
+
const ws = new WebSocket(url);
|
|
2624
|
+
await waitForOpen(ws, timeout);
|
|
2625
|
+
const stream = websocketStream(ws);
|
|
2626
|
+
const createStream = async () => {
|
|
2627
|
+
const newWs = new WebSocket(url);
|
|
2628
|
+
await waitForOpen(newWs, timeout);
|
|
2629
|
+
return websocketStream(newWs);
|
|
2630
|
+
};
|
|
2631
|
+
const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
|
|
2632
|
+
const client = new _ClientConnection(stream, {
|
|
2633
|
+
name: options?.name,
|
|
2634
|
+
capabilities: options?.capabilities,
|
|
2635
|
+
createStream,
|
|
2636
|
+
reconnection
|
|
2637
|
+
});
|
|
2638
|
+
await client.connect({ auth: options?.auth });
|
|
2639
|
+
return client;
|
|
2187
2640
|
}
|
|
2188
2641
|
/**
|
|
2189
|
-
*
|
|
2642
|
+
* Connect to a MAP server via agentic-mesh transport.
|
|
2190
2643
|
*
|
|
2191
|
-
*
|
|
2644
|
+
* Handles:
|
|
2645
|
+
* - Dynamic import of agentic-mesh (optional peer dependency)
|
|
2646
|
+
* - Stream creation over encrypted mesh tunnel
|
|
2647
|
+
* - Auto-configuration of createStream for reconnection
|
|
2648
|
+
* - Initial MAP protocol connect handshake
|
|
2649
|
+
*
|
|
2650
|
+
* Requires `agentic-mesh` to be installed as a peer dependency.
|
|
2651
|
+
*
|
|
2652
|
+
* @param options - Mesh connection options
|
|
2653
|
+
* @returns Connected ClientConnection instance
|
|
2192
2654
|
*
|
|
2193
2655
|
* @example
|
|
2194
2656
|
* ```typescript
|
|
2195
|
-
*
|
|
2196
|
-
*
|
|
2197
|
-
*
|
|
2198
|
-
*
|
|
2199
|
-
* }
|
|
2657
|
+
* import { createNebulaTransport } from 'agentic-mesh';
|
|
2658
|
+
*
|
|
2659
|
+
* const transport = createNebulaTransport({
|
|
2660
|
+
* configPath: '/etc/nebula/config.yml',
|
|
2661
|
+
* });
|
|
2662
|
+
*
|
|
2663
|
+
* const client = await ClientConnection.connectMesh({
|
|
2664
|
+
* transport,
|
|
2665
|
+
* peer: { peerId: 'server', address: '10.0.0.1', port: 4242 },
|
|
2666
|
+
* localPeerId: 'my-client',
|
|
2667
|
+
* name: 'MeshClient',
|
|
2668
|
+
* reconnection: true
|
|
2669
|
+
* });
|
|
2670
|
+
*
|
|
2671
|
+
* const agents = await client.listAgents();
|
|
2200
2672
|
* ```
|
|
2201
2673
|
*/
|
|
2202
|
-
async
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2674
|
+
static async connectMesh(options) {
|
|
2675
|
+
const { agenticMeshStream: agenticMeshStream2 } = await Promise.resolve().then(() => (init_agentic_mesh(), agentic_mesh_exports));
|
|
2676
|
+
const streamConfig = {
|
|
2677
|
+
transport: options.transport,
|
|
2678
|
+
peer: options.peer,
|
|
2679
|
+
localPeerId: options.localPeerId,
|
|
2680
|
+
timeout: options.timeout
|
|
2681
|
+
};
|
|
2682
|
+
const stream = await agenticMeshStream2(streamConfig);
|
|
2683
|
+
const createStream = async () => agenticMeshStream2(streamConfig);
|
|
2684
|
+
const reconnection = options.reconnection === true ? { enabled: true } : typeof options.reconnection === "object" ? options.reconnection : void 0;
|
|
2685
|
+
const client = new _ClientConnection(stream, {
|
|
2686
|
+
name: options.name,
|
|
2687
|
+
capabilities: options.capabilities,
|
|
2688
|
+
createStream,
|
|
2689
|
+
reconnection
|
|
2690
|
+
});
|
|
2691
|
+
await client.connect({ auth: options.auth });
|
|
2692
|
+
return client;
|
|
2216
2693
|
}
|
|
2217
2694
|
// ===========================================================================
|
|
2218
|
-
//
|
|
2695
|
+
// Connection Lifecycle
|
|
2219
2696
|
// ===========================================================================
|
|
2220
2697
|
/**
|
|
2221
|
-
*
|
|
2698
|
+
* Connect to the MAP system
|
|
2699
|
+
*
|
|
2700
|
+
* @param options - Connection options
|
|
2701
|
+
* @param options.sessionId - Specific session ID to use
|
|
2702
|
+
* @param options.resumeToken - Token to resume a previously disconnected session
|
|
2703
|
+
* @param options.auth - Authentication credentials
|
|
2704
|
+
* @param options.onTokenExpiring - Callback invoked before token expires for proactive refresh
|
|
2222
2705
|
*/
|
|
2223
|
-
async
|
|
2224
|
-
const params = {
|
|
2225
|
-
|
|
2226
|
-
|
|
2706
|
+
async connect(options) {
|
|
2707
|
+
const params = {
|
|
2708
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2709
|
+
participantType: "client",
|
|
2710
|
+
name: this.#options.name,
|
|
2711
|
+
capabilities: this.#options.capabilities,
|
|
2712
|
+
sessionId: options?.sessionId,
|
|
2713
|
+
resumeToken: options?.resumeToken,
|
|
2714
|
+
auth: options?.auth
|
|
2715
|
+
};
|
|
2716
|
+
const result = await this.#connection.sendRequest(CORE_METHODS.CONNECT, params);
|
|
2717
|
+
this.#sessionId = result.sessionId;
|
|
2718
|
+
this.#serverCapabilities = result.capabilities;
|
|
2719
|
+
this.#connected = true;
|
|
2720
|
+
if (result.resumeToken) {
|
|
2721
|
+
this.#lastResumeToken = result.resumeToken;
|
|
2722
|
+
}
|
|
2723
|
+
if (options?.onTokenExpiring) {
|
|
2724
|
+
this.#onTokenExpiring = options.onTokenExpiring;
|
|
2725
|
+
this.#setupTokenExpiryMonitoring(result);
|
|
2726
|
+
}
|
|
2727
|
+
this.#connection._transitionTo("connected");
|
|
2728
|
+
this.#lastConnectOptions = options;
|
|
2729
|
+
return result;
|
|
2227
2730
|
}
|
|
2228
|
-
// ===========================================================================
|
|
2229
|
-
// Lifecycle Control (requires canStop capability)
|
|
2230
|
-
// ===========================================================================
|
|
2231
2731
|
/**
|
|
2232
|
-
*
|
|
2732
|
+
* Get the resume token for this session.
|
|
2733
|
+
* Can be used to reconnect and restore session state after disconnection.
|
|
2734
|
+
*
|
|
2735
|
+
* @returns The resume token, or undefined if not available
|
|
2233
2736
|
*/
|
|
2234
|
-
|
|
2235
|
-
return this.#
|
|
2236
|
-
agentId,
|
|
2237
|
-
...options
|
|
2238
|
-
});
|
|
2737
|
+
getResumeToken() {
|
|
2738
|
+
return this.#lastResumeToken;
|
|
2239
2739
|
}
|
|
2240
2740
|
/**
|
|
2241
|
-
*
|
|
2741
|
+
* Reconnect to the server, optionally using a resume token to restore session.
|
|
2742
|
+
*
|
|
2743
|
+
* @param resumeToken - Token to resume previous session. If not provided, uses the last known token.
|
|
2744
|
+
* @returns Connect response result
|
|
2745
|
+
*
|
|
2746
|
+
* @example
|
|
2747
|
+
* ```typescript
|
|
2748
|
+
* // Save token before disconnect
|
|
2749
|
+
* const token = await client.disconnect();
|
|
2750
|
+
*
|
|
2751
|
+
* // Later, reconnect with the token
|
|
2752
|
+
* const result = await client.reconnect(token);
|
|
2753
|
+
* console.log('Reconnected:', result.reconnected);
|
|
2754
|
+
* ```
|
|
2242
2755
|
*/
|
|
2243
|
-
async
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2756
|
+
async reconnect(resumeToken) {
|
|
2757
|
+
const tokenToUse = resumeToken ?? this.#lastResumeToken;
|
|
2758
|
+
return this.connect({
|
|
2759
|
+
...this.#lastConnectOptions,
|
|
2760
|
+
resumeToken: tokenToUse
|
|
2247
2761
|
});
|
|
2248
2762
|
}
|
|
2249
2763
|
/**
|
|
2250
|
-
*
|
|
2251
|
-
*/
|
|
2252
|
-
async resumeAgent(agentId) {
|
|
2253
|
-
return this.#connection.sendRequest(STATE_METHODS.AGENTS_RESUME, { agentId });
|
|
2254
|
-
}
|
|
2255
|
-
// ===========================================================================
|
|
2256
|
-
// Reconnection
|
|
2257
|
-
// ===========================================================================
|
|
2258
|
-
/**
|
|
2259
|
-
* Current connection state
|
|
2260
|
-
*/
|
|
2261
|
-
get state() {
|
|
2262
|
-
return this.#connection.state;
|
|
2263
|
-
}
|
|
2264
|
-
/**
|
|
2265
|
-
* Whether the connection is currently reconnecting
|
|
2764
|
+
* Set up monitoring for token expiration
|
|
2266
2765
|
*/
|
|
2267
|
-
|
|
2268
|
-
|
|
2766
|
+
#setupTokenExpiryMonitoring(connectResult) {
|
|
2767
|
+
const principal = connectResult.principal;
|
|
2768
|
+
if (!principal?.expiresAt || !this.#onTokenExpiring) {
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const expiresAt = principal.expiresAt;
|
|
2772
|
+
const now = Date.now();
|
|
2773
|
+
const warningTime = expiresAt - 6e4;
|
|
2774
|
+
const delay = warningTime - now;
|
|
2775
|
+
if (delay > 0) {
|
|
2776
|
+
setTimeout(async () => {
|
|
2777
|
+
if (!this.#connected || !this.#onTokenExpiring) return;
|
|
2778
|
+
try {
|
|
2779
|
+
const newCredentials = await this.#onTokenExpiring(expiresAt);
|
|
2780
|
+
if (newCredentials) {
|
|
2781
|
+
const refreshResult = await this.refreshAuth({
|
|
2782
|
+
method: newCredentials.method,
|
|
2783
|
+
credential: newCredentials.credential
|
|
2784
|
+
});
|
|
2785
|
+
if (refreshResult.success && refreshResult.principal?.expiresAt) {
|
|
2786
|
+
this.#setupTokenExpiryMonitoring({
|
|
2787
|
+
...connectResult,
|
|
2788
|
+
principal: refreshResult.principal
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
} catch {
|
|
2793
|
+
}
|
|
2794
|
+
}, delay);
|
|
2795
|
+
}
|
|
2269
2796
|
}
|
|
2270
2797
|
/**
|
|
2271
|
-
*
|
|
2798
|
+
* Authenticate with the server after connection.
|
|
2272
2799
|
*
|
|
2273
|
-
*
|
|
2274
|
-
*
|
|
2800
|
+
* Use this when the server returns `authRequired` in the connect response,
|
|
2801
|
+
* indicating that authentication is needed before accessing protected resources.
|
|
2802
|
+
*
|
|
2803
|
+
* @param auth - Authentication credentials
|
|
2804
|
+
* @returns Authentication result with principal if successful
|
|
2275
2805
|
*
|
|
2276
2806
|
* @example
|
|
2277
2807
|
* ```typescript
|
|
2278
|
-
* const
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2281
|
-
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2285
|
-
*
|
|
2286
|
-
*
|
|
2287
|
-
*
|
|
2288
|
-
* break;
|
|
2289
|
-
* case 'reconnectFailed':
|
|
2290
|
-
* console.log('Failed to reconnect:', event.error);
|
|
2291
|
-
* break;
|
|
2808
|
+
* const connectResult = await client.connect();
|
|
2809
|
+
*
|
|
2810
|
+
* if (connectResult.authRequired) {
|
|
2811
|
+
* const authResult = await client.authenticate({
|
|
2812
|
+
* method: 'api-key',
|
|
2813
|
+
* credential: process.env.API_KEY,
|
|
2814
|
+
* });
|
|
2815
|
+
*
|
|
2816
|
+
* if (authResult.success) {
|
|
2817
|
+
* console.log('Authenticated as:', authResult.principal?.id);
|
|
2292
2818
|
* }
|
|
2293
|
-
* }
|
|
2819
|
+
* }
|
|
2294
2820
|
* ```
|
|
2295
2821
|
*/
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2822
|
+
async authenticate(auth) {
|
|
2823
|
+
const params = {
|
|
2824
|
+
method: auth.method,
|
|
2825
|
+
credential: auth.credential
|
|
2826
|
+
};
|
|
2827
|
+
const result = await this.#connection.sendRequest(AUTH_METHODS.AUTHENTICATE, params);
|
|
2828
|
+
if (result.success && result.sessionId) {
|
|
2829
|
+
this.#sessionId = result.sessionId;
|
|
2830
|
+
}
|
|
2831
|
+
return result;
|
|
2299
2832
|
}
|
|
2300
2833
|
/**
|
|
2301
|
-
*
|
|
2834
|
+
* Refresh authentication credentials.
|
|
2302
2835
|
*
|
|
2303
|
-
*
|
|
2304
|
-
*
|
|
2836
|
+
* Use this to update credentials before they expire for long-lived connections.
|
|
2837
|
+
*
|
|
2838
|
+
* @param auth - New authentication credentials
|
|
2839
|
+
* @returns Updated principal information
|
|
2305
2840
|
*/
|
|
2306
|
-
|
|
2307
|
-
|
|
2841
|
+
async refreshAuth(auth) {
|
|
2842
|
+
const params = {
|
|
2843
|
+
method: auth.method,
|
|
2844
|
+
credential: auth.credential
|
|
2845
|
+
};
|
|
2846
|
+
return this.#connection.sendRequest(AUTH_METHODS.AUTH_REFRESH, params);
|
|
2308
2847
|
}
|
|
2309
|
-
// ===========================================================================
|
|
2310
|
-
// Internal
|
|
2311
|
-
// ===========================================================================
|
|
2312
2848
|
/**
|
|
2313
|
-
*
|
|
2849
|
+
* Disconnect from the MAP system
|
|
2850
|
+
* @param reason - Optional reason for disconnecting
|
|
2851
|
+
* @returns Resume token that can be used to resume this session later
|
|
2314
2852
|
*/
|
|
2315
|
-
async
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2853
|
+
async disconnect(reason) {
|
|
2854
|
+
if (!this.#connected) return void 0;
|
|
2855
|
+
let resumeToken;
|
|
2856
|
+
try {
|
|
2857
|
+
const result = await this.#connection.sendRequest(
|
|
2858
|
+
CORE_METHODS.DISCONNECT,
|
|
2859
|
+
reason ? { reason } : void 0
|
|
2860
|
+
);
|
|
2861
|
+
resumeToken = result.resumeToken;
|
|
2862
|
+
if (resumeToken) {
|
|
2863
|
+
this.#lastResumeToken = resumeToken;
|
|
2326
2864
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2865
|
+
} finally {
|
|
2866
|
+
for (const stream of this.#acpStreams.values()) {
|
|
2867
|
+
await stream.close();
|
|
2329
2868
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2869
|
+
this.#acpStreams.clear();
|
|
2870
|
+
for (const subscription of this.#subscriptions.values()) {
|
|
2871
|
+
subscription._close();
|
|
2872
|
+
}
|
|
2873
|
+
this.#subscriptions.clear();
|
|
2874
|
+
await this.#connection.close();
|
|
2875
|
+
this.#connected = false;
|
|
2332
2876
|
}
|
|
2877
|
+
return resumeToken;
|
|
2333
2878
|
}
|
|
2334
2879
|
/**
|
|
2335
|
-
*
|
|
2880
|
+
* Whether the client is connected
|
|
2336
2881
|
*/
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
try {
|
|
2340
|
-
handler(event);
|
|
2341
|
-
} catch (error) {
|
|
2342
|
-
console.error("MAP: Reconnection event handler error:", error);
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2882
|
+
get isConnected() {
|
|
2883
|
+
return this.#connected && !this.#connection.isClosed;
|
|
2345
2884
|
}
|
|
2346
2885
|
/**
|
|
2347
|
-
*
|
|
2886
|
+
* Current session ID
|
|
2348
2887
|
*/
|
|
2349
|
-
|
|
2350
|
-
this.#
|
|
2351
|
-
this.#connected = false;
|
|
2352
|
-
this.#emitReconnectionEvent({ type: "disconnected" });
|
|
2353
|
-
try {
|
|
2354
|
-
await this.#attemptReconnect();
|
|
2355
|
-
} catch (error) {
|
|
2356
|
-
this.#isReconnecting = false;
|
|
2357
|
-
this.#emitReconnectionEvent({
|
|
2358
|
-
type: "reconnectFailed",
|
|
2359
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2888
|
+
get sessionId() {
|
|
2889
|
+
return this.#sessionId;
|
|
2362
2890
|
}
|
|
2363
2891
|
/**
|
|
2364
|
-
*
|
|
2892
|
+
* Server capabilities
|
|
2365
2893
|
*/
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
const createStream = this.#options.createStream;
|
|
2369
|
-
const retryPolicy = {
|
|
2370
|
-
maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
|
|
2371
|
-
baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
|
|
2372
|
-
maxDelayMs: options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
|
|
2373
|
-
jitter: options.jitter ?? DEFAULT_RETRY_POLICY.jitter
|
|
2374
|
-
};
|
|
2375
|
-
await withRetry(
|
|
2376
|
-
async () => {
|
|
2377
|
-
const newStream = await createStream();
|
|
2378
|
-
await this.#connection.reconnect(newStream);
|
|
2379
|
-
const connectResult = await this.connect(this.#lastConnectOptions);
|
|
2380
|
-
this.#sessionId = connectResult.sessionId;
|
|
2381
|
-
this.#serverCapabilities = connectResult.capabilities;
|
|
2382
|
-
},
|
|
2383
|
-
retryPolicy,
|
|
2384
|
-
{
|
|
2385
|
-
onRetry: (state) => {
|
|
2386
|
-
this.#emitReconnectionEvent({
|
|
2387
|
-
type: "reconnecting",
|
|
2388
|
-
attempt: state.attempt,
|
|
2389
|
-
delay: state.nextDelayMs,
|
|
2390
|
-
error: state.lastError
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
);
|
|
2395
|
-
this.#isReconnecting = false;
|
|
2396
|
-
this.#emitReconnectionEvent({ type: "reconnected" });
|
|
2397
|
-
if (options.restoreSubscriptions !== false) {
|
|
2398
|
-
await this.#restoreSubscriptions();
|
|
2399
|
-
}
|
|
2894
|
+
get serverCapabilities() {
|
|
2895
|
+
return this.#serverCapabilities;
|
|
2400
2896
|
}
|
|
2401
2897
|
/**
|
|
2402
|
-
*
|
|
2898
|
+
* AbortSignal that triggers when the connection closes
|
|
2403
2899
|
*/
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
const subscriptionEntries = Array.from(this.#subscriptionStates.entries());
|
|
2407
|
-
this.#subscriptions.clear();
|
|
2408
|
-
this.#subscriptionStates.clear();
|
|
2409
|
-
for (const [oldId, state] of subscriptionEntries) {
|
|
2410
|
-
try {
|
|
2411
|
-
const newSubscription = await this.subscribe(state.filter);
|
|
2412
|
-
const newId = newSubscription.id;
|
|
2413
|
-
if (options.replayOnRestore !== false && state.lastEventId) {
|
|
2414
|
-
const maxEvents = options.maxReplayEventsPerSubscription ?? 1e3;
|
|
2415
|
-
try {
|
|
2416
|
-
let replayedCount = 0;
|
|
2417
|
-
let afterEventId = state.lastEventId;
|
|
2418
|
-
let hasMore = true;
|
|
2419
|
-
while (hasMore && replayedCount < maxEvents) {
|
|
2420
|
-
const result = await this.replay({
|
|
2421
|
-
afterEventId,
|
|
2422
|
-
filter: state.filter,
|
|
2423
|
-
limit: Math.min(100, maxEvents - replayedCount)
|
|
2424
|
-
});
|
|
2425
|
-
for (const replayedEvent of result.events) {
|
|
2426
|
-
if (replayedCount >= maxEvents) break;
|
|
2427
|
-
newSubscription._pushEvent({
|
|
2428
|
-
subscriptionId: newId,
|
|
2429
|
-
sequenceNumber: replayedCount + 1,
|
|
2430
|
-
eventId: replayedEvent.eventId,
|
|
2431
|
-
timestamp: replayedEvent.timestamp,
|
|
2432
|
-
event: replayedEvent.event
|
|
2433
|
-
});
|
|
2434
|
-
replayedCount++;
|
|
2435
|
-
}
|
|
2436
|
-
hasMore = result.hasMore;
|
|
2437
|
-
afterEventId = result.events.at(-1)?.eventId;
|
|
2438
|
-
if (result.events.length === 0) {
|
|
2439
|
-
break;
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
} catch (replayError) {
|
|
2443
|
-
console.warn("MAP: Failed to replay events for subscription:", oldId, replayError);
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
this.#emitReconnectionEvent({
|
|
2447
|
-
type: "subscriptionRestored",
|
|
2448
|
-
subscriptionId: oldId,
|
|
2449
|
-
newSubscriptionId: newId
|
|
2450
|
-
});
|
|
2451
|
-
} catch (error) {
|
|
2452
|
-
this.#emitReconnectionEvent({
|
|
2453
|
-
type: "subscriptionRestoreFailed",
|
|
2454
|
-
subscriptionId: oldId,
|
|
2455
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
2456
|
-
});
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2900
|
+
get signal() {
|
|
2901
|
+
return this.#connection.signal;
|
|
2459
2902
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
#subscriptions = /* @__PURE__ */ new Map();
|
|
2466
|
-
#options;
|
|
2467
|
-
#messageHandlers = /* @__PURE__ */ new Set();
|
|
2468
|
-
#reconnectionHandlers = /* @__PURE__ */ new Set();
|
|
2469
|
-
#scopeMemberships = /* @__PURE__ */ new Set();
|
|
2470
|
-
#agentId = null;
|
|
2471
|
-
#sessionId = null;
|
|
2472
|
-
#serverCapabilities = null;
|
|
2473
|
-
#currentState = "registered";
|
|
2474
|
-
#connected = false;
|
|
2475
|
-
#lastConnectOptions;
|
|
2476
|
-
#isReconnecting = false;
|
|
2477
|
-
constructor(stream, options = {}) {
|
|
2478
|
-
this.#connection = new BaseConnection(stream, options);
|
|
2479
|
-
this.#options = options;
|
|
2480
|
-
this.#connection.setNotificationHandler(this.#handleNotification.bind(this));
|
|
2481
|
-
if (options.reconnection?.enabled && options.createStream) {
|
|
2482
|
-
this.#connection.onStateChange((newState) => {
|
|
2483
|
-
if (newState === "closed" && this.#connected && !this.#isReconnecting) {
|
|
2484
|
-
void this.#handleDisconnect();
|
|
2485
|
-
}
|
|
2486
|
-
});
|
|
2487
|
-
}
|
|
2903
|
+
/**
|
|
2904
|
+
* Promise that resolves when the connection closes
|
|
2905
|
+
*/
|
|
2906
|
+
get closed() {
|
|
2907
|
+
return this.#connection.closed;
|
|
2488
2908
|
}
|
|
2489
2909
|
// ===========================================================================
|
|
2490
|
-
//
|
|
2910
|
+
// Session Management
|
|
2491
2911
|
// ===========================================================================
|
|
2492
2912
|
/**
|
|
2493
|
-
*
|
|
2494
|
-
*
|
|
2495
|
-
* Handles:
|
|
2496
|
-
* - WebSocket creation and connection
|
|
2497
|
-
* - Stream wrapping
|
|
2498
|
-
* - Auto-configuration of createStream for reconnection
|
|
2499
|
-
* - Initial MAP protocol connect handshake
|
|
2500
|
-
* - Agent registration
|
|
2501
|
-
*
|
|
2502
|
-
* @param url - WebSocket URL (ws:// or wss://)
|
|
2503
|
-
* @param options - Connection and agent options
|
|
2504
|
-
* @returns Connected and registered AgentConnection instance
|
|
2505
|
-
*
|
|
2506
|
-
* @example
|
|
2507
|
-
* ```typescript
|
|
2508
|
-
* const agent = await AgentConnection.connect('ws://localhost:8080', {
|
|
2509
|
-
* name: 'Worker',
|
|
2510
|
-
* role: 'processor',
|
|
2511
|
-
* reconnection: true
|
|
2512
|
-
* });
|
|
2513
|
-
*
|
|
2514
|
-
* // Already registered, ready to work
|
|
2515
|
-
* agent.onMessage(handleMessage);
|
|
2516
|
-
* await agent.busy();
|
|
2517
|
-
* ```
|
|
2913
|
+
* List available sessions
|
|
2518
2914
|
*/
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
|
|
2522
|
-
throw new Error(
|
|
2523
|
-
`Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
|
|
2524
|
-
);
|
|
2525
|
-
}
|
|
2526
|
-
const timeout = options?.connectTimeout ?? 1e4;
|
|
2527
|
-
const ws = new WebSocket(url);
|
|
2528
|
-
await waitForOpen(ws, timeout);
|
|
2529
|
-
const stream = websocketStream(ws);
|
|
2530
|
-
const createStream = async () => {
|
|
2531
|
-
const newWs = new WebSocket(url);
|
|
2532
|
-
await waitForOpen(newWs, timeout);
|
|
2533
|
-
return websocketStream(newWs);
|
|
2534
|
-
};
|
|
2535
|
-
const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
|
|
2536
|
-
const agent = new _AgentConnection(stream, {
|
|
2537
|
-
name: options?.name,
|
|
2538
|
-
role: options?.role,
|
|
2539
|
-
capabilities: options?.capabilities,
|
|
2540
|
-
visibility: options?.visibility,
|
|
2541
|
-
parent: options?.parent,
|
|
2542
|
-
scopes: options?.scopes,
|
|
2543
|
-
createStream,
|
|
2544
|
-
reconnection
|
|
2545
|
-
});
|
|
2546
|
-
await agent.connect({ auth: options?.auth });
|
|
2547
|
-
return agent;
|
|
2915
|
+
async listSessions() {
|
|
2916
|
+
return this.#connection.sendRequest(SESSION_METHODS.SESSION_LIST);
|
|
2548
2917
|
}
|
|
2549
|
-
// ===========================================================================
|
|
2550
|
-
// Connection Lifecycle
|
|
2551
|
-
// ===========================================================================
|
|
2552
2918
|
/**
|
|
2553
|
-
*
|
|
2919
|
+
* Load an existing session
|
|
2554
2920
|
*/
|
|
2555
|
-
async
|
|
2556
|
-
|
|
2557
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
2558
|
-
participantType: "agent",
|
|
2559
|
-
participantId: options?.agentId,
|
|
2560
|
-
name: this.#options.name,
|
|
2561
|
-
capabilities: this.#options.capabilities,
|
|
2562
|
-
resumeToken: options?.resumeToken,
|
|
2563
|
-
auth: options?.auth
|
|
2564
|
-
};
|
|
2565
|
-
const connectResult = await this.#connection.sendRequest(CORE_METHODS.CONNECT, connectParams);
|
|
2566
|
-
this.#sessionId = connectResult.sessionId;
|
|
2567
|
-
this.#serverCapabilities = connectResult.capabilities;
|
|
2568
|
-
this.#connected = true;
|
|
2569
|
-
this.#lastConnectOptions = options;
|
|
2570
|
-
const registerParams = {
|
|
2571
|
-
agentId: options?.agentId,
|
|
2572
|
-
name: this.#options.name,
|
|
2573
|
-
role: this.#options.role,
|
|
2574
|
-
parent: this.#options.parent,
|
|
2575
|
-
scopes: this.#options.scopes,
|
|
2576
|
-
visibility: this.#options.visibility,
|
|
2577
|
-
capabilities: this.#options.capabilities
|
|
2578
|
-
};
|
|
2579
|
-
const registerResult = await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_REGISTER, registerParams);
|
|
2580
|
-
this.#agentId = registerResult.agent.id;
|
|
2581
|
-
this.#currentState = registerResult.agent.state;
|
|
2582
|
-
this.#connection._transitionTo("connected");
|
|
2583
|
-
return { connection: connectResult, agent: registerResult.agent };
|
|
2584
|
-
}
|
|
2585
|
-
/**
|
|
2586
|
-
* Disconnect from the MAP system
|
|
2587
|
-
* @param reason - Optional reason for disconnecting
|
|
2588
|
-
* @returns Resume token that can be used to resume this session later
|
|
2589
|
-
*/
|
|
2590
|
-
async disconnect(reason) {
|
|
2591
|
-
if (!this.#connected) return void 0;
|
|
2592
|
-
let resumeToken;
|
|
2593
|
-
try {
|
|
2594
|
-
if (this.#agentId) {
|
|
2595
|
-
await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_UNREGISTER, {
|
|
2596
|
-
agentId: this.#agentId,
|
|
2597
|
-
reason
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
|
-
const result = await this.#connection.sendRequest(
|
|
2601
|
-
CORE_METHODS.DISCONNECT,
|
|
2602
|
-
reason ? { reason } : void 0
|
|
2603
|
-
);
|
|
2604
|
-
resumeToken = result.resumeToken;
|
|
2605
|
-
} finally {
|
|
2606
|
-
for (const subscription of this.#subscriptions.values()) {
|
|
2607
|
-
subscription._close();
|
|
2608
|
-
}
|
|
2609
|
-
this.#subscriptions.clear();
|
|
2610
|
-
await this.#connection.close();
|
|
2611
|
-
this.#connected = false;
|
|
2612
|
-
}
|
|
2613
|
-
return resumeToken;
|
|
2614
|
-
}
|
|
2615
|
-
/**
|
|
2616
|
-
* Whether the agent is connected
|
|
2617
|
-
*/
|
|
2618
|
-
get isConnected() {
|
|
2619
|
-
return this.#connected && !this.#connection.isClosed;
|
|
2620
|
-
}
|
|
2621
|
-
/**
|
|
2622
|
-
* This agent's ID
|
|
2623
|
-
*/
|
|
2624
|
-
get agentId() {
|
|
2625
|
-
return this.#agentId;
|
|
2626
|
-
}
|
|
2627
|
-
/**
|
|
2628
|
-
* Current session ID
|
|
2629
|
-
*/
|
|
2630
|
-
get sessionId() {
|
|
2631
|
-
return this.#sessionId;
|
|
2921
|
+
async loadSession(sessionId) {
|
|
2922
|
+
return this.#connection.sendRequest(SESSION_METHODS.SESSION_LOAD, { sessionId });
|
|
2632
2923
|
}
|
|
2633
2924
|
/**
|
|
2634
|
-
*
|
|
2925
|
+
* Close the current session
|
|
2635
2926
|
*/
|
|
2636
|
-
|
|
2637
|
-
return this.#
|
|
2927
|
+
async closeSession(sessionId) {
|
|
2928
|
+
return this.#connection.sendRequest(SESSION_METHODS.SESSION_CLOSE, { sessionId });
|
|
2638
2929
|
}
|
|
2930
|
+
// ===========================================================================
|
|
2931
|
+
// Agent Queries
|
|
2932
|
+
// ===========================================================================
|
|
2639
2933
|
/**
|
|
2640
|
-
*
|
|
2934
|
+
* List agents with optional filters
|
|
2641
2935
|
*/
|
|
2642
|
-
|
|
2643
|
-
return this.#
|
|
2936
|
+
async listAgents(options) {
|
|
2937
|
+
return this.#connection.sendRequest(OBSERVATION_METHODS.AGENTS_LIST, options);
|
|
2644
2938
|
}
|
|
2645
2939
|
/**
|
|
2646
|
-
*
|
|
2940
|
+
* Get a single agent by ID
|
|
2647
2941
|
*/
|
|
2648
|
-
|
|
2649
|
-
|
|
2942
|
+
async getAgent(agentId, options) {
|
|
2943
|
+
const params = { agentId, ...options };
|
|
2944
|
+
return this.#connection.sendRequest(
|
|
2945
|
+
OBSERVATION_METHODS.AGENTS_GET,
|
|
2946
|
+
params
|
|
2947
|
+
);
|
|
2650
2948
|
}
|
|
2651
2949
|
/**
|
|
2652
|
-
*
|
|
2950
|
+
* Get the agent structure/hierarchy graph
|
|
2653
2951
|
*/
|
|
2654
|
-
|
|
2655
|
-
return this.#connection.
|
|
2952
|
+
async getStructureGraph(options) {
|
|
2953
|
+
return this.#connection.sendRequest(OBSERVATION_METHODS.STRUCTURE_GRAPH, options);
|
|
2656
2954
|
}
|
|
2657
2955
|
// ===========================================================================
|
|
2658
|
-
//
|
|
2956
|
+
// Scope Queries
|
|
2659
2957
|
// ===========================================================================
|
|
2660
2958
|
/**
|
|
2661
|
-
*
|
|
2662
|
-
*/
|
|
2663
|
-
onMessage(handler) {
|
|
2664
|
-
this.#messageHandlers.add(handler);
|
|
2665
|
-
return this;
|
|
2666
|
-
}
|
|
2667
|
-
/**
|
|
2668
|
-
* Remove a message handler
|
|
2959
|
+
* List scopes
|
|
2669
2960
|
*/
|
|
2670
|
-
|
|
2671
|
-
this.#
|
|
2672
|
-
return this;
|
|
2961
|
+
async listScopes(options) {
|
|
2962
|
+
return this.#connection.sendRequest(OBSERVATION_METHODS.SCOPES_LIST, options);
|
|
2673
2963
|
}
|
|
2674
|
-
// ===========================================================================
|
|
2675
|
-
// State Management
|
|
2676
|
-
// ===========================================================================
|
|
2677
2964
|
/**
|
|
2678
|
-
*
|
|
2965
|
+
* Get a single scope by ID
|
|
2679
2966
|
*/
|
|
2680
|
-
async
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
}
|
|
2684
|
-
const result = await this.#connection.sendRequest(STATE_METHODS.AGENTS_UPDATE, {
|
|
2685
|
-
agentId: this.#agentId,
|
|
2686
|
-
state
|
|
2687
|
-
});
|
|
2688
|
-
this.#currentState = result.agent.state;
|
|
2689
|
-
return result.agent;
|
|
2967
|
+
async getScope(scopeId) {
|
|
2968
|
+
const result = await this.#connection.sendRequest(OBSERVATION_METHODS.SCOPES_GET, { scopeId });
|
|
2969
|
+
return result.scope;
|
|
2690
2970
|
}
|
|
2691
2971
|
/**
|
|
2692
|
-
*
|
|
2972
|
+
* List members of a scope
|
|
2693
2973
|
*/
|
|
2694
|
-
async
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
const result = await this.#connection.sendRequest(STATE_METHODS.AGENTS_UPDATE, {
|
|
2699
|
-
agentId: this.#agentId,
|
|
2700
|
-
metadata
|
|
2974
|
+
async getScopeMembers(scopeId, options) {
|
|
2975
|
+
return this.#connection.sendRequest(OBSERVATION_METHODS.SCOPES_MEMBERS, {
|
|
2976
|
+
scopeId,
|
|
2977
|
+
...options
|
|
2701
2978
|
});
|
|
2702
|
-
return result.agent;
|
|
2703
|
-
}
|
|
2704
|
-
/**
|
|
2705
|
-
* Mark this agent as busy
|
|
2706
|
-
*/
|
|
2707
|
-
async busy() {
|
|
2708
|
-
return this.updateState("busy");
|
|
2709
|
-
}
|
|
2710
|
-
/**
|
|
2711
|
-
* Mark this agent as idle
|
|
2712
|
-
*/
|
|
2713
|
-
async idle() {
|
|
2714
|
-
return this.updateState("idle");
|
|
2715
|
-
}
|
|
2716
|
-
/**
|
|
2717
|
-
* Mark this agent as done/stopped
|
|
2718
|
-
*/
|
|
2719
|
-
async done(result) {
|
|
2720
|
-
if (!this.#agentId) {
|
|
2721
|
-
throw new Error("Agent not registered");
|
|
2722
|
-
}
|
|
2723
|
-
await this.updateState("stopped");
|
|
2724
|
-
if (result) {
|
|
2725
|
-
await this.updateMetadata({
|
|
2726
|
-
exitCode: result.exitCode,
|
|
2727
|
-
exitReason: result.exitReason
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
// ===========================================================================
|
|
2732
|
-
// Child Agent Management
|
|
2733
|
-
// ===========================================================================
|
|
2734
|
-
/**
|
|
2735
|
-
* Spawn a child agent
|
|
2736
|
-
*/
|
|
2737
|
-
async spawn(options) {
|
|
2738
|
-
if (!this.#agentId) {
|
|
2739
|
-
throw new Error("Agent not registered");
|
|
2740
|
-
}
|
|
2741
|
-
const params = {
|
|
2742
|
-
...options,
|
|
2743
|
-
parent: this.#agentId
|
|
2744
|
-
};
|
|
2745
|
-
return this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_SPAWN, params);
|
|
2746
2979
|
}
|
|
2747
2980
|
// ===========================================================================
|
|
2748
2981
|
// Messaging
|
|
@@ -2756,24 +2989,6 @@ var AgentConnection = class _AgentConnection {
|
|
|
2756
2989
|
if (meta) params.meta = meta;
|
|
2757
2990
|
return this.#connection.sendRequest(CORE_METHODS.SEND, params);
|
|
2758
2991
|
}
|
|
2759
|
-
/**
|
|
2760
|
-
* Send a message to the parent agent
|
|
2761
|
-
*/
|
|
2762
|
-
async sendToParent(payload, meta) {
|
|
2763
|
-
return this.send({ parent: true }, payload, {
|
|
2764
|
-
...meta,
|
|
2765
|
-
relationship: "child-to-parent"
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
/**
|
|
2769
|
-
* Send a message to child agents
|
|
2770
|
-
*/
|
|
2771
|
-
async sendToChildren(payload, meta) {
|
|
2772
|
-
return this.send({ children: true }, payload, {
|
|
2773
|
-
...meta,
|
|
2774
|
-
relationship: "parent-to-child"
|
|
2775
|
-
});
|
|
2776
|
-
}
|
|
2777
2992
|
/**
|
|
2778
2993
|
* Send a message to a specific agent
|
|
2779
2994
|
*/
|
|
@@ -2787,61 +3002,109 @@ var AgentConnection = class _AgentConnection {
|
|
|
2787
3002
|
return this.send({ scope: scopeId }, payload, meta);
|
|
2788
3003
|
}
|
|
2789
3004
|
/**
|
|
2790
|
-
* Send a message to
|
|
2791
|
-
*/
|
|
2792
|
-
async sendToSiblings(payload, meta) {
|
|
2793
|
-
return this.send({ siblings: true }, payload, {
|
|
2794
|
-
...meta,
|
|
2795
|
-
relationship: "peer"
|
|
2796
|
-
});
|
|
2797
|
-
}
|
|
2798
|
-
/**
|
|
2799
|
-
* Reply to a message (uses correlationId from original)
|
|
3005
|
+
* Send a message to agents with a specific role
|
|
2800
3006
|
*/
|
|
2801
|
-
async
|
|
2802
|
-
return this.send({
|
|
2803
|
-
...meta,
|
|
2804
|
-
correlationId: originalMessage.meta?.correlationId ?? originalMessage.id,
|
|
2805
|
-
isResult: true
|
|
2806
|
-
});
|
|
3007
|
+
async sendToRole(role, payload, meta, withinScope) {
|
|
3008
|
+
return this.send({ role, within: withinScope }, payload, meta);
|
|
2807
3009
|
}
|
|
2808
|
-
// ===========================================================================
|
|
2809
|
-
// Scope Management
|
|
2810
|
-
// ===========================================================================
|
|
2811
3010
|
/**
|
|
2812
|
-
*
|
|
3011
|
+
* Broadcast a message to all agents
|
|
2813
3012
|
*/
|
|
2814
|
-
async
|
|
2815
|
-
|
|
2816
|
-
return result.scope;
|
|
3013
|
+
async broadcast(payload, meta) {
|
|
3014
|
+
return this.send({ broadcast: true }, payload, meta);
|
|
2817
3015
|
}
|
|
2818
3016
|
/**
|
|
2819
|
-
*
|
|
3017
|
+
* Send a request and wait for a correlated response
|
|
3018
|
+
*
|
|
3019
|
+
* This is a higher-level pattern for request/response messaging.
|
|
3020
|
+
* A correlationId is automatically generated.
|
|
2820
3021
|
*/
|
|
2821
|
-
async
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_JOIN, {
|
|
2826
|
-
scopeId,
|
|
2827
|
-
agentId: this.#agentId
|
|
3022
|
+
async request(to, payload, options) {
|
|
3023
|
+
const correlationId = `req-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3024
|
+
const responseSub = await this.subscribe({
|
|
3025
|
+
// We'll filter in the handler since subscription filters don't support correlationId
|
|
2828
3026
|
});
|
|
2829
|
-
|
|
2830
|
-
|
|
3027
|
+
try {
|
|
3028
|
+
await this.send(to, payload, {
|
|
3029
|
+
...options?.meta,
|
|
3030
|
+
expectsResponse: true,
|
|
3031
|
+
correlationId
|
|
3032
|
+
});
|
|
3033
|
+
const timeout = options?.timeout ?? 3e4;
|
|
3034
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3035
|
+
setTimeout(() => reject(new Error(`Request timed out after ${timeout}ms`)), timeout);
|
|
3036
|
+
});
|
|
3037
|
+
const responsePromise = (async () => {
|
|
3038
|
+
for await (const event of responseSub) {
|
|
3039
|
+
if (event.type === "message_delivered" && event.data && event.data.correlationId === correlationId) {
|
|
3040
|
+
return event.data.message;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
throw new Error("Subscription closed before response received");
|
|
3044
|
+
})();
|
|
3045
|
+
return await Promise.race([responsePromise, timeoutPromise]);
|
|
3046
|
+
} finally {
|
|
3047
|
+
await responseSub.unsubscribe();
|
|
3048
|
+
}
|
|
2831
3049
|
}
|
|
3050
|
+
// ===========================================================================
|
|
3051
|
+
// ACP Streams
|
|
3052
|
+
// ===========================================================================
|
|
2832
3053
|
/**
|
|
2833
|
-
*
|
|
3054
|
+
* Create a virtual ACP stream connection to an agent.
|
|
3055
|
+
*
|
|
3056
|
+
* This allows clients to interact with ACP-compatible agents using the
|
|
3057
|
+
* familiar ACP interface while routing all messages through MAP.
|
|
3058
|
+
*
|
|
3059
|
+
* @param options - Stream configuration options
|
|
3060
|
+
* @returns ACPStreamConnection instance ready for initialize()
|
|
3061
|
+
*
|
|
3062
|
+
* @example
|
|
3063
|
+
* ```typescript
|
|
3064
|
+
* const acp = client.createACPStream({
|
|
3065
|
+
* targetAgent: 'coding-agent-1',
|
|
3066
|
+
* client: {
|
|
3067
|
+
* requestPermission: async (req) => ({
|
|
3068
|
+
* outcome: { outcome: 'selected', optionId: 'allow' }
|
|
3069
|
+
* }),
|
|
3070
|
+
* sessionUpdate: async (update) => {
|
|
3071
|
+
* console.log('Agent update:', update);
|
|
3072
|
+
* }
|
|
3073
|
+
* }
|
|
3074
|
+
* });
|
|
3075
|
+
*
|
|
3076
|
+
* await acp.initialize({
|
|
3077
|
+
* protocolVersion: 20241007,
|
|
3078
|
+
* clientInfo: { name: 'IDE', version: '1.0' }
|
|
3079
|
+
* });
|
|
3080
|
+
* const { sessionId } = await acp.newSession({ cwd: '/project', mcpServers: [] });
|
|
3081
|
+
* const result = await acp.prompt({
|
|
3082
|
+
* sessionId,
|
|
3083
|
+
* prompt: [{ type: 'text', text: 'Hello' }]
|
|
3084
|
+
* });
|
|
3085
|
+
*
|
|
3086
|
+
* await acp.close();
|
|
3087
|
+
* ```
|
|
2834
3088
|
*/
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
scopeId,
|
|
2841
|
-
agentId: this.#agentId
|
|
3089
|
+
createACPStream(options) {
|
|
3090
|
+
const stream = new ACPStreamConnection(this, options);
|
|
3091
|
+
this.#acpStreams.set(stream.streamId, stream);
|
|
3092
|
+
stream.on("close", () => {
|
|
3093
|
+
this.#acpStreams.delete(stream.streamId);
|
|
2842
3094
|
});
|
|
2843
|
-
|
|
2844
|
-
|
|
3095
|
+
return stream;
|
|
3096
|
+
}
|
|
3097
|
+
/**
|
|
3098
|
+
* Get an active ACP stream by ID.
|
|
3099
|
+
*/
|
|
3100
|
+
getACPStream(streamId) {
|
|
3101
|
+
return this.#acpStreams.get(streamId);
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Get all active ACP streams.
|
|
3105
|
+
*/
|
|
3106
|
+
get acpStreams() {
|
|
3107
|
+
return this.#acpStreams;
|
|
2845
3108
|
}
|
|
2846
3109
|
// ===========================================================================
|
|
2847
3110
|
// Subscriptions
|
|
@@ -2853,12 +3116,34 @@ var AgentConnection = class _AgentConnection {
|
|
|
2853
3116
|
const params = {};
|
|
2854
3117
|
if (filter) params.filter = filter;
|
|
2855
3118
|
const result = await this.#connection.sendRequest(CORE_METHODS.SUBSCRIBE, params);
|
|
3119
|
+
const serverSupportsAck = this.#serverCapabilities?.streaming?.supportsAck === true;
|
|
3120
|
+
const sendAck = serverSupportsAck ? (ackParams) => {
|
|
3121
|
+
this.#connection.sendNotification(NOTIFICATION_METHODS.SUBSCRIBE_ACK, ackParams);
|
|
3122
|
+
} : void 0;
|
|
2856
3123
|
const subscription = createSubscription(
|
|
2857
3124
|
result.subscriptionId,
|
|
2858
3125
|
() => this.unsubscribe(result.subscriptionId),
|
|
2859
|
-
{ filter }
|
|
3126
|
+
{ filter },
|
|
3127
|
+
sendAck
|
|
2860
3128
|
);
|
|
3129
|
+
if (serverSupportsAck) {
|
|
3130
|
+
subscription._setServerSupportsAck(true);
|
|
3131
|
+
}
|
|
2861
3132
|
this.#subscriptions.set(result.subscriptionId, subscription);
|
|
3133
|
+
if (this.#options.reconnection?.restoreSubscriptions !== false) {
|
|
3134
|
+
this.#subscriptionStates.set(result.subscriptionId, {
|
|
3135
|
+
filter,
|
|
3136
|
+
handlers: /* @__PURE__ */ new Set()
|
|
3137
|
+
});
|
|
3138
|
+
const originalPushEvent = subscription._pushEvent.bind(subscription);
|
|
3139
|
+
subscription._pushEvent = (event) => {
|
|
3140
|
+
const state = this.#subscriptionStates.get(result.subscriptionId);
|
|
3141
|
+
if (state && event.eventId) {
|
|
3142
|
+
state.lastEventId = event.eventId;
|
|
3143
|
+
}
|
|
3144
|
+
originalPushEvent(event);
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
2862
3147
|
return subscription;
|
|
2863
3148
|
}
|
|
2864
3149
|
/**
|
|
@@ -2870,15 +3155,119 @@ var AgentConnection = class _AgentConnection {
|
|
|
2870
3155
|
subscription._close();
|
|
2871
3156
|
this.#subscriptions.delete(subscriptionId);
|
|
2872
3157
|
}
|
|
3158
|
+
this.#subscriptionStates.delete(subscriptionId);
|
|
2873
3159
|
await this.#connection.sendRequest(CORE_METHODS.UNSUBSCRIBE, { subscriptionId });
|
|
2874
3160
|
}
|
|
2875
3161
|
// ===========================================================================
|
|
3162
|
+
// Event Replay
|
|
3163
|
+
// ===========================================================================
|
|
3164
|
+
/**
|
|
3165
|
+
* Replay historical events.
|
|
3166
|
+
*
|
|
3167
|
+
* Uses keyset pagination - pass the last eventId from the previous
|
|
3168
|
+
* response to get the next page.
|
|
3169
|
+
*
|
|
3170
|
+
* @example
|
|
3171
|
+
* ```typescript
|
|
3172
|
+
* // Replay all events from the last hour
|
|
3173
|
+
* const result = await client.replay({
|
|
3174
|
+
* fromTimestamp: Date.now() - 3600000,
|
|
3175
|
+
* filter: { eventTypes: ['agent.registered'] },
|
|
3176
|
+
* limit: 100
|
|
3177
|
+
* });
|
|
3178
|
+
*
|
|
3179
|
+
* // Paginate through results
|
|
3180
|
+
* let afterEventId: string | undefined;
|
|
3181
|
+
* do {
|
|
3182
|
+
* const page = await client.replay({ afterEventId, limit: 100 });
|
|
3183
|
+
* for (const item of page.events) {
|
|
3184
|
+
* console.log(item.eventId, item.event);
|
|
3185
|
+
* }
|
|
3186
|
+
* afterEventId = page.events.at(-1)?.eventId;
|
|
3187
|
+
* } while (page.hasMore);
|
|
3188
|
+
* ```
|
|
3189
|
+
*/
|
|
3190
|
+
async replay(params = {}) {
|
|
3191
|
+
const limit = Math.min(params.limit ?? 100, 1e3);
|
|
3192
|
+
return this.#connection.sendRequest(
|
|
3193
|
+
CORE_METHODS.REPLAY,
|
|
3194
|
+
{ ...params, limit }
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3197
|
+
/**
|
|
3198
|
+
* Replay all events matching filter, handling pagination automatically.
|
|
3199
|
+
*
|
|
3200
|
+
* Returns an async generator for streaming through all results.
|
|
3201
|
+
*
|
|
3202
|
+
* @example
|
|
3203
|
+
* ```typescript
|
|
3204
|
+
* for await (const item of client.replayAll({
|
|
3205
|
+
* filter: { eventTypes: ['agent.registered'] }
|
|
3206
|
+
* })) {
|
|
3207
|
+
* console.log(item.eventId, item.event);
|
|
3208
|
+
* }
|
|
3209
|
+
* ```
|
|
3210
|
+
*/
|
|
3211
|
+
async *replayAll(params = {}) {
|
|
3212
|
+
let afterEventId;
|
|
3213
|
+
let hasMore = true;
|
|
3214
|
+
while (hasMore) {
|
|
3215
|
+
const result = await this.replay({ ...params, afterEventId });
|
|
3216
|
+
for (const item of result.events) {
|
|
3217
|
+
yield item;
|
|
3218
|
+
}
|
|
3219
|
+
hasMore = result.hasMore;
|
|
3220
|
+
afterEventId = result.events.at(-1)?.eventId;
|
|
3221
|
+
if (result.events.length === 0) {
|
|
3222
|
+
break;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
// ===========================================================================
|
|
3227
|
+
// Steering (requires canSteer capability)
|
|
3228
|
+
// ===========================================================================
|
|
3229
|
+
/**
|
|
3230
|
+
* Inject context into a running agent
|
|
3231
|
+
*/
|
|
3232
|
+
async inject(agentId, content, delivery) {
|
|
3233
|
+
const params = { agentId, content };
|
|
3234
|
+
if (delivery) params.delivery = delivery;
|
|
3235
|
+
return this.#connection.sendRequest(STEERING_METHODS.INJECT, params);
|
|
3236
|
+
}
|
|
3237
|
+
// ===========================================================================
|
|
3238
|
+
// Lifecycle Control (requires canStop capability)
|
|
3239
|
+
// ===========================================================================
|
|
3240
|
+
/**
|
|
3241
|
+
* Request an agent to stop
|
|
3242
|
+
*/
|
|
3243
|
+
async stopAgent(agentId, options) {
|
|
3244
|
+
return this.#connection.sendRequest(STATE_METHODS.AGENTS_STOP, {
|
|
3245
|
+
agentId,
|
|
3246
|
+
...options
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Suspend an agent
|
|
3251
|
+
*/
|
|
3252
|
+
async suspendAgent(agentId, reason) {
|
|
3253
|
+
return this.#connection.sendRequest(STATE_METHODS.AGENTS_SUSPEND, {
|
|
3254
|
+
agentId,
|
|
3255
|
+
reason
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Resume a suspended agent
|
|
3260
|
+
*/
|
|
3261
|
+
async resumeAgent(agentId) {
|
|
3262
|
+
return this.#connection.sendRequest(STATE_METHODS.AGENTS_RESUME, { agentId });
|
|
3263
|
+
}
|
|
3264
|
+
// ===========================================================================
|
|
2876
3265
|
// Reconnection
|
|
2877
3266
|
// ===========================================================================
|
|
2878
3267
|
/**
|
|
2879
3268
|
* Current connection state
|
|
2880
3269
|
*/
|
|
2881
|
-
get
|
|
3270
|
+
get state() {
|
|
2882
3271
|
return this.#connection.state;
|
|
2883
3272
|
}
|
|
2884
3273
|
/**
|
|
@@ -2892,6 +3281,26 @@ var AgentConnection = class _AgentConnection {
|
|
|
2892
3281
|
*
|
|
2893
3282
|
* @param handler - Function called when reconnection events occur
|
|
2894
3283
|
* @returns Unsubscribe function to remove the handler
|
|
3284
|
+
*
|
|
3285
|
+
* @example
|
|
3286
|
+
* ```typescript
|
|
3287
|
+
* const unsubscribe = client.onReconnection((event) => {
|
|
3288
|
+
* switch (event.type) {
|
|
3289
|
+
* case 'disconnected':
|
|
3290
|
+
* console.log('Connection lost');
|
|
3291
|
+
* break;
|
|
3292
|
+
* case 'reconnecting':
|
|
3293
|
+
* console.log(`Reconnecting, attempt ${event.attempt}`);
|
|
3294
|
+
* break;
|
|
3295
|
+
* case 'reconnected':
|
|
3296
|
+
* console.log('Reconnected successfully');
|
|
3297
|
+
* break;
|
|
3298
|
+
* case 'reconnectFailed':
|
|
3299
|
+
* console.log('Failed to reconnect:', event.error);
|
|
3300
|
+
* break;
|
|
3301
|
+
* }
|
|
3302
|
+
* });
|
|
3303
|
+
* ```
|
|
2895
3304
|
*/
|
|
2896
3305
|
onReconnection(handler) {
|
|
2897
3306
|
this.#reconnectionHandlers.add(handler);
|
|
@@ -2919,18 +3328,12 @@ var AgentConnection = class _AgentConnection {
|
|
|
2919
3328
|
const subscription = this.#subscriptions.get(eventParams.subscriptionId);
|
|
2920
3329
|
if (subscription) {
|
|
2921
3330
|
subscription._pushEvent(eventParams);
|
|
3331
|
+
} else {
|
|
3332
|
+
console.warn("MAP: Event for unknown subscription:", eventParams.subscriptionId);
|
|
2922
3333
|
}
|
|
2923
3334
|
break;
|
|
2924
3335
|
}
|
|
2925
3336
|
case NOTIFICATION_METHODS.MESSAGE: {
|
|
2926
|
-
const messageParams = params;
|
|
2927
|
-
for (const handler of this.#messageHandlers) {
|
|
2928
|
-
try {
|
|
2929
|
-
await handler(messageParams.message);
|
|
2930
|
-
} catch (error) {
|
|
2931
|
-
console.error("MAP: Message handler error:", error);
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
3337
|
break;
|
|
2935
3338
|
}
|
|
2936
3339
|
default:
|
|
@@ -2978,19 +3381,13 @@ var AgentConnection = class _AgentConnection {
|
|
|
2978
3381
|
maxDelayMs: options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
|
|
2979
3382
|
jitter: options.jitter ?? DEFAULT_RETRY_POLICY.jitter
|
|
2980
3383
|
};
|
|
2981
|
-
const scopesToRestore = Array.from(this.#scopeMemberships);
|
|
2982
3384
|
await withRetry(
|
|
2983
3385
|
async () => {
|
|
2984
3386
|
const newStream = await createStream();
|
|
2985
3387
|
await this.#connection.reconnect(newStream);
|
|
2986
|
-
const
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
});
|
|
2990
|
-
this.#agentId = result.agent.id;
|
|
2991
|
-
this.#sessionId = result.connection.sessionId;
|
|
2992
|
-
this.#serverCapabilities = result.connection.capabilities;
|
|
2993
|
-
this.#currentState = result.agent.state;
|
|
3388
|
+
const connectResult = await this.connect(this.#lastConnectOptions);
|
|
3389
|
+
this.#sessionId = connectResult.sessionId;
|
|
3390
|
+
this.#serverCapabilities = connectResult.capabilities;
|
|
2994
3391
|
},
|
|
2995
3392
|
retryPolicy,
|
|
2996
3393
|
{
|
|
@@ -3006,26 +3403,754 @@ var AgentConnection = class _AgentConnection {
|
|
|
3006
3403
|
);
|
|
3007
3404
|
this.#isReconnecting = false;
|
|
3008
3405
|
this.#emitReconnectionEvent({ type: "reconnected" });
|
|
3009
|
-
if (options.
|
|
3010
|
-
await this.#
|
|
3406
|
+
if (options.restoreSubscriptions !== false) {
|
|
3407
|
+
await this.#restoreSubscriptions();
|
|
3011
3408
|
}
|
|
3012
3409
|
}
|
|
3013
3410
|
/**
|
|
3014
|
-
* Restore
|
|
3411
|
+
* Restore subscriptions after reconnection
|
|
3015
3412
|
*/
|
|
3016
|
-
async #
|
|
3017
|
-
this.#
|
|
3018
|
-
|
|
3413
|
+
async #restoreSubscriptions() {
|
|
3414
|
+
const options = this.#options.reconnection;
|
|
3415
|
+
const subscriptionEntries = Array.from(this.#subscriptionStates.entries());
|
|
3416
|
+
this.#subscriptions.clear();
|
|
3417
|
+
this.#subscriptionStates.clear();
|
|
3418
|
+
for (const [oldId, state] of subscriptionEntries) {
|
|
3019
3419
|
try {
|
|
3020
|
-
await this.
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3420
|
+
const newSubscription = await this.subscribe(state.filter);
|
|
3421
|
+
const newId = newSubscription.id;
|
|
3422
|
+
if (options.replayOnRestore !== false && state.lastEventId) {
|
|
3423
|
+
const maxEvents = options.maxReplayEventsPerSubscription ?? 1e3;
|
|
3424
|
+
try {
|
|
3425
|
+
let replayedCount = 0;
|
|
3426
|
+
let afterEventId = state.lastEventId;
|
|
3427
|
+
let hasMore = true;
|
|
3428
|
+
while (hasMore && replayedCount < maxEvents) {
|
|
3429
|
+
const result = await this.replay({
|
|
3430
|
+
afterEventId,
|
|
3431
|
+
filter: state.filter,
|
|
3432
|
+
limit: Math.min(100, maxEvents - replayedCount)
|
|
3433
|
+
});
|
|
3434
|
+
for (const replayedEvent of result.events) {
|
|
3435
|
+
if (replayedCount >= maxEvents) break;
|
|
3436
|
+
newSubscription._pushEvent({
|
|
3437
|
+
subscriptionId: newId,
|
|
3438
|
+
sequenceNumber: replayedCount + 1,
|
|
3439
|
+
eventId: replayedEvent.eventId,
|
|
3440
|
+
timestamp: replayedEvent.timestamp,
|
|
3441
|
+
event: replayedEvent.event
|
|
3442
|
+
});
|
|
3443
|
+
replayedCount++;
|
|
3444
|
+
}
|
|
3445
|
+
hasMore = result.hasMore;
|
|
3446
|
+
afterEventId = result.events.at(-1)?.eventId;
|
|
3447
|
+
if (result.events.length === 0) {
|
|
3448
|
+
break;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
} catch (replayError) {
|
|
3452
|
+
console.warn("MAP: Failed to replay events for subscription:", oldId, replayError);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
this.#emitReconnectionEvent({
|
|
3456
|
+
type: "subscriptionRestored",
|
|
3457
|
+
subscriptionId: oldId,
|
|
3458
|
+
newSubscriptionId: newId
|
|
3459
|
+
});
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
this.#emitReconnectionEvent({
|
|
3462
|
+
type: "subscriptionRestoreFailed",
|
|
3463
|
+
subscriptionId: oldId,
|
|
3464
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
|
|
3471
|
+
// src/connection/agent.ts
|
|
3472
|
+
var AgentConnection = class _AgentConnection {
|
|
3473
|
+
#connection;
|
|
3474
|
+
#subscriptions = /* @__PURE__ */ new Map();
|
|
3475
|
+
#options;
|
|
3476
|
+
#messageHandlers = /* @__PURE__ */ new Set();
|
|
3477
|
+
#reconnectionHandlers = /* @__PURE__ */ new Set();
|
|
3478
|
+
#scopeMemberships = /* @__PURE__ */ new Set();
|
|
3479
|
+
#agentId = null;
|
|
3480
|
+
#sessionId = null;
|
|
3481
|
+
#serverCapabilities = null;
|
|
3482
|
+
#currentState = "registered";
|
|
3483
|
+
#connected = false;
|
|
3484
|
+
#lastConnectOptions;
|
|
3485
|
+
#isReconnecting = false;
|
|
3486
|
+
constructor(stream, options = {}) {
|
|
3487
|
+
this.#connection = new BaseConnection(stream, options);
|
|
3488
|
+
this.#options = options;
|
|
3489
|
+
this.#connection.setNotificationHandler(this.#handleNotification.bind(this));
|
|
3490
|
+
if (options.reconnection?.enabled && options.createStream) {
|
|
3491
|
+
this.#connection.onStateChange((newState) => {
|
|
3492
|
+
if (newState === "closed" && this.#connected && !this.#isReconnecting) {
|
|
3493
|
+
void this.#handleDisconnect();
|
|
3494
|
+
}
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
// ===========================================================================
|
|
3499
|
+
// Static Factory Methods
|
|
3500
|
+
// ===========================================================================
|
|
3501
|
+
/**
|
|
3502
|
+
* Connect and register an agent via WebSocket URL.
|
|
3503
|
+
*
|
|
3504
|
+
* Handles:
|
|
3505
|
+
* - WebSocket creation and connection
|
|
3506
|
+
* - Stream wrapping
|
|
3507
|
+
* - Auto-configuration of createStream for reconnection
|
|
3508
|
+
* - Initial MAP protocol connect handshake
|
|
3509
|
+
* - Agent registration
|
|
3510
|
+
*
|
|
3511
|
+
* @param url - WebSocket URL (ws:// or wss://)
|
|
3512
|
+
* @param options - Connection and agent options
|
|
3513
|
+
* @returns Connected and registered AgentConnection instance
|
|
3514
|
+
*
|
|
3515
|
+
* @example
|
|
3516
|
+
* ```typescript
|
|
3517
|
+
* const agent = await AgentConnection.connect('ws://localhost:8080', {
|
|
3518
|
+
* name: 'Worker',
|
|
3519
|
+
* role: 'processor',
|
|
3520
|
+
* reconnection: true
|
|
3521
|
+
* });
|
|
3522
|
+
*
|
|
3523
|
+
* // Already registered, ready to work
|
|
3524
|
+
* agent.onMessage(handleMessage);
|
|
3525
|
+
* await agent.busy();
|
|
3526
|
+
* ```
|
|
3527
|
+
*/
|
|
3528
|
+
static async connect(url, options) {
|
|
3529
|
+
const parsedUrl = new URL(url);
|
|
3530
|
+
if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
|
|
3531
|
+
throw new Error(
|
|
3532
|
+
`Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
const timeout = options?.connectTimeout ?? 1e4;
|
|
3536
|
+
const ws = new WebSocket(url);
|
|
3537
|
+
await waitForOpen(ws, timeout);
|
|
3538
|
+
const stream = websocketStream(ws);
|
|
3539
|
+
const createStream = async () => {
|
|
3540
|
+
const newWs = new WebSocket(url);
|
|
3541
|
+
await waitForOpen(newWs, timeout);
|
|
3542
|
+
return websocketStream(newWs);
|
|
3543
|
+
};
|
|
3544
|
+
const reconnection = options?.reconnection === true ? { enabled: true } : typeof options?.reconnection === "object" ? options.reconnection : void 0;
|
|
3545
|
+
const agent = new _AgentConnection(stream, {
|
|
3546
|
+
name: options?.name,
|
|
3547
|
+
role: options?.role,
|
|
3548
|
+
capabilities: options?.capabilities,
|
|
3549
|
+
visibility: options?.visibility,
|
|
3550
|
+
parent: options?.parent,
|
|
3551
|
+
scopes: options?.scopes,
|
|
3552
|
+
createStream,
|
|
3553
|
+
reconnection
|
|
3554
|
+
});
|
|
3555
|
+
await agent.connect({ auth: options?.auth });
|
|
3556
|
+
return agent;
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* Connect and register an agent via agentic-mesh transport.
|
|
3560
|
+
*
|
|
3561
|
+
* Handles:
|
|
3562
|
+
* - Dynamic import of agentic-mesh (optional peer dependency)
|
|
3563
|
+
* - Stream creation over encrypted mesh tunnel
|
|
3564
|
+
* - Auto-configuration of createStream for reconnection
|
|
3565
|
+
* - Initial MAP protocol connect handshake
|
|
3566
|
+
* - Agent registration
|
|
3567
|
+
*
|
|
3568
|
+
* Requires `agentic-mesh` to be installed as a peer dependency.
|
|
3569
|
+
*
|
|
3570
|
+
* @param options - Mesh connection and agent options
|
|
3571
|
+
* @returns Connected and registered AgentConnection instance
|
|
3572
|
+
*
|
|
3573
|
+
* @example
|
|
3574
|
+
* ```typescript
|
|
3575
|
+
* import { createNebulaTransport } from 'agentic-mesh';
|
|
3576
|
+
*
|
|
3577
|
+
* const transport = createNebulaTransport({
|
|
3578
|
+
* configPath: '/etc/nebula/config.yml',
|
|
3579
|
+
* });
|
|
3580
|
+
*
|
|
3581
|
+
* const agent = await AgentConnection.connectMesh({
|
|
3582
|
+
* transport,
|
|
3583
|
+
* peer: { peerId: 'server', address: '10.0.0.1', port: 4242 },
|
|
3584
|
+
* localPeerId: 'my-agent',
|
|
3585
|
+
* name: 'MeshWorker',
|
|
3586
|
+
* role: 'processor',
|
|
3587
|
+
* reconnection: true
|
|
3588
|
+
* });
|
|
3589
|
+
*
|
|
3590
|
+
* agent.onMessage(handleMessage);
|
|
3591
|
+
* await agent.busy();
|
|
3592
|
+
* ```
|
|
3593
|
+
*/
|
|
3594
|
+
static async connectMesh(options) {
|
|
3595
|
+
const { agenticMeshStream: agenticMeshStream2 } = await Promise.resolve().then(() => (init_agentic_mesh(), agentic_mesh_exports));
|
|
3596
|
+
const streamConfig = {
|
|
3597
|
+
transport: options.transport,
|
|
3598
|
+
peer: options.peer,
|
|
3599
|
+
localPeerId: options.localPeerId,
|
|
3600
|
+
timeout: options.timeout
|
|
3601
|
+
};
|
|
3602
|
+
const stream = await agenticMeshStream2(streamConfig);
|
|
3603
|
+
const createStream = async () => agenticMeshStream2(streamConfig);
|
|
3604
|
+
const reconnection = options.reconnection === true ? { enabled: true } : typeof options.reconnection === "object" ? options.reconnection : void 0;
|
|
3605
|
+
const agent = new _AgentConnection(stream, {
|
|
3606
|
+
name: options.name,
|
|
3607
|
+
role: options.role,
|
|
3608
|
+
capabilities: options.capabilities,
|
|
3609
|
+
visibility: options.visibility,
|
|
3610
|
+
parent: options.parent,
|
|
3611
|
+
scopes: options.scopes,
|
|
3612
|
+
createStream,
|
|
3613
|
+
reconnection
|
|
3614
|
+
});
|
|
3615
|
+
await agent.connect({ auth: options.auth });
|
|
3616
|
+
return agent;
|
|
3617
|
+
}
|
|
3618
|
+
// ===========================================================================
|
|
3619
|
+
// Connection Lifecycle
|
|
3620
|
+
// ===========================================================================
|
|
3621
|
+
/**
|
|
3622
|
+
* Connect and register with the MAP system
|
|
3623
|
+
*/
|
|
3624
|
+
async connect(options) {
|
|
3625
|
+
const connectParams = {
|
|
3626
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
3627
|
+
participantType: "agent",
|
|
3628
|
+
participantId: options?.agentId,
|
|
3629
|
+
name: this.#options.name,
|
|
3630
|
+
capabilities: this.#options.capabilities,
|
|
3631
|
+
resumeToken: options?.resumeToken,
|
|
3632
|
+
auth: options?.auth
|
|
3633
|
+
};
|
|
3634
|
+
const connectResult = await this.#connection.sendRequest(CORE_METHODS.CONNECT, connectParams);
|
|
3635
|
+
this.#sessionId = connectResult.sessionId;
|
|
3636
|
+
this.#serverCapabilities = connectResult.capabilities;
|
|
3637
|
+
this.#connected = true;
|
|
3638
|
+
this.#lastConnectOptions = options;
|
|
3639
|
+
const registerParams = {
|
|
3640
|
+
agentId: options?.agentId,
|
|
3641
|
+
name: this.#options.name,
|
|
3642
|
+
role: this.#options.role,
|
|
3643
|
+
parent: this.#options.parent,
|
|
3644
|
+
scopes: this.#options.scopes,
|
|
3645
|
+
visibility: this.#options.visibility,
|
|
3646
|
+
capabilities: this.#options.capabilities
|
|
3647
|
+
};
|
|
3648
|
+
const registerResult = await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_REGISTER, registerParams);
|
|
3649
|
+
this.#agentId = registerResult.agent.id;
|
|
3650
|
+
this.#currentState = registerResult.agent.state;
|
|
3651
|
+
this.#connection._transitionTo("connected");
|
|
3652
|
+
return { connection: connectResult, agent: registerResult.agent };
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* Authenticate with the server after connection.
|
|
3656
|
+
*
|
|
3657
|
+
* Use this when the server returns `authRequired` in the connect response,
|
|
3658
|
+
* indicating that authentication is needed before registering or accessing
|
|
3659
|
+
* protected resources.
|
|
3660
|
+
*
|
|
3661
|
+
* @param auth - Authentication credentials
|
|
3662
|
+
* @returns Authentication result with principal if successful
|
|
3663
|
+
*
|
|
3664
|
+
* @example
|
|
3665
|
+
* ```typescript
|
|
3666
|
+
* const agent = new AgentConnection(stream, { name: 'MyAgent' });
|
|
3667
|
+
*
|
|
3668
|
+
* // First connect to get auth requirements
|
|
3669
|
+
* const connectResult = await agent.connectOnly();
|
|
3670
|
+
*
|
|
3671
|
+
* if (connectResult.authRequired) {
|
|
3672
|
+
* const authResult = await agent.authenticate({
|
|
3673
|
+
* method: 'api-key',
|
|
3674
|
+
* token: process.env.AGENT_API_KEY,
|
|
3675
|
+
* });
|
|
3676
|
+
*
|
|
3677
|
+
* if (authResult.success) {
|
|
3678
|
+
* // Now register the agent
|
|
3679
|
+
* await agent.register({ name: 'MyAgent', role: 'worker' });
|
|
3680
|
+
* }
|
|
3681
|
+
* }
|
|
3682
|
+
* ```
|
|
3683
|
+
*/
|
|
3684
|
+
async authenticate(auth) {
|
|
3685
|
+
const params = {
|
|
3686
|
+
method: auth.method,
|
|
3687
|
+
credential: auth.token
|
|
3688
|
+
};
|
|
3689
|
+
const result = await this.#connection.sendRequest(AUTH_METHODS.AUTHENTICATE, params);
|
|
3690
|
+
if (result.success && result.sessionId) {
|
|
3691
|
+
this.#sessionId = result.sessionId;
|
|
3692
|
+
}
|
|
3693
|
+
return result;
|
|
3694
|
+
}
|
|
3695
|
+
/**
|
|
3696
|
+
* Refresh authentication credentials.
|
|
3697
|
+
*
|
|
3698
|
+
* Use this to update credentials before they expire for long-lived connections.
|
|
3699
|
+
*
|
|
3700
|
+
* @param auth - New authentication credentials
|
|
3701
|
+
* @returns Updated principal information
|
|
3702
|
+
*/
|
|
3703
|
+
async refreshAuth(auth) {
|
|
3704
|
+
const params = {
|
|
3705
|
+
method: auth.method,
|
|
3706
|
+
credential: auth.token
|
|
3707
|
+
};
|
|
3708
|
+
return this.#connection.sendRequest(AUTH_METHODS.AUTH_REFRESH, params);
|
|
3709
|
+
}
|
|
3710
|
+
/**
|
|
3711
|
+
* Disconnect from the MAP system
|
|
3712
|
+
* @param reason - Optional reason for disconnecting
|
|
3713
|
+
* @returns Resume token that can be used to resume this session later
|
|
3714
|
+
*/
|
|
3715
|
+
async disconnect(reason) {
|
|
3716
|
+
if (!this.#connected) return void 0;
|
|
3717
|
+
let resumeToken;
|
|
3718
|
+
try {
|
|
3719
|
+
if (this.#agentId) {
|
|
3720
|
+
await this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_UNREGISTER, {
|
|
3721
|
+
agentId: this.#agentId,
|
|
3722
|
+
reason
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
const result = await this.#connection.sendRequest(
|
|
3726
|
+
CORE_METHODS.DISCONNECT,
|
|
3727
|
+
reason ? { reason } : void 0
|
|
3728
|
+
);
|
|
3729
|
+
resumeToken = result.resumeToken;
|
|
3730
|
+
} finally {
|
|
3731
|
+
for (const subscription of this.#subscriptions.values()) {
|
|
3732
|
+
subscription._close();
|
|
3733
|
+
}
|
|
3734
|
+
this.#subscriptions.clear();
|
|
3735
|
+
await this.#connection.close();
|
|
3736
|
+
this.#connected = false;
|
|
3737
|
+
}
|
|
3738
|
+
return resumeToken;
|
|
3739
|
+
}
|
|
3740
|
+
/**
|
|
3741
|
+
* Whether the agent is connected
|
|
3742
|
+
*/
|
|
3743
|
+
get isConnected() {
|
|
3744
|
+
return this.#connected && !this.#connection.isClosed;
|
|
3745
|
+
}
|
|
3746
|
+
/**
|
|
3747
|
+
* This agent's ID
|
|
3748
|
+
*/
|
|
3749
|
+
get agentId() {
|
|
3750
|
+
return this.#agentId;
|
|
3751
|
+
}
|
|
3752
|
+
/**
|
|
3753
|
+
* Current session ID
|
|
3754
|
+
*/
|
|
3755
|
+
get sessionId() {
|
|
3756
|
+
return this.#sessionId;
|
|
3757
|
+
}
|
|
3758
|
+
/**
|
|
3759
|
+
* Server capabilities
|
|
3760
|
+
*/
|
|
3761
|
+
get serverCapabilities() {
|
|
3762
|
+
return this.#serverCapabilities;
|
|
3763
|
+
}
|
|
3764
|
+
/**
|
|
3765
|
+
* Current agent state
|
|
3766
|
+
*/
|
|
3767
|
+
get state() {
|
|
3768
|
+
return this.#currentState;
|
|
3769
|
+
}
|
|
3770
|
+
/**
|
|
3771
|
+
* AbortSignal that triggers when the connection closes
|
|
3772
|
+
*/
|
|
3773
|
+
get signal() {
|
|
3774
|
+
return this.#connection.signal;
|
|
3775
|
+
}
|
|
3776
|
+
/**
|
|
3777
|
+
* Promise that resolves when the connection closes
|
|
3778
|
+
*/
|
|
3779
|
+
get closed() {
|
|
3780
|
+
return this.#connection.closed;
|
|
3781
|
+
}
|
|
3782
|
+
// ===========================================================================
|
|
3783
|
+
// Message Handling
|
|
3784
|
+
// ===========================================================================
|
|
3785
|
+
/**
|
|
3786
|
+
* Register a handler for incoming messages
|
|
3787
|
+
*/
|
|
3788
|
+
onMessage(handler) {
|
|
3789
|
+
this.#messageHandlers.add(handler);
|
|
3790
|
+
return this;
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Remove a message handler
|
|
3794
|
+
*/
|
|
3795
|
+
offMessage(handler) {
|
|
3796
|
+
this.#messageHandlers.delete(handler);
|
|
3797
|
+
return this;
|
|
3798
|
+
}
|
|
3799
|
+
// ===========================================================================
|
|
3800
|
+
// State Management
|
|
3801
|
+
// ===========================================================================
|
|
3802
|
+
/**
|
|
3803
|
+
* Update this agent's state
|
|
3804
|
+
*/
|
|
3805
|
+
async updateState(state) {
|
|
3806
|
+
if (!this.#agentId) {
|
|
3807
|
+
throw new Error("Agent not registered");
|
|
3808
|
+
}
|
|
3809
|
+
const result = await this.#connection.sendRequest(STATE_METHODS.AGENTS_UPDATE, {
|
|
3810
|
+
agentId: this.#agentId,
|
|
3811
|
+
state
|
|
3812
|
+
});
|
|
3813
|
+
this.#currentState = result.agent.state;
|
|
3814
|
+
return result.agent;
|
|
3815
|
+
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Update this agent's metadata
|
|
3818
|
+
*/
|
|
3819
|
+
async updateMetadata(metadata) {
|
|
3820
|
+
if (!this.#agentId) {
|
|
3821
|
+
throw new Error("Agent not registered");
|
|
3822
|
+
}
|
|
3823
|
+
const result = await this.#connection.sendRequest(STATE_METHODS.AGENTS_UPDATE, {
|
|
3824
|
+
agentId: this.#agentId,
|
|
3825
|
+
metadata
|
|
3826
|
+
});
|
|
3827
|
+
return result.agent;
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Mark this agent as busy
|
|
3831
|
+
*/
|
|
3832
|
+
async busy() {
|
|
3833
|
+
return this.updateState("busy");
|
|
3834
|
+
}
|
|
3835
|
+
/**
|
|
3836
|
+
* Mark this agent as idle
|
|
3837
|
+
*/
|
|
3838
|
+
async idle() {
|
|
3839
|
+
return this.updateState("idle");
|
|
3840
|
+
}
|
|
3841
|
+
/**
|
|
3842
|
+
* Mark this agent as done/stopped
|
|
3843
|
+
*/
|
|
3844
|
+
async done(result) {
|
|
3845
|
+
if (!this.#agentId) {
|
|
3846
|
+
throw new Error("Agent not registered");
|
|
3847
|
+
}
|
|
3848
|
+
await this.updateState("stopped");
|
|
3849
|
+
if (result) {
|
|
3850
|
+
await this.updateMetadata({
|
|
3851
|
+
exitCode: result.exitCode,
|
|
3852
|
+
exitReason: result.exitReason
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
// ===========================================================================
|
|
3857
|
+
// Child Agent Management
|
|
3858
|
+
// ===========================================================================
|
|
3859
|
+
/**
|
|
3860
|
+
* Spawn a child agent
|
|
3861
|
+
*/
|
|
3862
|
+
async spawn(options) {
|
|
3863
|
+
if (!this.#agentId) {
|
|
3864
|
+
throw new Error("Agent not registered");
|
|
3865
|
+
}
|
|
3866
|
+
const params = {
|
|
3867
|
+
...options,
|
|
3868
|
+
parent: this.#agentId
|
|
3869
|
+
};
|
|
3870
|
+
return this.#connection.sendRequest(LIFECYCLE_METHODS.AGENTS_SPAWN, params);
|
|
3871
|
+
}
|
|
3872
|
+
// ===========================================================================
|
|
3873
|
+
// Messaging
|
|
3874
|
+
// ===========================================================================
|
|
3875
|
+
/**
|
|
3876
|
+
* Send a message to an address
|
|
3877
|
+
*/
|
|
3878
|
+
async send(to, payload, meta) {
|
|
3879
|
+
const params = { to };
|
|
3880
|
+
if (payload !== void 0) params.payload = payload;
|
|
3881
|
+
if (meta) params.meta = meta;
|
|
3882
|
+
return this.#connection.sendRequest(CORE_METHODS.SEND, params);
|
|
3883
|
+
}
|
|
3884
|
+
/**
|
|
3885
|
+
* Send a message to the parent agent
|
|
3886
|
+
*/
|
|
3887
|
+
async sendToParent(payload, meta) {
|
|
3888
|
+
return this.send({ parent: true }, payload, {
|
|
3889
|
+
...meta,
|
|
3890
|
+
relationship: "child-to-parent"
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
* Send a message to child agents
|
|
3895
|
+
*/
|
|
3896
|
+
async sendToChildren(payload, meta) {
|
|
3897
|
+
return this.send({ children: true }, payload, {
|
|
3898
|
+
...meta,
|
|
3899
|
+
relationship: "parent-to-child"
|
|
3900
|
+
});
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* Send a message to a specific agent
|
|
3904
|
+
*/
|
|
3905
|
+
async sendToAgent(agentId, payload, meta) {
|
|
3906
|
+
return this.send({ agent: agentId }, payload, meta);
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3909
|
+
* Send a message to all agents in a scope
|
|
3910
|
+
*/
|
|
3911
|
+
async sendToScope(scopeId, payload, meta) {
|
|
3912
|
+
return this.send({ scope: scopeId }, payload, meta);
|
|
3913
|
+
}
|
|
3914
|
+
/**
|
|
3915
|
+
* Send a message to sibling agents
|
|
3916
|
+
*/
|
|
3917
|
+
async sendToSiblings(payload, meta) {
|
|
3918
|
+
return this.send({ siblings: true }, payload, {
|
|
3919
|
+
...meta,
|
|
3920
|
+
relationship: "peer"
|
|
3921
|
+
});
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Reply to a message (uses correlationId from original)
|
|
3925
|
+
*/
|
|
3926
|
+
async reply(originalMessage, payload, meta) {
|
|
3927
|
+
return this.send({ agent: originalMessage.from }, payload, {
|
|
3928
|
+
...meta,
|
|
3929
|
+
correlationId: originalMessage.meta?.correlationId ?? originalMessage.id,
|
|
3930
|
+
isResult: true
|
|
3931
|
+
});
|
|
3932
|
+
}
|
|
3933
|
+
// ===========================================================================
|
|
3934
|
+
// Scope Management
|
|
3935
|
+
// ===========================================================================
|
|
3936
|
+
/**
|
|
3937
|
+
* Create a new scope
|
|
3938
|
+
*/
|
|
3939
|
+
async createScope(options) {
|
|
3940
|
+
const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_CREATE, options);
|
|
3941
|
+
return result.scope;
|
|
3942
|
+
}
|
|
3943
|
+
/**
|
|
3944
|
+
* Join a scope
|
|
3945
|
+
*/
|
|
3946
|
+
async joinScope(scopeId) {
|
|
3947
|
+
if (!this.#agentId) {
|
|
3948
|
+
throw new Error("Agent not registered");
|
|
3949
|
+
}
|
|
3950
|
+
const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_JOIN, {
|
|
3951
|
+
scopeId,
|
|
3952
|
+
agentId: this.#agentId
|
|
3953
|
+
});
|
|
3954
|
+
this.#scopeMemberships.add(scopeId);
|
|
3955
|
+
return result;
|
|
3956
|
+
}
|
|
3957
|
+
/**
|
|
3958
|
+
* Leave a scope
|
|
3959
|
+
*/
|
|
3960
|
+
async leaveScope(scopeId) {
|
|
3961
|
+
if (!this.#agentId) {
|
|
3962
|
+
throw new Error("Agent not registered");
|
|
3963
|
+
}
|
|
3964
|
+
const result = await this.#connection.sendRequest(SCOPE_METHODS.SCOPES_LEAVE, {
|
|
3965
|
+
scopeId,
|
|
3966
|
+
agentId: this.#agentId
|
|
3967
|
+
});
|
|
3968
|
+
this.#scopeMemberships.delete(scopeId);
|
|
3969
|
+
return result;
|
|
3970
|
+
}
|
|
3971
|
+
// ===========================================================================
|
|
3972
|
+
// Subscriptions
|
|
3973
|
+
// ===========================================================================
|
|
3974
|
+
/**
|
|
3975
|
+
* Subscribe to events
|
|
3976
|
+
*/
|
|
3977
|
+
async subscribe(filter) {
|
|
3978
|
+
const params = {};
|
|
3979
|
+
if (filter) params.filter = filter;
|
|
3980
|
+
const result = await this.#connection.sendRequest(CORE_METHODS.SUBSCRIBE, params);
|
|
3981
|
+
const subscription = createSubscription(
|
|
3982
|
+
result.subscriptionId,
|
|
3983
|
+
() => this.unsubscribe(result.subscriptionId),
|
|
3984
|
+
{ filter }
|
|
3985
|
+
);
|
|
3986
|
+
this.#subscriptions.set(result.subscriptionId, subscription);
|
|
3987
|
+
return subscription;
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* Unsubscribe from events
|
|
3991
|
+
*/
|
|
3992
|
+
async unsubscribe(subscriptionId) {
|
|
3993
|
+
const subscription = this.#subscriptions.get(subscriptionId);
|
|
3994
|
+
if (subscription) {
|
|
3995
|
+
subscription._close();
|
|
3996
|
+
this.#subscriptions.delete(subscriptionId);
|
|
3997
|
+
}
|
|
3998
|
+
await this.#connection.sendRequest(CORE_METHODS.UNSUBSCRIBE, { subscriptionId });
|
|
3999
|
+
}
|
|
4000
|
+
// ===========================================================================
|
|
4001
|
+
// Reconnection
|
|
4002
|
+
// ===========================================================================
|
|
4003
|
+
/**
|
|
4004
|
+
* Current connection state
|
|
4005
|
+
*/
|
|
4006
|
+
get connectionState() {
|
|
4007
|
+
return this.#connection.state;
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* Whether the connection is currently reconnecting
|
|
4011
|
+
*/
|
|
4012
|
+
get isReconnecting() {
|
|
4013
|
+
return this.#isReconnecting;
|
|
4014
|
+
}
|
|
4015
|
+
/**
|
|
4016
|
+
* Register a handler for reconnection events.
|
|
4017
|
+
*
|
|
4018
|
+
* @param handler - Function called when reconnection events occur
|
|
4019
|
+
* @returns Unsubscribe function to remove the handler
|
|
4020
|
+
*/
|
|
4021
|
+
onReconnection(handler) {
|
|
4022
|
+
this.#reconnectionHandlers.add(handler);
|
|
4023
|
+
return () => this.#reconnectionHandlers.delete(handler);
|
|
4024
|
+
}
|
|
4025
|
+
/**
|
|
4026
|
+
* Register a handler for connection state changes.
|
|
4027
|
+
*
|
|
4028
|
+
* @param handler - Function called when state changes
|
|
4029
|
+
* @returns Unsubscribe function to remove the handler
|
|
4030
|
+
*/
|
|
4031
|
+
onStateChange(handler) {
|
|
4032
|
+
return this.#connection.onStateChange(handler);
|
|
4033
|
+
}
|
|
4034
|
+
// ===========================================================================
|
|
4035
|
+
// Internal
|
|
4036
|
+
// ===========================================================================
|
|
4037
|
+
/**
|
|
4038
|
+
* Handle incoming notifications
|
|
4039
|
+
*/
|
|
4040
|
+
async #handleNotification(method, params) {
|
|
4041
|
+
switch (method) {
|
|
4042
|
+
case NOTIFICATION_METHODS.EVENT: {
|
|
4043
|
+
const eventParams = params;
|
|
4044
|
+
const subscription = this.#subscriptions.get(eventParams.subscriptionId);
|
|
4045
|
+
if (subscription) {
|
|
4046
|
+
subscription._pushEvent(eventParams);
|
|
4047
|
+
}
|
|
4048
|
+
break;
|
|
4049
|
+
}
|
|
4050
|
+
case NOTIFICATION_METHODS.MESSAGE: {
|
|
4051
|
+
const messageParams = params;
|
|
4052
|
+
for (const handler of this.#messageHandlers) {
|
|
4053
|
+
try {
|
|
4054
|
+
await handler(messageParams.message);
|
|
4055
|
+
} catch (error) {
|
|
4056
|
+
console.error("MAP: Message handler error:", error);
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
break;
|
|
4060
|
+
}
|
|
4061
|
+
default:
|
|
4062
|
+
console.warn("MAP: Unknown notification:", method);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
/**
|
|
4066
|
+
* Emit a reconnection event to all registered handlers
|
|
4067
|
+
*/
|
|
4068
|
+
#emitReconnectionEvent(event) {
|
|
4069
|
+
for (const handler of this.#reconnectionHandlers) {
|
|
4070
|
+
try {
|
|
4071
|
+
handler(event);
|
|
4072
|
+
} catch (error) {
|
|
4073
|
+
console.error("MAP: Reconnection event handler error:", error);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* Handle disconnect when auto-reconnect is enabled
|
|
4079
|
+
*/
|
|
4080
|
+
async #handleDisconnect() {
|
|
4081
|
+
this.#isReconnecting = true;
|
|
4082
|
+
this.#connected = false;
|
|
4083
|
+
this.#emitReconnectionEvent({ type: "disconnected" });
|
|
4084
|
+
try {
|
|
4085
|
+
await this.#attemptReconnect();
|
|
4086
|
+
} catch (error) {
|
|
4087
|
+
this.#isReconnecting = false;
|
|
4088
|
+
this.#emitReconnectionEvent({
|
|
4089
|
+
type: "reconnectFailed",
|
|
4090
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Attempt to reconnect with retry logic
|
|
4096
|
+
*/
|
|
4097
|
+
async #attemptReconnect() {
|
|
4098
|
+
const options = this.#options.reconnection;
|
|
4099
|
+
const createStream = this.#options.createStream;
|
|
4100
|
+
const retryPolicy = {
|
|
4101
|
+
maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
|
|
4102
|
+
baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
|
|
4103
|
+
maxDelayMs: options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
|
|
4104
|
+
jitter: options.jitter ?? DEFAULT_RETRY_POLICY.jitter
|
|
4105
|
+
};
|
|
4106
|
+
const scopesToRestore = Array.from(this.#scopeMemberships);
|
|
4107
|
+
await withRetry(
|
|
4108
|
+
async () => {
|
|
4109
|
+
const newStream = await createStream();
|
|
4110
|
+
await this.#connection.reconnect(newStream);
|
|
4111
|
+
const result = await this.connect({
|
|
4112
|
+
agentId: this.#agentId ?? this.#lastConnectOptions?.agentId,
|
|
4113
|
+
auth: this.#lastConnectOptions?.auth
|
|
4114
|
+
});
|
|
4115
|
+
this.#agentId = result.agent.id;
|
|
4116
|
+
this.#sessionId = result.connection.sessionId;
|
|
4117
|
+
this.#serverCapabilities = result.connection.capabilities;
|
|
4118
|
+
this.#currentState = result.agent.state;
|
|
4119
|
+
},
|
|
4120
|
+
retryPolicy,
|
|
4121
|
+
{
|
|
4122
|
+
onRetry: (state) => {
|
|
4123
|
+
this.#emitReconnectionEvent({
|
|
4124
|
+
type: "reconnecting",
|
|
4125
|
+
attempt: state.attempt,
|
|
4126
|
+
delay: state.nextDelayMs,
|
|
4127
|
+
error: state.lastError
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
);
|
|
4132
|
+
this.#isReconnecting = false;
|
|
4133
|
+
this.#emitReconnectionEvent({ type: "reconnected" });
|
|
4134
|
+
if (options.restoreScopeMemberships !== false) {
|
|
4135
|
+
await this.#restoreScopeMemberships(scopesToRestore);
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
4139
|
+
* Restore scope memberships after reconnection
|
|
4140
|
+
*/
|
|
4141
|
+
async #restoreScopeMemberships(scopes) {
|
|
4142
|
+
this.#scopeMemberships.clear();
|
|
4143
|
+
for (const scopeId of scopes) {
|
|
4144
|
+
try {
|
|
4145
|
+
await this.joinScope(scopeId);
|
|
4146
|
+
} catch (error) {
|
|
4147
|
+
console.warn("MAP: Failed to restore scope membership:", scopeId, error);
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
};
|
|
4152
|
+
|
|
4153
|
+
// src/federation/envelope.ts
|
|
3029
4154
|
function createFederationEnvelope(payload, sourceSystem, targetSystem, options) {
|
|
3030
4155
|
return {
|
|
3031
4156
|
payload,
|
|
@@ -3873,7 +4998,7 @@ var EventSchema = zod.z.object({
|
|
|
3873
4998
|
var SubscriptionFilterSchema = zod.z.object({
|
|
3874
4999
|
eventTypes: zod.z.array(EventTypeSchema).optional(),
|
|
3875
5000
|
scopes: zod.z.array(ScopeIdSchema).optional(),
|
|
3876
|
-
|
|
5001
|
+
fromAgents: zod.z.array(AgentIdSchema).optional(),
|
|
3877
5002
|
includeChildren: zod.z.boolean().optional(),
|
|
3878
5003
|
_meta: MetaSchema
|
|
3879
5004
|
}).strict();
|
|
@@ -4335,283 +5460,945 @@ function canPerformAction(context, action) {
|
|
|
4335
5460
|
}
|
|
4336
5461
|
}
|
|
4337
5462
|
}
|
|
4338
|
-
const requiredCaps = getRequiredCapabilities(action.method);
|
|
4339
|
-
for (const cap of requiredCaps) {
|
|
4340
|
-
if (!hasCapability(context.participant.capabilities, cap)) {
|
|
4341
|
-
return {
|
|
4342
|
-
allowed: false,
|
|
4343
|
-
reason: `Missing required capability: ${cap}`,
|
|
4344
|
-
layer: 2
|
|
4345
|
-
};
|
|
4346
|
-
}
|
|
5463
|
+
const requiredCaps = getRequiredCapabilities(action.method);
|
|
5464
|
+
for (const cap of requiredCaps) {
|
|
5465
|
+
if (!hasCapability(context.participant.capabilities, cap)) {
|
|
5466
|
+
return {
|
|
5467
|
+
allowed: false,
|
|
5468
|
+
reason: `Missing required capability: ${cap}`,
|
|
5469
|
+
layer: 2
|
|
5470
|
+
};
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
return { allowed: true };
|
|
5474
|
+
}
|
|
5475
|
+
function filterVisibleAgents(agents, context) {
|
|
5476
|
+
const ownedAgentIds = context.ownedAgentIds ?? [];
|
|
5477
|
+
return agents.filter((agent) => {
|
|
5478
|
+
if (!isAgentExposed(context.system.exposure, agent.id)) {
|
|
5479
|
+
return false;
|
|
5480
|
+
}
|
|
5481
|
+
if (!canSeeAgent(agent, context.participant, ownedAgentIds)) {
|
|
5482
|
+
return false;
|
|
5483
|
+
}
|
|
5484
|
+
return true;
|
|
5485
|
+
});
|
|
5486
|
+
}
|
|
5487
|
+
function filterVisibleScopes(scopes, context) {
|
|
5488
|
+
const scopeMembership = context.scopeMembership ?? /* @__PURE__ */ new Map();
|
|
5489
|
+
return scopes.filter((scope) => {
|
|
5490
|
+
if (!isScopeExposed(context.system.exposure, scope.id)) {
|
|
5491
|
+
return false;
|
|
5492
|
+
}
|
|
5493
|
+
const memberAgentIds = scopeMembership.get(scope.id) ?? [];
|
|
5494
|
+
if (!canSeeScope(scope, context.participant, memberAgentIds)) {
|
|
5495
|
+
return false;
|
|
5496
|
+
}
|
|
5497
|
+
return true;
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
5500
|
+
function filterVisibleEvents(events, context) {
|
|
5501
|
+
return events.filter((event) => {
|
|
5502
|
+
if (!isEventTypeExposed(context.system.exposure, event.type)) {
|
|
5503
|
+
return false;
|
|
5504
|
+
}
|
|
5505
|
+
return true;
|
|
5506
|
+
});
|
|
5507
|
+
}
|
|
5508
|
+
function matchesPatterns(value, patterns) {
|
|
5509
|
+
return patterns.some((pattern) => matchGlob(value, pattern));
|
|
5510
|
+
}
|
|
5511
|
+
function matchGlob(value, pattern) {
|
|
5512
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
5513
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
5514
|
+
return regex.test(value);
|
|
5515
|
+
}
|
|
5516
|
+
function deepClone(obj) {
|
|
5517
|
+
return JSON.parse(JSON.stringify(obj));
|
|
5518
|
+
}
|
|
5519
|
+
function deepMergePermissions(base, override) {
|
|
5520
|
+
const result = { ...base };
|
|
5521
|
+
if (override.canSee) {
|
|
5522
|
+
result.canSee = { ...base.canSee, ...override.canSee };
|
|
5523
|
+
}
|
|
5524
|
+
if (override.canMessage) {
|
|
5525
|
+
result.canMessage = { ...base.canMessage, ...override.canMessage };
|
|
5526
|
+
}
|
|
5527
|
+
if (override.acceptsFrom) {
|
|
5528
|
+
result.acceptsFrom = { ...base.acceptsFrom, ...override.acceptsFrom };
|
|
5529
|
+
}
|
|
5530
|
+
return result;
|
|
5531
|
+
}
|
|
5532
|
+
function mapVisibilityToRule(visibility) {
|
|
5533
|
+
switch (visibility) {
|
|
5534
|
+
case "public":
|
|
5535
|
+
return "all";
|
|
5536
|
+
case "parent-only":
|
|
5537
|
+
return "hierarchy";
|
|
5538
|
+
case "scope":
|
|
5539
|
+
return "scoped";
|
|
5540
|
+
case "system":
|
|
5541
|
+
return "direct";
|
|
5542
|
+
default:
|
|
5543
|
+
return "all";
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
var DEFAULT_AGENT_PERMISSION_CONFIG = {
|
|
5547
|
+
defaultPermissions: {
|
|
5548
|
+
canSee: {
|
|
5549
|
+
agents: "all",
|
|
5550
|
+
scopes: "all",
|
|
5551
|
+
structure: "full"
|
|
5552
|
+
},
|
|
5553
|
+
canMessage: {
|
|
5554
|
+
agents: "all",
|
|
5555
|
+
scopes: "all"
|
|
5556
|
+
},
|
|
5557
|
+
acceptsFrom: {
|
|
5558
|
+
agents: "all",
|
|
5559
|
+
clients: "all",
|
|
5560
|
+
systems: "all"
|
|
5561
|
+
}
|
|
5562
|
+
},
|
|
5563
|
+
rolePermissions: {}
|
|
5564
|
+
};
|
|
5565
|
+
function resolveAgentPermissions(agent, config = DEFAULT_AGENT_PERMISSION_CONFIG) {
|
|
5566
|
+
let permissions = deepClone(config.defaultPermissions);
|
|
5567
|
+
if (agent.role && config.rolePermissions[agent.role]) {
|
|
5568
|
+
permissions = deepMergePermissions(permissions, config.rolePermissions[agent.role]);
|
|
5569
|
+
}
|
|
5570
|
+
if (agent.permissionOverrides) {
|
|
5571
|
+
permissions = deepMergePermissions(permissions, agent.permissionOverrides);
|
|
5572
|
+
}
|
|
5573
|
+
if (agent.visibility && !agent.permissionOverrides?.canSee?.agents) {
|
|
5574
|
+
permissions.canSee = permissions.canSee ?? {};
|
|
5575
|
+
permissions.canSee.agents = mapVisibilityToRule(agent.visibility);
|
|
5576
|
+
}
|
|
5577
|
+
return permissions;
|
|
5578
|
+
}
|
|
5579
|
+
function checkAgentAcceptance(rule, context) {
|
|
5580
|
+
if (!rule || rule === "all") return true;
|
|
5581
|
+
if (rule === "hierarchy") {
|
|
5582
|
+
return context.isParent === true || context.isChild === true || context.isAncestor === true || context.isDescendant === true;
|
|
5583
|
+
}
|
|
5584
|
+
if (rule === "scoped") {
|
|
5585
|
+
return (context.sharedScopes?.length ?? 0) > 0;
|
|
5586
|
+
}
|
|
5587
|
+
if (typeof rule === "object" && "include" in rule) {
|
|
5588
|
+
return context.senderAgentId !== void 0 && rule.include.includes(context.senderAgentId);
|
|
5589
|
+
}
|
|
5590
|
+
return false;
|
|
5591
|
+
}
|
|
5592
|
+
function checkClientAcceptance(rule, senderId) {
|
|
5593
|
+
if (!rule || rule === "all") return true;
|
|
5594
|
+
if (rule === "none") return false;
|
|
5595
|
+
if (typeof rule === "object" && "include" in rule) {
|
|
5596
|
+
return rule.include.includes(senderId);
|
|
5597
|
+
}
|
|
5598
|
+
return false;
|
|
5599
|
+
}
|
|
5600
|
+
function checkSystemAcceptance(rule, senderSystemId) {
|
|
5601
|
+
if (!rule || rule === "all") return true;
|
|
5602
|
+
if (rule === "none") return false;
|
|
5603
|
+
if (typeof rule === "object" && "include" in rule) {
|
|
5604
|
+
return senderSystemId !== void 0 && rule.include.includes(senderSystemId);
|
|
5605
|
+
}
|
|
5606
|
+
return false;
|
|
5607
|
+
}
|
|
5608
|
+
function canAgentAcceptMessage(targetAgent, context, config = DEFAULT_AGENT_PERMISSION_CONFIG) {
|
|
5609
|
+
const permissions = resolveAgentPermissions(targetAgent, config);
|
|
5610
|
+
const acceptsFrom = permissions.acceptsFrom;
|
|
5611
|
+
if (!acceptsFrom) return true;
|
|
5612
|
+
switch (context.senderType) {
|
|
5613
|
+
case "agent":
|
|
5614
|
+
return checkAgentAcceptance(acceptsFrom.agents, context);
|
|
5615
|
+
case "client":
|
|
5616
|
+
return checkClientAcceptance(acceptsFrom.clients, context.senderId);
|
|
5617
|
+
case "system":
|
|
5618
|
+
case "gateway":
|
|
5619
|
+
return checkSystemAcceptance(acceptsFrom.systems, context.senderSystemId);
|
|
5620
|
+
default:
|
|
5621
|
+
return false;
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
function canAgentSeeAgent(viewerAgent, targetAgentId, context, config = DEFAULT_AGENT_PERMISSION_CONFIG) {
|
|
5625
|
+
const permissions = resolveAgentPermissions(viewerAgent, config);
|
|
5626
|
+
const canSee = permissions.canSee?.agents;
|
|
5627
|
+
if (!canSee || canSee === "all") return true;
|
|
5628
|
+
if (canSee === "hierarchy") {
|
|
5629
|
+
return context.isParent === true || context.isChild === true || context.isAncestor === true || context.isDescendant === true;
|
|
5630
|
+
}
|
|
5631
|
+
if (canSee === "scoped") {
|
|
5632
|
+
return (context.sharedScopes?.length ?? 0) > 0;
|
|
5633
|
+
}
|
|
5634
|
+
if (canSee === "direct") {
|
|
5635
|
+
return false;
|
|
5636
|
+
}
|
|
5637
|
+
if (typeof canSee === "object" && "include" in canSee) {
|
|
5638
|
+
return canSee.include.includes(targetAgentId);
|
|
5639
|
+
}
|
|
5640
|
+
return false;
|
|
5641
|
+
}
|
|
5642
|
+
function canAgentMessageAgent(senderAgent, targetAgentId, context, config = DEFAULT_AGENT_PERMISSION_CONFIG) {
|
|
5643
|
+
const permissions = resolveAgentPermissions(senderAgent, config);
|
|
5644
|
+
const canMessage = permissions.canMessage?.agents;
|
|
5645
|
+
if (!canMessage || canMessage === "all") return true;
|
|
5646
|
+
if (canMessage === "hierarchy") {
|
|
5647
|
+
return context.isParent === true || context.isChild === true || context.isAncestor === true || context.isDescendant === true;
|
|
5648
|
+
}
|
|
5649
|
+
if (canMessage === "scoped") {
|
|
5650
|
+
return (context.sharedScopes?.length ?? 0) > 0;
|
|
5651
|
+
}
|
|
5652
|
+
if (typeof canMessage === "object" && "include" in canMessage) {
|
|
5653
|
+
return canMessage.include.includes(targetAgentId);
|
|
5654
|
+
}
|
|
5655
|
+
return false;
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5658
|
+
// src/server/messages/address.ts
|
|
5659
|
+
var SEPARATOR = ":";
|
|
5660
|
+
var VALID_ADDRESS_TYPES = ["agent", "scope"];
|
|
5661
|
+
var InvalidAddressError = class extends Error {
|
|
5662
|
+
constructor(address, reason) {
|
|
5663
|
+
super(`Invalid address "${address}": ${reason}`);
|
|
5664
|
+
this.name = "InvalidAddressError";
|
|
5665
|
+
}
|
|
5666
|
+
};
|
|
5667
|
+
function formatAddress(type, id) {
|
|
5668
|
+
if (!id) {
|
|
5669
|
+
throw new InvalidAddressError("", "ID cannot be empty");
|
|
5670
|
+
}
|
|
5671
|
+
if (id.includes(SEPARATOR)) {
|
|
5672
|
+
throw new InvalidAddressError(id, "ID cannot contain colon separator");
|
|
5673
|
+
}
|
|
5674
|
+
return `${type}${SEPARATOR}${id}`;
|
|
5675
|
+
}
|
|
5676
|
+
function parseAddress(address) {
|
|
5677
|
+
const separatorIndex = address.indexOf(SEPARATOR);
|
|
5678
|
+
if (separatorIndex === -1) {
|
|
5679
|
+
throw new InvalidAddressError(address, "missing type prefix");
|
|
5680
|
+
}
|
|
5681
|
+
const type = address.slice(0, separatorIndex);
|
|
5682
|
+
const id = address.slice(separatorIndex + 1);
|
|
5683
|
+
if (!VALID_ADDRESS_TYPES.includes(type)) {
|
|
5684
|
+
throw new InvalidAddressError(address, `invalid type "${type}", must be agent or scope`);
|
|
5685
|
+
}
|
|
5686
|
+
if (!id) {
|
|
5687
|
+
throw new InvalidAddressError(address, "ID cannot be empty");
|
|
4347
5688
|
}
|
|
4348
|
-
return {
|
|
5689
|
+
return {
|
|
5690
|
+
type,
|
|
5691
|
+
id
|
|
5692
|
+
};
|
|
4349
5693
|
}
|
|
4350
|
-
function
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
if (!isAgentExposed(context.system.exposure, agent.id)) {
|
|
4354
|
-
return false;
|
|
4355
|
-
}
|
|
4356
|
-
if (!canSeeAgent(agent, context.participant, ownedAgentIds)) {
|
|
4357
|
-
return false;
|
|
4358
|
-
}
|
|
5694
|
+
function isAddress(address) {
|
|
5695
|
+
try {
|
|
5696
|
+
parseAddress(address);
|
|
4359
5697
|
return true;
|
|
4360
|
-
}
|
|
5698
|
+
} catch {
|
|
5699
|
+
return false;
|
|
5700
|
+
}
|
|
4361
5701
|
}
|
|
4362
|
-
function
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
if (!canSeeScope(scope, context.participant, memberAgentIds)) {
|
|
4370
|
-
return false;
|
|
4371
|
-
}
|
|
4372
|
-
return true;
|
|
4373
|
-
});
|
|
5702
|
+
function isAgentAddress(address) {
|
|
5703
|
+
try {
|
|
5704
|
+
const parsed = parseAddress(address);
|
|
5705
|
+
return parsed.type === "agent";
|
|
5706
|
+
} catch {
|
|
5707
|
+
return false;
|
|
5708
|
+
}
|
|
4374
5709
|
}
|
|
4375
|
-
function
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
return
|
|
4381
|
-
}
|
|
5710
|
+
function isScopeAddress(address) {
|
|
5711
|
+
try {
|
|
5712
|
+
const parsed = parseAddress(address);
|
|
5713
|
+
return parsed.type === "scope";
|
|
5714
|
+
} catch {
|
|
5715
|
+
return false;
|
|
5716
|
+
}
|
|
4382
5717
|
}
|
|
4383
|
-
function
|
|
4384
|
-
|
|
5718
|
+
function extractId(address) {
|
|
5719
|
+
try {
|
|
5720
|
+
const parsed = parseAddress(address);
|
|
5721
|
+
return parsed.id;
|
|
5722
|
+
} catch {
|
|
5723
|
+
return address;
|
|
5724
|
+
}
|
|
4385
5725
|
}
|
|
4386
|
-
function
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
5726
|
+
function extractType(address) {
|
|
5727
|
+
try {
|
|
5728
|
+
const parsed = parseAddress(address);
|
|
5729
|
+
return parsed.type;
|
|
5730
|
+
} catch {
|
|
5731
|
+
return void 0;
|
|
5732
|
+
}
|
|
4390
5733
|
}
|
|
4391
|
-
function
|
|
4392
|
-
return
|
|
5734
|
+
function toAgent(agentId) {
|
|
5735
|
+
return formatAddress("agent", agentId);
|
|
4393
5736
|
}
|
|
4394
|
-
function
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
5737
|
+
function toScope(scopeId) {
|
|
5738
|
+
return formatAddress("scope", scopeId);
|
|
5739
|
+
}
|
|
5740
|
+
var MAPMeshPeer = class _MAPMeshPeer {
|
|
5741
|
+
#meshPeer;
|
|
5742
|
+
#config;
|
|
5743
|
+
#gitService;
|
|
5744
|
+
constructor(meshPeer, config) {
|
|
5745
|
+
this.#meshPeer = meshPeer;
|
|
5746
|
+
this.#config = config;
|
|
5747
|
+
this.#gitService = meshPeer.git ? new GitSyncServiceImpl(meshPeer.git, config.git?.repoPath ?? process.cwd()) : null;
|
|
4398
5748
|
}
|
|
4399
|
-
|
|
4400
|
-
|
|
5749
|
+
// ===========================================================================
|
|
5750
|
+
// Static Factory
|
|
5751
|
+
// ===========================================================================
|
|
5752
|
+
/**
|
|
5753
|
+
* Create a new MAPMeshPeer.
|
|
5754
|
+
*
|
|
5755
|
+
* Requires `agentic-mesh` to be installed as a peer dependency.
|
|
5756
|
+
*
|
|
5757
|
+
* @param config - Peer configuration
|
|
5758
|
+
* @returns Promise resolving to the created peer (not yet started)
|
|
5759
|
+
*/
|
|
5760
|
+
static async create(config) {
|
|
5761
|
+
const meshPeer = agenticMesh.createMeshPeer({
|
|
5762
|
+
peerId: config.peerId,
|
|
5763
|
+
peerName: config.peerName,
|
|
5764
|
+
transport: config.transport,
|
|
5765
|
+
git: config.git,
|
|
5766
|
+
peers: config.peers,
|
|
5767
|
+
map: config.map
|
|
5768
|
+
});
|
|
5769
|
+
return new _MAPMeshPeer(meshPeer, config);
|
|
4401
5770
|
}
|
|
4402
|
-
|
|
4403
|
-
|
|
5771
|
+
// ===========================================================================
|
|
5772
|
+
// Properties
|
|
5773
|
+
// ===========================================================================
|
|
5774
|
+
/** Unique peer identifier */
|
|
5775
|
+
get peerId() {
|
|
5776
|
+
return this.#meshPeer.peerId;
|
|
4404
5777
|
}
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
5778
|
+
/** Display name for this peer */
|
|
5779
|
+
get peerName() {
|
|
5780
|
+
return this.#meshPeer.peerName;
|
|
5781
|
+
}
|
|
5782
|
+
/** Whether the peer is currently running */
|
|
5783
|
+
get isRunning() {
|
|
5784
|
+
return this.#meshPeer.isRunning;
|
|
5785
|
+
}
|
|
5786
|
+
/** List of connected peer IDs */
|
|
5787
|
+
get connectedPeers() {
|
|
5788
|
+
return this.#meshPeer.connectedPeers;
|
|
5789
|
+
}
|
|
5790
|
+
/** Git sync service (null if git not enabled) */
|
|
5791
|
+
get git() {
|
|
5792
|
+
return this.#gitService;
|
|
5793
|
+
}
|
|
5794
|
+
// ===========================================================================
|
|
5795
|
+
// Lifecycle
|
|
5796
|
+
// ===========================================================================
|
|
5797
|
+
/**
|
|
5798
|
+
* Start the mesh peer.
|
|
5799
|
+
*
|
|
5800
|
+
* This starts the transport, MAP server, and git service (if enabled),
|
|
5801
|
+
* then connects to any initial peers specified in the config.
|
|
5802
|
+
*/
|
|
5803
|
+
async start() {
|
|
5804
|
+
await this.#meshPeer.start(this.#config.transport);
|
|
5805
|
+
}
|
|
5806
|
+
/**
|
|
5807
|
+
* Stop the mesh peer.
|
|
5808
|
+
*
|
|
5809
|
+
* This disconnects from all peers, unregisters all agents,
|
|
5810
|
+
* stops the git service, MAP server, and transport.
|
|
5811
|
+
*/
|
|
5812
|
+
async stop() {
|
|
5813
|
+
await this.#meshPeer.stop();
|
|
5814
|
+
}
|
|
5815
|
+
// ===========================================================================
|
|
5816
|
+
// Peer Connections
|
|
5817
|
+
// ===========================================================================
|
|
5818
|
+
/**
|
|
5819
|
+
* Connect to a remote peer.
|
|
5820
|
+
*
|
|
5821
|
+
* After connecting, agents on both peers will be discoverable
|
|
5822
|
+
* and messages can be routed between them.
|
|
5823
|
+
*
|
|
5824
|
+
* @param endpoint - Peer endpoint to connect to
|
|
5825
|
+
*/
|
|
5826
|
+
async connectToPeer(endpoint) {
|
|
5827
|
+
await this.#meshPeer.connectToPeer(endpoint);
|
|
5828
|
+
}
|
|
5829
|
+
/**
|
|
5830
|
+
* Disconnect from a peer.
|
|
5831
|
+
*
|
|
5832
|
+
* @param peerId - ID of peer to disconnect from
|
|
5833
|
+
* @param reason - Optional reason for disconnecting
|
|
5834
|
+
*/
|
|
5835
|
+
async disconnectFromPeer(peerId, reason) {
|
|
5836
|
+
await this.#meshPeer.disconnectFromPeer(peerId, reason);
|
|
5837
|
+
}
|
|
5838
|
+
/**
|
|
5839
|
+
* Check if connected to a specific peer.
|
|
5840
|
+
*
|
|
5841
|
+
* @param peerId - Peer ID to check
|
|
5842
|
+
* @returns true if connected
|
|
5843
|
+
*/
|
|
5844
|
+
isConnectedTo(peerId) {
|
|
5845
|
+
return this.connectedPeers.includes(peerId);
|
|
5846
|
+
}
|
|
5847
|
+
// ===========================================================================
|
|
5848
|
+
// Agent Management
|
|
5849
|
+
// ===========================================================================
|
|
5850
|
+
/**
|
|
5851
|
+
* Create and register a local agent on this peer's MapServer.
|
|
5852
|
+
*
|
|
5853
|
+
* @param config - Agent configuration
|
|
5854
|
+
* @returns The created agent
|
|
5855
|
+
*/
|
|
5856
|
+
async createAgent(config) {
|
|
5857
|
+
const agentConn = await this.#meshPeer.createAgent(config);
|
|
5858
|
+
return new LocalAgentImpl(agentConn);
|
|
5859
|
+
}
|
|
5860
|
+
/**
|
|
5861
|
+
* Get a local agent by ID.
|
|
5862
|
+
*
|
|
5863
|
+
* @param agentId - Agent ID to look up
|
|
5864
|
+
* @returns The agent, or undefined if not found
|
|
5865
|
+
*/
|
|
5866
|
+
getAgent(agentId) {
|
|
5867
|
+
const conn = this.#meshPeer.getAgentConnection(agentId);
|
|
5868
|
+
return conn ? new LocalAgentImpl(conn) : void 0;
|
|
5869
|
+
}
|
|
5870
|
+
/**
|
|
5871
|
+
* Get all local agents on this peer.
|
|
5872
|
+
*/
|
|
5873
|
+
getLocalAgents() {
|
|
5874
|
+
return this.#meshPeer.getLocalAgents();
|
|
5875
|
+
}
|
|
5876
|
+
/**
|
|
5877
|
+
* Get all known agents (local and discovered from connected peers).
|
|
5878
|
+
*/
|
|
5879
|
+
getAllAgents() {
|
|
5880
|
+
return this.#meshPeer.getAllAgents();
|
|
5881
|
+
}
|
|
5882
|
+
// ===========================================================================
|
|
5883
|
+
// Scope Management
|
|
5884
|
+
// ===========================================================================
|
|
5885
|
+
/**
|
|
5886
|
+
* Create a scope on this peer's MapServer.
|
|
5887
|
+
*
|
|
5888
|
+
* @param config - Scope configuration
|
|
5889
|
+
* @returns The created scope
|
|
5890
|
+
*/
|
|
5891
|
+
createScope(config) {
|
|
5892
|
+
return this.#meshPeer.createScope(config);
|
|
5893
|
+
}
|
|
5894
|
+
/**
|
|
5895
|
+
* Get a scope by ID.
|
|
5896
|
+
*
|
|
5897
|
+
* @param scopeId - Scope ID to look up
|
|
5898
|
+
* @returns The scope, or undefined if not found
|
|
5899
|
+
*/
|
|
5900
|
+
getScope(scopeId) {
|
|
5901
|
+
return this.#meshPeer.getScope(scopeId);
|
|
5902
|
+
}
|
|
5903
|
+
/**
|
|
5904
|
+
* List all scopes on this peer.
|
|
5905
|
+
*/
|
|
5906
|
+
listScopes() {
|
|
5907
|
+
return this.#meshPeer.listScopes();
|
|
5908
|
+
}
|
|
5909
|
+
// ===========================================================================
|
|
5910
|
+
// Messaging
|
|
5911
|
+
// ===========================================================================
|
|
5912
|
+
/**
|
|
5913
|
+
* Send a message from an agent to an address.
|
|
5914
|
+
*
|
|
5915
|
+
* Messages are routed to local agents or forwarded to connected peers.
|
|
5916
|
+
*
|
|
5917
|
+
* @param from - Sending agent ID
|
|
5918
|
+
* @param to - Destination address
|
|
5919
|
+
* @param payload - Message payload
|
|
5920
|
+
* @param meta - Optional message metadata
|
|
5921
|
+
* @returns Send result with delivery status
|
|
5922
|
+
*/
|
|
5923
|
+
async send(from, to, payload, meta) {
|
|
5924
|
+
return this.#meshPeer.send(from, to, payload, meta);
|
|
5925
|
+
}
|
|
5926
|
+
// ===========================================================================
|
|
5927
|
+
// Events
|
|
5928
|
+
// ===========================================================================
|
|
5929
|
+
/**
|
|
5930
|
+
* Subscribe to events from this peer's MapServer.
|
|
5931
|
+
*
|
|
5932
|
+
* @param filter - Optional filter for event types
|
|
5933
|
+
* @returns Event subscription
|
|
5934
|
+
*/
|
|
5935
|
+
subscribe(filter) {
|
|
5936
|
+
return this.#meshPeer.subscribe(this.peerId, filter);
|
|
5937
|
+
}
|
|
5938
|
+
/**
|
|
5939
|
+
* Register a handler for peer connection events.
|
|
5940
|
+
*
|
|
5941
|
+
* @param handler - Function called when a peer connects
|
|
5942
|
+
* @returns Unsubscribe function
|
|
5943
|
+
*/
|
|
5944
|
+
onPeerConnected(handler) {
|
|
5945
|
+
this.#meshPeer.on("peer:connected", handler);
|
|
5946
|
+
return () => this.#meshPeer.off("peer:connected", handler);
|
|
5947
|
+
}
|
|
5948
|
+
/**
|
|
5949
|
+
* Register a handler for peer disconnection events.
|
|
5950
|
+
*
|
|
5951
|
+
* @param handler - Function called when a peer disconnects
|
|
5952
|
+
* @returns Unsubscribe function
|
|
5953
|
+
*/
|
|
5954
|
+
onPeerDisconnected(handler) {
|
|
5955
|
+
this.#meshPeer.on("peer:disconnected", handler);
|
|
5956
|
+
return () => this.#meshPeer.off("peer:disconnected", handler);
|
|
5957
|
+
}
|
|
5958
|
+
/**
|
|
5959
|
+
* Register a handler for agent registration events.
|
|
5960
|
+
*
|
|
5961
|
+
* @param handler - Function called when an agent registers
|
|
5962
|
+
* @returns Unsubscribe function
|
|
5963
|
+
*/
|
|
5964
|
+
onAgentRegistered(handler) {
|
|
5965
|
+
this.#meshPeer.on("agent:registered", handler);
|
|
5966
|
+
return () => this.#meshPeer.off("agent:registered", handler);
|
|
5967
|
+
}
|
|
5968
|
+
/**
|
|
5969
|
+
* Register a handler for agent unregistration events.
|
|
5970
|
+
*
|
|
5971
|
+
* @param handler - Function called when an agent unregisters
|
|
5972
|
+
* @returns Unsubscribe function
|
|
5973
|
+
*/
|
|
5974
|
+
onAgentUnregistered(handler) {
|
|
5975
|
+
this.#meshPeer.on("agent:unregistered", handler);
|
|
5976
|
+
return () => this.#meshPeer.off("agent:unregistered", handler);
|
|
5977
|
+
}
|
|
5978
|
+
/**
|
|
5979
|
+
* Register a handler for error events.
|
|
5980
|
+
*
|
|
5981
|
+
* @param handler - Function called when an error occurs
|
|
5982
|
+
* @returns Unsubscribe function
|
|
5983
|
+
*/
|
|
5984
|
+
onError(handler) {
|
|
5985
|
+
this.#meshPeer.on("error", handler);
|
|
5986
|
+
return () => this.#meshPeer.off("error", handler);
|
|
4419
5987
|
}
|
|
4420
|
-
}
|
|
4421
|
-
var DEFAULT_AGENT_PERMISSION_CONFIG = {
|
|
4422
|
-
defaultPermissions: {
|
|
4423
|
-
canSee: {
|
|
4424
|
-
agents: "all",
|
|
4425
|
-
scopes: "all",
|
|
4426
|
-
structure: "full"
|
|
4427
|
-
},
|
|
4428
|
-
canMessage: {
|
|
4429
|
-
agents: "all",
|
|
4430
|
-
scopes: "all"
|
|
4431
|
-
},
|
|
4432
|
-
acceptsFrom: {
|
|
4433
|
-
agents: "all",
|
|
4434
|
-
clients: "all",
|
|
4435
|
-
systems: "all"
|
|
4436
|
-
}
|
|
4437
|
-
},
|
|
4438
|
-
rolePermissions: {}
|
|
4439
5988
|
};
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
5989
|
+
var LocalAgentImpl = class {
|
|
5990
|
+
#conn;
|
|
5991
|
+
constructor(conn) {
|
|
5992
|
+
this.#conn = conn;
|
|
5993
|
+
}
|
|
5994
|
+
get agentId() {
|
|
5995
|
+
return this.#conn.agentId;
|
|
5996
|
+
}
|
|
5997
|
+
get name() {
|
|
5998
|
+
return this.#conn.name;
|
|
5999
|
+
}
|
|
6000
|
+
get role() {
|
|
6001
|
+
return this.#conn.role;
|
|
6002
|
+
}
|
|
6003
|
+
get state() {
|
|
6004
|
+
return this.#conn.state;
|
|
6005
|
+
}
|
|
6006
|
+
async busy() {
|
|
6007
|
+
await this.#conn.updateState("busy");
|
|
6008
|
+
}
|
|
6009
|
+
async idle() {
|
|
6010
|
+
await this.#conn.updateState("idle");
|
|
6011
|
+
}
|
|
6012
|
+
async updateState(state) {
|
|
6013
|
+
await this.#conn.updateState(state);
|
|
4444
6014
|
}
|
|
4445
|
-
|
|
4446
|
-
|
|
6015
|
+
async updateMetadata(metadata) {
|
|
6016
|
+
await this.#conn.updateMetadata(metadata);
|
|
4447
6017
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
permissions.canSee.agents = mapVisibilityToRule(agent.visibility);
|
|
6018
|
+
async send(to, payload, meta) {
|
|
6019
|
+
return this.#conn.send(to, payload, meta);
|
|
4451
6020
|
}
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
6021
|
+
onMessage(handler) {
|
|
6022
|
+
const wrappedHandler = (message) => handler(message);
|
|
6023
|
+
this.#conn.on("message", wrappedHandler);
|
|
6024
|
+
return () => {
|
|
6025
|
+
this.#conn.off("message", wrappedHandler);
|
|
6026
|
+
};
|
|
4458
6027
|
}
|
|
4459
|
-
|
|
4460
|
-
|
|
6028
|
+
async unregister(reason) {
|
|
6029
|
+
await this.#conn.unregister(reason);
|
|
4461
6030
|
}
|
|
4462
|
-
|
|
4463
|
-
|
|
6031
|
+
};
|
|
6032
|
+
var GitSyncServiceImpl = class {
|
|
6033
|
+
#service;
|
|
6034
|
+
#defaultRepoPath;
|
|
6035
|
+
#defaultClient = null;
|
|
6036
|
+
constructor(service, defaultRepoPath) {
|
|
6037
|
+
this.#service = service;
|
|
6038
|
+
this.#defaultRepoPath = defaultRepoPath;
|
|
6039
|
+
}
|
|
6040
|
+
get isRunning() {
|
|
6041
|
+
return true;
|
|
4464
6042
|
}
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
function checkClientAcceptance(rule, senderId) {
|
|
4468
|
-
if (!rule || rule === "all") return true;
|
|
4469
|
-
if (rule === "none") return false;
|
|
4470
|
-
if (typeof rule === "object" && "include" in rule) {
|
|
4471
|
-
return rule.include.includes(senderId);
|
|
6043
|
+
get httpPort() {
|
|
6044
|
+
return this.#service.httpPort;
|
|
4472
6045
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
function checkSystemAcceptance(rule, senderSystemId) {
|
|
4476
|
-
if (!rule || rule === "all") return true;
|
|
4477
|
-
if (rule === "none") return false;
|
|
4478
|
-
if (typeof rule === "object" && "include" in rule) {
|
|
4479
|
-
return senderSystemId !== void 0 && rule.include.includes(senderSystemId);
|
|
6046
|
+
createSyncClient(repoPath) {
|
|
6047
|
+
return this.#service.createSyncClient(repoPath);
|
|
4480
6048
|
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
if (!acceptsFrom) return true;
|
|
4487
|
-
switch (context.senderType) {
|
|
4488
|
-
case "agent":
|
|
4489
|
-
return checkAgentAcceptance(acceptsFrom.agents, context);
|
|
4490
|
-
case "client":
|
|
4491
|
-
return checkClientAcceptance(acceptsFrom.clients, context.senderId);
|
|
4492
|
-
case "system":
|
|
4493
|
-
case "gateway":
|
|
4494
|
-
return checkSystemAcceptance(acceptsFrom.systems, context.senderSystemId);
|
|
4495
|
-
default:
|
|
4496
|
-
return false;
|
|
6049
|
+
#getDefaultClient() {
|
|
6050
|
+
if (!this.#defaultClient) {
|
|
6051
|
+
this.#defaultClient = this.#service.createSyncClient(this.#defaultRepoPath);
|
|
6052
|
+
}
|
|
6053
|
+
return this.#defaultClient;
|
|
4497
6054
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
const permissions = resolveAgentPermissions(viewerAgent, config);
|
|
4501
|
-
const canSee = permissions.canSee?.agents;
|
|
4502
|
-
if (!canSee || canSee === "all") return true;
|
|
4503
|
-
if (canSee === "hierarchy") {
|
|
4504
|
-
return context.isParent === true || context.isChild === true || context.isAncestor === true || context.isDescendant === true;
|
|
6055
|
+
async sync(peerId, options) {
|
|
6056
|
+
return this.#getDefaultClient().sync(peerId, options);
|
|
4505
6057
|
}
|
|
4506
|
-
|
|
4507
|
-
return (
|
|
6058
|
+
async pull(peerId, branch, options) {
|
|
6059
|
+
return this.#getDefaultClient().pull(peerId, branch, options);
|
|
4508
6060
|
}
|
|
4509
|
-
|
|
4510
|
-
return
|
|
6061
|
+
async push(peerId, branch, options) {
|
|
6062
|
+
return this.#getDefaultClient().push(peerId, branch, options);
|
|
4511
6063
|
}
|
|
4512
|
-
|
|
4513
|
-
return
|
|
6064
|
+
async clone(peerId, destPath, options) {
|
|
6065
|
+
return this.#getDefaultClient().clone(peerId, destPath, options);
|
|
4514
6066
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
6067
|
+
};
|
|
6068
|
+
|
|
6069
|
+
// src/acp/adapter.ts
|
|
6070
|
+
var ACPAgentAdapter = class {
|
|
6071
|
+
#mapAgent;
|
|
6072
|
+
#handler;
|
|
6073
|
+
#streamContexts = /* @__PURE__ */ new Map();
|
|
6074
|
+
#pendingClientRequests = /* @__PURE__ */ new Map();
|
|
6075
|
+
#clientRequestTimeout;
|
|
6076
|
+
/**
|
|
6077
|
+
* Create a new ACP agent adapter.
|
|
6078
|
+
*
|
|
6079
|
+
* @param mapAgent - The underlying MAP agent connection
|
|
6080
|
+
* @param handler - Handler implementing ACP agent methods
|
|
6081
|
+
* @param options - Optional configuration
|
|
6082
|
+
*/
|
|
6083
|
+
constructor(mapAgent, handler, options) {
|
|
6084
|
+
this.#mapAgent = mapAgent;
|
|
6085
|
+
this.#handler = handler;
|
|
6086
|
+
this.#clientRequestTimeout = options?.clientRequestTimeout ?? 3e4;
|
|
6087
|
+
mapAgent.onMessage((message) => {
|
|
6088
|
+
if (!message.payload || !isACPEnvelope(message.payload)) {
|
|
6089
|
+
return;
|
|
6090
|
+
}
|
|
6091
|
+
const envelope = message.payload;
|
|
6092
|
+
const { acp, acpContext } = envelope;
|
|
6093
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
6094
|
+
void this.#handleMessage(message);
|
|
6095
|
+
return;
|
|
6096
|
+
}
|
|
6097
|
+
if (acp.method) {
|
|
6098
|
+
this.#trackStreamContext(acpContext, message.from);
|
|
6099
|
+
}
|
|
6100
|
+
queueMicrotask(() => void this.#handleMessage(message));
|
|
6101
|
+
});
|
|
4523
6102
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
6103
|
+
/**
|
|
6104
|
+
* Track stream context for a client request.
|
|
6105
|
+
* This is called synchronously so that hasStream() works immediately.
|
|
6106
|
+
*/
|
|
6107
|
+
#trackStreamContext(acpCtx, clientParticipantId) {
|
|
6108
|
+
if (!this.#streamContexts.has(acpCtx.streamId)) {
|
|
6109
|
+
this.#streamContexts.set(acpCtx.streamId, {
|
|
6110
|
+
clientParticipantId,
|
|
6111
|
+
sessionId: acpCtx.sessionId
|
|
6112
|
+
});
|
|
6113
|
+
} else if (acpCtx.sessionId) {
|
|
6114
|
+
const streamCtx = this.#streamContexts.get(acpCtx.streamId);
|
|
6115
|
+
streamCtx.sessionId = acpCtx.sessionId;
|
|
6116
|
+
}
|
|
4526
6117
|
}
|
|
4527
|
-
|
|
4528
|
-
|
|
6118
|
+
// ===========================================================================
|
|
6119
|
+
// Message Handling
|
|
6120
|
+
// ===========================================================================
|
|
6121
|
+
/**
|
|
6122
|
+
* Handle incoming messages from MAP.
|
|
6123
|
+
*/
|
|
6124
|
+
async #handleMessage(message) {
|
|
6125
|
+
if (!message.payload || !isACPEnvelope(message.payload)) {
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6128
|
+
const envelope = message.payload;
|
|
6129
|
+
const { acp, acpContext } = envelope;
|
|
6130
|
+
if (acp.id !== void 0 && !acp.method) {
|
|
6131
|
+
const requestId = String(acp.id);
|
|
6132
|
+
const pending = this.#pendingClientRequests.get(requestId);
|
|
6133
|
+
if (pending) {
|
|
6134
|
+
clearTimeout(pending.timeout);
|
|
6135
|
+
this.#pendingClientRequests.delete(requestId);
|
|
6136
|
+
if (acp.error) {
|
|
6137
|
+
pending.reject(ACPError.fromResponse(acp.error));
|
|
6138
|
+
} else {
|
|
6139
|
+
pending.resolve(acp.result);
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
return;
|
|
6143
|
+
}
|
|
6144
|
+
if (acp.method) {
|
|
6145
|
+
await this.#handleClientRequest(
|
|
6146
|
+
acp.id,
|
|
6147
|
+
acp.method,
|
|
6148
|
+
acp.params,
|
|
6149
|
+
acpContext,
|
|
6150
|
+
message
|
|
6151
|
+
);
|
|
6152
|
+
}
|
|
4529
6153
|
}
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
6154
|
+
/**
|
|
6155
|
+
* Handle a client→agent request.
|
|
6156
|
+
*/
|
|
6157
|
+
async #handleClientRequest(requestId, method, params, acpCtx, originalMessage) {
|
|
6158
|
+
const ctx = {
|
|
6159
|
+
streamId: acpCtx.streamId,
|
|
6160
|
+
sessionId: acpCtx.sessionId,
|
|
6161
|
+
clientParticipantId: originalMessage.from
|
|
6162
|
+
};
|
|
6163
|
+
let result;
|
|
6164
|
+
let error;
|
|
6165
|
+
try {
|
|
6166
|
+
switch (method) {
|
|
6167
|
+
case ACP_METHODS.INITIALIZE:
|
|
6168
|
+
result = await this.#handler.initialize(
|
|
6169
|
+
params,
|
|
6170
|
+
ctx
|
|
6171
|
+
);
|
|
6172
|
+
break;
|
|
6173
|
+
case ACP_METHODS.AUTHENTICATE:
|
|
6174
|
+
if (!this.#handler.authenticate) {
|
|
6175
|
+
throw new ACPError(-32601, "Method not implemented: authenticate");
|
|
6176
|
+
}
|
|
6177
|
+
result = await this.#handler.authenticate(
|
|
6178
|
+
params,
|
|
6179
|
+
ctx
|
|
6180
|
+
);
|
|
6181
|
+
break;
|
|
6182
|
+
case ACP_METHODS.SESSION_NEW:
|
|
6183
|
+
result = await this.#handler.newSession(
|
|
6184
|
+
params,
|
|
6185
|
+
ctx
|
|
6186
|
+
);
|
|
6187
|
+
const newSessionResult = result;
|
|
6188
|
+
const streamContext = this.#streamContexts.get(acpCtx.streamId);
|
|
6189
|
+
if (streamContext) {
|
|
6190
|
+
streamContext.sessionId = newSessionResult.sessionId;
|
|
6191
|
+
}
|
|
6192
|
+
break;
|
|
6193
|
+
case ACP_METHODS.SESSION_LOAD:
|
|
6194
|
+
if (!this.#handler.loadSession) {
|
|
6195
|
+
throw new ACPError(-32601, "Method not implemented: session/load");
|
|
6196
|
+
}
|
|
6197
|
+
result = await this.#handler.loadSession(
|
|
6198
|
+
params,
|
|
6199
|
+
ctx
|
|
6200
|
+
);
|
|
6201
|
+
break;
|
|
6202
|
+
case ACP_METHODS.SESSION_SET_MODE:
|
|
6203
|
+
if (!this.#handler.setSessionMode) {
|
|
6204
|
+
throw new ACPError(-32601, "Method not implemented: session/set_mode");
|
|
6205
|
+
}
|
|
6206
|
+
result = await this.#handler.setSessionMode(
|
|
6207
|
+
params,
|
|
6208
|
+
ctx
|
|
6209
|
+
);
|
|
6210
|
+
break;
|
|
6211
|
+
case ACP_METHODS.SESSION_PROMPT:
|
|
6212
|
+
result = await this.#handler.prompt(params, ctx);
|
|
6213
|
+
break;
|
|
6214
|
+
case ACP_METHODS.SESSION_CANCEL:
|
|
6215
|
+
await this.#handler.cancel(params, ctx);
|
|
6216
|
+
return;
|
|
6217
|
+
default:
|
|
6218
|
+
throw new ACPError(-32601, `Unknown method: ${method}`);
|
|
6219
|
+
}
|
|
6220
|
+
} catch (e) {
|
|
6221
|
+
if (e instanceof ACPError) {
|
|
6222
|
+
error = e;
|
|
6223
|
+
} else {
|
|
6224
|
+
error = new ACPError(-32603, e.message);
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
if (requestId !== void 0) {
|
|
6228
|
+
const responseEnvelope = {
|
|
6229
|
+
acp: {
|
|
6230
|
+
jsonrpc: "2.0",
|
|
6231
|
+
id: requestId,
|
|
6232
|
+
...error ? { error: error.toErrorObject() } : { result }
|
|
6233
|
+
},
|
|
6234
|
+
acpContext: {
|
|
6235
|
+
streamId: acpCtx.streamId,
|
|
6236
|
+
sessionId: this.#streamContexts.get(acpCtx.streamId)?.sessionId ?? null,
|
|
6237
|
+
direction: "agent-to-client"
|
|
6238
|
+
}
|
|
6239
|
+
};
|
|
6240
|
+
this.#mapAgent.send(
|
|
6241
|
+
{ participant: originalMessage.from },
|
|
6242
|
+
responseEnvelope,
|
|
6243
|
+
{
|
|
6244
|
+
protocol: "acp",
|
|
6245
|
+
correlationId: originalMessage.id
|
|
6246
|
+
}
|
|
6247
|
+
).catch((err) => {
|
|
6248
|
+
console.error("ACP: Failed to send response:", err);
|
|
6249
|
+
});
|
|
6250
|
+
}
|
|
4540
6251
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
6252
|
+
// ===========================================================================
|
|
6253
|
+
// Agent→Client Communication
|
|
6254
|
+
// ===========================================================================
|
|
6255
|
+
/**
|
|
6256
|
+
* Send a session update notification to the client.
|
|
6257
|
+
*/
|
|
6258
|
+
async sendSessionUpdate(streamId, notification) {
|
|
6259
|
+
const streamCtx = this.#streamContexts.get(streamId);
|
|
6260
|
+
if (!streamCtx) {
|
|
6261
|
+
throw new Error(`Unknown stream: ${streamId}`);
|
|
6262
|
+
}
|
|
6263
|
+
const envelope = {
|
|
6264
|
+
acp: {
|
|
6265
|
+
jsonrpc: "2.0",
|
|
6266
|
+
method: ACP_METHODS.SESSION_UPDATE,
|
|
6267
|
+
params: notification
|
|
6268
|
+
},
|
|
6269
|
+
acpContext: {
|
|
6270
|
+
streamId,
|
|
6271
|
+
sessionId: streamCtx.sessionId,
|
|
6272
|
+
direction: "agent-to-client"
|
|
6273
|
+
}
|
|
6274
|
+
};
|
|
6275
|
+
await this.#mapAgent.send(
|
|
6276
|
+
{ participant: streamCtx.clientParticipantId },
|
|
6277
|
+
envelope,
|
|
6278
|
+
{ protocol: "acp" }
|
|
6279
|
+
);
|
|
4545
6280
|
}
|
|
4546
|
-
|
|
4547
|
-
|
|
6281
|
+
/**
|
|
6282
|
+
* Send an agent→client request and wait for response.
|
|
6283
|
+
*/
|
|
6284
|
+
async #sendClientRequest(streamId, method, params) {
|
|
6285
|
+
const streamCtx = this.#streamContexts.get(streamId);
|
|
6286
|
+
if (!streamCtx) {
|
|
6287
|
+
throw new Error(`Unknown stream: ${streamId}`);
|
|
6288
|
+
}
|
|
6289
|
+
const correlationId = `agent-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
6290
|
+
const envelope = {
|
|
6291
|
+
acp: {
|
|
6292
|
+
jsonrpc: "2.0",
|
|
6293
|
+
id: correlationId,
|
|
6294
|
+
method,
|
|
6295
|
+
params
|
|
6296
|
+
},
|
|
6297
|
+
acpContext: {
|
|
6298
|
+
streamId,
|
|
6299
|
+
sessionId: streamCtx.sessionId,
|
|
6300
|
+
direction: "agent-to-client",
|
|
6301
|
+
pendingClientRequest: {
|
|
6302
|
+
requestId: correlationId,
|
|
6303
|
+
method,
|
|
6304
|
+
timeout: this.#clientRequestTimeout
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
};
|
|
6308
|
+
await this.#mapAgent.send(
|
|
6309
|
+
{ participant: streamCtx.clientParticipantId },
|
|
6310
|
+
envelope,
|
|
6311
|
+
{ protocol: "acp", correlationId }
|
|
6312
|
+
);
|
|
6313
|
+
return new Promise((resolve, reject) => {
|
|
6314
|
+
const timeoutHandle = setTimeout(() => {
|
|
6315
|
+
this.#pendingClientRequests.delete(correlationId);
|
|
6316
|
+
reject(new Error(`Client request timed out: ${method}`));
|
|
6317
|
+
}, this.#clientRequestTimeout);
|
|
6318
|
+
this.#pendingClientRequests.set(correlationId, {
|
|
6319
|
+
resolve,
|
|
6320
|
+
reject,
|
|
6321
|
+
timeout: timeoutHandle,
|
|
6322
|
+
method
|
|
6323
|
+
});
|
|
6324
|
+
});
|
|
4548
6325
|
}
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
throw new InvalidAddressError(address, "missing type prefix");
|
|
6326
|
+
/**
|
|
6327
|
+
* Request permission from the client.
|
|
6328
|
+
*/
|
|
6329
|
+
async requestPermission(streamId, request) {
|
|
6330
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.REQUEST_PERMISSION, request);
|
|
4555
6331
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
6332
|
+
/**
|
|
6333
|
+
* Read a text file from the client.
|
|
6334
|
+
*/
|
|
6335
|
+
async readTextFile(streamId, request) {
|
|
6336
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.FS_READ_TEXT_FILE, request);
|
|
4560
6337
|
}
|
|
4561
|
-
|
|
4562
|
-
|
|
6338
|
+
/**
|
|
6339
|
+
* Write a text file on the client.
|
|
6340
|
+
*/
|
|
6341
|
+
async writeTextFile(streamId, request) {
|
|
6342
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.FS_WRITE_TEXT_FILE, request);
|
|
4563
6343
|
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
function isAddress(address) {
|
|
4570
|
-
try {
|
|
4571
|
-
parseAddress(address);
|
|
4572
|
-
return true;
|
|
4573
|
-
} catch {
|
|
4574
|
-
return false;
|
|
6344
|
+
/**
|
|
6345
|
+
* Create a terminal on the client.
|
|
6346
|
+
*/
|
|
6347
|
+
async createTerminal(streamId, request) {
|
|
6348
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_CREATE, request);
|
|
4575
6349
|
}
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
return
|
|
4581
|
-
} catch {
|
|
4582
|
-
return false;
|
|
6350
|
+
/**
|
|
6351
|
+
* Get terminal output from the client.
|
|
6352
|
+
*/
|
|
6353
|
+
async terminalOutput(streamId, request) {
|
|
6354
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_OUTPUT, request);
|
|
4583
6355
|
}
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
return
|
|
4589
|
-
} catch {
|
|
4590
|
-
return false;
|
|
6356
|
+
/**
|
|
6357
|
+
* Release a terminal on the client.
|
|
6358
|
+
*/
|
|
6359
|
+
async releaseTerminal(streamId, request) {
|
|
6360
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_RELEASE, request);
|
|
4591
6361
|
}
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
return
|
|
4597
|
-
} catch {
|
|
4598
|
-
return address;
|
|
6362
|
+
/**
|
|
6363
|
+
* Wait for a terminal to exit on the client.
|
|
6364
|
+
*/
|
|
6365
|
+
async waitForTerminalExit(streamId, request) {
|
|
6366
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_WAIT_FOR_EXIT, request);
|
|
4599
6367
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
return
|
|
4605
|
-
} catch {
|
|
4606
|
-
return void 0;
|
|
6368
|
+
/**
|
|
6369
|
+
* Kill a terminal command on the client.
|
|
6370
|
+
*/
|
|
6371
|
+
async killTerminal(streamId, request) {
|
|
6372
|
+
return this.#sendClientRequest(streamId, ACP_METHODS.TERMINAL_KILL, request);
|
|
4607
6373
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
6374
|
+
// ===========================================================================
|
|
6375
|
+
// Utility Methods
|
|
6376
|
+
// ===========================================================================
|
|
6377
|
+
/**
|
|
6378
|
+
* Get the current session ID for a stream.
|
|
6379
|
+
*/
|
|
6380
|
+
getSessionId(streamId) {
|
|
6381
|
+
return this.#streamContexts.get(streamId)?.sessionId ?? null;
|
|
6382
|
+
}
|
|
6383
|
+
/**
|
|
6384
|
+
* Get the client participant ID for a stream.
|
|
6385
|
+
*/
|
|
6386
|
+
getClientParticipantId(streamId) {
|
|
6387
|
+
return this.#streamContexts.get(streamId)?.clientParticipantId;
|
|
6388
|
+
}
|
|
6389
|
+
/**
|
|
6390
|
+
* Check if a stream is active.
|
|
6391
|
+
*/
|
|
6392
|
+
hasStream(streamId) {
|
|
6393
|
+
return this.#streamContexts.has(streamId);
|
|
6394
|
+
}
|
|
6395
|
+
/**
|
|
6396
|
+
* Remove a stream context (e.g., on disconnect).
|
|
6397
|
+
*/
|
|
6398
|
+
removeStream(streamId) {
|
|
6399
|
+
return this.#streamContexts.delete(streamId);
|
|
6400
|
+
}
|
|
6401
|
+
};
|
|
4615
6402
|
|
|
4616
6403
|
Object.defineProperty(exports, "monotonicFactory", {
|
|
4617
6404
|
enumerable: true,
|
|
@@ -4621,6 +6408,12 @@ Object.defineProperty(exports, "ulid", {
|
|
|
4621
6408
|
enumerable: true,
|
|
4622
6409
|
get: function () { return ulid.ulid; }
|
|
4623
6410
|
});
|
|
6411
|
+
exports.ACPAgentAdapter = ACPAgentAdapter;
|
|
6412
|
+
exports.ACPError = ACPError;
|
|
6413
|
+
exports.ACPStreamConnection = ACPStreamConnection;
|
|
6414
|
+
exports.ACP_ERROR_CODES = ACP_ERROR_CODES;
|
|
6415
|
+
exports.ACP_METHODS = ACP_METHODS;
|
|
6416
|
+
exports.ACP_PROTOCOL_VERSION = ACP_PROTOCOL_VERSION;
|
|
4624
6417
|
exports.AGENT_ERROR_CODES = AGENT_ERROR_CODES;
|
|
4625
6418
|
exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
|
|
4626
6419
|
exports.AUTH_METHODS = AUTH_METHODS;
|
|
@@ -4662,6 +6455,7 @@ exports.LIFECYCLE_METHODS = LIFECYCLE_METHODS;
|
|
|
4662
6455
|
exports.MAPConnectionError = MAPConnectionError;
|
|
4663
6456
|
exports.MAPErrorDataSchema = MAPErrorDataSchema;
|
|
4664
6457
|
exports.MAPErrorSchema = MAPErrorSchema;
|
|
6458
|
+
exports.MAPMeshPeer = MAPMeshPeer;
|
|
4665
6459
|
exports.MAPNotificationSchema = MAPNotificationSchema;
|
|
4666
6460
|
exports.MAPRequestError = MAPRequestError;
|
|
4667
6461
|
exports.MAPRequestSchema = MAPRequestSchema;
|
|
@@ -4711,6 +6505,7 @@ exports.SubscriptionIdSchema = SubscriptionIdSchema;
|
|
|
4711
6505
|
exports.SystemAddressSchema = SystemAddressSchema;
|
|
4712
6506
|
exports.TimestampSchema = TimestampSchema;
|
|
4713
6507
|
exports.TransportTypeSchema = TransportTypeSchema;
|
|
6508
|
+
exports.agenticMeshStream = agenticMeshStream;
|
|
4714
6509
|
exports.buildAgentsGetResponse = buildAgentsGetResponse;
|
|
4715
6510
|
exports.buildAgentsListResponse = buildAgentsListResponse;
|
|
4716
6511
|
exports.buildAgentsRegisterResponse = buildAgentsRegisterResponse;
|
|
@@ -4739,6 +6534,7 @@ exports.canSeeAgent = canSeeAgent;
|
|
|
4739
6534
|
exports.canSeeScope = canSeeScope;
|
|
4740
6535
|
exports.canSendToScope = canSendToScope;
|
|
4741
6536
|
exports.compareUlid = compareUlid;
|
|
6537
|
+
exports.createACPStream = createACPStream;
|
|
4742
6538
|
exports.createErrorResponse = createErrorResponse;
|
|
4743
6539
|
exports.createEvent = createEvent;
|
|
4744
6540
|
exports.createFederationEnvelope = createFederationEnvelope;
|
|
@@ -4761,6 +6557,12 @@ exports.getMethodsByCategory = getMethodsByCategory;
|
|
|
4761
6557
|
exports.getRequiredCapabilities = getRequiredCapabilities;
|
|
4762
6558
|
exports.hasCapability = hasCapability;
|
|
4763
6559
|
exports.hasRequiredCapabilities = hasRequiredCapabilities;
|
|
6560
|
+
exports.isACPEnvelope = isACPEnvelope;
|
|
6561
|
+
exports.isACPErrorResponse = isACPErrorResponse;
|
|
6562
|
+
exports.isACPNotification = isACPNotification;
|
|
6563
|
+
exports.isACPRequest = isACPRequest;
|
|
6564
|
+
exports.isACPResponse = isACPResponse;
|
|
6565
|
+
exports.isACPSuccessResponse = isACPSuccessResponse;
|
|
4764
6566
|
exports.isAddress = isAddress;
|
|
4765
6567
|
exports.isAgentAddress = isAgentAddress;
|
|
4766
6568
|
exports.isAgentExposed = isAgentExposed;
|