@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,753 @@
1
+ /**
2
+ * Production Dashboard Example - Hono + Bun
3
+ *
4
+ * A comprehensive example demonstrating ALL Teneo Protocol SDK features:
5
+ * - WebSocket connection with auto-reconnection
6
+ * - Ethereum wallet authentication
7
+ * - Private key encryption in memory (SEC-3)
8
+ * - Message signature verification (SEC-2)
9
+ * - Message deduplication cache (CB-4)
10
+ * - Indexed agent lookups (PERF-3)
11
+ * - Configurable retry strategies (REL-3)
12
+ * - Webhook integration with circuit breaker
13
+ * - Rate limiting
14
+ * - Health monitoring
15
+ * - Real-time event streaming
16
+ * - Complete error handling
17
+ *
18
+ * Run with: bun run server.ts
19
+ */
20
+
21
+ import { serve } from "@hono/node-server";
22
+ import * as fs from "fs";
23
+ import { Hono } from "hono";
24
+ import * as path from "path";
25
+ import type { AgentResponse } from "../../dist/index.js";
26
+ import { SDKConfigBuilder, TeneoSDK, SecurePrivateKey } from "../../dist/index.js";
27
+
28
+ // Load environment variables
29
+ const PORT = parseInt(process.env.PORT || "3000");
30
+ const WS_URL =
31
+ process.env.WS_URL;
32
+ const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
33
+ const WALLET_ADDRESS = process.env.WALLET_ADDRESS || "";
34
+ const DEFAULT_ROOM = process.env.DEFAULT_ROOM || "as1LfBarJNzOIpOQJQ7PH";
35
+ const ENABLE_SIG_VERIFICATION = process.env.ENABLE_SIGNATURE_VERIFICATION === "true";
36
+ const TRUSTED_ADDRESSES = process.env.TRUSTED_ADDRESSES?.split(",").filter(Boolean) || [];
37
+
38
+ // Create Hono app
39
+ const app = new Hono();
40
+
41
+ // In-memory storage for demo
42
+ interface StoredEvent {
43
+ type: string;
44
+ timestamp: string;
45
+ data: any;
46
+ }
47
+
48
+ interface StoredMessage {
49
+ id: string;
50
+ timestamp: string;
51
+ content: string;
52
+ from: string;
53
+ response?: AgentResponse;
54
+ }
55
+
56
+ const recentEvents: StoredEvent[] = [];
57
+ const recentMessages: StoredMessage[] = [];
58
+ const recentWebhooks: any[] = [];
59
+ let messageCounter = 0;
60
+ let errorCounter = 0;
61
+ let sdk: TeneoSDK | null = null;
62
+ const sseClients: Set<ReadableStreamDefaultController> = new Set();
63
+
64
+ // Initialize SDK with all features
65
+ async function initializeSDK() {
66
+ console.log("[SERVER] Initializing Teneo SDK with all features...");
67
+
68
+ try {
69
+ // SEC-3: Use encrypted private key in memory
70
+ // This protects the private key from memory dumps and inspection
71
+ const secureKey = new SecurePrivateKey(PRIVATE_KEY);
72
+ console.log("[SDK] Private key encrypted in memory (SEC-3)");
73
+
74
+ const config = new SDKConfigBuilder()
75
+ .withWebSocketUrl(WS_URL)
76
+ .withAuthentication(secureKey, WALLET_ADDRESS) // Pass SecurePrivateKey instead of plain string
77
+ .withAutoJoinRooms([DEFAULT_ROOM])
78
+ .withReconnection({ enabled: true, delay: 5000, maxAttempts: 10 })
79
+ // REL-3: Configure custom retry strategies for production resilience
80
+ .withReconnectionStrategy({
81
+ type: "exponential",
82
+ baseDelay: 3000, // Start with 3s instead of default 5s
83
+ maxDelay: 120000, // Max 2 minutes instead of default 1 minute
84
+ maxAttempts: 15, // More attempts for production reliability
85
+ jitter: true, // Prevent thundering herd
86
+ backoffMultiplier: 2.5 // Faster backoff than default 2
87
+ })
88
+ .withWebhookRetryStrategy({
89
+ type: "exponential",
90
+ baseDelay: 1000,
91
+ maxDelay: 30000,
92
+ maxAttempts: 5, // More attempts for webhook reliability
93
+ jitter: false, // Predictable delays for webhooks
94
+ backoffMultiplier: 2
95
+ })
96
+ .withResponseFormat({ format: "both", includeMetadata: true })
97
+ .withLogging("debug")
98
+ .withCache(true, 300000, 100)
99
+ // CB-4: Message deduplication to prevent duplicate processing
100
+ .withMessageDeduplication(
101
+ true, // Enabled by default
102
+ 120000, // 2 minute TTL (increased from default 60s for production)
103
+ 50000 // 50k message cache (increased from default 10k for high volume)
104
+ )
105
+ .withSignatureVerification({
106
+ enabled: ENABLE_SIG_VERIFICATION,
107
+ trustedAddresses: TRUSTED_ADDRESSES,
108
+ requireFor: ["task_response", "agent_selected"],
109
+ strictMode: false
110
+ })
111
+ .build();
112
+
113
+ // Allow localhost webhooks for development
114
+ config.allowInsecureWebhooks = true;
115
+
116
+ sdk = new TeneoSDK(config);
117
+
118
+ // Configure webhook to point to our server
119
+ sdk.configureWebhook(`http://localhost:${PORT}/webhook`, {
120
+ "X-API-Key": "production-dashboard-secret",
121
+ "Content-Type": "application/json"
122
+ });
123
+
124
+ // Set up comprehensive event listeners
125
+ setupSDKEventListeners(sdk);
126
+
127
+ // Connect to Teneo network
128
+ console.log("[SDK] Connecting to Teneo network...");
129
+ await sdk.connect();
130
+ console.log("[SDK] Successfully connected and authenticated!");
131
+
132
+ return sdk;
133
+ } catch (error) {
134
+ console.error("[SDK] Failed to initialize:", error);
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ // Setup all SDK event listeners
140
+ function setupSDKEventListeners(sdk: TeneoSDK) {
141
+ // Connection events
142
+ sdk.on("connection:open", () => {
143
+ addEvent("connection:open", { message: "Connected to WebSocket" });
144
+ broadcastSSE({ type: "connection", status: "connected" });
145
+ });
146
+
147
+ sdk.on("connection:close", (code, reason) => {
148
+ addEvent("connection:close", { code, reason });
149
+ broadcastSSE({ type: "connection", status: "disconnected", code, reason });
150
+ });
151
+
152
+ sdk.on("connection:reconnecting", (attempt) => {
153
+ addEvent("connection:reconnecting", { attempt });
154
+ broadcastSSE({ type: "connection", status: "reconnecting", attempt });
155
+ });
156
+
157
+ sdk.on("connection:reconnected", () => {
158
+ addEvent("connection:reconnected", { message: "Reconnected successfully" });
159
+ broadcastSSE({ type: "connection", status: "reconnected" });
160
+ });
161
+
162
+ // Authentication events
163
+ sdk.on("auth:challenge", (challenge) => {
164
+ addEvent("auth:challenge", { challenge: challenge.substring(0, 20) + "..." });
165
+ });
166
+
167
+ sdk.on("auth:success", (state) => {
168
+ addEvent("auth:success", {
169
+ walletAddress: state.walletAddress,
170
+ rooms: state.rooms?.length || 0,
171
+ isWhitelisted: state.isWhitelisted
172
+ });
173
+ broadcastSSE({ type: "auth", status: "success", state });
174
+ });
175
+
176
+ sdk.on("auth:error", (error) => {
177
+ addEvent("auth:error", { error });
178
+ errorCounter++;
179
+ });
180
+
181
+ // Signature verification events
182
+ sdk.on("signature:verified", (messageType, address) => {
183
+ addEvent("signature:verified", { messageType, address });
184
+ });
185
+
186
+ sdk.on("signature:failed", (messageType, reason, address) => {
187
+ addEvent("signature:failed", { messageType, reason, address });
188
+ errorCounter++;
189
+ });
190
+
191
+ // Deduplication events (CB-4)
192
+ sdk.on("message:duplicate", (message) => {
193
+ addEvent("message:duplicate", {
194
+ messageType: message.type,
195
+ messageId: message.id,
196
+ from: message.from
197
+ });
198
+ broadcastSSE({ type: "message:duplicate", message: { type: message.type, id: message.id } });
199
+ });
200
+
201
+ // Agent events
202
+ sdk.on("agent:selected", (data) => {
203
+ addEvent("agent:selected", {
204
+ agentName: data.agentName,
205
+ reasoning: data.reasoning,
206
+ command: data.command
207
+ });
208
+ broadcastSSE({ type: "agent:selected", data });
209
+ });
210
+
211
+ sdk.on("agent:response", (response) => {
212
+ addEvent("agent:response", {
213
+ agentName: response.agentName,
214
+ success: response.success,
215
+ contentLength: response.content?.length || 0
216
+ });
217
+
218
+ // Update stored message with response
219
+ const msg = recentMessages.find((m) => !m.response);
220
+ if (msg) {
221
+ msg.response = response;
222
+ }
223
+
224
+ broadcastSSE({ type: "agent:response", response });
225
+ });
226
+
227
+ sdk.on("agent:list", (agents) => {
228
+ addEvent("agent:list", { count: agents.length });
229
+ broadcastSSE({ type: "agent:list", agents });
230
+ });
231
+
232
+ // Room events
233
+ sdk.on("room:subscribed", (data) => {
234
+ addEvent("room:subscribed", { roomId: data.roomId, subscriptions: data.subscriptions });
235
+ broadcastSSE({ type: "room:subscribed", data });
236
+ });
237
+
238
+ sdk.on("room:unsubscribed", (data) => {
239
+ addEvent("room:unsubscribed", { roomId: data.roomId, subscriptions: data.subscriptions });
240
+ broadcastSSE({ type: "room:unsubscribed", data });
241
+ });
242
+
243
+ sdk.on("room:list", (rooms) => {
244
+ addEvent("room:list", { count: rooms.length });
245
+ broadcastSSE({ type: "room:list", rooms });
246
+ });
247
+
248
+ // Webhook events
249
+ sdk.on("webhook:sent", (payload, url) => {
250
+ addEvent("webhook:sent", { event: payload.event, url });
251
+ });
252
+
253
+ sdk.on("webhook:success", (response, url) => {
254
+ addEvent("webhook:success", { url, status: response.status });
255
+ });
256
+
257
+ sdk.on("webhook:error", (error, url) => {
258
+ addEvent("webhook:error", { error: error.message, url });
259
+ errorCounter++;
260
+ });
261
+
262
+ sdk.on("webhook:retry", (attempt, url) => {
263
+ addEvent("webhook:retry", { attempt, url });
264
+ });
265
+
266
+ // Error events
267
+ sdk.on("error", (error) => {
268
+ addEvent("error", {
269
+ name: error.name,
270
+ message: error.message,
271
+ code: error.code,
272
+ recoverable: error.recoverable
273
+ });
274
+ errorCounter++;
275
+ broadcastSSE({ type: "error", error: { message: error.message, code: error.code } });
276
+ });
277
+
278
+ sdk.on("warning", (warning) => {
279
+ addEvent("warning", { warning });
280
+ });
281
+
282
+ // Lifecycle events
283
+ sdk.on("ready", () => {
284
+ addEvent("ready", { message: "SDK ready" });
285
+ broadcastSSE({ type: "ready" });
286
+ });
287
+ }
288
+
289
+ // Helper functions
290
+ function addEvent(type: string, data: any) {
291
+ const event: StoredEvent = {
292
+ type,
293
+ timestamp: new Date().toISOString(),
294
+ data
295
+ };
296
+ recentEvents.unshift(event);
297
+ if (recentEvents.length > 100) recentEvents.pop();
298
+ }
299
+
300
+ function broadcastSSE(data: any) {
301
+ const message = `data: ${JSON.stringify(data)}\n\n`;
302
+ sseClients.forEach((controller) => {
303
+ try {
304
+ controller.enqueue(new TextEncoder().encode(message));
305
+ } catch (error) {
306
+ sseClients.delete(controller);
307
+ }
308
+ });
309
+ }
310
+
311
+ // ===== API ROUTES =====
312
+
313
+ // Serve dashboard
314
+ app.get("/", (c) => {
315
+ const htmlPath = path.join(__dirname, "public", "dashboard.html");
316
+ const html = fs.readFileSync(htmlPath, "utf-8");
317
+ return c.html(html);
318
+ });
319
+
320
+ // Health check endpoint
321
+ app.get("/health", async (c) => {
322
+ if (!sdk) {
323
+ return c.json({ status: "unhealthy", error: "SDK not initialized" }, 503);
324
+ }
325
+
326
+ const health = sdk.getHealth();
327
+ return c.json(health);
328
+ });
329
+
330
+ // Metrics endpoint
331
+ app.get("/metrics", (c) => {
332
+ if (!sdk) {
333
+ return c.json({ error: "SDK not initialized" }, 503);
334
+ }
335
+
336
+ const connectionState = sdk.getConnectionState();
337
+ const authState = sdk.getAuthState();
338
+ const webhookStatus = sdk.getWebhookStatus();
339
+ const agents = sdk.getAgents();
340
+ const rooms = sdk.getRooms();
341
+
342
+ return c.json({
343
+ connection: {
344
+ connected: sdk.isConnected,
345
+ authenticated: sdk.isAuthenticated,
346
+ reconnectAttempts: connectionState.reconnectAttempts
347
+ },
348
+ auth: {
349
+ walletAddress: authState.walletAddress,
350
+ rooms: authState.rooms?.length || 0
351
+ },
352
+ agents: {
353
+ total: agents.length,
354
+ online: agents.filter((a) => a.status === "online").length
355
+ },
356
+ rooms: {
357
+ total: rooms.length,
358
+ subscribedRooms: sdk.getSubscribedRooms()
359
+ },
360
+ webhooks: {
361
+ configured: webhookStatus.configured,
362
+ pending: webhookStatus.queue.pending,
363
+ failed: webhookStatus.queue.failed,
364
+ circuitState: webhookStatus.queue.circuitState
365
+ },
366
+ messages: {
367
+ sent: messageCounter,
368
+ recent: recentMessages.length
369
+ },
370
+ errors: {
371
+ total: errorCounter
372
+ },
373
+ uptime: process.uptime()
374
+ });
375
+ });
376
+
377
+ // CB-4: Deduplication status endpoint
378
+ app.get("/api/deduplication", (c) => {
379
+ if (!sdk) {
380
+ return c.json({ error: "SDK not initialized" }, 503);
381
+ }
382
+
383
+ const status = sdk.getDeduplicationStatus();
384
+
385
+ if (!status) {
386
+ return c.json({
387
+ enabled: false,
388
+ message: "Message deduplication is not enabled"
389
+ });
390
+ }
391
+
392
+ return c.json({
393
+ enabled: true,
394
+ ...status,
395
+ utilization: ((status.cacheSize / status.maxSize) * 100).toFixed(2) + "%"
396
+ });
397
+ });
398
+
399
+ // Webhook receiver endpoint
400
+ app.post("/webhook", async (c) => {
401
+ const payload = await c.req.json();
402
+
403
+ console.log("[WEBHOOK] Received:", payload.event);
404
+
405
+ // Store webhook for display
406
+ recentWebhooks.unshift({
407
+ ...payload,
408
+ receivedAt: new Date().toISOString()
409
+ });
410
+ if (recentWebhooks.length > 50) recentWebhooks.pop();
411
+
412
+ // Update message with full task response if available
413
+ if (payload.event === "task_response" && payload.data) {
414
+ const msg = recentMessages.find((m) => !m.response || m.response.content?.includes("Started"));
415
+ if (msg) {
416
+ msg.response = {
417
+ taskId: payload.data.taskId,
418
+ agentId: payload.data.agentId,
419
+ agentName: payload.data.agentName || "Agent",
420
+ content: payload.data.content,
421
+ contentType: payload.data.contentType,
422
+ success: payload.data.success !== false,
423
+ timestamp: payload.data.timestamp || new Date().toISOString(),
424
+ humanized: payload.data.humanized || payload.data.content
425
+ };
426
+ broadcastSSE({ type: "message:updated", message: msg });
427
+ }
428
+ }
429
+
430
+ // Broadcast to dashboard
431
+ broadcastSSE({ type: "webhook:received", payload });
432
+
433
+ return c.json({ status: "success", received: true });
434
+ });
435
+
436
+ // Send message
437
+ app.post("/api/message", async (c) => {
438
+ if (!sdk || !sdk.isConnected) {
439
+ return c.json({ error: "SDK not connected" }, 503);
440
+ }
441
+
442
+ try {
443
+ const { content, waitForResponse = false } = await c.req.json();
444
+
445
+ if (!content || typeof content !== "string") {
446
+ return c.json({ error: "Content is required" }, 400);
447
+ }
448
+
449
+ const messageId = `msg_${Date.now()}`;
450
+ const storedMessage: StoredMessage = {
451
+ id: messageId,
452
+ timestamp: new Date().toISOString(),
453
+ content,
454
+ from: "dashboard"
455
+ };
456
+ recentMessages.unshift(storedMessage);
457
+ if (recentMessages.length > 100) recentMessages.pop();
458
+ messageCounter++;
459
+
460
+ const response = await sdk.sendMessage(content, {
461
+ room: DEFAULT_ROOM,
462
+ waitForResponse,
463
+ timeout: 60000
464
+ });
465
+
466
+ if (response) {
467
+ storedMessage.response = response as AgentResponse;
468
+ }
469
+
470
+ return c.json({
471
+ success: true,
472
+ messageId,
473
+ response: response || null
474
+ });
475
+ } catch (error: any) {
476
+ return c.json({ error: error.message }, 500);
477
+ }
478
+ });
479
+
480
+ // Send direct command
481
+ app.post("/api/direct-command", async (c) => {
482
+ if (!sdk || !sdk.isConnected) {
483
+ return c.json({ error: "SDK not connected" }, 503);
484
+ }
485
+
486
+ try {
487
+ const { agent, command, room } = await c.req.json();
488
+
489
+ if (!agent || !command) {
490
+ return c.json({ error: "Agent and command are required" }, 400);
491
+ }
492
+
493
+ await sdk.sendDirectCommand({ agent, command, room });
494
+
495
+ return c.json({ success: true });
496
+ } catch (error: any) {
497
+ return c.json({ error: error.message }, 500);
498
+ }
499
+ });
500
+
501
+ // Get agents
502
+ app.get("/api/agents", (c) => {
503
+ if (!sdk) {
504
+ return c.json({ error: "SDK not initialized" }, 503);
505
+ }
506
+
507
+ const agents = sdk.getAgents();
508
+ return c.json(agents);
509
+ });
510
+
511
+ // PERF-3: Search agents by capability (O(1) indexed lookup)
512
+ app.get("/api/agents/search/capability/:capability", (c) => {
513
+ if (!sdk) {
514
+ return c.json({ error: "SDK not initialized" }, 503);
515
+ }
516
+
517
+ try {
518
+ const capability = c.req.param("capability");
519
+ const agents = sdk.findAgentsByCapability(capability);
520
+ return c.json({
521
+ capability,
522
+ count: agents.length,
523
+ agents
524
+ });
525
+ } catch (error: any) {
526
+ return c.json({ error: error.message }, 400);
527
+ }
528
+ });
529
+
530
+ // PERF-3: Search agents by name (O(k) token-based lookup)
531
+ app.get("/api/agents/search/name/:name", (c) => {
532
+ if (!sdk) {
533
+ return c.json({ error: "SDK not initialized" }, 503);
534
+ }
535
+
536
+ try {
537
+ const name = c.req.param("name");
538
+ const agents = sdk.findAgentsByName(name);
539
+ return c.json({
540
+ query: name,
541
+ count: agents.length,
542
+ agents
543
+ });
544
+ } catch (error: any) {
545
+ return c.json({ error: error.message }, 400);
546
+ }
547
+ });
548
+
549
+ // PERF-3: Search agents by status (O(1) indexed lookup)
550
+ app.get("/api/agents/search/status/:status", (c) => {
551
+ if (!sdk) {
552
+ return c.json({ error: "SDK not initialized" }, 503);
553
+ }
554
+
555
+ try {
556
+ const status = c.req.param("status");
557
+ const agents = sdk.findAgentsByStatus(status);
558
+ return c.json({
559
+ status,
560
+ count: agents.length,
561
+ agents
562
+ });
563
+ } catch (error: any) {
564
+ return c.json({ error: error.message }, 400);
565
+ }
566
+ });
567
+
568
+ // Get rooms
569
+ app.get("/api/rooms", (c) => {
570
+ if (!sdk) {
571
+ return c.json({ error: "SDK not initialized" }, 503);
572
+ }
573
+
574
+ const rooms = sdk.getRooms();
575
+ return c.json(rooms);
576
+ });
577
+
578
+ // List all rooms
579
+ app.get("/api/rooms/list", async (c) => {
580
+ if (!sdk || !sdk.isConnected) {
581
+ return c.json({ error: "SDK not connected" }, 503);
582
+ }
583
+
584
+ try {
585
+ const rooms = await sdk.listRooms();
586
+ return c.json(rooms);
587
+ } catch (error: any) {
588
+ return c.json({ error: error.message }, 500);
589
+ }
590
+ });
591
+
592
+ // Join room
593
+ app.post("/api/room/join", async (c) => {
594
+ if (!sdk || !sdk.isConnected) {
595
+ return c.json({ error: "SDK not connected" }, 503);
596
+ }
597
+
598
+ try {
599
+ const { roomId } = await c.req.json();
600
+
601
+ if (!roomId) {
602
+ return c.json({ error: "Room ID is required" }, 400);
603
+ }
604
+
605
+ await sdk.subscribeToRoom(roomId);
606
+ return c.json({ success: true });
607
+ } catch (error: any) {
608
+ return c.json({ error: error.message }, 500);
609
+ }
610
+ });
611
+
612
+ // Leave room
613
+ app.post("/api/room/leave", async (c) => {
614
+ if (!sdk || !sdk.isConnected) {
615
+ return c.json({ error: "SDK not connected" }, 503);
616
+ }
617
+
618
+ try {
619
+ const { roomId } = await c.req.json();
620
+
621
+ if (!roomId) {
622
+ return c.json({ error: "Room ID is required" }, 400);
623
+ }
624
+
625
+ await sdk.unsubscribeFromRoom(roomId);
626
+ return c.json({ success: true });
627
+ } catch (error: any) {
628
+ return c.json({ error: error.message }, 500);
629
+ }
630
+ });
631
+
632
+ // Get recent events
633
+ app.get("/api/events", (c) => {
634
+ return c.json(recentEvents.slice(0, 50));
635
+ });
636
+
637
+ // Get recent messages
638
+ app.get("/api/messages", (c) => {
639
+ return c.json(recentMessages.slice(0, 20));
640
+ });
641
+
642
+ // Get recent webhooks
643
+ app.get("/api/webhooks", (c) => {
644
+ return c.json(recentWebhooks.slice(0, 20));
645
+ });
646
+
647
+ // Server-Sent Events for real-time updates
648
+ app.get("/api/sse", (c) => {
649
+ const stream = new ReadableStream({
650
+ start(controller) {
651
+ sseClients.add(controller);
652
+
653
+ // Send initial connection and auth status
654
+ if (sdk) {
655
+ const connectionStatus = sdk.isConnected ? "connected" : "disconnected";
656
+ const authStatus = sdk.isAuthenticated ? "success" : "pending";
657
+
658
+ controller.enqueue(
659
+ new TextEncoder().encode(
660
+ `data: ${JSON.stringify({ type: "connection", status: connectionStatus })}\n\n`
661
+ )
662
+ );
663
+ controller.enqueue(
664
+ new TextEncoder().encode(
665
+ `data: ${JSON.stringify({ type: "auth", status: authStatus })}\n\n`
666
+ )
667
+ );
668
+ }
669
+
670
+ // Keep-alive ping every 30 seconds
671
+ const keepAlive = setInterval(() => {
672
+ try {
673
+ controller.enqueue(new TextEncoder().encode(": ping\n\n"));
674
+ } catch {
675
+ clearInterval(keepAlive);
676
+ sseClients.delete(controller);
677
+ }
678
+ }, 30000);
679
+
680
+ return () => {
681
+ clearInterval(keepAlive);
682
+ sseClients.delete(controller);
683
+ };
684
+ },
685
+ cancel() {
686
+ sseClients.delete(this as any);
687
+ }
688
+ });
689
+
690
+ return c.newResponse(stream, {
691
+ headers: {
692
+ "Content-Type": "text/event-stream",
693
+ "Cache-Control": "no-cache",
694
+ Connection: "keep-alive"
695
+ }
696
+ });
697
+ });
698
+
699
+ // Start server
700
+ async function startServer() {
701
+ try {
702
+ // Initialize SDK first
703
+ await initializeSDK();
704
+
705
+ // Start HTTP server
706
+ console.log(`[SERVER] Starting Hono server on port ${PORT}...`);
707
+
708
+ serve({
709
+ fetch: app.fetch,
710
+ port: PORT
711
+ });
712
+
713
+ console.log(`\nšŸš€ Production Dashboard running!`);
714
+ console.log(`šŸ“Š Dashboard: http://localhost:${PORT}`);
715
+ console.log(`ā¤ļø Health: http://localhost:${PORT}/health`);
716
+ console.log(`šŸ“ˆ Metrics: http://localhost:${PORT}/metrics`);
717
+ console.log(`\nPress Ctrl+C to stop\n`);
718
+ } catch (error) {
719
+ console.error("[SERVER] Failed to start:", error);
720
+ process.exit(1);
721
+ }
722
+ }
723
+
724
+ // Graceful shutdown
725
+ process.on("SIGINT", () => {
726
+ console.log("\n[SERVER] Shutting down gracefully...");
727
+
728
+ if (sdk) {
729
+ sdk.disconnect();
730
+ sdk.destroy();
731
+ }
732
+
733
+ sseClients.clear();
734
+
735
+ setTimeout(() => {
736
+ process.exit(0);
737
+ }, 1000);
738
+ });
739
+
740
+ process.on("SIGTERM", () => {
741
+ console.log("\n[SERVER] Received SIGTERM, shutting down...");
742
+
743
+ if (sdk) {
744
+ sdk.disconnect();
745
+ sdk.destroy();
746
+ }
747
+
748
+ sseClients.clear();
749
+ process.exit(0);
750
+ });
751
+
752
+ // Start the server
753
+ startServer();