@teneo-protocol/sdk 1.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 (281) hide show
  1. package/.dockerignore +14 -0
  2. package/.env.test.example +14 -0
  3. package/.eslintrc.json +26 -0
  4. package/.github/workflows/claude-code-review.yml +78 -0
  5. package/.github/workflows/claude-reviewer.yml +64 -0
  6. package/.github/workflows/publish-npm.yml +38 -0
  7. package/.github/workflows/push-to-main.yml +23 -0
  8. package/.node-version +1 -0
  9. package/.prettierrc +11 -0
  10. package/Dockerfile +25 -0
  11. package/LICENCE +661 -0
  12. package/README.md +709 -0
  13. package/dist/constants.d.ts +42 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +45 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/core/websocket-client.d.ts +261 -0
  18. package/dist/core/websocket-client.d.ts.map +1 -0
  19. package/dist/core/websocket-client.js +875 -0
  20. package/dist/core/websocket-client.js.map +1 -0
  21. package/dist/formatters/response-formatter.d.ts +354 -0
  22. package/dist/formatters/response-formatter.d.ts.map +1 -0
  23. package/dist/formatters/response-formatter.js +575 -0
  24. package/dist/formatters/response-formatter.js.map +1 -0
  25. package/dist/handlers/message-handler-registry.d.ts +155 -0
  26. package/dist/handlers/message-handler-registry.d.ts.map +1 -0
  27. package/dist/handlers/message-handler-registry.js +216 -0
  28. package/dist/handlers/message-handler-registry.js.map +1 -0
  29. package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
  30. package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
  31. package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
  32. package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
  33. package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
  34. package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
  35. package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
  36. package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
  37. package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
  38. package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
  39. package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
  40. package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
  41. package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
  42. package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
  43. package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
  44. package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
  45. package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
  46. package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
  47. package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
  48. package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
  49. package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
  50. package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
  51. package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
  52. package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
  53. package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
  54. package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
  55. package/dist/handlers/message-handlers/base-handler.js +83 -0
  56. package/dist/handlers/message-handlers/base-handler.js.map +1 -0
  57. package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
  58. package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
  59. package/dist/handlers/message-handlers/challenge-handler.js +47 -0
  60. package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
  61. package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
  62. package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
  63. package/dist/handlers/message-handlers/error-message-handler.js +29 -0
  64. package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
  65. package/dist/handlers/message-handlers/index.d.ts +28 -0
  66. package/dist/handlers/message-handlers/index.d.ts.map +1 -0
  67. package/dist/handlers/message-handlers/index.js +100 -0
  68. package/dist/handlers/message-handlers/index.js.map +1 -0
  69. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
  70. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
  71. package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
  72. package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
  73. package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
  74. package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
  75. package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
  76. package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
  77. package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
  78. package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
  79. package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
  80. package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
  81. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
  82. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
  83. package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
  84. package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
  85. package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
  86. package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
  87. package/dist/handlers/message-handlers/task-response-handler.js +44 -0
  88. package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
  89. package/dist/handlers/message-handlers/types.d.ts +51 -0
  90. package/dist/handlers/message-handlers/types.d.ts.map +1 -0
  91. package/dist/handlers/message-handlers/types.js +7 -0
  92. package/dist/handlers/message-handlers/types.js.map +1 -0
  93. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
  94. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
  95. package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
  96. package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
  97. package/dist/handlers/webhook-handler.d.ts +202 -0
  98. package/dist/handlers/webhook-handler.d.ts.map +1 -0
  99. package/dist/handlers/webhook-handler.js +511 -0
  100. package/dist/handlers/webhook-handler.js.map +1 -0
  101. package/dist/index.d.ts +71 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +217 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/managers/agent-registry.d.ts +173 -0
  106. package/dist/managers/agent-registry.d.ts.map +1 -0
  107. package/dist/managers/agent-registry.js +310 -0
  108. package/dist/managers/agent-registry.js.map +1 -0
  109. package/dist/managers/connection-manager.d.ts +134 -0
  110. package/dist/managers/connection-manager.d.ts.map +1 -0
  111. package/dist/managers/connection-manager.js +176 -0
  112. package/dist/managers/connection-manager.js.map +1 -0
  113. package/dist/managers/index.d.ts +9 -0
  114. package/dist/managers/index.d.ts.map +1 -0
  115. package/dist/managers/index.js +16 -0
  116. package/dist/managers/index.js.map +1 -0
  117. package/dist/managers/message-router.d.ts +112 -0
  118. package/dist/managers/message-router.d.ts.map +1 -0
  119. package/dist/managers/message-router.js +260 -0
  120. package/dist/managers/message-router.js.map +1 -0
  121. package/dist/managers/room-manager.d.ts +165 -0
  122. package/dist/managers/room-manager.d.ts.map +1 -0
  123. package/dist/managers/room-manager.js +227 -0
  124. package/dist/managers/room-manager.js.map +1 -0
  125. package/dist/teneo-sdk.d.ts +703 -0
  126. package/dist/teneo-sdk.d.ts.map +1 -0
  127. package/dist/teneo-sdk.js +907 -0
  128. package/dist/teneo-sdk.js.map +1 -0
  129. package/dist/types/config.d.ts +1047 -0
  130. package/dist/types/config.d.ts.map +1 -0
  131. package/dist/types/config.js +720 -0
  132. package/dist/types/config.js.map +1 -0
  133. package/dist/types/error-codes.d.ts +29 -0
  134. package/dist/types/error-codes.d.ts.map +1 -0
  135. package/dist/types/error-codes.js +41 -0
  136. package/dist/types/error-codes.js.map +1 -0
  137. package/dist/types/events.d.ts +616 -0
  138. package/dist/types/events.d.ts.map +1 -0
  139. package/dist/types/events.js +261 -0
  140. package/dist/types/events.js.map +1 -0
  141. package/dist/types/health.d.ts +40 -0
  142. package/dist/types/health.d.ts.map +1 -0
  143. package/dist/types/health.js +6 -0
  144. package/dist/types/health.js.map +1 -0
  145. package/dist/types/index.d.ts +10 -0
  146. package/dist/types/index.d.ts.map +1 -0
  147. package/dist/types/index.js +123 -0
  148. package/dist/types/index.js.map +1 -0
  149. package/dist/types/messages.d.ts +3734 -0
  150. package/dist/types/messages.d.ts.map +1 -0
  151. package/dist/types/messages.js +482 -0
  152. package/dist/types/messages.js.map +1 -0
  153. package/dist/types/validation.d.ts +81 -0
  154. package/dist/types/validation.d.ts.map +1 -0
  155. package/dist/types/validation.js +115 -0
  156. package/dist/types/validation.js.map +1 -0
  157. package/dist/utils/bounded-queue.d.ts +127 -0
  158. package/dist/utils/bounded-queue.d.ts.map +1 -0
  159. package/dist/utils/bounded-queue.js +181 -0
  160. package/dist/utils/bounded-queue.js.map +1 -0
  161. package/dist/utils/circuit-breaker.d.ts +141 -0
  162. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  163. package/dist/utils/circuit-breaker.js +215 -0
  164. package/dist/utils/circuit-breaker.js.map +1 -0
  165. package/dist/utils/deduplication-cache.d.ts +110 -0
  166. package/dist/utils/deduplication-cache.d.ts.map +1 -0
  167. package/dist/utils/deduplication-cache.js +177 -0
  168. package/dist/utils/deduplication-cache.js.map +1 -0
  169. package/dist/utils/event-waiter.d.ts +101 -0
  170. package/dist/utils/event-waiter.d.ts.map +1 -0
  171. package/dist/utils/event-waiter.js +118 -0
  172. package/dist/utils/event-waiter.js.map +1 -0
  173. package/dist/utils/index.d.ts +51 -0
  174. package/dist/utils/index.d.ts.map +1 -0
  175. package/dist/utils/index.js +72 -0
  176. package/dist/utils/index.js.map +1 -0
  177. package/dist/utils/logger.d.ts +22 -0
  178. package/dist/utils/logger.d.ts.map +1 -0
  179. package/dist/utils/logger.js +91 -0
  180. package/dist/utils/logger.js.map +1 -0
  181. package/dist/utils/rate-limiter.d.ts +122 -0
  182. package/dist/utils/rate-limiter.d.ts.map +1 -0
  183. package/dist/utils/rate-limiter.js +190 -0
  184. package/dist/utils/rate-limiter.js.map +1 -0
  185. package/dist/utils/retry-policy.d.ts +191 -0
  186. package/dist/utils/retry-policy.d.ts.map +1 -0
  187. package/dist/utils/retry-policy.js +225 -0
  188. package/dist/utils/retry-policy.js.map +1 -0
  189. package/dist/utils/secure-private-key.d.ts +113 -0
  190. package/dist/utils/secure-private-key.d.ts.map +1 -0
  191. package/dist/utils/secure-private-key.js +188 -0
  192. package/dist/utils/secure-private-key.js.map +1 -0
  193. package/dist/utils/signature-verifier.d.ts +143 -0
  194. package/dist/utils/signature-verifier.d.ts.map +1 -0
  195. package/dist/utils/signature-verifier.js +238 -0
  196. package/dist/utils/signature-verifier.js.map +1 -0
  197. package/dist/utils/ssrf-validator.d.ts +36 -0
  198. package/dist/utils/ssrf-validator.d.ts.map +1 -0
  199. package/dist/utils/ssrf-validator.js +195 -0
  200. package/dist/utils/ssrf-validator.js.map +1 -0
  201. package/examples/.env.example +17 -0
  202. package/examples/basic-usage.ts +211 -0
  203. package/examples/production-dashboard/.env.example +153 -0
  204. package/examples/production-dashboard/package.json +39 -0
  205. package/examples/production-dashboard/public/dashboard.html +642 -0
  206. package/examples/production-dashboard/server.ts +753 -0
  207. package/examples/webhook-integration.ts +239 -0
  208. package/examples/x-influencer-battle-redesign.html +1065 -0
  209. package/examples/x-influencer-battle-server.ts +217 -0
  210. package/examples/x-influencer-battle.html +787 -0
  211. package/package.json +65 -0
  212. package/src/constants.ts +43 -0
  213. package/src/core/websocket-client.test.ts +512 -0
  214. package/src/core/websocket-client.ts +1056 -0
  215. package/src/formatters/response-formatter.test.ts +571 -0
  216. package/src/formatters/response-formatter.ts +677 -0
  217. package/src/handlers/message-handler-registry.ts +239 -0
  218. package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
  219. package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
  220. package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
  221. package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
  222. package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
  223. package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
  224. package/src/handlers/message-handlers/base-handler.ts +101 -0
  225. package/src/handlers/message-handlers/challenge-handler.ts +57 -0
  226. package/src/handlers/message-handlers/error-message-handler.ts +27 -0
  227. package/src/handlers/message-handlers/index.ts +77 -0
  228. package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
  229. package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
  230. package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
  231. package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
  232. package/src/handlers/message-handlers/task-response-handler.ts +45 -0
  233. package/src/handlers/message-handlers/types.ts +77 -0
  234. package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
  235. package/src/handlers/webhook-handler.test.ts +789 -0
  236. package/src/handlers/webhook-handler.ts +576 -0
  237. package/src/index.ts +269 -0
  238. package/src/managers/agent-registry.test.ts +466 -0
  239. package/src/managers/agent-registry.ts +347 -0
  240. package/src/managers/connection-manager.ts +195 -0
  241. package/src/managers/index.ts +9 -0
  242. package/src/managers/message-router.ts +349 -0
  243. package/src/managers/room-manager.ts +248 -0
  244. package/src/teneo-sdk.ts +1022 -0
  245. package/src/types/config.test.ts +325 -0
  246. package/src/types/config.ts +799 -0
  247. package/src/types/error-codes.ts +44 -0
  248. package/src/types/events.test.ts +302 -0
  249. package/src/types/events.ts +382 -0
  250. package/src/types/health.ts +46 -0
  251. package/src/types/index.ts +199 -0
  252. package/src/types/messages.test.ts +660 -0
  253. package/src/types/messages.ts +570 -0
  254. package/src/types/validation.ts +123 -0
  255. package/src/utils/bounded-queue.test.ts +356 -0
  256. package/src/utils/bounded-queue.ts +205 -0
  257. package/src/utils/circuit-breaker.test.ts +394 -0
  258. package/src/utils/circuit-breaker.ts +262 -0
  259. package/src/utils/deduplication-cache.test.ts +380 -0
  260. package/src/utils/deduplication-cache.ts +198 -0
  261. package/src/utils/event-waiter.test.ts +381 -0
  262. package/src/utils/event-waiter.ts +172 -0
  263. package/src/utils/index.ts +74 -0
  264. package/src/utils/logger.ts +87 -0
  265. package/src/utils/rate-limiter.test.ts +341 -0
  266. package/src/utils/rate-limiter.ts +211 -0
  267. package/src/utils/retry-policy.test.ts +558 -0
  268. package/src/utils/retry-policy.ts +272 -0
  269. package/src/utils/secure-private-key.test.ts +356 -0
  270. package/src/utils/secure-private-key.ts +205 -0
  271. package/src/utils/signature-verifier.test.ts +464 -0
  272. package/src/utils/signature-verifier.ts +298 -0
  273. package/src/utils/ssrf-validator.test.ts +372 -0
  274. package/src/utils/ssrf-validator.ts +224 -0
  275. package/tests/integration/real-server.test.ts +740 -0
  276. package/tests/integration/websocket.test.ts +381 -0
  277. package/tests/integration-setup.ts +16 -0
  278. package/tests/setup.ts +34 -0
  279. package/tsconfig.json +32 -0
  280. package/vitest.config.ts +42 -0
  281. package/vitest.integration.config.ts +23 -0
@@ -0,0 +1,466 @@
1
+ /**
2
+ * Tests for AgentRegistry - Focus on PERF-3 indexed lookup optimizations
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from "vitest";
6
+ import { AgentRegistry } from "./agent-registry";
7
+ import { Agent, Logger } from "../types";
8
+
9
+ describe("AgentRegistry", () => {
10
+ let registry: AgentRegistry;
11
+ let mockLogger: Logger;
12
+
13
+ beforeEach(() => {
14
+ mockLogger = {
15
+ debug: vi.fn(),
16
+ info: vi.fn(),
17
+ warn: vi.fn(),
18
+ error: vi.fn()
19
+ };
20
+ registry = new AgentRegistry(mockLogger);
21
+ });
22
+
23
+ // Helper to create test agents
24
+ const createAgent = (
25
+ id: string,
26
+ name: string,
27
+ capabilityNames: string[] = [],
28
+ status: "online" | "offline" = "online"
29
+ ): Agent => ({
30
+ id,
31
+ name,
32
+ status,
33
+ capabilities: capabilityNames.map((cap) => ({
34
+ name: cap,
35
+ description: `Capability: ${cap}`
36
+ }))
37
+ });
38
+
39
+ describe("getAgents", () => {
40
+ it("should return empty array when no agents", () => {
41
+ const agents = registry.getAgents();
42
+ expect(agents).toEqual([]);
43
+ });
44
+
45
+ it("should return all agents", () => {
46
+ const agent1 = createAgent("agent-1", "Weather Agent", ["weather", "forecast"]);
47
+ const agent2 = createAgent("agent-2", "News Agent", ["news"]);
48
+
49
+ registry.updateAgents([agent1, agent2]);
50
+
51
+ const agents = registry.getAgents();
52
+ expect(agents).toHaveLength(2);
53
+ expect(agents[0].id).toBe("agent-1");
54
+ expect(agents[1].id).toBe("agent-2");
55
+ });
56
+
57
+ it("should return defensive copies to prevent mutation", () => {
58
+ const agent = createAgent("agent-1", "Test Agent");
59
+ registry.updateAgents([agent]);
60
+
61
+ const agents = registry.getAgents();
62
+ const firstAgent = agents[0] as any;
63
+
64
+ // Mutate returned object
65
+ firstAgent.name = "Modified";
66
+
67
+ // Original should be unchanged
68
+ const agents2 = registry.getAgents();
69
+ expect(agents2[0].name).toBe("Test Agent");
70
+ });
71
+
72
+ it("should use cached array on subsequent calls", () => {
73
+ const agent = createAgent("agent-1", "Test Agent");
74
+ registry.updateAgents([agent]);
75
+
76
+ const agents1 = registry.getAgents();
77
+ const agents2 = registry.getAgents();
78
+
79
+ // Should be different array instances but same content
80
+ expect(agents1).not.toBe(agents2); // Different refs due to defensive copying
81
+ expect(agents1[0].id).toBe(agents2[0].id);
82
+ });
83
+
84
+ it("should rebuild cache when agents are updated", () => {
85
+ const agent1 = createAgent("agent-1", "Agent 1");
86
+ registry.updateAgents([agent1]);
87
+
88
+ const agents1 = registry.getAgents();
89
+ expect(agents1).toHaveLength(1);
90
+
91
+ const agent2 = createAgent("agent-2", "Agent 2");
92
+ registry.updateAgent(agent2);
93
+
94
+ const agents2 = registry.getAgents();
95
+ expect(agents2).toHaveLength(2);
96
+ });
97
+ });
98
+
99
+ describe("getAgent", () => {
100
+ it("should return agent by ID", () => {
101
+ const agent = createAgent("agent-1", "Test Agent");
102
+ registry.updateAgents([agent]);
103
+
104
+ const result = registry.getAgent("agent-1");
105
+ expect(result).toBeDefined();
106
+ expect(result!.name).toBe("Test Agent");
107
+ });
108
+
109
+ it("should return undefined for non-existent agent", () => {
110
+ const result = registry.getAgent("non-existent");
111
+ expect(result).toBeUndefined();
112
+ });
113
+
114
+ it("should return defensive copy", () => {
115
+ const agent = createAgent("agent-1", "Test Agent");
116
+ registry.updateAgents([agent]);
117
+
118
+ const result = registry.getAgent("agent-1") as any;
119
+ result.name = "Modified";
120
+
121
+ const result2 = registry.getAgent("agent-1");
122
+ expect(result2!.name).toBe("Test Agent");
123
+ });
124
+ });
125
+
126
+ describe("findByCapability - PERF-3 indexed lookup", () => {
127
+ beforeEach(() => {
128
+ const agents = [
129
+ createAgent("agent-1", "Weather Agent", ["weather", "forecast"]),
130
+ createAgent("agent-2", "News Agent", ["news", "headlines"]),
131
+ createAgent("agent-3", "Weather API", ["weather", "api"]),
132
+ createAgent("agent-4", "Sports Agent", ["sports"])
133
+ ];
134
+ registry.updateAgents(agents);
135
+ });
136
+
137
+ it("should find agents by capability (case-insensitive)", () => {
138
+ const weatherAgents = registry.findByCapability("weather");
139
+ expect(weatherAgents).toHaveLength(2);
140
+ expect(weatherAgents.map((a) => a.id)).toContain("agent-1");
141
+ expect(weatherAgents.map((a) => a.id)).toContain("agent-3");
142
+ });
143
+
144
+ it("should find agents by capability (uppercase)", () => {
145
+ const weatherAgents = registry.findByCapability("WEATHER");
146
+ expect(weatherAgents).toHaveLength(2);
147
+ });
148
+
149
+ it("should return empty array for non-existent capability", () => {
150
+ const agents = registry.findByCapability("non-existent");
151
+ expect(agents).toEqual([]);
152
+ });
153
+
154
+ it("should find agents with specific capability", () => {
155
+ const forecastAgents = registry.findByCapability("forecast");
156
+ expect(forecastAgents).toHaveLength(1);
157
+ expect(forecastAgents[0].id).toBe("agent-1");
158
+ });
159
+
160
+ it("should return defensive copies", () => {
161
+ const agents = registry.findByCapability("weather") as any[];
162
+ agents[0].name = "Modified";
163
+
164
+ const agents2 = registry.findByCapability("weather");
165
+ expect(agents2[0].name).not.toBe("Modified");
166
+ });
167
+
168
+ it("should work with 100+ agents (performance test)", () => {
169
+ // Create 100 agents with various capabilities
170
+ const largeAgentSet: Agent[] = [];
171
+ for (let i = 0; i < 100; i++) {
172
+ const caps =
173
+ i % 10 === 0
174
+ ? ["rare-capability"]
175
+ : i % 3 === 0
176
+ ? ["common-capability-1", "common-capability-2"]
177
+ : ["common-capability-1"];
178
+ largeAgentSet.push(createAgent(`agent-${i}`, `Agent ${i}`, caps));
179
+ }
180
+ registry.updateAgents(largeAgentSet);
181
+
182
+ const start = Date.now();
183
+ const rareAgents = registry.findByCapability("rare-capability");
184
+ const duration = Date.now() - start;
185
+
186
+ expect(rareAgents).toHaveLength(10);
187
+ // Should be nearly instant with index (< 5ms)
188
+ expect(duration).toBeLessThan(5);
189
+ });
190
+ });
191
+
192
+ describe("findByName - PERF-3 token-based indexed lookup", () => {
193
+ beforeEach(() => {
194
+ const agents = [
195
+ createAgent("agent-1", "Weather Agent"),
196
+ createAgent("agent-2", "Weather API"),
197
+ createAgent("agent-3", "News Agent"),
198
+ createAgent("agent-4", "Sports Weather Agent"),
199
+ createAgent("agent-5", "API Gateway")
200
+ ];
201
+ registry.updateAgents(agents);
202
+ });
203
+
204
+ it("should find agents by single token", () => {
205
+ const weatherAgents = registry.findByName("weather");
206
+ expect(weatherAgents).toHaveLength(3);
207
+ expect(weatherAgents.map((a) => a.id)).toContain("agent-1");
208
+ expect(weatherAgents.map((a) => a.id)).toContain("agent-2");
209
+ expect(weatherAgents.map((a) => a.id)).toContain("agent-4");
210
+ });
211
+
212
+ it("should find agents by single token (case-insensitive)", () => {
213
+ const weatherAgents = registry.findByName("WEATHER");
214
+ expect(weatherAgents).toHaveLength(3);
215
+ });
216
+
217
+ it("should find agents by partial match", () => {
218
+ const apiAgents = registry.findByName("api");
219
+ expect(apiAgents).toHaveLength(2);
220
+ expect(apiAgents.map((a) => a.id)).toContain("agent-2");
221
+ expect(apiAgents.map((a) => a.id)).toContain("agent-5");
222
+ });
223
+
224
+ it("should find agents by multiple tokens (union)", () => {
225
+ const agents = registry.findByName("weather api");
226
+ // Should return all agents with "weather" OR "api"
227
+ expect(agents).toHaveLength(4); // agent-1, agent-2, agent-4, agent-5
228
+ });
229
+
230
+ it("should return empty array for non-matching name", () => {
231
+ const agents = registry.findByName("nonexistent");
232
+ expect(agents).toEqual([]);
233
+ });
234
+
235
+ it("should handle special characters in tokenization", () => {
236
+ const agent = createAgent("agent-6", "Weather-API_v2.0");
237
+ registry.updateAgent(agent);
238
+
239
+ const apiAgents = registry.findByName("api");
240
+ expect(apiAgents.some((a) => a.id === "agent-6")).toBe(true);
241
+
242
+ const v2Agents = registry.findByName("v2");
243
+ expect(v2Agents.some((a) => a.id === "agent-6")).toBe(true);
244
+ });
245
+
246
+ it("should return defensive copies", () => {
247
+ const agents = registry.findByName("weather") as any[];
248
+ agents[0].name = "Modified";
249
+
250
+ const agents2 = registry.findByName("weather");
251
+ expect(agents2[0].name).not.toBe("Modified");
252
+ });
253
+
254
+ it("should work with 100+ agents (performance test)", () => {
255
+ const largeAgentSet: Agent[] = [];
256
+ for (let i = 0; i < 100; i++) {
257
+ const name =
258
+ i % 10 === 0
259
+ ? `Rare Agent ${i}`
260
+ : i % 3 === 0
261
+ ? `Common Weather Agent ${i}`
262
+ : `Common Agent ${i}`;
263
+ largeAgentSet.push(createAgent(`agent-${i}`, name));
264
+ }
265
+ registry.updateAgents(largeAgentSet);
266
+
267
+ const start = Date.now();
268
+ const rareAgents = registry.findByName("rare");
269
+ const duration = Date.now() - start;
270
+
271
+ expect(rareAgents).toHaveLength(10);
272
+ // Should be nearly instant with index (< 5ms)
273
+ expect(duration).toBeLessThan(5);
274
+ });
275
+ });
276
+
277
+ describe("findByStatus - PERF-3 indexed lookup", () => {
278
+ beforeEach(() => {
279
+ const agents = [
280
+ createAgent("agent-1", "Agent 1", [], "online"),
281
+ createAgent("agent-2", "Agent 2", [], "offline"),
282
+ createAgent("agent-3", "Agent 3", [], "online"),
283
+ createAgent("agent-4", "Agent 4", [], "offline"),
284
+ createAgent("agent-5", "Agent 5", [], "online")
285
+ ];
286
+ registry.updateAgents(agents);
287
+ });
288
+
289
+ it("should find online agents", () => {
290
+ const onlineAgents = registry.findByStatus("online");
291
+ expect(onlineAgents).toHaveLength(3);
292
+ expect(onlineAgents.map((a) => a.id)).toContain("agent-1");
293
+ expect(onlineAgents.map((a) => a.id)).toContain("agent-3");
294
+ expect(onlineAgents.map((a) => a.id)).toContain("agent-5");
295
+ });
296
+
297
+ it("should find offline agents", () => {
298
+ const offlineAgents = registry.findByStatus("offline");
299
+ expect(offlineAgents).toHaveLength(2);
300
+ expect(offlineAgents.map((a) => a.id)).toContain("agent-2");
301
+ expect(offlineAgents.map((a) => a.id)).toContain("agent-4");
302
+ });
303
+
304
+ it("should be case-insensitive", () => {
305
+ const onlineAgents = registry.findByStatus("ONLINE");
306
+ expect(onlineAgents).toHaveLength(3);
307
+ });
308
+
309
+ it("should return empty array for invalid status", () => {
310
+ const agents = registry.findByStatus("unknown");
311
+ expect(agents).toEqual([]);
312
+ });
313
+
314
+ it("should return defensive copies", () => {
315
+ const agents = registry.findByStatus("online") as any[];
316
+ agents[0].name = "Modified";
317
+
318
+ const agents2 = registry.findByStatus("online");
319
+ expect(agents2[0].name).not.toBe("Modified");
320
+ });
321
+
322
+ it("should update when agent status changes", () => {
323
+ let onlineAgents = registry.findByStatus("online");
324
+ expect(onlineAgents).toHaveLength(3);
325
+
326
+ // Update agent-1 to offline
327
+ const updatedAgent = createAgent("agent-1", "Agent 1", [], "offline");
328
+ registry.updateAgent(updatedAgent);
329
+
330
+ onlineAgents = registry.findByStatus("online");
331
+ expect(onlineAgents).toHaveLength(2);
332
+
333
+ const offlineAgents = registry.findByStatus("offline");
334
+ expect(offlineAgents).toHaveLength(3);
335
+ });
336
+ });
337
+
338
+ describe("updateAgents", () => {
339
+ it("should add new agents", () => {
340
+ const agents = [createAgent("agent-1", "Agent 1"), createAgent("agent-2", "Agent 2")];
341
+ registry.updateAgents(agents);
342
+
343
+ expect(registry.getAgents()).toHaveLength(2);
344
+ });
345
+
346
+ it("should emit agent:list event", () => {
347
+ const emitSpy = vi.spyOn(registry, "emit");
348
+ const agents = [createAgent("agent-1", "Agent 1")];
349
+
350
+ registry.updateAgents(agents);
351
+
352
+ expect(emitSpy).toHaveBeenCalledWith("agent:list", agents);
353
+ });
354
+
355
+ it("should mark cache as dirty", () => {
356
+ const agent1 = createAgent("agent-1", "Agent 1");
357
+ registry.updateAgents([agent1]);
358
+
359
+ const agents1 = registry.getAgents();
360
+
361
+ const agent2 = createAgent("agent-2", "Agent 2");
362
+ registry.updateAgents([agent2]);
363
+
364
+ const agents2 = registry.getAgents();
365
+
366
+ // Cache should have been rebuilt
367
+ expect(agents2).toHaveLength(2);
368
+ });
369
+ });
370
+
371
+ describe("updateAgent", () => {
372
+ it("should update existing agent", () => {
373
+ const agent = createAgent("agent-1", "Original Name");
374
+ registry.updateAgents([agent]);
375
+
376
+ const updatedAgent = createAgent("agent-1", "New Name");
377
+ registry.updateAgent(updatedAgent);
378
+
379
+ const result = registry.getAgent("agent-1");
380
+ expect(result!.name).toBe("New Name");
381
+ });
382
+
383
+ it("should add new agent if not exists", () => {
384
+ const agent = createAgent("agent-1", "Agent 1");
385
+ registry.updateAgent(agent);
386
+
387
+ expect(registry.getAgents()).toHaveLength(1);
388
+ });
389
+
390
+ it("should mark cache as dirty", () => {
391
+ const agent = createAgent("agent-1", "Agent 1");
392
+ registry.updateAgent(agent);
393
+
394
+ const agents = registry.getAgents();
395
+ expect(agents).toHaveLength(1);
396
+ });
397
+ });
398
+
399
+ describe("clear", () => {
400
+ it("should remove all agents", () => {
401
+ const agents = [createAgent("agent-1", "Agent 1"), createAgent("agent-2", "Agent 2")];
402
+ registry.updateAgents(agents);
403
+
404
+ registry.clear();
405
+
406
+ expect(registry.getAgents()).toEqual([]);
407
+ });
408
+
409
+ it("should clear all indices", () => {
410
+ const agent = createAgent("agent-1", "Test Agent", ["weather"]);
411
+ registry.updateAgents([agent]);
412
+
413
+ registry.clear();
414
+
415
+ expect(registry.findByCapability("weather")).toEqual([]);
416
+ expect(registry.findByName("test")).toEqual([]);
417
+ expect(registry.findByStatus("online")).toEqual([]);
418
+ });
419
+ });
420
+
421
+ describe("destroy", () => {
422
+ it("should clear all agents", () => {
423
+ const agents = [createAgent("agent-1", "Agent 1")];
424
+ registry.updateAgents(agents);
425
+
426
+ registry.destroy();
427
+
428
+ expect(registry.getAgents()).toEqual([]);
429
+ });
430
+
431
+ it("should remove all event listeners", () => {
432
+ const handler = vi.fn();
433
+ registry.on("agent:list", handler);
434
+
435
+ registry.destroy();
436
+
437
+ registry.updateAgents([createAgent("agent-1", "Agent 1")]);
438
+
439
+ expect(handler).not.toHaveBeenCalled();
440
+ });
441
+ });
442
+
443
+ describe("Cache consistency", () => {
444
+ it("should keep indices in sync with agent updates", () => {
445
+ const agent1 = createAgent("agent-1", "Weather Agent", ["weather"], "online");
446
+ registry.updateAgents([agent1]);
447
+
448
+ // Initial state
449
+ expect(registry.findByCapability("weather")).toHaveLength(1);
450
+ expect(registry.findByName("weather")).toHaveLength(1);
451
+ expect(registry.findByStatus("online")).toHaveLength(1);
452
+
453
+ // Update agent
454
+ const agent2 = createAgent("agent-1", "News Agent", ["news"], "offline");
455
+ registry.updateAgent(agent2);
456
+
457
+ // Indices should be updated
458
+ expect(registry.findByCapability("weather")).toHaveLength(0);
459
+ expect(registry.findByCapability("news")).toHaveLength(1);
460
+ expect(registry.findByName("weather")).toHaveLength(0);
461
+ expect(registry.findByName("news")).toHaveLength(1);
462
+ expect(registry.findByStatus("online")).toHaveLength(0);
463
+ expect(registry.findByStatus("offline")).toHaveLength(1);
464
+ });
465
+ });
466
+ });