@teneo-protocol/sdk 1.0.0 → 1.0.1

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 (97) hide show
  1. package/dist/core/websocket-client.d.ts +1 -0
  2. package/dist/core/websocket-client.d.ts.map +1 -1
  3. package/dist/core/websocket-client.js +12 -1
  4. package/dist/core/websocket-client.js.map +1 -1
  5. package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -1
  6. package/dist/handlers/message-handlers/regular-message-handler.js +1 -0
  7. package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -1
  8. package/dist/managers/message-router.d.ts +1 -1
  9. package/dist/managers/message-router.d.ts.map +1 -1
  10. package/dist/managers/message-router.js +41 -4
  11. package/dist/managers/message-router.js.map +1 -1
  12. package/dist/managers/room-manager.d.ts.map +1 -1
  13. package/dist/managers/room-manager.js +1 -1
  14. package/dist/managers/room-manager.js.map +1 -1
  15. package/dist/teneo-sdk.d.ts +29 -1
  16. package/dist/teneo-sdk.d.ts.map +1 -1
  17. package/dist/teneo-sdk.js +29 -6
  18. package/dist/teneo-sdk.js.map +1 -1
  19. package/dist/types/config.d.ts.map +1 -1
  20. package/dist/types/config.js +1 -1
  21. package/dist/types/config.js.map +1 -1
  22. package/dist/types/validation.d.ts.map +1 -1
  23. package/dist/types/validation.js +1 -1
  24. package/dist/types/validation.js.map +1 -1
  25. package/dist/utils/bounded-queue.d.ts +1 -1
  26. package/dist/utils/bounded-queue.js +6 -6
  27. package/dist/utils/circuit-breaker.d.ts.map +1 -1
  28. package/dist/utils/circuit-breaker.js.map +1 -1
  29. package/dist/utils/event-waiter.d.ts.map +1 -1
  30. package/dist/utils/event-waiter.js +2 -1
  31. package/dist/utils/event-waiter.js.map +1 -1
  32. package/dist/utils/rate-limiter.d.ts.map +1 -1
  33. package/dist/utils/rate-limiter.js +4 -6
  34. package/dist/utils/rate-limiter.js.map +1 -1
  35. package/dist/utils/secure-private-key.d.ts.map +1 -1
  36. package/dist/utils/secure-private-key.js +9 -15
  37. package/dist/utils/secure-private-key.js.map +1 -1
  38. package/dist/utils/signature-verifier.d.ts +2 -2
  39. package/dist/utils/signature-verifier.d.ts.map +1 -1
  40. package/dist/utils/signature-verifier.js +5 -5
  41. package/dist/utils/signature-verifier.js.map +1 -1
  42. package/examples/claude-agent-x-follower/.env.example +117 -0
  43. package/examples/claude-agent-x-follower/QUICKSTART.md +243 -0
  44. package/examples/claude-agent-x-follower/README.md +540 -0
  45. package/examples/claude-agent-x-follower/index.ts +248 -0
  46. package/examples/claude-agent-x-follower/package.json +37 -0
  47. package/examples/claude-agent-x-follower/tsconfig.json +20 -0
  48. package/examples/n8n-teneo/.env.example +127 -0
  49. package/examples/n8n-teneo/Dockerfile +42 -0
  50. package/examples/n8n-teneo/README.md +564 -0
  51. package/examples/n8n-teneo/docker-compose.yml +71 -0
  52. package/examples/n8n-teneo/index.ts +177 -0
  53. package/examples/n8n-teneo/package.json +22 -0
  54. package/examples/n8n-teneo/tsconfig.json +12 -0
  55. package/examples/n8n-teneo/workflows/x-timeline.json +66 -0
  56. package/examples/openai-teneo/.env.example +130 -0
  57. package/examples/openai-teneo/README.md +635 -0
  58. package/examples/openai-teneo/index.ts +280 -0
  59. package/examples/openai-teneo/package.json +24 -0
  60. package/examples/openai-teneo/tsconfig.json +16 -0
  61. package/examples/production-dashboard/.env.example +5 -3
  62. package/examples/production-dashboard/README.md +762 -0
  63. package/examples/production-dashboard/pnpm-lock.yaml +92 -0
  64. package/examples/production-dashboard/public/dashboard.html +84 -10
  65. package/examples/production-dashboard/server.ts +83 -9
  66. package/examples/usage/.env.example +17 -0
  67. package/examples/usage/01-connect.ts +116 -0
  68. package/examples/usage/02-list-agents.ts +153 -0
  69. package/examples/usage/03-pick-agent.ts +201 -0
  70. package/examples/usage/04-find-by-capability.ts +237 -0
  71. package/examples/usage/05-webhook-example.ts +319 -0
  72. package/examples/usage/06-simple-api-server.ts +396 -0
  73. package/examples/usage/07-event-listener.ts +402 -0
  74. package/examples/usage/README.md +383 -0
  75. package/examples/usage/package.json +42 -0
  76. package/package.json +5 -3
  77. package/src/core/websocket-client.ts +17 -7
  78. package/src/formatters/response-formatter.test.ts +8 -2
  79. package/src/handlers/message-handlers/regular-message-handler.ts +1 -0
  80. package/src/handlers/webhook-handler.test.ts +13 -10
  81. package/src/managers/message-router.ts +48 -6
  82. package/src/managers/room-manager.ts +9 -2
  83. package/src/teneo-sdk.ts +39 -7
  84. package/src/types/config.ts +3 -1
  85. package/src/types/validation.ts +4 -1
  86. package/src/utils/bounded-queue.ts +9 -9
  87. package/src/utils/circuit-breaker.ts +4 -1
  88. package/src/utils/deduplication-cache.test.ts +2 -6
  89. package/src/utils/event-waiter.test.ts +4 -1
  90. package/src/utils/event-waiter.ts +5 -7
  91. package/src/utils/rate-limiter.test.ts +5 -17
  92. package/src/utils/rate-limiter.ts +6 -9
  93. package/src/utils/secure-private-key.test.ts +66 -59
  94. package/src/utils/secure-private-key.ts +10 -16
  95. package/src/utils/signature-verifier.test.ts +75 -70
  96. package/src/utils/signature-verifier.ts +7 -8
  97. package/src/utils/ssrf-validator.test.ts +3 -3
@@ -0,0 +1,383 @@
1
+ # Teneo SDK Usage Examples
2
+
3
+ This directory contains progressive, hands-on examples demonstrating how to use the Teneo Consumer SDK. Each example builds on concepts from the previous ones, taking you from basic connection to building production-ready applications.
4
+
5
+ ## 📚 Examples Overview
6
+
7
+ | # | Example | Description | Key Concepts |
8
+ |---|---------|-------------|--------------|
9
+ | 1 | [Connect](#1-basic-connection) | Basic WebSocket connection | Config builder, authentication, lifecycle |
10
+ | 2 | [List Agents](#2-list-agents) | Retrieve available agents | Agent registry, properties, statistics |
11
+ | 3 | [Pick Agent](#3-pick-specific-agent) | Direct agent communication | sendDirectCommand, response handling |
12
+ | 4 | [Find by Capability](#4-find-by-capability) | Indexed agent search | O(1) lookups, capability matching |
13
+ | 5 | [Webhooks](#5-webhook-integration) | HTTP event notifications | Webhook setup, retry logic, circuit breaker |
14
+ | 6 | [API Server](#6-simple-api-server) | REST API wrapper | Express integration, endpoint design |
15
+ | 7 | [Event Listener](#7-event-listener) | Event-driven patterns | 30+ events, real-time monitoring |
16
+
17
+ ## 🚀 Prerequisites
18
+
19
+ Before running these examples, make sure you have:
20
+
21
+ 1. **Node.js 18+** installed
22
+ 2. **Built the SDK**: Run `npm run build` in the project root
23
+ 3. **Environment variables** set up (see below)
24
+
25
+ ## ⚙️ Environment Setup
26
+
27
+ Create a `.env` file in the project root or export these variables:
28
+
29
+ ```bash
30
+ # Required
31
+ PRIVATE_KEY=your_ethereum_private_key_here
32
+ WS_URL=wss://dev-rooms-websocket-ai-core-o9fmb.ondigitalocean.app/ws
33
+
34
+ # Optional
35
+ DEFAULT_ROOM=general
36
+ WALLET_ADDRESS=0x... # Auto-derived if not provided
37
+ LOG_LEVEL=info
38
+ ```
39
+
40
+ ## 📖 Usage Examples
41
+
42
+ ### 1. Basic Connection
43
+
44
+ **File**: `01-connect.ts`
45
+
46
+ The simplest example - connect to Teneo and authenticate.
47
+
48
+ ```bash
49
+ npx tsx examples/usage/01-connect.ts
50
+ ```
51
+
52
+ **What you'll learn**:
53
+ - Using `SDKConfigBuilder` for configuration
54
+ - Connecting to the WebSocket server
55
+ - Ethereum wallet authentication
56
+ - Event listeners for connection lifecycle
57
+ - Graceful disconnection and cleanup
58
+
59
+ **Output**:
60
+ ```
61
+ 🚀 Example 1: Basic SDK Connection
62
+ ⚙️ Step 1: Building SDK configuration...
63
+ ✅ Configuration built
64
+ ...
65
+ ✅ Connected and authenticated!
66
+ ```
67
+
68
+ ---
69
+
70
+ ### 2. List Agents
71
+
72
+ **File**: `02-list-agents.ts`
73
+
74
+ Retrieve and inspect all available agents in the network.
75
+
76
+ ```bash
77
+ npx tsx examples/usage/02-list-agents.ts
78
+ ```
79
+
80
+ **What you'll learn**:
81
+ - Getting the agent list
82
+ - Inspecting agent properties (name, description, status)
83
+ - Viewing agent capabilities and commands
84
+ - Getting statistics (online/offline counts)
85
+
86
+ **Output**:
87
+ ```
88
+ 📊 Agent Details:
89
+ 1. X Platform Agent
90
+ ID: x-agent-001
91
+ Status: online
92
+ Capabilities: social-media, twitter
93
+ ...
94
+ ```
95
+
96
+ ---
97
+
98
+ ### 3. Pick Specific Agent
99
+
100
+ **File**: `03-pick-agent.ts`
101
+
102
+ Find and communicate with a specific agent.
103
+
104
+ ```bash
105
+ # Pick by name
106
+ npx tsx examples/usage/03-pick-agent.ts "X Platform Agent"
107
+
108
+ # Or let it auto-select
109
+ npx tsx examples/usage/03-pick-agent.ts
110
+ ```
111
+
112
+ **What you'll learn**:
113
+ - Finding agents by ID or name
114
+ - Sending direct commands to agents
115
+ - Waiting for responses with timeout
116
+ - Handling both humanized and raw responses
117
+ - Response metadata (task ID, duration, etc.)
118
+
119
+ **Output**:
120
+ ```
121
+ ✅ Selected: X Platform Agent
122
+ ⚙️ Sending command to agent...
123
+ Command: timeline @elonmusk 3
124
+ Waiting for response...
125
+ ✅ Response received!
126
+ 📝 Humanized Response: [response content]
127
+ ```
128
+
129
+ ---
130
+
131
+ ### 4. Find by Capability
132
+
133
+ **File**: `04-find-by-capability.ts`
134
+
135
+ Use the SDK's indexed search for efficient agent discovery.
136
+
137
+ ```bash
138
+ npx tsx examples/usage/04-find-by-capability.ts
139
+ ```
140
+
141
+ **What you'll learn**:
142
+ - **PERF-3**: O(1) capability lookups
143
+ - O(1) status lookups (online/offline)
144
+ - O(k) name-based token search
145
+ - Performance comparison
146
+ - Practical agent selection strategies
147
+
148
+ **Output**:
149
+ ```
150
+ 🔍 Searching for capability: "weather-forecast"
151
+ ✅ Found 2 agent(s) (0.123ms):
152
+ • Weather Agent
153
+ • Climate Data Agent
154
+
155
+ 📊 Performance Summary:
156
+ • Capability search: O(1) - constant time
157
+ • Status search: O(1) - constant time
158
+ ```
159
+
160
+ ---
161
+
162
+ ### 5. Webhook Integration
163
+
164
+ **File**: `05-webhook-example.ts`
165
+
166
+ Set up HTTP webhooks to receive SDK events.
167
+
168
+ ```bash
169
+ npx tsx examples/usage/05-webhook-example.ts
170
+ ```
171
+
172
+ **What you'll learn**:
173
+ - Creating a local webhook receiver
174
+ - Configuring SDK webhook delivery
175
+ - Receiving webhook events
176
+ - Automatic retry with exponential backoff
177
+ - Circuit breaker pattern
178
+ - Queue management
179
+
180
+ **Output**:
181
+ ```
182
+ ✅ Webhook server running on http://localhost:3001
183
+ 🎯 Webhook received!
184
+ Event: agent:response
185
+ Timestamp: 2025-10-28T...
186
+ ```
187
+
188
+ ---
189
+
190
+ ### 6. Simple API Server
191
+
192
+ **File**: `06-simple-api-server.ts`
193
+
194
+ Build a REST API that wraps the Teneo SDK.
195
+
196
+ ```bash
197
+ npx tsx examples/usage/06-simple-api-server.ts
198
+ ```
199
+
200
+ Then test with curl:
201
+
202
+ ```bash
203
+ # Health check
204
+ curl http://localhost:3000/health
205
+
206
+ # List agents
207
+ curl http://localhost:3000/agents
208
+
209
+ # Send message
210
+ curl -X POST http://localhost:3000/message \
211
+ -H "Content-Type: application/json" \
212
+ -d '{"message":"hello","waitForResponse":true}'
213
+
214
+ # Find by capability
215
+ curl http://localhost:3000/agents/capability/weather-forecast
216
+ ```
217
+
218
+ **What you'll learn**:
219
+ - Building Express REST API
220
+ - Wrapping SDK in HTTP endpoints
221
+ - Error handling patterns
222
+ - Health monitoring
223
+ - Production-ready server design
224
+
225
+ **Endpoints**:
226
+ - `GET /health` - Server and SDK health
227
+ - `GET /agents` - List all agents
228
+ - `GET /agents/:id` - Get specific agent
229
+ - `GET /agents/capability/:capability` - Find by capability
230
+ - `POST /message` - Send message to agent
231
+ - `GET /rooms` - List rooms
232
+ - `POST /rooms/:roomId/subscribe` - Subscribe to room
233
+
234
+ ---
235
+
236
+ ### 7. Event Listener
237
+
238
+ **File**: `07-event-listener.ts`
239
+
240
+ Listen to all SDK events for real-time monitoring.
241
+
242
+ ```bash
243
+ npx tsx examples/usage/07-event-listener.ts
244
+ ```
245
+
246
+ **What you'll learn**:
247
+ - All 30+ SDK event types
248
+ - Event categorization (connection, auth, agent, message, etc.)
249
+ - Real-time event monitoring
250
+ - Event-driven architecture
251
+ - Statistics and analytics
252
+
253
+ **Output**:
254
+ ```
255
+ 🔌 [CONNECTION] WebSocket connection opened
256
+ 🔐 [AUTH] ✅ Authentication successful!
257
+ 🤖 [AGENT] Agent list updated: 5 agents
258
+ 📤 [MESSAGE] Message sent: Type: message
259
+ ...
260
+ 📊 EVENT STATISTICS
261
+ Connection events: 12
262
+ Authentication events: 4
263
+ Agent events: 8
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 🎯 Learning Path
269
+
270
+ We recommend following this learning path:
271
+
272
+ 1. **Start with 01-connect.ts** to understand basic setup
273
+ 2. **Try 02-list-agents.ts** to explore the agent registry
274
+ 3. **Run 03-pick-agent.ts** to learn agent communication
275
+ 4. **Explore 04-find-by-capability.ts** for efficient search
276
+ 5. **Set up 05-webhook-example.ts** for event notifications
277
+ 6. **Build 06-simple-api-server.ts** for production patterns
278
+ 7. **Monitor with 07-event-listener.ts** for observability
279
+
280
+ ## 🔧 Common Patterns
281
+
282
+ ### Pattern 1: Basic Message Flow
283
+
284
+ ```typescript
285
+ const sdk = new TeneoSDK(config);
286
+ await sdk.connect();
287
+
288
+ // Send and wait for response
289
+ const response = await sdk.sendMessage('hello', {
290
+ room: 'general',
291
+ waitForResponse: true,
292
+ timeout: 30000
293
+ });
294
+
295
+ console.log(response.humanized);
296
+ ```
297
+
298
+ ### Pattern 2: Direct Agent Command
299
+
300
+ ```typescript
301
+ // Find agent by capability
302
+ const agents = sdk.findAgentsByCapability('weather');
303
+ const weatherAgent = agents[0];
304
+
305
+ // Send direct command
306
+ const response = await sdk.sendDirectCommand({
307
+ agent: weatherAgent.id,
308
+ command: 'forecast for NYC',
309
+ room: 'general'
310
+ }, true);
311
+ ```
312
+
313
+ ### Pattern 3: Event-Driven
314
+
315
+ ```typescript
316
+ sdk.on('agent:response', (response) => {
317
+ console.log('Response:', response.humanized);
318
+ // Process response asynchronously
319
+ });
320
+
321
+ // Fire and forget
322
+ await sdk.sendMessage('hello', { room: 'general' });
323
+ ```
324
+
325
+ ## 🐛 Troubleshooting
326
+
327
+ ### "PRIVATE_KEY is required"
328
+
329
+ Set the environment variable:
330
+ ```bash
331
+ export PRIVATE_KEY=your_private_key_here
332
+ ```
333
+
334
+ ### "No agents available"
335
+
336
+ Wait a bit longer after connecting:
337
+ ```typescript
338
+ await sdk.connect();
339
+ await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s
340
+ ```
341
+
342
+ ### "Webhook delivery failed"
343
+
344
+ Ensure your webhook URL is accessible:
345
+ - Use `http://localhost:PORT` for local testing
346
+ - Use HTTPS for production webhooks
347
+ - Check firewall settings
348
+
349
+ ### "Connection timeout"
350
+
351
+ Check your WebSocket URL and network:
352
+ ```typescript
353
+ const config = new SDKConfigBuilder()
354
+ .withWebSocketUrl('wss://correct-url.com/ws')
355
+ .withReconnection({ enabled: true, maxAttempts: 10 })
356
+ .build();
357
+ ```
358
+
359
+ ## 📚 Next Steps
360
+
361
+ After completing these examples, check out:
362
+
363
+ - **[Integration Examples](../INTEGRATION_EXAMPLES.md)** - Claude, OpenAI, n8n integrations
364
+ - **[Production Dashboard](../production-dashboard/)** - Full-featured monitoring UI
365
+ - **[API Documentation](../../docs/)** - Complete API reference
366
+ - **[Main README](../../README.md)** - Full SDK documentation
367
+
368
+ ## 💡 Tips
369
+
370
+ 1. **Always build the SDK first**: Run `npm run build` before running examples
371
+ 2. **Use environment variables**: Don't hardcode credentials
372
+ 3. **Enable debug logging**: Set `LOG_LEVEL=debug` for troubleshooting
373
+ 4. **Check health status**: Use `sdk.getHealth()` to monitor SDK state
374
+ 5. **Handle errors**: Always use try-catch blocks in production code
375
+
376
+ ## 🤝 Contributing
377
+
378
+ Found an issue or want to add an example? Please open an issue or PR!
379
+
380
+ ## 📄 License
381
+
382
+ MIT - See [LICENSE](../../LICENSE) for details
383
+
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@teneo/usage-examples",
3
+ "version": "1.0.0",
4
+ "description": "Step-by-step usage examples for Teneo Protocol SDK",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "01:connect": "tsx 01-connect.ts",
9
+ "02:list-agents": "tsx 02-list-agents.ts",
10
+ "03:pick-agent": "tsx 03-pick-agent.ts",
11
+ "04:find-by-capability": "tsx 04-find-by-capability.ts",
12
+ "05:webhook": "tsx 05-webhook-example.ts",
13
+ "06:api-server": "tsx 06-simple-api-server.ts",
14
+ "07:event-listener": "tsx 07-event-listener.ts",
15
+ "all": "npm run 01:connect && npm run 02:list-agents && npm run 03:pick-agent && npm run 04:find-by-capability"
16
+ },
17
+ "dependencies": {
18
+ "dotenv": "^16.4.0",
19
+ "express": "^4.18.2"
20
+ },
21
+ "devDependencies": {
22
+ "@types/express": "^4.17.21",
23
+ "@types/node": "^20.10.5",
24
+ "tsx": "^4.7.0",
25
+ "typescript": "^5.3.3"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "keywords": [
31
+ "teneo",
32
+ "sdk",
33
+ "examples",
34
+ "usage",
35
+ "tutorial",
36
+ "websocket",
37
+ "ai",
38
+ "agents"
39
+ ],
40
+ "author": "Teneo Protocol",
41
+ "license": "MIT"
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teneo-protocol/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "TypeScript SDK for external platforms to interact with Teneo agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,7 +17,6 @@
17
17
  "license": "AGPL-3.0",
18
18
  "dependencies": {
19
19
  "eventemitter3": "^5.0.1",
20
- "hono": "^4.9.12",
21
20
  "node-fetch": "^3.3.2",
22
21
  "pino": "^8.17.2",
23
22
  "uuid": "^9.0.1",
@@ -60,6 +59,9 @@
60
59
  "lint:fix": "eslint . --ext .ts --fix",
61
60
  "format": "prettier --write \"src/**/*.ts\"",
62
61
  "example:battle": "ts-node examples/x-influencer-battle-server.ts",
63
- "example:dashboard": "cd examples/production-dashboard && bun run server.ts"
62
+ "example:dashboard": "cd examples/production-dashboard && bun run server.ts",
63
+ "example:claude": "tsx examples/claude-agent-x-follower/index.ts",
64
+ "example:openai": "cd examples/openai-teneo && tsx index.ts",
65
+ "example:n8n": "cd examples/n8n-teneo && tsx index.ts"
64
66
  }
65
67
  }
@@ -56,6 +56,7 @@ export class WebSocketClient extends EventEmitter<SDKEvents> {
56
56
  private deduplicationCache?: DeduplicationCache;
57
57
  private reconnectPolicy: RetryPolicy;
58
58
  private roomManager?: any; // Reference to RoomManager for handler context
59
+ private intentionalDisconnect: boolean = false; // Track intentional disconnect to prevent reconnection
59
60
 
60
61
  private connectionState: ConnectionState = {
61
62
  connected: false,
@@ -94,15 +95,13 @@ export class WebSocketClient extends EventEmitter<SDKEvents> {
94
95
  if (config.privateKey) {
95
96
  try {
96
97
  // Check if privateKey is already a SecurePrivateKey instance (SEC-3)
97
- if (typeof config.privateKey === 'object' && 'use' in config.privateKey) {
98
+ if (typeof config.privateKey === "object" && "use" in config.privateKey) {
98
99
  // Use the provided SecurePrivateKey directly
99
100
  this.secureKey = config.privateKey;
100
101
  this.ownsSecureKey = false; // User provided it, we don't own it
101
102
 
102
103
  // Create account using the secure key
103
- this.account = this.secureKey.use((key) =>
104
- privateKeyToAccount(key as `0x${string}`)
105
- );
104
+ this.account = this.secureKey.use((key) => privateKeyToAccount(key as `0x${string}`));
106
105
  } else {
107
106
  // privateKey is a plain string - encrypt it immediately
108
107
  const privateKeyString = config.privateKey as string;
@@ -117,9 +116,7 @@ export class WebSocketClient extends EventEmitter<SDKEvents> {
117
116
  this.ownsSecureKey = true; // We created it, we own it
118
117
 
119
118
  // Create account using the secure key
120
- this.account = this.secureKey.use((key) =>
121
- privateKeyToAccount(key as `0x${string}`)
122
- );
119
+ this.account = this.secureKey.use((key) => privateKeyToAccount(key as `0x${string}`));
123
120
  }
124
121
 
125
122
  if (
@@ -241,6 +238,10 @@ export class WebSocketClient extends EventEmitter<SDKEvents> {
241
238
  // Clear any existing connection
242
239
  this.disconnect();
243
240
 
241
+ // Reset intentional disconnect flag after clearing connection
242
+ // This allows automatic reconnection for this new connection
243
+ this.intentionalDisconnect = false;
244
+
244
245
  // Build connection URL with webhook parameter
245
246
  let url = this.config.wsUrl;
246
247
  if (this.config.webhookUrl) {
@@ -362,6 +363,9 @@ export class WebSocketClient extends EventEmitter<SDKEvents> {
362
363
  public disconnect(): void {
363
364
  this.logger.info("Disconnecting from WebSocket server");
364
365
 
366
+ // Mark as intentional disconnect to prevent reconnection
367
+ this.intentionalDisconnect = true;
368
+
365
369
  // Clear all timers
366
370
  if (this.reconnectTimer) {
367
371
  clearTimeout(this.reconnectTimer);
@@ -795,6 +799,12 @@ export class WebSocketClient extends EventEmitter<SDKEvents> {
795
799
  * Handle reconnection logic with configurable retry strategy (REL-3)
796
800
  */
797
801
  private handleReconnection(): void {
802
+ // Don't reconnect if disconnect was intentional
803
+ if (this.intentionalDisconnect) {
804
+ this.logger.debug("Skipping reconnection - disconnect was intentional");
805
+ return;
806
+ }
807
+
798
808
  if (!this.config.reconnect || this.connectionState.reconnecting) {
799
809
  return;
800
810
  }
@@ -428,12 +428,18 @@ describe("ResponseFormatter", () => {
428
428
  data: { message: "Test", code: 1 }
429
429
  };
430
430
 
431
- const rawResult = ResponseFormatter.validateAndFormat(message, { format: "raw", includeMetadata: false });
431
+ const rawResult = ResponseFormatter.validateAndFormat(message, {
432
+ format: "raw",
433
+ includeMetadata: false
434
+ });
432
435
  expect(rawResult.raw).toBeDefined();
433
436
  expect(rawResult.humanized).toBeUndefined();
434
437
  expect(rawResult.metadata).toBeUndefined();
435
438
 
436
- const bothWithMeta = ResponseFormatter.validateAndFormat(message, { format: "both", includeMetadata: true });
439
+ const bothWithMeta = ResponseFormatter.validateAndFormat(message, {
440
+ format: "both",
441
+ includeMetadata: true
442
+ });
437
443
  expect(bothWithMeta.raw).toBeDefined();
438
444
  expect(bothWithMeta.humanized).toBeDefined();
439
445
  expect(bothWithMeta.metadata).toBeDefined();
@@ -54,6 +54,7 @@ export class RegularMessageHandler extends BaseMessageHandler<UserMessage> {
54
54
  contentType: message.content_type || "text/plain",
55
55
  success: true,
56
56
  timestamp: new Date(),
57
+ raw: message as any, // Include raw message for request correlation
57
58
  humanized:
58
59
  typeof message.content === "string" ? message.content : JSON.stringify(message.content)
59
60
  };
@@ -118,7 +118,10 @@ describe("WebhookHandler", () => {
118
118
  expect(queue[0].payload.timestamp).toBeDefined();
119
119
  expect(queue[1].payload.timestamp).toBeDefined();
120
120
  // Each webhook gets unique timestamp
121
- expect(queue[0].payload.timestamp !== queue[1].payload.timestamp || queue[0].payload.data.content !== queue[1].payload.data.content).toBe(true);
121
+ expect(
122
+ queue[0].payload.timestamp !== queue[1].payload.timestamp ||
123
+ queue[0].payload.data.content !== queue[1].payload.data.content
124
+ ).toBe(true);
122
125
  });
123
126
 
124
127
  it("should include timestamp in webhook payload", async () => {
@@ -584,20 +587,17 @@ describe("WebhookHandler", () => {
584
587
 
585
588
  describe("Protocol Validation", () => {
586
589
  it("should reject HTTP for non-localhost without allowInsecureWebhooks", () => {
587
- const urls = [
588
- "http://example.com/webhook"
589
- ];
590
+ const urls = ["http://example.com/webhook"];
590
591
 
591
592
  urls.forEach((url) => {
592
593
  expect(() => (handler as any).validateWebhookUrl(url)).toThrow(WebhookError);
593
- expect(() => (handler as any).validateWebhookUrl(url)).toThrow(/must use HTTPS|Only HTTPS/i);
594
+ expect(() => (handler as any).validateWebhookUrl(url)).toThrow(
595
+ /must use HTTPS|Only HTTPS/i
596
+ );
594
597
  });
595
598
 
596
599
  // Private IPs are blocked for different reason (private IP, not HTTPS requirement)
597
- const privateUrls = [
598
- "http://192.168.1.1/webhook",
599
- "http://10.0.0.1/webhook"
600
- ];
600
+ const privateUrls = ["http://192.168.1.1/webhook", "http://10.0.0.1/webhook"];
601
601
  privateUrls.forEach((url) => {
602
602
  expect(() => (handler as any).validateWebhookUrl(url)).toThrow(WebhookError);
603
603
  expect(() => (handler as any).validateWebhookUrl(url)).toThrow(/private IP/i);
@@ -720,7 +720,10 @@ describe("WebhookHandler", () => {
720
720
  vi.advanceTimersByTime(10000);
721
721
  }
722
722
 
723
- expect(errorHandler).toHaveBeenCalledWith(expect.any(Error), "https://webhook.example.com/events");
723
+ expect(errorHandler).toHaveBeenCalledWith(
724
+ expect.any(Error),
725
+ "https://webhook.example.com/events"
726
+ );
724
727
  });
725
728
  });
726
729
 
@@ -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
  });