@teneo-protocol/sdk 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.github/workflows/publish-npm.yml +8 -6
  2. package/CHANGELOG.md +265 -0
  3. package/README.md +406 -53
  4. package/dist/core/websocket-client.d.ts +13 -0
  5. package/dist/core/websocket-client.d.ts.map +1 -1
  6. package/dist/core/websocket-client.js +34 -3
  7. package/dist/core/websocket-client.js.map +1 -1
  8. package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts +76 -0
  9. package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts.map +1 -0
  10. package/dist/handlers/message-handlers/agent-room-operation-response-handler.js +70 -0
  11. package/dist/handlers/message-handlers/agent-room-operation-response-handler.js.map +1 -0
  12. package/dist/handlers/message-handlers/agent-selected-handler.d.ts +92 -38
  13. package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -1
  14. package/dist/handlers/message-handlers/agent-status-update-handler.d.ts +904 -0
  15. package/dist/handlers/message-handlers/agent-status-update-handler.d.ts.map +1 -0
  16. package/dist/handlers/message-handlers/agent-status-update-handler.js +51 -0
  17. package/dist/handlers/message-handlers/agent-status-update-handler.js.map +1 -0
  18. package/dist/handlers/message-handlers/auth-error-handler.d.ts +45 -31
  19. package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -1
  20. package/dist/handlers/message-handlers/auth-message-handler.d.ts +6 -0
  21. package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -1
  22. package/dist/handlers/message-handlers/auth-message-handler.js +65 -5
  23. package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -1
  24. package/dist/handlers/message-handlers/auth-required-handler.d.ts +49 -31
  25. package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -1
  26. package/dist/handlers/message-handlers/auth-success-handler.d.ts +6 -0
  27. package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -1
  28. package/dist/handlers/message-handlers/auth-success-handler.js +46 -4
  29. package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -1
  30. package/dist/handlers/message-handlers/challenge-handler.d.ts +45 -31
  31. package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -1
  32. package/dist/handlers/message-handlers/error-message-handler.d.ts +49 -31
  33. package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -1
  34. package/dist/handlers/message-handlers/index.d.ts +5 -0
  35. package/dist/handlers/message-handlers/index.d.ts.map +1 -1
  36. package/dist/handlers/message-handlers/index.js +23 -1
  37. package/dist/handlers/message-handlers/index.js.map +1 -1
  38. package/dist/handlers/message-handlers/list-available-agents-handler.d.ts +877 -0
  39. package/dist/handlers/message-handlers/list-available-agents-handler.d.ts.map +1 -0
  40. package/dist/handlers/message-handlers/list-available-agents-handler.js +38 -0
  41. package/dist/handlers/message-handlers/list-available-agents-handler.js.map +1 -0
  42. package/dist/handlers/message-handlers/list-room-agents-handler.d.ts +886 -0
  43. package/dist/handlers/message-handlers/list-room-agents-handler.d.ts.map +1 -0
  44. package/dist/handlers/message-handlers/list-room-agents-handler.js +51 -0
  45. package/dist/handlers/message-handlers/list-room-agents-handler.js.map +1 -0
  46. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +178 -89
  47. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -1
  48. package/dist/handlers/message-handlers/ping-pong-handler.d.ts +62 -58
  49. package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -1
  50. package/dist/handlers/message-handlers/regular-message-handler.d.ts +31 -29
  51. package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -1
  52. package/dist/handlers/message-handlers/regular-message-handler.js +1 -0
  53. package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -1
  54. package/dist/handlers/message-handlers/room-operation-response-handler.d.ts +328 -0
  55. package/dist/handlers/message-handlers/room-operation-response-handler.d.ts.map +1 -0
  56. package/dist/handlers/message-handlers/room-operation-response-handler.js +92 -0
  57. package/dist/handlers/message-handlers/room-operation-response-handler.js.map +1 -0
  58. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +53 -31
  59. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -1
  60. package/dist/handlers/message-handlers/types.d.ts +2 -0
  61. package/dist/handlers/message-handlers/types.d.ts.map +1 -1
  62. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +53 -31
  63. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -1
  64. package/dist/managers/agent-room-manager.d.ts +222 -0
  65. package/dist/managers/agent-room-manager.d.ts.map +1 -0
  66. package/dist/managers/agent-room-manager.js +508 -0
  67. package/dist/managers/agent-room-manager.js.map +1 -0
  68. package/dist/managers/index.d.ts +2 -0
  69. package/dist/managers/index.d.ts.map +1 -1
  70. package/dist/managers/index.js +5 -1
  71. package/dist/managers/index.js.map +1 -1
  72. package/dist/managers/message-router.d.ts +1 -1
  73. package/dist/managers/message-router.d.ts.map +1 -1
  74. package/dist/managers/message-router.js +41 -4
  75. package/dist/managers/message-router.js.map +1 -1
  76. package/dist/managers/room-management-manager.d.ts +213 -0
  77. package/dist/managers/room-management-manager.d.ts.map +1 -0
  78. package/dist/managers/room-management-manager.js +440 -0
  79. package/dist/managers/room-management-manager.js.map +1 -0
  80. package/dist/managers/room-manager.d.ts +4 -4
  81. package/dist/managers/room-manager.d.ts.map +1 -1
  82. package/dist/managers/room-manager.js +1 -1
  83. package/dist/managers/room-manager.js.map +1 -1
  84. package/dist/teneo-sdk.d.ts +362 -14
  85. package/dist/teneo-sdk.d.ts.map +1 -1
  86. package/dist/teneo-sdk.js +497 -7
  87. package/dist/teneo-sdk.js.map +1 -1
  88. package/dist/types/config.d.ts +63 -54
  89. package/dist/types/config.d.ts.map +1 -1
  90. package/dist/types/config.js +9 -5
  91. package/dist/types/config.js.map +1 -1
  92. package/dist/types/error-codes.d.ts +2 -0
  93. package/dist/types/error-codes.d.ts.map +1 -1
  94. package/dist/types/error-codes.js +3 -0
  95. package/dist/types/error-codes.js.map +1 -1
  96. package/dist/types/events.d.ts +132 -68
  97. package/dist/types/events.d.ts.map +1 -1
  98. package/dist/types/events.js.map +1 -1
  99. package/dist/types/index.d.ts +1 -1
  100. package/dist/types/index.d.ts.map +1 -1
  101. package/dist/types/index.js +27 -2
  102. package/dist/types/index.js.map +1 -1
  103. package/dist/types/messages.d.ts +11396 -2559
  104. package/dist/types/messages.d.ts.map +1 -1
  105. package/dist/types/messages.js +294 -27
  106. package/dist/types/messages.js.map +1 -1
  107. package/dist/types/validation.d.ts.map +1 -1
  108. package/dist/types/validation.js +1 -1
  109. package/dist/types/validation.js.map +1 -1
  110. package/dist/utils/bounded-queue.d.ts +1 -1
  111. package/dist/utils/bounded-queue.js +6 -6
  112. package/dist/utils/circuit-breaker.d.ts.map +1 -1
  113. package/dist/utils/circuit-breaker.js.map +1 -1
  114. package/dist/utils/event-waiter.d.ts.map +1 -1
  115. package/dist/utils/event-waiter.js +2 -1
  116. package/dist/utils/event-waiter.js.map +1 -1
  117. package/dist/utils/rate-limiter.d.ts.map +1 -1
  118. package/dist/utils/rate-limiter.js +4 -6
  119. package/dist/utils/rate-limiter.js.map +1 -1
  120. package/dist/utils/secure-private-key.d.ts.map +1 -1
  121. package/dist/utils/secure-private-key.js +9 -15
  122. package/dist/utils/secure-private-key.js.map +1 -1
  123. package/dist/utils/signature-verifier.d.ts +2 -2
  124. package/dist/utils/signature-verifier.d.ts.map +1 -1
  125. package/dist/utils/signature-verifier.js +5 -5
  126. package/dist/utils/signature-verifier.js.map +1 -1
  127. package/examples/.env.example +1 -1
  128. package/examples/agent-room-management-example.ts +334 -0
  129. package/examples/claude-agent-x-follower/.env.example +117 -0
  130. package/examples/claude-agent-x-follower/QUICKSTART.md +243 -0
  131. package/examples/claude-agent-x-follower/README.md +540 -0
  132. package/examples/claude-agent-x-follower/index.ts +248 -0
  133. package/examples/claude-agent-x-follower/package.json +37 -0
  134. package/examples/claude-agent-x-follower/tsconfig.json +20 -0
  135. package/examples/n8n-teneo/.env.example +127 -0
  136. package/examples/n8n-teneo/Dockerfile +42 -0
  137. package/examples/n8n-teneo/README.md +564 -0
  138. package/examples/n8n-teneo/docker-compose.yml +71 -0
  139. package/examples/n8n-teneo/index.ts +177 -0
  140. package/examples/n8n-teneo/package.json +22 -0
  141. package/examples/n8n-teneo/tsconfig.json +12 -0
  142. package/examples/n8n-teneo/workflows/x-timeline.json +66 -0
  143. package/examples/openai-teneo/.env.example +130 -0
  144. package/examples/openai-teneo/README.md +635 -0
  145. package/examples/openai-teneo/index.ts +280 -0
  146. package/examples/openai-teneo/package.json +24 -0
  147. package/examples/openai-teneo/tsconfig.json +16 -0
  148. package/examples/production-dashboard/.env.example +5 -3
  149. package/examples/production-dashboard/README.md +839 -0
  150. package/examples/production-dashboard/pnpm-lock.yaml +92 -0
  151. package/examples/production-dashboard/public/dashboard.html +1150 -504
  152. package/examples/production-dashboard/server.ts +428 -12
  153. package/examples/room-management-example.ts +285 -0
  154. package/examples/usage/.env.example +17 -0
  155. package/examples/usage/01-connect.ts +116 -0
  156. package/examples/usage/02-list-agents.ts +153 -0
  157. package/examples/usage/03-pick-agent.ts +201 -0
  158. package/examples/usage/04-find-by-capability.ts +237 -0
  159. package/examples/usage/05-webhook-example.ts +319 -0
  160. package/examples/usage/06-simple-api-server.ts +396 -0
  161. package/examples/usage/07-event-listener.ts +402 -0
  162. package/examples/usage/README.md +383 -0
  163. package/examples/usage/package.json +42 -0
  164. package/package.json +13 -3
  165. package/src/core/websocket-client.ts +43 -9
  166. package/src/formatters/response-formatter.test.ts +8 -2
  167. package/src/handlers/message-handlers/agent-room-operation-response-handler.ts +83 -0
  168. package/src/handlers/message-handlers/agent-status-update-handler.ts +58 -0
  169. package/src/handlers/message-handlers/auth-message-handler.ts +73 -5
  170. package/src/handlers/message-handlers/auth-success-handler.ts +58 -6
  171. package/src/handlers/message-handlers/index.ts +19 -0
  172. package/src/handlers/message-handlers/list-available-agents-handler.ts +41 -0
  173. package/src/handlers/message-handlers/list-room-agents-handler.ts +61 -0
  174. package/src/handlers/message-handlers/regular-message-handler.ts +1 -0
  175. package/src/handlers/message-handlers/room-operation-response-handler.ts +105 -0
  176. package/src/handlers/message-handlers/types.ts +6 -0
  177. package/src/handlers/webhook-handler.test.ts +13 -10
  178. package/src/managers/agent-room-manager.ts +609 -0
  179. package/src/managers/index.ts +2 -0
  180. package/src/managers/message-router.ts +48 -6
  181. package/src/managers/room-management-manager.ts +523 -0
  182. package/src/managers/room-manager.ts +12 -6
  183. package/src/teneo-sdk.ts +543 -10
  184. package/src/types/config.ts +13 -6
  185. package/src/types/error-codes.ts +4 -0
  186. package/src/types/events.ts +24 -0
  187. package/src/types/index.ts +55 -0
  188. package/src/types/messages.ts +374 -41
  189. package/src/types/validation.ts +4 -1
  190. package/src/utils/bounded-queue.ts +9 -9
  191. package/src/utils/circuit-breaker.ts +4 -1
  192. package/src/utils/deduplication-cache.test.ts +2 -6
  193. package/src/utils/event-waiter.test.ts +4 -1
  194. package/src/utils/event-waiter.ts +5 -7
  195. package/src/utils/rate-limiter.test.ts +5 -17
  196. package/src/utils/rate-limiter.ts +6 -9
  197. package/src/utils/secure-private-key.test.ts +66 -59
  198. package/src/utils/secure-private-key.ts +10 -16
  199. package/src/utils/signature-verifier.test.ts +75 -70
  200. package/src/utils/signature-verifier.ts +7 -8
  201. package/src/utils/ssrf-validator.test.ts +3 -3
  202. package/tests/integration/room-management.test.ts +514 -0
  203. package/tests/integration/websocket.test.ts +1 -1
  204. package/tests/unit/handlers/agent-room-operation-response-handler.test.ts +394 -0
  205. package/tests/unit/handlers/agent-status-update-handler.test.ts +407 -0
  206. package/tests/unit/handlers/auth-success-handler-rooms.test.ts +699 -0
  207. package/tests/unit/handlers/list-available-agents-handler.test.ts +256 -0
  208. package/tests/unit/handlers/list-room-agents-handler.test.ts +294 -0
  209. package/tests/unit/handlers/room-operation-response-handler.test.ts +527 -0
  210. package/tests/unit/managers/agent-room-manager.test.ts +534 -0
  211. package/tests/unit/managers/room-management-manager.test.ts +438 -0
@@ -0,0 +1,609 @@
1
+ /**
2
+ * AgentRoomManager - Manages agent-room associations (v2.0.0)
3
+ * Allows room owners to control which agents are available in their rooms
4
+ * Implements caching with 5-minute TTL for performance
5
+ */
6
+
7
+ import { EventEmitter } from "eventemitter3";
8
+ import { WebSocketClient } from "../core/websocket-client";
9
+ import { Logger } from "../types";
10
+ import { SDKEvents, SDKError } from "../types/events";
11
+ import { ErrorCode } from "../types/error-codes";
12
+ import { RoomManagementManager } from "./room-management-manager";
13
+
14
+ // AgentRoomInfo from server response
15
+ export interface AgentRoomInfo {
16
+ agent_id: string;
17
+ agent_name?: string;
18
+ description?: string;
19
+ capabilities?: Array<{ name: string; description?: string }>;
20
+ commands?: Array<{ trigger: string; argument?: string; description?: string }>;
21
+ image?: string;
22
+ status?: string;
23
+ added_by?: string;
24
+ added_at?: string;
25
+ }
26
+
27
+ // Cache TTL: 5 minutes
28
+ const CACHE_TTL_MS = 5 * 60 * 1000;
29
+
30
+ export class AgentRoomManager extends EventEmitter<SDKEvents> {
31
+ private readonly wsClient: WebSocketClient;
32
+ private readonly logger: Logger;
33
+ private readonly roomManagementManager: RoomManagementManager; // Reference to check ownership
34
+
35
+ // Caches with TTL
36
+ private readonly roomAgentsCache = new Map<string, AgentRoomInfo[]>();
37
+ private readonly availableAgentsCache = new Map<string, AgentRoomInfo[]>();
38
+ private readonly roomAgentsCacheTime = new Map<string, number>();
39
+ private readonly availableAgentsCacheTime = new Map<string, number>();
40
+
41
+ constructor(
42
+ wsClient: WebSocketClient,
43
+ logger: Logger,
44
+ roomManagementManager: RoomManagementManager
45
+ ) {
46
+ super();
47
+ this.wsClient = wsClient;
48
+ this.logger = logger;
49
+ this.roomManagementManager = roomManagementManager;
50
+ }
51
+
52
+ // ============================================================================
53
+ // AGENT ROOM OPERATIONS
54
+ // ============================================================================
55
+
56
+ /**
57
+ * Adds an agent to a room, making it available for interactions in that room.
58
+ * User must own the room to perform this operation.
59
+ *
60
+ * @param roomId - ID of the room to add agent to
61
+ * @param agentId - ID of the agent to add
62
+ * @returns Promise that resolves when agent is added
63
+ * @throws {SDKError} If not connected, not room owner, or operation fails
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * await sdk.addAgentToRoom('room-123', 'agent-456');
68
+ * console.log('Agent added to room');
69
+ * ```
70
+ */
71
+ public async addAgentToRoom(roomId: string, agentId: string): Promise<void> {
72
+ if (!this.wsClient.isConnected) {
73
+ throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
74
+ }
75
+
76
+ // Validate inputs
77
+ this.validateRoomId(roomId);
78
+ this.validateAgentId(agentId);
79
+
80
+ // Verify room exists
81
+ if (!this.roomExists(roomId)) {
82
+ throw new SDKError("Room not found", ErrorCode.VALIDATION_ERROR);
83
+ }
84
+
85
+ // Verify user owns room
86
+ if (!this.verifyOwnership(roomId)) {
87
+ throw new SDKError(
88
+ "Cannot add agent to room: You don't own this room",
89
+ ErrorCode.PERMISSION_DENIED
90
+ );
91
+ }
92
+
93
+ this.logger.debug("AgentRoomManager: Adding agent to room", { roomId, agentId });
94
+
95
+ const message = {
96
+ type: "add_agent_to_room" as const,
97
+ data: {
98
+ room_id: roomId,
99
+ agent_id: agentId
100
+ }
101
+ };
102
+
103
+ return new Promise((resolve, reject) => {
104
+ const timeout = setTimeout(() => {
105
+ cleanup();
106
+ reject(new SDKError("Add agent to room timeout", ErrorCode.TIMEOUT));
107
+ }, 30000);
108
+
109
+ const onSuccess = (responseRoomId: string, responseAgentId: string) => {
110
+ if (responseRoomId === roomId && responseAgentId === agentId) {
111
+ cleanup();
112
+ // Invalidate caches for this room
113
+ this.invalidateRoomCaches(roomId);
114
+ resolve();
115
+ }
116
+ };
117
+
118
+ const onError = (error: Error, responseRoomId?: string) => {
119
+ if (!responseRoomId || responseRoomId === roomId) {
120
+ cleanup();
121
+ reject(error);
122
+ }
123
+ };
124
+
125
+ const cleanup = () => {
126
+ clearTimeout(timeout);
127
+ this.off("agent_room:agent_added", onSuccess);
128
+ this.off("agent_room:add_error", onError);
129
+ };
130
+
131
+ this.once("agent_room:agent_added", onSuccess);
132
+ this.once("agent_room:add_error", onError);
133
+
134
+ this.wsClient.sendMessage(message);
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Removes an agent from a room.
140
+ * User must own the room to perform this operation.
141
+ *
142
+ * @param roomId - ID of the room to remove agent from
143
+ * @param agentId - ID of the agent to remove
144
+ * @returns Promise that resolves when agent is removed
145
+ * @throws {SDKError} If not connected, not room owner, or operation fails
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * await sdk.removeAgentFromRoom('room-123', 'agent-456');
150
+ * console.log('Agent removed from room');
151
+ * ```
152
+ */
153
+ public async removeAgentFromRoom(roomId: string, agentId: string): Promise<void> {
154
+ if (!this.wsClient.isConnected) {
155
+ throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
156
+ }
157
+
158
+ // Validate inputs
159
+ this.validateRoomId(roomId);
160
+ this.validateAgentId(agentId);
161
+
162
+ // Verify user owns room
163
+ if (!this.verifyOwnership(roomId)) {
164
+ throw new SDKError(
165
+ "Cannot remove agent from room: You don't own this room",
166
+ ErrorCode.PERMISSION_DENIED
167
+ );
168
+ }
169
+
170
+ this.logger.debug("AgentRoomManager: Removing agent from room", { roomId, agentId });
171
+
172
+ const message = {
173
+ type: "remove_agent_from_room" as const,
174
+ data: {
175
+ room_id: roomId,
176
+ agent_id: agentId
177
+ }
178
+ };
179
+
180
+ return new Promise((resolve, reject) => {
181
+ const timeout = setTimeout(() => {
182
+ cleanup();
183
+ reject(new SDKError("Remove agent from room timeout", ErrorCode.TIMEOUT));
184
+ }, 30000);
185
+
186
+ const onSuccess = (responseRoomId: string, responseAgentId: string) => {
187
+ if (responseRoomId === roomId && responseAgentId === agentId) {
188
+ cleanup();
189
+ // Invalidate caches for this room
190
+ this.invalidateRoomCaches(roomId);
191
+ resolve();
192
+ }
193
+ };
194
+
195
+ const onError = (error: Error, responseRoomId?: string) => {
196
+ if (!responseRoomId || responseRoomId === roomId) {
197
+ cleanup();
198
+ reject(error);
199
+ }
200
+ };
201
+
202
+ const cleanup = () => {
203
+ clearTimeout(timeout);
204
+ this.off("agent_room:agent_removed", onSuccess);
205
+ this.off("agent_room:remove_error", onError);
206
+ };
207
+
208
+ this.once("agent_room:agent_removed", onSuccess);
209
+ this.once("agent_room:remove_error", onError);
210
+
211
+ this.wsClient.sendMessage(message);
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Lists all agents currently in a room.
217
+ * Results are cached for 5 minutes for performance.
218
+ *
219
+ * @param roomId - ID of the room to list agents for
220
+ * @param useCache - Whether to use cached data (default: true)
221
+ * @returns Promise that resolves with array of agents
222
+ * @throws {SDKError} If not connected or operation fails
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * const agents = await sdk.listRoomAgents('room-123');
227
+ * console.log(`Room has ${agents.length} agents`);
228
+ *
229
+ * // Force fresh data
230
+ * const freshAgents = await sdk.listRoomAgents('room-123', false);
231
+ * ```
232
+ */
233
+ public async listRoomAgents(
234
+ roomId: string,
235
+ useCache: boolean = true
236
+ ): Promise<AgentRoomInfo[]> {
237
+ if (!this.wsClient.isConnected) {
238
+ throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
239
+ }
240
+
241
+ // Validate input
242
+ this.validateRoomId(roomId);
243
+
244
+ // Check cache if enabled
245
+ if (useCache && this.isCacheValid(this.roomAgentsCacheTime, roomId)) {
246
+ const cached = this.roomAgentsCache.get(roomId);
247
+ if (cached) {
248
+ this.logger.debug("AgentRoomManager: Returning cached room agents", {
249
+ roomId,
250
+ count: cached.length
251
+ });
252
+ return cached.map((agent) => ({ ...agent })); // Return deep copy
253
+ }
254
+ }
255
+
256
+ this.logger.debug("AgentRoomManager: Listing room agents", { roomId });
257
+
258
+ const message = {
259
+ type: "list_room_agents" as const,
260
+ data: {
261
+ room_id: roomId
262
+ }
263
+ };
264
+
265
+ return new Promise((resolve, reject) => {
266
+ const timeout = setTimeout(() => {
267
+ cleanup();
268
+ reject(new SDKError("List room agents timeout", ErrorCode.TIMEOUT));
269
+ }, 30000);
270
+
271
+ const onSuccess = (responseRoomId: string, agents: AgentRoomInfo[]) => {
272
+ if (responseRoomId === roomId) {
273
+ cleanup();
274
+ resolve(agents);
275
+ }
276
+ };
277
+
278
+ const onError = (error: Error, responseRoomId?: string) => {
279
+ if (!responseRoomId || responseRoomId === roomId) {
280
+ cleanup();
281
+ reject(error);
282
+ }
283
+ };
284
+
285
+ const cleanup = () => {
286
+ clearTimeout(timeout);
287
+ this.off("agent_room:agents_listed", onSuccess);
288
+ this.off("agent_room:list_error", onError);
289
+ };
290
+
291
+ this.once("agent_room:agents_listed", onSuccess);
292
+ this.once("agent_room:list_error", onError);
293
+
294
+ this.wsClient.sendMessage(message);
295
+ });
296
+ }
297
+
298
+ /**
299
+ * Lists all agents available to add to a room (not yet in the room).
300
+ * Results are cached for 5 minutes for performance.
301
+ *
302
+ * @param roomId - ID of the room to check available agents for
303
+ * @param useCache - Whether to use cached data (default: true)
304
+ * @returns Promise that resolves with array of available agents
305
+ * @throws {SDKError} If not connected or operation fails
306
+ *
307
+ * @example
308
+ * ```typescript
309
+ * const availableAgents = await sdk.listAvailableAgents('room-123');
310
+ * console.log(`${availableAgents.length} agents can be added`);
311
+ * ```
312
+ */
313
+ public async listAvailableAgents(
314
+ roomId: string,
315
+ useCache: boolean = true
316
+ ): Promise<AgentRoomInfo[]> {
317
+ if (!this.wsClient.isConnected) {
318
+ throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
319
+ }
320
+
321
+ // Validate input
322
+ this.validateRoomId(roomId);
323
+
324
+ // Check cache if enabled
325
+ if (useCache && this.isCacheValid(this.availableAgentsCacheTime, roomId)) {
326
+ const cached = this.availableAgentsCache.get(roomId);
327
+ if (cached) {
328
+ this.logger.debug("AgentRoomManager: Returning cached available agents", {
329
+ roomId,
330
+ count: cached.length
331
+ });
332
+ return cached.map((agent) => ({ ...agent })); // Return deep copy
333
+ }
334
+ }
335
+
336
+ this.logger.debug("AgentRoomManager: Listing available agents", { roomId });
337
+
338
+ const message = {
339
+ type: "list_available_agents" as const,
340
+ data: {
341
+ room_id: roomId
342
+ }
343
+ };
344
+
345
+ return new Promise((resolve, reject) => {
346
+ const timeout = setTimeout(() => {
347
+ cleanup();
348
+ reject(new SDKError("List available agents timeout", ErrorCode.TIMEOUT));
349
+ }, 30000);
350
+
351
+ const onSuccess = (agents: AgentRoomInfo[]) => {
352
+ cleanup();
353
+ resolve(agents);
354
+ };
355
+
356
+ const onError = (error: Error) => {
357
+ cleanup();
358
+ reject(error);
359
+ };
360
+
361
+ const cleanup = () => {
362
+ clearTimeout(timeout);
363
+ this.off("agent_room:available_agents_listed", onSuccess);
364
+ this.off("agent_room:list_available_error", onError);
365
+ };
366
+
367
+ this.once("agent_room:available_agents_listed", onSuccess);
368
+ this.once("agent_room:list_available_error", onError);
369
+
370
+ this.wsClient.sendMessage(message);
371
+ });
372
+ }
373
+
374
+ // ============================================================================
375
+ // QUERY METHODS (Synchronous, from cache)
376
+ // ============================================================================
377
+
378
+ /**
379
+ * Gets agents currently in a room from cache (synchronous).
380
+ * Returns undefined if not cached or cache expired.
381
+ *
382
+ * @param roomId - Room ID to query
383
+ * @returns Array of agents or undefined if not cached
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * const agents = sdk.getRoomAgents('room-123');
388
+ * if (agents) {
389
+ * console.log(`Found ${agents.length} agents in cache`);
390
+ * }
391
+ * ```
392
+ */
393
+ public getRoomAgents(roomId: string): AgentRoomInfo[] | undefined {
394
+ if (!this.isCacheValid(this.roomAgentsCacheTime, roomId)) {
395
+ return undefined;
396
+ }
397
+ const cached = this.roomAgentsCache.get(roomId);
398
+ return cached ? cached.map((agent) => ({ ...agent })) : undefined;
399
+ }
400
+
401
+ /**
402
+ * Gets available agents for a room from cache (synchronous).
403
+ * Returns undefined if not cached or cache expired.
404
+ *
405
+ * @param roomId - Room ID to query
406
+ * @returns Array of available agents or undefined if not cached
407
+ *
408
+ * @example
409
+ * ```typescript
410
+ * const available = sdk.getAvailableAgents('room-123');
411
+ * if (available) {
412
+ * console.log(`${available.length} agents can be added`);
413
+ * }
414
+ * ```
415
+ */
416
+ public getAvailableAgents(roomId: string): AgentRoomInfo[] | undefined {
417
+ if (!this.isCacheValid(this.availableAgentsCacheTime, roomId)) {
418
+ return undefined;
419
+ }
420
+ const cached = this.availableAgentsCache.get(roomId);
421
+ return cached ? cached.map((agent) => ({ ...agent })) : undefined;
422
+ }
423
+
424
+ /**
425
+ * Checks if an agent is currently in a room (from cache).
426
+ * Returns undefined if cache is invalid.
427
+ *
428
+ * @param roomId - Room ID to check
429
+ * @param agentId - Agent ID to check
430
+ * @returns True if agent in room, false if not, undefined if cache invalid
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * const isInRoom = sdk.isAgentInRoom('room-123', 'agent-456');
435
+ * if (isInRoom === true) {
436
+ * console.log('Agent is in this room');
437
+ * }
438
+ * ```
439
+ */
440
+ public isAgentInRoom(roomId: string, agentId: string): boolean | undefined {
441
+ const agents = this.getRoomAgents(roomId);
442
+ if (!agents) return undefined;
443
+ return agents.some((agent) => agent.agent_id === agentId);
444
+ }
445
+
446
+ /**
447
+ * Gets the count of agents in a room (from cache).
448
+ * Returns undefined if cache is invalid.
449
+ *
450
+ * @param roomId - Room ID to count agents for
451
+ * @returns Number of agents or undefined if cache invalid
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * const count = sdk.getRoomAgentCount('room-123');
456
+ * if (count !== undefined) {
457
+ * console.log(`Room has ${count} agents`);
458
+ * }
459
+ * ```
460
+ */
461
+ public getRoomAgentCount(roomId: string): number | undefined {
462
+ const agents = this.getRoomAgents(roomId);
463
+ return agents ? agents.length : undefined;
464
+ }
465
+
466
+ // ============================================================================
467
+ // CACHE MANAGEMENT (Public methods)
468
+ // ============================================================================
469
+
470
+ /**
471
+ * Manually invalidates all caches for a specific room.
472
+ * Useful after operations that might have changed agent assignments.
473
+ *
474
+ * @param roomId - Room ID to invalidate cache for
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * // After bulk operations
479
+ * sdk.invalidateCache('room-123');
480
+ * const freshAgents = await sdk.listRoomAgents('room-123', false);
481
+ * ```
482
+ */
483
+ public invalidateCache(roomId: string): void {
484
+ this.invalidateRoomCaches(roomId);
485
+ this.logger.debug("AgentRoomManager: Cache invalidated", { roomId });
486
+ }
487
+
488
+ /**
489
+ * Clears all caches for all rooms.
490
+ * Called automatically on disconnect.
491
+ * @internal
492
+ */
493
+ public clearAllCaches(): void {
494
+ this.roomAgentsCache.clear();
495
+ this.availableAgentsCache.clear();
496
+ this.roomAgentsCacheTime.clear();
497
+ this.availableAgentsCacheTime.clear();
498
+ this.logger.debug("AgentRoomManager: All caches cleared");
499
+ }
500
+
501
+ // ============================================================================
502
+ // INTERNAL METHODS
503
+ // ============================================================================
504
+
505
+ /**
506
+ * Handles agent status updates from server.
507
+ * Invalidates cache when agent status changes.
508
+ * @internal
509
+ */
510
+ public handleStatusUpdate(roomId: string, agentId: string, status: string): void {
511
+ this.logger.debug("AgentRoomManager: Agent status update", {
512
+ roomId,
513
+ agentId,
514
+ status
515
+ });
516
+
517
+ // Invalidate cache for this room as agent list may have changed
518
+ this.invalidateRoomCaches(roomId);
519
+ }
520
+
521
+ /**
522
+ * Called by handlers to cache room agents.
523
+ * @internal
524
+ */
525
+ public cacheRoomAgents(roomId: string, agents: AgentRoomInfo[]): void {
526
+ this.roomAgentsCache.set(roomId, agents);
527
+ this.roomAgentsCacheTime.set(roomId, Date.now());
528
+ this.logger.debug("AgentRoomManager: Cached room agents", {
529
+ roomId,
530
+ count: agents.length
531
+ });
532
+ }
533
+
534
+ /**
535
+ * Called by handlers to cache available agents.
536
+ * @internal
537
+ */
538
+ public cacheAvailableAgents(roomId: string, agents: AgentRoomInfo[]): void {
539
+ this.availableAgentsCache.set(roomId, agents);
540
+ this.availableAgentsCacheTime.set(roomId, Date.now());
541
+ this.logger.debug("AgentRoomManager: Cached available agents", {
542
+ roomId,
543
+ count: agents.length
544
+ });
545
+ }
546
+
547
+ // ============================================================================
548
+ // PRIVATE HELPERS
549
+ // ============================================================================
550
+
551
+ /**
552
+ * Checks if cache is still valid (within TTL)
553
+ */
554
+ private isCacheValid(cacheTimeMap: Map<string, number>, roomId: string): boolean {
555
+ const cacheTime = cacheTimeMap.get(roomId);
556
+ if (!cacheTime) return false;
557
+
558
+ const age = Date.now() - cacheTime;
559
+ return age < CACHE_TTL_MS;
560
+ }
561
+
562
+ /**
563
+ * Invalidates all caches for a specific room
564
+ */
565
+ private invalidateRoomCaches(roomId: string): void {
566
+ this.roomAgentsCache.delete(roomId);
567
+ this.availableAgentsCache.delete(roomId);
568
+ this.roomAgentsCacheTime.delete(roomId);
569
+ this.availableAgentsCacheTime.delete(roomId);
570
+ }
571
+
572
+ /**
573
+ * Verifies user owns the room
574
+ */
575
+ private validateRoomId(roomId: string): void {
576
+ if (!roomId || roomId.trim() === "") {
577
+ throw new SDKError("Room ID cannot be empty", ErrorCode.VALIDATION_ERROR);
578
+ }
579
+ }
580
+
581
+ private validateAgentId(agentId: string): void {
582
+ if (!agentId || agentId.trim() === "") {
583
+ throw new SDKError("Agent ID cannot be empty", ErrorCode.VALIDATION_ERROR);
584
+ }
585
+ }
586
+
587
+ private roomExists(roomId: string): boolean {
588
+ if (!this.roomManagementManager) return true; // Skip check if manager not available
589
+
590
+ // If getRoomById method doesn't exist, skip check
591
+ if (typeof this.roomManagementManager.getRoomById !== "function") {
592
+ return true;
593
+ }
594
+
595
+ // Check if room exists (in owned or shared rooms)
596
+ const room = this.roomManagementManager.getRoomById(roomId);
597
+ return room !== undefined;
598
+ }
599
+
600
+ private verifyOwnership(roomId: string): boolean {
601
+ if (!this.roomManagementManager) return true; // Skip check if manager not available
602
+
603
+ // Check if room is in owned rooms
604
+ const ownedRooms = this.roomManagementManager.getOwnedRooms?.();
605
+ if (!ownedRooms) return true; // Skip check if method not available
606
+
607
+ return ownedRooms.some((room: any) => room.id === roomId);
608
+ }
609
+ }
@@ -5,5 +5,7 @@
5
5
 
6
6
  export { ConnectionManager } from "./connection-manager";
7
7
  export { RoomManager } from "./room-manager";
8
+ export { RoomManagementManager } from "./room-management-manager";
9
+ export { AgentRoomManager, type AgentRoomInfo } from "./agent-room-manager";
8
10
  export { AgentRegistry } from "./agent-registry";
9
11
  export { MessageRouter, type SendMessageOptions, type AgentCommand } from "./message-router";
@@ -156,7 +156,10 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
156
156
  * });
157
157
  * ```
158
158
  */
159
- public async sendDirectCommand(command: AgentCommand): Promise<FormattedResponse | void> {
159
+ public async sendDirectCommand(
160
+ command: AgentCommand,
161
+ waitForResponse: boolean = false
162
+ ): Promise<FormattedResponse | void> {
160
163
  if (!this.wsClient.isConnected) {
161
164
  throw new SDKError("Not connected to Teneo network", ErrorCode.NOT_CONNECTED);
162
165
  }
@@ -185,11 +188,21 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
185
188
  from: walletAddress
186
189
  });
187
190
 
188
- await this.wsClient.sendMessage(message);
189
- await this.webhookHandler.sendMessageWebhook(message);
190
- }
191
-
191
+ const options: SendMessageOptions = {
192
+ room,
193
+ from: walletAddress,
194
+ waitForResponse,
195
+ timeout: this.messageTimeout,
196
+ format: this.responseFormat
197
+ };
192
198
 
199
+ if (waitForResponse) {
200
+ return await this.sendMessageAndWaitForResponse(message, options);
201
+ } else {
202
+ await this.wsClient.sendMessage(message);
203
+ await this.webhookHandler.sendMessageWebhook(message);
204
+ }
205
+ }
193
206
 
194
207
  /**
195
208
  * Send message and wait for agent response
@@ -220,16 +233,45 @@ export class MessageRouter extends EventEmitter<SDKEvents> {
220
233
 
221
234
  // Wait for agent response with automatic timeout and cleanup
222
235
  // The filter ensures we only match responses for THIS specific request
236
+ const requestTimestamp = Date.now();
237
+ let responseMatched = false;
238
+
223
239
  const response = await waitForEvent<AgentResponse>(this.wsClient, "agent:response", {
224
240
  timeout,
225
241
  filter: (r) => {
242
+ // Prevent double-matching
243
+ if (responseMatched) return false;
244
+
226
245
  // Try to match by client_request_id if server echoes it back
227
246
  const responseRequestId =
228
247
  r.raw?.data && "client_request_id" in r.raw.data
229
248
  ? (r.raw.data as any).client_request_id
230
249
  : undefined;
231
250
 
232
- return responseRequestId === requestId;
251
+ if (responseRequestId === requestId) {
252
+ responseMatched = true;
253
+ return true;
254
+ }
255
+
256
+ // Fallback: If server doesn't support client_request_id,
257
+ // match the first response from the expected room within 60 seconds
258
+ // This handles servers that don't echo back client_request_id
259
+ const timeSinceRequest = Date.now() - requestTimestamp;
260
+ const responseRoom = r.raw?.room;
261
+ const isFromExpectedRoom = responseRoom === message.room;
262
+ const isWithinTimeWindow = timeSinceRequest < 60000; // 60 second window
263
+
264
+ if (isFromExpectedRoom && isWithinTimeWindow && !responseRequestId) {
265
+ this.logger.debug("Matching response without client_request_id (server fallback)", {
266
+ responseRoom,
267
+ expectedRoom: message.room,
268
+ timeSinceRequest
269
+ });
270
+ responseMatched = true;
271
+ return true;
272
+ }
273
+
274
+ return false;
233
275
  },
234
276
  timeoutMessage: `Message timeout - no response received after ${timeout}ms (requestId: ${requestId})`
235
277
  });