@teneo-protocol/sdk 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.github/workflows/publish-npm.yml +8 -6
  2. package/CHANGELOG.md +265 -0
  3. package/README.md +406 -53
  4. package/dist/core/websocket-client.d.ts +13 -0
  5. package/dist/core/websocket-client.d.ts.map +1 -1
  6. package/dist/core/websocket-client.js +34 -3
  7. package/dist/core/websocket-client.js.map +1 -1
  8. package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts +76 -0
  9. package/dist/handlers/message-handlers/agent-room-operation-response-handler.d.ts.map +1 -0
  10. package/dist/handlers/message-handlers/agent-room-operation-response-handler.js +70 -0
  11. package/dist/handlers/message-handlers/agent-room-operation-response-handler.js.map +1 -0
  12. package/dist/handlers/message-handlers/agent-selected-handler.d.ts +92 -38
  13. package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -1
  14. package/dist/handlers/message-handlers/agent-status-update-handler.d.ts +904 -0
  15. package/dist/handlers/message-handlers/agent-status-update-handler.d.ts.map +1 -0
  16. package/dist/handlers/message-handlers/agent-status-update-handler.js +51 -0
  17. package/dist/handlers/message-handlers/agent-status-update-handler.js.map +1 -0
  18. package/dist/handlers/message-handlers/auth-error-handler.d.ts +45 -31
  19. package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -1
  20. package/dist/handlers/message-handlers/auth-message-handler.d.ts +6 -0
  21. package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -1
  22. package/dist/handlers/message-handlers/auth-message-handler.js +65 -5
  23. package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -1
  24. package/dist/handlers/message-handlers/auth-required-handler.d.ts +49 -31
  25. package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -1
  26. package/dist/handlers/message-handlers/auth-success-handler.d.ts +6 -0
  27. package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -1
  28. package/dist/handlers/message-handlers/auth-success-handler.js +46 -4
  29. package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -1
  30. package/dist/handlers/message-handlers/challenge-handler.d.ts +45 -31
  31. package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -1
  32. package/dist/handlers/message-handlers/error-message-handler.d.ts +49 -31
  33. package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -1
  34. package/dist/handlers/message-handlers/index.d.ts +5 -0
  35. package/dist/handlers/message-handlers/index.d.ts.map +1 -1
  36. package/dist/handlers/message-handlers/index.js +23 -1
  37. package/dist/handlers/message-handlers/index.js.map +1 -1
  38. package/dist/handlers/message-handlers/list-available-agents-handler.d.ts +877 -0
  39. package/dist/handlers/message-handlers/list-available-agents-handler.d.ts.map +1 -0
  40. package/dist/handlers/message-handlers/list-available-agents-handler.js +38 -0
  41. package/dist/handlers/message-handlers/list-available-agents-handler.js.map +1 -0
  42. package/dist/handlers/message-handlers/list-room-agents-handler.d.ts +886 -0
  43. package/dist/handlers/message-handlers/list-room-agents-handler.d.ts.map +1 -0
  44. package/dist/handlers/message-handlers/list-room-agents-handler.js +51 -0
  45. package/dist/handlers/message-handlers/list-room-agents-handler.js.map +1 -0
  46. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +178 -89
  47. package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -1
  48. package/dist/handlers/message-handlers/ping-pong-handler.d.ts +62 -58
  49. package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -1
  50. package/dist/handlers/message-handlers/regular-message-handler.d.ts +31 -29
  51. package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -1
  52. package/dist/handlers/message-handlers/regular-message-handler.js +1 -0
  53. package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -1
  54. package/dist/handlers/message-handlers/room-operation-response-handler.d.ts +328 -0
  55. package/dist/handlers/message-handlers/room-operation-response-handler.d.ts.map +1 -0
  56. package/dist/handlers/message-handlers/room-operation-response-handler.js +92 -0
  57. package/dist/handlers/message-handlers/room-operation-response-handler.js.map +1 -0
  58. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +53 -31
  59. package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -1
  60. package/dist/handlers/message-handlers/types.d.ts +2 -0
  61. package/dist/handlers/message-handlers/types.d.ts.map +1 -1
  62. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +53 -31
  63. package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -1
  64. package/dist/managers/agent-room-manager.d.ts +222 -0
  65. package/dist/managers/agent-room-manager.d.ts.map +1 -0
  66. package/dist/managers/agent-room-manager.js +508 -0
  67. package/dist/managers/agent-room-manager.js.map +1 -0
  68. package/dist/managers/index.d.ts +2 -0
  69. package/dist/managers/index.d.ts.map +1 -1
  70. package/dist/managers/index.js +5 -1
  71. package/dist/managers/index.js.map +1 -1
  72. package/dist/managers/message-router.d.ts +1 -1
  73. package/dist/managers/message-router.d.ts.map +1 -1
  74. package/dist/managers/message-router.js +41 -4
  75. package/dist/managers/message-router.js.map +1 -1
  76. package/dist/managers/room-management-manager.d.ts +213 -0
  77. package/dist/managers/room-management-manager.d.ts.map +1 -0
  78. package/dist/managers/room-management-manager.js +440 -0
  79. package/dist/managers/room-management-manager.js.map +1 -0
  80. package/dist/managers/room-manager.d.ts +4 -4
  81. package/dist/managers/room-manager.d.ts.map +1 -1
  82. package/dist/managers/room-manager.js +1 -1
  83. package/dist/managers/room-manager.js.map +1 -1
  84. package/dist/teneo-sdk.d.ts +362 -14
  85. package/dist/teneo-sdk.d.ts.map +1 -1
  86. package/dist/teneo-sdk.js +497 -7
  87. package/dist/teneo-sdk.js.map +1 -1
  88. package/dist/types/config.d.ts +63 -54
  89. package/dist/types/config.d.ts.map +1 -1
  90. package/dist/types/config.js +9 -5
  91. package/dist/types/config.js.map +1 -1
  92. package/dist/types/error-codes.d.ts +2 -0
  93. package/dist/types/error-codes.d.ts.map +1 -1
  94. package/dist/types/error-codes.js +3 -0
  95. package/dist/types/error-codes.js.map +1 -1
  96. package/dist/types/events.d.ts +132 -68
  97. package/dist/types/events.d.ts.map +1 -1
  98. package/dist/types/events.js.map +1 -1
  99. package/dist/types/index.d.ts +1 -1
  100. package/dist/types/index.d.ts.map +1 -1
  101. package/dist/types/index.js +27 -2
  102. package/dist/types/index.js.map +1 -1
  103. package/dist/types/messages.d.ts +11396 -2559
  104. package/dist/types/messages.d.ts.map +1 -1
  105. package/dist/types/messages.js +294 -27
  106. package/dist/types/messages.js.map +1 -1
  107. package/dist/types/validation.d.ts.map +1 -1
  108. package/dist/types/validation.js +1 -1
  109. package/dist/types/validation.js.map +1 -1
  110. package/dist/utils/bounded-queue.d.ts +1 -1
  111. package/dist/utils/bounded-queue.js +6 -6
  112. package/dist/utils/circuit-breaker.d.ts.map +1 -1
  113. package/dist/utils/circuit-breaker.js.map +1 -1
  114. package/dist/utils/event-waiter.d.ts.map +1 -1
  115. package/dist/utils/event-waiter.js +2 -1
  116. package/dist/utils/event-waiter.js.map +1 -1
  117. package/dist/utils/rate-limiter.d.ts.map +1 -1
  118. package/dist/utils/rate-limiter.js +4 -6
  119. package/dist/utils/rate-limiter.js.map +1 -1
  120. package/dist/utils/secure-private-key.d.ts.map +1 -1
  121. package/dist/utils/secure-private-key.js +9 -15
  122. package/dist/utils/secure-private-key.js.map +1 -1
  123. package/dist/utils/signature-verifier.d.ts +2 -2
  124. package/dist/utils/signature-verifier.d.ts.map +1 -1
  125. package/dist/utils/signature-verifier.js +5 -5
  126. package/dist/utils/signature-verifier.js.map +1 -1
  127. package/examples/.env.example +1 -1
  128. package/examples/agent-room-management-example.ts +334 -0
  129. package/examples/claude-agent-x-follower/.env.example +117 -0
  130. package/examples/claude-agent-x-follower/QUICKSTART.md +243 -0
  131. package/examples/claude-agent-x-follower/README.md +540 -0
  132. package/examples/claude-agent-x-follower/index.ts +248 -0
  133. package/examples/claude-agent-x-follower/package.json +37 -0
  134. package/examples/claude-agent-x-follower/tsconfig.json +20 -0
  135. package/examples/n8n-teneo/.env.example +127 -0
  136. package/examples/n8n-teneo/Dockerfile +42 -0
  137. package/examples/n8n-teneo/README.md +564 -0
  138. package/examples/n8n-teneo/docker-compose.yml +71 -0
  139. package/examples/n8n-teneo/index.ts +177 -0
  140. package/examples/n8n-teneo/package.json +22 -0
  141. package/examples/n8n-teneo/tsconfig.json +12 -0
  142. package/examples/n8n-teneo/workflows/x-timeline.json +66 -0
  143. package/examples/openai-teneo/.env.example +130 -0
  144. package/examples/openai-teneo/README.md +635 -0
  145. package/examples/openai-teneo/index.ts +280 -0
  146. package/examples/openai-teneo/package.json +24 -0
  147. package/examples/openai-teneo/tsconfig.json +16 -0
  148. package/examples/production-dashboard/.env.example +5 -3
  149. package/examples/production-dashboard/README.md +839 -0
  150. package/examples/production-dashboard/pnpm-lock.yaml +92 -0
  151. package/examples/production-dashboard/public/dashboard.html +1150 -504
  152. package/examples/production-dashboard/server.ts +428 -12
  153. package/examples/room-management-example.ts +285 -0
  154. package/examples/usage/.env.example +17 -0
  155. package/examples/usage/01-connect.ts +116 -0
  156. package/examples/usage/02-list-agents.ts +153 -0
  157. package/examples/usage/03-pick-agent.ts +201 -0
  158. package/examples/usage/04-find-by-capability.ts +237 -0
  159. package/examples/usage/05-webhook-example.ts +319 -0
  160. package/examples/usage/06-simple-api-server.ts +396 -0
  161. package/examples/usage/07-event-listener.ts +402 -0
  162. package/examples/usage/README.md +383 -0
  163. package/examples/usage/package.json +42 -0
  164. package/package.json +13 -3
  165. package/src/core/websocket-client.ts +43 -9
  166. package/src/formatters/response-formatter.test.ts +8 -2
  167. package/src/handlers/message-handlers/agent-room-operation-response-handler.ts +83 -0
  168. package/src/handlers/message-handlers/agent-status-update-handler.ts +58 -0
  169. package/src/handlers/message-handlers/auth-message-handler.ts +73 -5
  170. package/src/handlers/message-handlers/auth-success-handler.ts +58 -6
  171. package/src/handlers/message-handlers/index.ts +19 -0
  172. package/src/handlers/message-handlers/list-available-agents-handler.ts +41 -0
  173. package/src/handlers/message-handlers/list-room-agents-handler.ts +61 -0
  174. package/src/handlers/message-handlers/regular-message-handler.ts +1 -0
  175. package/src/handlers/message-handlers/room-operation-response-handler.ts +105 -0
  176. package/src/handlers/message-handlers/types.ts +6 -0
  177. package/src/handlers/webhook-handler.test.ts +13 -10
  178. package/src/managers/agent-room-manager.ts +609 -0
  179. package/src/managers/index.ts +2 -0
  180. package/src/managers/message-router.ts +48 -6
  181. package/src/managers/room-management-manager.ts +523 -0
  182. package/src/managers/room-manager.ts +12 -6
  183. package/src/teneo-sdk.ts +543 -10
  184. package/src/types/config.ts +13 -6
  185. package/src/types/error-codes.ts +4 -0
  186. package/src/types/events.ts +24 -0
  187. package/src/types/index.ts +55 -0
  188. package/src/types/messages.ts +374 -41
  189. package/src/types/validation.ts +4 -1
  190. package/src/utils/bounded-queue.ts +9 -9
  191. package/src/utils/circuit-breaker.ts +4 -1
  192. package/src/utils/deduplication-cache.test.ts +2 -6
  193. package/src/utils/event-waiter.test.ts +4 -1
  194. package/src/utils/event-waiter.ts +5 -7
  195. package/src/utils/rate-limiter.test.ts +5 -17
  196. package/src/utils/rate-limiter.ts +6 -9
  197. package/src/utils/secure-private-key.test.ts +66 -59
  198. package/src/utils/secure-private-key.ts +10 -16
  199. package/src/utils/signature-verifier.test.ts +75 -70
  200. package/src/utils/signature-verifier.ts +7 -8
  201. package/src/utils/ssrf-validator.test.ts +3 -3
  202. package/tests/integration/room-management.test.ts +514 -0
  203. package/tests/integration/websocket.test.ts +1 -1
  204. package/tests/unit/handlers/agent-room-operation-response-handler.test.ts +394 -0
  205. package/tests/unit/handlers/agent-status-update-handler.test.ts +407 -0
  206. package/tests/unit/handlers/auth-success-handler-rooms.test.ts +699 -0
  207. package/tests/unit/handlers/list-available-agents-handler.test.ts +256 -0
  208. package/tests/unit/handlers/list-room-agents-handler.test.ts +294 -0
  209. package/tests/unit/handlers/room-operation-response-handler.test.ts +527 -0
  210. package/tests/unit/managers/agent-room-manager.test.ts +534 -0
  211. package/tests/unit/managers/room-management-manager.test.ts +438 -0
@@ -1,357 +1,607 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Teneo SDK - Production Dashboard</title>
7
7
  <script src="https://cdn.tailwindcss.com"></script>
8
8
  <style>
9
- .status-dot {
10
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
9
+ .status-dot {
10
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
11
+ }
12
+ @keyframes pulse {
13
+ 0%,
14
+ 100% {
15
+ opacity: 1;
11
16
  }
12
- @keyframes pulse {
13
- 0%, 100% { opacity: 1; }
14
- 50% { opacity: .5; }
17
+ 50% {
18
+ opacity: 0.5;
15
19
  }
16
- .event-item {
17
- animation: slideIn 0.3s ease-out;
20
+ }
21
+ .event-item {
22
+ animation: slideIn 0.3s ease-out;
23
+ }
24
+ @keyframes slideIn {
25
+ from {
26
+ opacity: 0;
27
+ transform: translateY(-10px);
18
28
  }
19
- @keyframes slideIn {
20
- from {
21
- opacity: 0;
22
- transform: translateY(-10px);
23
- }
24
- to {
25
- opacity: 1;
26
- transform: translateY(0);
27
- }
28
- }
29
- .scrollbar-thin::-webkit-scrollbar {
30
- width: 6px;
31
- }
32
- .scrollbar-thin::-webkit-scrollbar-track {
33
- background: #1f2937;
34
- }
35
- .scrollbar-thin::-webkit-scrollbar-thumb {
36
- background: #4b5563;
37
- border-radius: 3px;
29
+ to {
30
+ opacity: 1;
31
+ transform: translateY(0);
38
32
  }
33
+ }
34
+ .scrollbar-thin::-webkit-scrollbar {
35
+ width: 6px;
36
+ }
37
+ .scrollbar-thin::-webkit-scrollbar-track {
38
+ background: #1f2937;
39
+ }
40
+ .scrollbar-thin::-webkit-scrollbar-thumb {
41
+ background: #4b5563;
42
+ border-radius: 3px;
43
+ }
39
44
  </style>
40
- </head>
41
- <body class="bg-gray-900 text-gray-100">
45
+ </head>
46
+ <body class="bg-gray-900 text-gray-100">
42
47
  <div class="container mx-auto px-4 py-6 max-w-7xl">
43
- <!-- Header -->
44
- <div class="mb-8">
45
- <h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
46
- Teneo SDK Production Dashboard
47
- </h1>
48
- <p class="text-gray-400">Comprehensive example showcasing all SDK features</p>
48
+ <!-- Header -->
49
+ <div class="mb-8">
50
+ <h1
51
+ class="text-4xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent"
52
+ >
53
+ Teneo SDK Production Dashboard
54
+ </h1>
55
+ <p class="text-gray-400">Comprehensive example showcasing all SDK features</p>
56
+ </div>
57
+
58
+ <!-- Status Grid -->
59
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
60
+ <!-- Connection Status -->
61
+ <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
62
+ <div class="flex items-center justify-between mb-2">
63
+ <span class="text-gray-400 text-sm">Connection</span>
64
+ <div id="connection-dot" class="status-dot w-3 h-3 rounded-full bg-gray-500"></div>
65
+ </div>
66
+ <div id="connection-status" class="text-2xl font-bold text-gray-500">Connecting...</div>
49
67
  </div>
50
68
 
51
- <!-- Status Grid -->
52
- <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
53
- <!-- Connection Status -->
54
- <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
55
- <div class="flex items-center justify-between mb-2">
56
- <span class="text-gray-400 text-sm">Connection</span>
57
- <div id="connection-dot" class="status-dot w-3 h-3 rounded-full bg-gray-500"></div>
58
- </div>
59
- <div id="connection-status" class="text-2xl font-bold text-gray-500">Connecting...</div>
69
+ <!-- Auth Status -->
70
+ <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
71
+ <div class="flex items-center justify-between mb-2">
72
+ <span class="text-gray-400 text-sm">Authentication</span>
73
+ <div id="auth-dot" class="status-dot w-3 h-3 rounded-full bg-gray-500"></div>
74
+ </div>
75
+ <div id="auth-status" class="text-2xl font-bold text-gray-500">Pending</div>
76
+ </div>
77
+
78
+ <!-- Agents Count -->
79
+ <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
80
+ <div class="flex items-center justify-between mb-2">
81
+ <span class="text-gray-400 text-sm">Agents</span>
82
+ <svg
83
+ class="w-5 h-5 text-blue-400"
84
+ fill="none"
85
+ stroke="currentColor"
86
+ viewBox="0 0 24 24"
87
+ >
88
+ <path
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ stroke-width="2"
92
+ d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
93
+ ></path>
94
+ </svg>
95
+ </div>
96
+ <div id="agents-count" class="text-2xl font-bold">0</div>
97
+ </div>
98
+
99
+ <!-- Messages Count -->
100
+ <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
101
+ <div class="flex items-center justify-between mb-2">
102
+ <span class="text-gray-400 text-sm">Messages</span>
103
+ <svg
104
+ class="w-5 h-5 text-green-400"
105
+ fill="none"
106
+ stroke="currentColor"
107
+ viewBox="0 0 24 24"
108
+ >
109
+ <path
110
+ stroke-linecap="round"
111
+ stroke-linejoin="round"
112
+ stroke-width="2"
113
+ d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
114
+ ></path>
115
+ </svg>
116
+ </div>
117
+ <div id="messages-count" class="text-2xl font-bold">0</div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Main Content Grid -->
122
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
123
+ <!-- Send Message Panel -->
124
+ <div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
125
+ <h2 class="text-xl font-bold mb-4 flex items-center">
126
+ <svg
127
+ class="w-6 h-6 mr-2 text-blue-400"
128
+ fill="none"
129
+ stroke="currentColor"
130
+ viewBox="0 0 24 24"
131
+ >
132
+ <path
133
+ stroke-linecap="round"
134
+ stroke-linejoin="round"
135
+ stroke-width="2"
136
+ d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
137
+ ></path>
138
+ </svg>
139
+ Send Message
140
+ </h2>
141
+ <div class="mb-3">
142
+ <label for="room-select" class="block text-sm text-gray-400 mb-2">Select Room:</label>
143
+ <select
144
+ id="room-select"
145
+ class="w-full bg-gray-700 rounded-lg p-2 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none"
146
+ >
147
+ <option value="">Loading rooms...</option>
148
+ </select>
149
+ </div>
150
+ <textarea
151
+ id="message-input"
152
+ class="w-full bg-gray-700 rounded-lg p-3 mb-3 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none resize-none"
153
+ rows="3"
154
+ placeholder="Enter your message to agents..."
155
+ ></textarea>
156
+ <div class="flex gap-3">
157
+ <button
158
+ id="send-message-btn"
159
+ class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed"
160
+ disabled
161
+ >
162
+ Send Message
163
+ </button>
164
+ <button
165
+ id="send-wait-btn"
166
+ class="flex-1 bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed"
167
+ disabled
168
+ >
169
+ Send & Wait
170
+ </button>
171
+ </div>
172
+ </div>
173
+
174
+ <!-- Health Status -->
175
+ <div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
176
+ <h2 class="text-xl font-bold mb-4 flex items-center">
177
+ <svg
178
+ class="w-6 h-6 mr-2 text-green-400"
179
+ fill="none"
180
+ stroke="currentColor"
181
+ viewBox="0 0 24 24"
182
+ >
183
+ <path
184
+ stroke-linecap="round"
185
+ stroke-linejoin="round"
186
+ stroke-width="2"
187
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
188
+ ></path>
189
+ </svg>
190
+ System Health
191
+ </h2>
192
+ <div id="health-status" class="space-y-2">
193
+ <div class="flex justify-between items-center">
194
+ <span class="text-gray-400">Overall Status</span>
195
+ <span id="health-overall" class="text-gray-500">Loading...</span>
196
+ </div>
197
+ <div class="flex justify-between items-center">
198
+ <span class="text-gray-400">Webhook Circuit</span>
199
+ <span id="health-circuit" class="text-gray-500">-</span>
60
200
  </div>
201
+ <div class="flex justify-between items-center">
202
+ <span class="text-gray-400">Rate Limiter</span>
203
+ <span id="health-ratelimit" class="text-gray-500">-</span>
204
+ </div>
205
+ <div class="flex justify-between items-center">
206
+ <span class="text-gray-400">Uptime</span>
207
+ <span id="health-uptime" class="text-gray-500">-</span>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
61
212
 
62
- <!-- Auth Status -->
63
- <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
64
- <div class="flex items-center justify-between mb-2">
65
- <span class="text-gray-400 text-sm">Authentication</span>
66
- <div id="auth-dot" class="status-dot w-3 h-3 rounded-full bg-gray-500"></div>
67
- </div>
68
- <div id="auth-status" class="text-2xl font-bold text-gray-500">Pending</div>
213
+ <!-- Tabs -->
214
+ <div class="bg-gray-800 rounded-lg border border-gray-700 overflow-hidden">
215
+ <div class="flex border-b border-gray-700 overflow-x-auto">
216
+ <button
217
+ class="tab-btn active px-6 py-3 font-semibold whitespace-nowrap"
218
+ data-tab="agents"
219
+ >
220
+ Agents
221
+ </button>
222
+ <button class="tab-btn px-6 py-3 font-semibold whitespace-nowrap" data-tab="rooms">
223
+ Rooms
224
+ </button>
225
+ <button class="tab-btn px-6 py-3 font-semibold whitespace-nowrap" data-tab="room-mgmt">
226
+ 🆕 Room Mgmt
227
+ </button>
228
+ <button class="tab-btn px-6 py-3 font-semibold whitespace-nowrap" data-tab="agent-room">
229
+ 🆕 Agent-Room
230
+ </button>
231
+ <button class="tab-btn px-6 py-3 font-semibold whitespace-nowrap" data-tab="messages">
232
+ Messages
233
+ </button>
234
+ <button class="tab-btn px-6 py-3 font-semibold whitespace-nowrap" data-tab="webhooks">
235
+ Webhooks
236
+ </button>
237
+ <button class="tab-btn px-6 py-3 font-semibold whitespace-nowrap" data-tab="events">
238
+ Events
239
+ </button>
240
+ </div>
241
+
242
+ <div class="p-6">
243
+ <!-- Agents Tab -->
244
+ <div id="agents-tab" class="tab-content">
245
+ <div id="agents-list" class="space-y-3 max-h-96 overflow-y-auto scrollbar-thin">
246
+ <p class="text-gray-500">Loading agents...</p>
69
247
  </div>
248
+ </div>
70
249
 
71
- <!-- Agents Count -->
72
- <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
73
- <div class="flex items-center justify-between mb-2">
74
- <span class="text-gray-400 text-sm">Agents</span>
75
- <svg class="w-5 h-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
77
- </svg>
78
- </div>
79
- <div id="agents-count" class="text-2xl font-bold">0</div>
250
+ <!-- Rooms Tab -->
251
+ <div id="rooms-tab" class="tab-content hidden">
252
+ <div class="mb-4">
253
+ <input
254
+ id="room-id-input"
255
+ type="text"
256
+ class="bg-gray-700 rounded-lg p-2 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none"
257
+ placeholder="Room ID"
258
+ />
259
+ <button
260
+ id="subscribe-room-btn"
261
+ class="ml-2 bg-green-600 hover:bg-green-700 text-white font-semibold py-2 px-4 rounded-lg transition"
262
+ >
263
+ Subscribe
264
+ </button>
265
+ </div>
266
+ <div id="rooms-list" class="space-y-3 max-h-80 overflow-y-auto scrollbar-thin">
267
+ <p class="text-gray-500">Loading rooms...</p>
80
268
  </div>
269
+ </div>
81
270
 
82
- <!-- Messages Count -->
83
- <div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
84
- <div class="flex items-center justify-between mb-2">
85
- <span class="text-gray-400 text-sm">Messages</span>
86
- <svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
87
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
88
- </svg>
89
- </div>
90
- <div id="messages-count" class="text-2xl font-bold">0</div>
271
+ <!-- Room Management Tab (v2.0) -->
272
+ <div id="room-mgmt-tab" class="tab-content hidden">
273
+ <!-- Room Limit Info -->
274
+ <div class="bg-gray-700 rounded-lg p-4 mb-4 border border-gray-600">
275
+ <div class="flex justify-between items-center">
276
+ <span class="text-gray-400">Room Capacity:</span>
277
+ <span id="room-limit-text" class="font-semibold">Loading...</span>
278
+ </div>
91
279
  </div>
92
- </div>
93
280
 
94
- <!-- Main Content Grid -->
95
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
96
- <!-- Send Message Panel -->
97
- <div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
98
- <h2 class="text-xl font-bold mb-4 flex items-center">
99
- <svg class="w-6 h-6 mr-2 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
100
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
101
- </svg>
102
- Send Message
103
- </h2>
104
- <textarea id="message-input"
105
- class="w-full bg-gray-700 rounded-lg p-3 mb-3 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none resize-none"
106
- rows="3"
107
- placeholder="Enter your message to agents..."></textarea>
108
- <div class="flex gap-3">
109
- <button id="send-message-btn"
110
- class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed">
111
- Send Message
112
- </button>
113
- <button id="send-wait-btn"
114
- class="flex-1 bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed">
115
- Send & Wait
116
- </button>
117
- </div>
281
+ <!-- Create Room Form -->
282
+ <div class="bg-gray-700 rounded-lg p-4 mb-4 border border-gray-600">
283
+ <h3 class="font-semibold mb-3">Create New Room</h3>
284
+ <div class="space-y-2">
285
+ <input
286
+ id="new-room-name"
287
+ type="text"
288
+ placeholder="Room Name"
289
+ class="w-full bg-gray-800 rounded p-2 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none"
290
+ />
291
+ <input
292
+ id="new-room-desc"
293
+ type="text"
294
+ placeholder="Description (optional)"
295
+ class="w-full bg-gray-800 rounded p-2 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none"
296
+ />
297
+ <button
298
+ id="create-room-btn"
299
+ class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-lg transition"
300
+ >
301
+ Create Room
302
+ </button>
303
+ </div>
118
304
  </div>
119
305
 
120
- <!-- Health Status -->
121
- <div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
122
- <h2 class="text-xl font-bold mb-4 flex items-center">
123
- <svg class="w-6 h-6 mr-2 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
124
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
125
- </svg>
126
- System Health
127
- </h2>
128
- <div id="health-status" class="space-y-2">
129
- <div class="flex justify-between items-center">
130
- <span class="text-gray-400">Overall Status</span>
131
- <span id="health-overall" class="text-gray-500">Loading...</span>
132
- </div>
133
- <div class="flex justify-between items-center">
134
- <span class="text-gray-400">Webhook Circuit</span>
135
- <span id="health-circuit" class="text-gray-500">-</span>
136
- </div>
137
- <div class="flex justify-between items-center">
138
- <span class="text-gray-400">Rate Limiter</span>
139
- <span id="health-ratelimit" class="text-gray-500">-</span>
140
- </div>
141
- <div class="flex justify-between items-center">
142
- <span class="text-gray-400">Uptime</span>
143
- <span id="health-uptime" class="text-gray-500">-</span>
144
- </div>
145
- </div>
306
+ <!-- Private Rooms -->
307
+ <div>
308
+ <h3 class="font-semibold mb-2">📁 Private Rooms</h3>
309
+ <div
310
+ id="private-rooms-list"
311
+ class="space-y-2 max-h-96 overflow-y-auto scrollbar-thin"
312
+ >
313
+ <p class="text-gray-500">Loading...</p>
314
+ </div>
146
315
  </div>
147
- </div>
316
+ </div>
148
317
 
149
- <!-- Tabs -->
150
- <div class="bg-gray-800 rounded-lg border border-gray-700 overflow-hidden">
151
- <div class="flex border-b border-gray-700">
152
- <button class="tab-btn active px-6 py-3 font-semibold" data-tab="agents">Agents</button>
153
- <button class="tab-btn px-6 py-3 font-semibold" data-tab="rooms">Rooms</button>
154
- <button class="tab-btn px-6 py-3 font-semibold" data-tab="messages">Messages</button>
155
- <button class="tab-btn px-6 py-3 font-semibold" data-tab="webhooks">Webhooks</button>
156
- <button class="tab-btn px-6 py-3 font-semibold" data-tab="events">Events</button>
318
+ <!-- Agent-Room Management Tab (v2.0) -->
319
+ <div id="agent-room-tab" class="tab-content hidden">
320
+ <!-- Room Selector -->
321
+ <div class="bg-gray-700 rounded-lg p-4 mb-4 border border-gray-600">
322
+ <label class="block text-sm text-gray-400 mb-2">Select Room:</label>
323
+ <select
324
+ id="agent-room-select"
325
+ class="w-full bg-gray-800 rounded-lg p-2 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none"
326
+ >
327
+ <option value="">Select a room...</option>
328
+ </select>
157
329
  </div>
158
330
 
159
- <div class="p-6">
160
- <!-- Agents Tab -->
161
- <div id="agents-tab" class="tab-content">
162
- <div id="agents-list" class="space-y-3 max-h-96 overflow-y-auto scrollbar-thin">
163
- <p class="text-gray-500">Loading agents...</p>
164
- </div>
165
- </div>
331
+ <!-- Agents in Room -->
332
+ <div class="mb-4">
333
+ <div class="flex justify-between items-center mb-2">
334
+ <h3 class="font-semibold">🤖 Agents in Room</h3>
335
+ <span id="room-agents-count" class="text-sm text-gray-400">-</span>
336
+ </div>
337
+ <div id="room-agents-list" class="space-y-2 max-h-60 overflow-y-auto scrollbar-thin">
338
+ <p class="text-gray-500">Select a room first</p>
339
+ </div>
340
+ </div>
166
341
 
167
- <!-- Rooms Tab -->
168
- <div id="rooms-tab" class="tab-content hidden">
169
- <div class="mb-4">
170
- <input id="room-id-input"
171
- type="text"
172
- class="bg-gray-700 rounded-lg p-2 text-gray-100 border border-gray-600 focus:border-blue-500 focus:outline-none"
173
- placeholder="Room ID">
174
- <button id="subscribe-room-btn" class="ml-2 bg-green-600 hover:bg-green-700 text-white font-semibold py-2 px-4 rounded-lg transition">
175
- Subscribe
176
- </button>
177
- </div>
178
- <div id="rooms-list" class="space-y-3 max-h-80 overflow-y-auto scrollbar-thin">
179
- <p class="text-gray-500">Loading rooms...</p>
180
- </div>
181
- </div>
342
+ <!-- Available Agents -->
343
+ <div>
344
+ <div class="flex justify-between items-center mb-2">
345
+ <h3 class="font-semibold">➕ Available to Add</h3>
346
+ <span id="available-agents-count" class="text-sm text-gray-400">-</span>
347
+ </div>
348
+ <div
349
+ id="available-agents-list"
350
+ class="space-y-2 max-h-60 overflow-y-auto scrollbar-thin"
351
+ >
352
+ <p class="text-gray-500">Select a room first</p>
353
+ </div>
354
+ </div>
355
+ </div>
182
356
 
183
- <!-- Messages Tab -->
184
- <div id="messages-tab" class="tab-content hidden">
185
- <div id="messages-list" class="space-y-4 max-h-96 overflow-y-auto scrollbar-thin">
186
- <p class="text-gray-500">No messages yet. Send a message to see it here!</p>
187
- </div>
188
- </div>
357
+ <!-- Messages Tab -->
358
+ <div id="messages-tab" class="tab-content hidden">
359
+ <div id="messages-list" class="space-y-4 max-h-96 overflow-y-auto scrollbar-thin">
360
+ <p class="text-gray-500">No messages yet. Send a message to see it here!</p>
361
+ </div>
362
+ </div>
189
363
 
190
- <!-- Webhooks Tab -->
191
- <div id="webhooks-tab" class="tab-content hidden">
192
- <div id="webhooks-list" class="space-y-3 max-h-96 overflow-y-auto scrollbar-thin">
193
- <p class="text-gray-500">No webhooks received yet.</p>
194
- </div>
195
- </div>
364
+ <!-- Webhooks Tab -->
365
+ <div id="webhooks-tab" class="tab-content hidden">
366
+ <div id="webhooks-list" class="space-y-3 max-h-96 overflow-y-auto scrollbar-thin">
367
+ <p class="text-gray-500">No webhooks received yet.</p>
368
+ </div>
369
+ </div>
196
370
 
197
- <!-- Events Tab -->
198
- <div id="events-tab" class="tab-content hidden">
199
- <div id="events-list" class="space-y-2 max-h-96 overflow-y-auto scrollbar-thin">
200
- <p class="text-gray-500">Waiting for events...</p>
201
- </div>
202
- </div>
371
+ <!-- Events Tab -->
372
+ <div id="events-tab" class="tab-content hidden">
373
+ <div id="events-list" class="space-y-2 max-h-96 overflow-y-auto scrollbar-thin">
374
+ <p class="text-gray-500">Waiting for events...</p>
203
375
  </div>
376
+ </div>
204
377
  </div>
378
+ </div>
205
379
  </div>
206
380
 
207
381
  <script>
208
- // State
209
- let eventSource = null;
210
- let connectionStatus = 'disconnected';
211
- let authStatus = 'pending';
212
-
213
- // Initialize
214
- document.addEventListener('DOMContentLoaded', () => {
215
- setupTabs();
216
- setupEventSource();
217
- loadInitialData();
218
- setupMessageSending();
219
- setupRoomManagement();
220
- startHealthCheck();
382
+ // State
383
+ let eventSource = null;
384
+ let connectionStatus = "disconnected";
385
+ let authStatus = "pending";
386
+
387
+ // Initialize
388
+ document.addEventListener("DOMContentLoaded", () => {
389
+ setupTabs();
390
+ setupEventSource();
391
+ loadInitialData();
392
+ setupMessageSending();
393
+ setupRoomManagement();
394
+ setupRoomManagementV2();
395
+ setupAgentRoomManagement();
396
+ startHealthCheck();
397
+ });
398
+
399
+ // Tab management
400
+ function setupTabs() {
401
+ document.querySelectorAll(".tab-btn").forEach((btn) => {
402
+ btn.addEventListener("click", () => {
403
+ const tab = btn.dataset.tab;
404
+ document
405
+ .querySelectorAll(".tab-btn")
406
+ .forEach((b) => b.classList.remove("active", "bg-gray-700", "text-blue-400"));
407
+ document.querySelectorAll(".tab-content").forEach((c) => c.classList.add("hidden"));
408
+ btn.classList.add("active", "bg-gray-700", "text-blue-400");
409
+ document.getElementById(`${tab}-tab`).classList.remove("hidden");
410
+
411
+ // Load data when switching to room management tab
412
+ if (tab === "room-mgmt") {
413
+ loadRoomLimitV2();
414
+ loadPrivateRoomsV2();
415
+ }
416
+
417
+ // Load data when switching to agent-room tab
418
+ if (tab === "agent-room") {
419
+ loadPrivateRoomsV2().then(() => {
420
+ // Populate room selector after rooms are loaded
421
+ fetch("/api/v2/rooms/owned")
422
+ .then((r) => r.json())
423
+ .then((rooms) => {
424
+ populateAgentRoomSelector(rooms);
425
+ });
426
+ });
427
+ }
428
+ });
221
429
  });
430
+ }
222
431
 
223
- // Tab management
224
- function setupTabs() {
225
- document.querySelectorAll('.tab-btn').forEach(btn => {
226
- btn.addEventListener('click', () => {
227
- const tab = btn.dataset.tab;
228
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active', 'bg-gray-700', 'text-blue-400'));
229
- document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
230
- btn.classList.add('active', 'bg-gray-700', 'text-blue-400');
231
- document.getElementById(`${tab}-tab`).classList.remove('hidden');
232
- });
233
- });
234
- }
432
+ // Server-Sent Events
433
+ function setupEventSource() {
434
+ eventSource = new EventSource("/api/sse");
235
435
 
236
- // Server-Sent Events
237
- function setupEventSource() {
238
- eventSource = new EventSource('/api/sse');
239
-
240
- eventSource.addEventListener('message', (e) => {
241
- try {
242
- const data = JSON.parse(e.data);
243
- handleSSEEvent(data);
244
- } catch (error) {
245
- console.error('SSE parse error:', error);
246
- }
247
- });
436
+ eventSource.addEventListener("message", (e) => {
437
+ try {
438
+ const data = JSON.parse(e.data);
439
+ handleSSEEvent(data);
440
+ } catch (error) {
441
+ console.error("SSE parse error:", error);
442
+ }
443
+ });
248
444
 
249
- eventSource.addEventListener('error', () => {
250
- console.error('SSE connection error');
251
- setTimeout(setupEventSource, 5000);
252
- });
253
- }
445
+ eventSource.addEventListener("error", () => {
446
+ console.error("SSE connection error");
447
+ setTimeout(setupEventSource, 5000);
448
+ });
449
+ }
254
450
 
255
- function handleSSEEvent(data) {
256
- switch (data.type) {
257
- case 'connection':
258
- updateConnectionStatus(data.status);
259
- break;
260
- case 'auth':
261
- updateAuthStatus(data.status);
262
- break;
263
- case 'agent:selected':
264
- addEventItem(`Agent Selected: ${data.data.agentName}`, 'blue');
265
- break;
266
- case 'agent:response':
267
- addEventItem(`Agent Response from ${data.response.agentName}`, 'green');
268
- loadMessages();
269
- break;
270
- case 'message:updated':
271
- addEventItem(`Message Updated: Full response received`, 'green');
272
- loadMessages();
273
- break;
274
- case 'agent:list':
275
- loadAgents();
276
- break;
277
- case 'room:subscribed':
278
- addEventItem(`Subscribed to room: ${data.data.roomId}`, 'green');
279
- loadRooms();
280
- break;
281
- case 'room:unsubscribed':
282
- addEventItem(`Unsubscribed from room: ${data.data.roomId}`, 'yellow');
283
- loadRooms();
284
- break;
285
- case 'room:list':
286
- addEventItem(`Room list updated (${data.rooms.length} rooms)`, 'blue');
287
- break;
288
- case 'webhook:received':
289
- addEventItem(`Webhook: ${data.payload.event}`, 'purple');
290
- loadWebhooks();
291
- break;
292
- case 'error':
293
- addEventItem(`Error: ${data.error.message}`, 'red');
294
- break;
451
+ function handleSSEEvent(data) {
452
+ switch (data.type) {
453
+ case "connection":
454
+ updateConnectionStatus(data.status);
455
+ break;
456
+ case "auth":
457
+ updateAuthStatus(data.status);
458
+ if (data.status === "success") {
459
+ // Reload available rooms after authentication
460
+ setTimeout(loadAvailableRooms, 500);
461
+ }
462
+ break;
463
+ case "agent:selected":
464
+ addEventItem(`Agent Selected: ${data.data.agentName}`, "blue");
465
+ break;
466
+ case "agent:response":
467
+ addEventItem(`Agent Response from ${data.response.agentName}`, "green");
468
+ loadMessages();
469
+ break;
470
+ case "message:updated":
471
+ addEventItem(`Message Updated: Full response received`, "green");
472
+ loadMessages();
473
+ break;
474
+ case "agent:list":
475
+ loadAgents();
476
+ break;
477
+ case "room:subscribed":
478
+ addEventItem(`Subscribed to room: ${data.data.roomId}`, "green");
479
+ loadRooms();
480
+ loadAvailableRooms();
481
+ break;
482
+ case "room:unsubscribed":
483
+ addEventItem(`Unsubscribed from room: ${data.data.roomId}`, "yellow");
484
+ loadRooms();
485
+ loadAvailableRooms();
486
+ break;
487
+ case "room:list":
488
+ addEventItem(`Room list updated (${data.rooms.length} rooms)`, "blue");
489
+ break;
490
+ case "room:created":
491
+ addEventItem(`Room created: ${data.room.name}`, "green");
492
+ loadRoomLimitV2();
493
+ loadPrivateRoomsV2();
494
+ loadAvailableRooms();
495
+ break;
496
+ case "room:updated":
497
+ addEventItem(`Room updated: ${data.room.name}`, "blue");
498
+ loadPrivateRoomsV2();
499
+ loadAvailableRooms();
500
+ break;
501
+ case "room:deleted":
502
+ addEventItem(`Room deleted: ${data.roomId}`, "yellow");
503
+ loadRoomLimitV2();
504
+ loadPrivateRoomsV2();
505
+ loadAvailableRooms();
506
+ break;
507
+ case "agent_room:agent_added":
508
+ addEventItem(`Agent added to room: ${data.agentId}`, "green");
509
+ // Reload agent lists if we're viewing that room
510
+ const currentRoom = document.getElementById("agent-room-select").value;
511
+ if (currentRoom === data.roomId) {
512
+ loadRoomAgentsV2(data.roomId);
513
+ loadAvailableAgentsV2(data.roomId);
514
+ }
515
+ break;
516
+ case "agent_room:agent_removed":
517
+ addEventItem(`Agent removed from room: ${data.agentId}`, "yellow");
518
+ // Reload agent lists if we're viewing that room
519
+ const currentRoom2 = document.getElementById("agent-room-select").value;
520
+ if (currentRoom2 === data.roomId) {
521
+ loadRoomAgentsV2(data.roomId);
522
+ loadAvailableAgentsV2(data.roomId);
295
523
  }
524
+ break;
525
+ case "webhook:received":
526
+ addEventItem(`Webhook: ${data.payload.event}`, "purple");
527
+ loadWebhooks();
528
+ break;
529
+ case "error":
530
+ addEventItem(`Error: ${data.error.message}`, "red");
531
+ break;
296
532
  }
533
+ }
297
534
 
298
- // Load initial data
299
- async function loadInitialData() {
300
- await Promise.all([
301
- loadAgents(),
302
- loadRooms(),
303
- loadMessages(),
304
- loadWebhooks(),
305
- loadEvents(),
306
- loadMetrics()
307
- ]);
308
- }
535
+ // Load initial data
536
+ async function loadInitialData() {
537
+ await Promise.all([
538
+ loadAgents(),
539
+ loadRooms(),
540
+ loadAvailableRooms(),
541
+ loadMessages(),
542
+ loadWebhooks(),
543
+ loadEvents(),
544
+ loadMetrics(),
545
+ loadRoomLimitV2(),
546
+ loadPrivateRoomsV2()
547
+ ]);
548
+ }
309
549
 
310
- // Load agents
311
- async function loadAgents() {
312
- try {
313
- const response = await fetch('/api/agents');
314
- const agents = await response.json();
315
- document.getElementById('agents-count').textContent = agents.length;
550
+ // Load agents
551
+ async function loadAgents() {
552
+ try {
553
+ const response = await fetch("/api/agents");
554
+ const agents = await response.json();
555
+ document.getElementById("agents-count").textContent = agents.length;
316
556
 
317
- const list = document.getElementById('agents-list');
318
- if (agents.length === 0) {
319
- list.innerHTML = '<p class="text-gray-500">No agents available</p>';
320
- return;
321
- }
557
+ const list = document.getElementById("agents-list");
558
+ if (agents.length === 0) {
559
+ list.innerHTML = '<p class="text-gray-500">No agents available</p>';
560
+ return;
561
+ }
322
562
 
323
- list.innerHTML = agents.map(agent => `
563
+ list.innerHTML = agents
564
+ .map(
565
+ (agent) => `
324
566
  <div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
325
567
  <div class="flex items-center justify-between mb-2">
326
568
  <span class="font-semibold">${agent.name}</span>
327
- <span class="text-xs px-2 py-1 rounded ${agent.status === 'online' ? 'bg-green-600' : 'bg-gray-600'}">${agent.status || 'unknown'}</span>
569
+ <span class="text-xs px-2 py-1 rounded ${agent.status === "online" ? "bg-green-600" : "bg-gray-600"}">${agent.status || "unknown"}</span>
328
570
  </div>
329
- <p class="text-sm text-gray-400">${agent.description || 'No description'}</p>
330
- ${agent.capabilities ? `
571
+ <p class="text-sm text-gray-400">${agent.description || "No description"}</p>
572
+ ${
573
+ agent.capabilities
574
+ ? `
331
575
  <div class="mt-2 flex flex-wrap gap-1">
332
- ${agent.capabilities.map(cap => `<span class="text-xs px-2 py-1 bg-blue-900 rounded">${cap.name}</span>`).join('')}
576
+ ${agent.capabilities.map((cap) => `<span class="text-xs px-2 py-1 bg-blue-900 rounded">${cap.name}</span>`).join("")}
333
577
  </div>
334
- ` : ''}
578
+ `
579
+ : ""
580
+ }
335
581
  </div>
336
- `).join('');
337
- } catch (error) {
338
- console.error('Failed to load agents:', error);
339
- }
582
+ `
583
+ )
584
+ .join("");
585
+ } catch (error) {
586
+ console.error("Failed to load agents:", error);
340
587
  }
588
+ }
341
589
 
342
- // Load rooms
343
- async function loadRooms() {
344
- try {
345
- const response = await fetch('/api/rooms');
346
- const rooms = await response.json();
590
+ // Load rooms
591
+ async function loadRooms() {
592
+ try {
593
+ const response = await fetch("/api/rooms");
594
+ const rooms = await response.json();
347
595
 
348
- const list = document.getElementById('rooms-list');
349
- if (rooms.length === 0) {
350
- list.innerHTML = '<p class="text-gray-500">No rooms available</p>';
351
- return;
352
- }
596
+ const list = document.getElementById("rooms-list");
597
+ if (rooms.length === 0) {
598
+ list.innerHTML = '<p class="text-gray-500">No rooms available</p>';
599
+ return;
600
+ }
353
601
 
354
- list.innerHTML = rooms.map(room => `
602
+ list.innerHTML = rooms
603
+ .map(
604
+ (room) => `
355
605
  <div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
356
606
  <div class="flex items-center justify-between">
357
607
  <div>
@@ -363,60 +613,114 @@
363
613
  </button>
364
614
  </div>
365
615
  </div>
366
- `).join('');
367
- } catch (error) {
368
- console.error('Failed to load rooms:', error);
369
- }
616
+ `
617
+ )
618
+ .join("");
619
+ } catch (error) {
620
+ console.error("Failed to load rooms:", error);
621
+ }
622
+ }
623
+
624
+ // Load available rooms for sending messages
625
+ async function loadAvailableRooms() {
626
+ try {
627
+ const response = await fetch("/api/rooms/available");
628
+ const rooms = await response.json();
629
+
630
+ const select = document.getElementById("room-select");
631
+ const sendBtn = document.getElementById("send-message-btn");
632
+ const sendWaitBtn = document.getElementById("send-wait-btn");
633
+
634
+ if (rooms.length === 0) {
635
+ select.innerHTML =
636
+ '<option value="">No rooms available - Subscribe to a room first</option>';
637
+ sendBtn.disabled = true;
638
+ sendWaitBtn.disabled = true;
639
+ return;
640
+ }
641
+
642
+ // Build options with room type indicators
643
+ const options = rooms
644
+ .map((room) => {
645
+ const typeLabel =
646
+ room.type === "private" ? "🔒" : room.type === "subscribed" ? "📌" : "🌐";
647
+ const displayName = room.name || room.id;
648
+ return `<option value="${room.id}">${typeLabel} ${displayName}</option>`;
649
+ })
650
+ .join("");
651
+
652
+ select.innerHTML = options;
653
+
654
+ // Enable buttons only if connected
655
+ if (connectionStatus === "connected") {
656
+ sendBtn.disabled = false;
657
+ sendWaitBtn.disabled = false;
658
+ }
659
+ } catch (error) {
660
+ console.error("Failed to load available rooms:", error);
661
+ const select = document.getElementById("room-select");
662
+ select.innerHTML = '<option value="">Error loading rooms</option>';
370
663
  }
664
+ }
371
665
 
372
- // Load messages
373
- async function loadMessages() {
374
- try {
375
- const response = await fetch('/api/messages');
376
- const messages = await response.json();
377
- document.getElementById('messages-count').textContent = messages.length;
666
+ // Load messages
667
+ async function loadMessages() {
668
+ try {
669
+ const response = await fetch("/api/messages");
670
+ const messages = await response.json();
671
+ document.getElementById("messages-count").textContent = messages.length;
378
672
 
379
- const list = document.getElementById('messages-list');
380
- if (messages.length === 0) {
381
- list.innerHTML = '<p class="text-gray-500">No messages yet</p>';
382
- return;
383
- }
673
+ const list = document.getElementById("messages-list");
674
+ if (messages.length === 0) {
675
+ list.innerHTML = '<p class="text-gray-500">No messages yet</p>';
676
+ return;
677
+ }
384
678
 
385
- list.innerHTML = messages.map(msg => `
679
+ list.innerHTML = messages
680
+ .map(
681
+ (msg) => `
386
682
  <div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
387
683
  <div class="flex items-center justify-between mb-2">
388
684
  <span class="text-sm text-gray-400">${new Date(msg.timestamp).toLocaleTimeString()}</span>
389
- <span class="text-xs px-2 py-1 rounded ${msg.response ? 'bg-green-600' : 'bg-yellow-600'}">
390
- ${msg.response ? 'Responded' : 'Pending'}
685
+ <span class="text-xs px-2 py-1 rounded ${msg.response ? "bg-green-600" : "bg-yellow-600"}">
686
+ ${msg.response ? "Responded" : "Pending"}
391
687
  </span>
392
688
  </div>
393
689
  <p class="text-sm mb-2">${msg.content}</p>
394
- ${msg.response ? `
690
+ ${
691
+ msg.response
692
+ ? `
395
693
  <div class="mt-3 p-3 bg-gray-800 rounded border-l-4 border-green-500">
396
694
  <p class="text-xs text-gray-400 mb-1">Response from ${msg.response.agentName}:</p>
397
695
  <p class="text-sm">${msg.response.humanized || msg.response.content}</p>
398
696
  </div>
399
- ` : ''}
697
+ `
698
+ : ""
699
+ }
400
700
  </div>
401
- `).join('');
402
- } catch (error) {
403
- console.error('Failed to load messages:', error);
404
- }
701
+ `
702
+ )
703
+ .join("");
704
+ } catch (error) {
705
+ console.error("Failed to load messages:", error);
405
706
  }
707
+ }
406
708
 
407
- // Load webhooks
408
- async function loadWebhooks() {
409
- try {
410
- const response = await fetch('/api/webhooks');
411
- const webhooks = await response.json();
709
+ // Load webhooks
710
+ async function loadWebhooks() {
711
+ try {
712
+ const response = await fetch("/api/webhooks");
713
+ const webhooks = await response.json();
412
714
 
413
- const list = document.getElementById('webhooks-list');
414
- if (webhooks.length === 0) {
415
- list.innerHTML = '<p class="text-gray-500">No webhooks received yet</p>';
416
- return;
417
- }
715
+ const list = document.getElementById("webhooks-list");
716
+ if (webhooks.length === 0) {
717
+ list.innerHTML = '<p class="text-gray-500">No webhooks received yet</p>';
718
+ return;
719
+ }
418
720
 
419
- list.innerHTML = webhooks.map(wh => `
721
+ list.innerHTML = webhooks
722
+ .map(
723
+ (wh) => `
420
724
  <div class="bg-gray-700 rounded-lg p-3 border border-gray-600">
421
725
  <div class="flex items-center justify-between mb-1">
422
726
  <span class="font-mono text-sm text-purple-400">${wh.event}</span>
@@ -424,25 +728,29 @@
424
728
  </div>
425
729
  <pre class="text-xs text-gray-400 overflow-x-auto">${JSON.stringify(wh.data, null, 2)}</pre>
426
730
  </div>
427
- `).join('');
428
- } catch (error) {
429
- console.error('Failed to load webhooks:', error);
430
- }
731
+ `
732
+ )
733
+ .join("");
734
+ } catch (error) {
735
+ console.error("Failed to load webhooks:", error);
431
736
  }
737
+ }
432
738
 
433
- // Load events
434
- async function loadEvents() {
435
- try {
436
- const response = await fetch('/api/events');
437
- const events = await response.json();
739
+ // Load events
740
+ async function loadEvents() {
741
+ try {
742
+ const response = await fetch("/api/events");
743
+ const events = await response.json();
438
744
 
439
- const list = document.getElementById('events-list');
440
- if (events.length === 0) {
441
- list.innerHTML = '<p class="text-gray-500">No events yet</p>';
442
- return;
443
- }
745
+ const list = document.getElementById("events-list");
746
+ if (events.length === 0) {
747
+ list.innerHTML = '<p class="text-gray-500">No events yet</p>';
748
+ return;
749
+ }
444
750
 
445
- list.innerHTML = events.map(evt => `
751
+ list.innerHTML = events
752
+ .map(
753
+ (evt) => `
446
754
  <div class="event-item bg-gray-700 rounded p-2 border border-gray-600">
447
755
  <div class="flex items-center justify-between">
448
756
  <span class="font-mono text-xs text-blue-400">${evt.type}</span>
@@ -450,193 +758,531 @@
450
758
  </div>
451
759
  <p class="text-xs text-gray-400 mt-1">${JSON.stringify(evt.data)}</p>
452
760
  </div>
453
- `).join('');
454
- } catch (error) {
455
- console.error('Failed to load events:', error);
456
- }
761
+ `
762
+ )
763
+ .join("");
764
+ } catch (error) {
765
+ console.error("Failed to load events:", error);
457
766
  }
767
+ }
458
768
 
459
- // Load metrics
460
- async function loadMetrics() {
461
- try {
462
- const response = await fetch('/metrics');
463
- const metrics = await response.json();
769
+ // Load metrics
770
+ async function loadMetrics() {
771
+ try {
772
+ const response = await fetch("/metrics");
773
+ const metrics = await response.json();
464
774
 
465
- document.getElementById('health-ratelimit').textContent = metrics.messages?.sent || 0;
466
- } catch (error) {
467
- console.error('Failed to load metrics:', error);
468
- }
775
+ document.getElementById("health-ratelimit").textContent = metrics.messages?.sent || 0;
776
+ } catch (error) {
777
+ console.error("Failed to load metrics:", error);
469
778
  }
779
+ }
470
780
 
471
- // Message sending
472
- function setupMessageSending() {
473
- document.getElementById('send-message-btn').addEventListener('click', () => sendMessage(false));
474
- document.getElementById('send-wait-btn').addEventListener('click', () => sendMessage(true));
475
- }
781
+ // Message sending
782
+ function setupMessageSending() {
783
+ document
784
+ .getElementById("send-message-btn")
785
+ .addEventListener("click", () => sendMessage(false));
786
+ document.getElementById("send-wait-btn").addEventListener("click", () => sendMessage(true));
787
+ }
476
788
 
477
- async function sendMessage(waitForResponse) {
478
- const input = document.getElementById('message-input');
479
- const content = input.value.trim();
480
-
481
- if (!content) return;
482
-
483
- try {
484
- const response = await fetch('/api/message', {
485
- method: 'POST',
486
- headers: { 'Content-Type': 'application/json' },
487
- body: JSON.stringify({ content, waitForResponse })
488
- });
489
-
490
- if (response.ok) {
491
- input.value = '';
492
- addEventItem(`Sent: ${content}`, 'green');
493
- setTimeout(loadMessages, 500);
494
- } else {
495
- addEventItem('Failed to send message', 'red');
496
- }
497
- } catch (error) {
498
- console.error('Send error:', error);
499
- addEventItem('Error sending message', 'red');
500
- }
501
- }
789
+ async function sendMessage(waitForResponse) {
790
+ const input = document.getElementById("message-input");
791
+ const roomSelect = document.getElementById("room-select");
792
+ const content = input.value.trim();
793
+ const room = roomSelect.value;
502
794
 
503
- // Room management
504
- function setupRoomManagement() {
505
- document.getElementById('subscribe-room-btn').addEventListener('click', async () => {
506
- const input = document.getElementById('room-id-input');
507
- const roomId = input.value.trim();
508
-
509
- if (!roomId) return;
510
-
511
- try {
512
- const response = await fetch('/api/room/join', {
513
- method: 'POST',
514
- headers: { 'Content-Type': 'application/json' },
515
- body: JSON.stringify({ roomId })
516
- });
517
-
518
- if (response.ok) {
519
- input.value = '';
520
- addEventItem(`Subscribing to room: ${roomId}`, 'green');
521
- setTimeout(loadRooms, 500);
522
- }
523
- } catch (error) {
524
- console.error('Subscribe room error:', error);
525
- }
526
- });
795
+ if (!content) {
796
+ addEventItem("Please enter a message", "yellow");
797
+ return;
527
798
  }
528
799
 
529
- async function unsubscribeRoom(roomId) {
530
- try {
531
- const response = await fetch('/api/room/leave', {
532
- method: 'POST',
533
- headers: { 'Content-Type': 'application/json' },
534
- body: JSON.stringify({ roomId })
535
- });
536
-
537
- if (response.ok) {
538
- addEventItem(`Unsubscribing from room: ${roomId}`, 'yellow');
539
- setTimeout(loadRooms, 500);
540
- }
541
- } catch (error) {
542
- console.error('Unsubscribe room error:', error);
543
- }
800
+ if (!room) {
801
+ addEventItem("Please select a room", "yellow");
802
+ return;
544
803
  }
545
804
 
546
- // Update status displays
547
- function updateConnectionStatus(status) {
548
- connectionStatus = status;
549
- const dot = document.getElementById('connection-dot');
550
- const text = document.getElementById('connection-status');
551
-
552
- if (status === 'connected') {
553
- dot.className = 'status-dot w-3 h-3 rounded-full bg-green-500';
554
- text.textContent = 'Connected';
555
- text.className = 'text-2xl font-bold text-green-500';
556
- document.getElementById('send-message-btn').disabled = false;
557
- document.getElementById('send-wait-btn').disabled = false;
558
- } else if (status === 'reconnecting') {
559
- dot.className = 'status-dot w-3 h-3 rounded-full bg-yellow-500';
560
- text.textContent = 'Reconnecting';
561
- text.className = 'text-2xl font-bold text-yellow-500';
562
- } else {
563
- dot.className = 'status-dot w-3 h-3 rounded-full bg-red-500';
564
- text.textContent = 'Disconnected';
565
- text.className = 'text-2xl font-bold text-red-500';
566
- document.getElementById('send-message-btn').disabled = true;
567
- document.getElementById('send-wait-btn').disabled = true;
568
- }
805
+ try {
806
+ const response = await fetch("/api/message", {
807
+ method: "POST",
808
+ headers: { "Content-Type": "application/json" },
809
+ body: JSON.stringify({ content, room, waitForResponse })
810
+ });
811
+
812
+ const result = await response.json();
813
+
814
+ if (response.ok) {
815
+ input.value = "";
816
+ addEventItem(
817
+ `Sent to ${room}: ${content.substring(0, 50)}${content.length > 50 ? "..." : ""}`,
818
+ "green"
819
+ );
820
+ setTimeout(loadMessages, 500);
821
+ } else {
822
+ addEventItem(`Failed to send: ${result.error}`, "red");
823
+ }
824
+ } catch (error) {
825
+ console.error("Send error:", error);
826
+ addEventItem("Error sending message", "red");
569
827
  }
828
+ }
570
829
 
571
- function updateAuthStatus(status) {
572
- authStatus = status;
573
- const dot = document.getElementById('auth-dot');
574
- const text = document.getElementById('auth-status');
830
+ // Room management
831
+ function setupRoomManagement() {
832
+ document.getElementById("subscribe-room-btn").addEventListener("click", async () => {
833
+ const input = document.getElementById("room-id-input");
834
+ const roomId = input.value.trim();
575
835
 
576
- if (status === 'success') {
577
- dot.className = 'status-dot w-3 h-3 rounded-full bg-green-500';
578
- text.textContent = 'Authenticated';
579
- text.className = 'text-2xl font-bold text-green-500';
580
- } else {
581
- dot.className = 'status-dot w-3 h-3 rounded-full bg-yellow-500';
582
- text.textContent = 'Pending';
583
- text.className = 'text-2xl font-bold text-yellow-500';
836
+ if (!roomId) return;
837
+
838
+ try {
839
+ const response = await fetch("/api/room/join", {
840
+ method: "POST",
841
+ headers: { "Content-Type": "application/json" },
842
+ body: JSON.stringify({ roomId })
843
+ });
844
+
845
+ if (response.ok) {
846
+ input.value = "";
847
+ addEventItem(`Subscribing to room: ${roomId}`, "green");
848
+ setTimeout(loadRooms, 500);
584
849
  }
850
+ } catch (error) {
851
+ console.error("Subscribe room error:", error);
852
+ }
853
+ });
854
+ }
855
+
856
+ async function unsubscribeRoom(roomId) {
857
+ try {
858
+ const response = await fetch("/api/room/leave", {
859
+ method: "POST",
860
+ headers: { "Content-Type": "application/json" },
861
+ body: JSON.stringify({ roomId })
862
+ });
863
+
864
+ if (response.ok) {
865
+ addEventItem(`Unsubscribing from room: ${roomId}`, "yellow");
866
+ setTimeout(loadRooms, 500);
867
+ }
868
+ } catch (error) {
869
+ console.error("Unsubscribe room error:", error);
585
870
  }
871
+ }
586
872
 
587
- // Health check
588
- function startHealthCheck() {
589
- setInterval(async () => {
590
- try {
591
- const response = await fetch('/health');
592
- const health = await response.json();
593
-
594
- document.getElementById('health-overall').textContent = health.status;
595
- document.getElementById('health-overall').className = health.status === 'healthy' ? 'text-green-500' : 'text-yellow-500';
596
- document.getElementById('health-circuit').textContent = health.webhook?.circuitState || 'N/A';
597
- document.getElementById('health-uptime').textContent = formatUptime(health.timestamp);
598
- } catch (error) {
599
- console.error('Health check failed:', error);
600
- }
601
- }, 5000);
873
+ // Update status displays
874
+ function updateConnectionStatus(status) {
875
+ connectionStatus = status;
876
+ const dot = document.getElementById("connection-dot");
877
+ const text = document.getElementById("connection-status");
878
+ const sendBtn = document.getElementById("send-message-btn");
879
+ const sendWaitBtn = document.getElementById("send-wait-btn");
880
+ const roomSelect = document.getElementById("room-select");
881
+
882
+ if (status === "connected") {
883
+ dot.className = "status-dot w-3 h-3 rounded-full bg-green-500";
884
+ text.textContent = "Connected";
885
+ text.className = "text-2xl font-bold text-green-500";
886
+ // Only enable if there are rooms available
887
+ if (roomSelect.value) {
888
+ sendBtn.disabled = false;
889
+ sendWaitBtn.disabled = false;
890
+ }
891
+ } else if (status === "reconnecting") {
892
+ dot.className = "status-dot w-3 h-3 rounded-full bg-yellow-500";
893
+ text.textContent = "Reconnecting";
894
+ text.className = "text-2xl font-bold text-yellow-500";
895
+ sendBtn.disabled = true;
896
+ sendWaitBtn.disabled = true;
897
+ } else {
898
+ dot.className = "status-dot w-3 h-3 rounded-full bg-red-500";
899
+ text.textContent = "Disconnected";
900
+ text.className = "text-2xl font-bold text-red-500";
901
+ sendBtn.disabled = true;
902
+ sendWaitBtn.disabled = true;
602
903
  }
904
+ }
905
+
906
+ function updateAuthStatus(status) {
907
+ authStatus = status;
908
+ const dot = document.getElementById("auth-dot");
909
+ const text = document.getElementById("auth-status");
603
910
 
604
- function formatUptime(timestamp) {
605
- const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000);
606
- const hours = Math.floor(seconds / 3600);
607
- const minutes = Math.floor((seconds % 3600) / 60);
608
- return `${hours}h ${minutes}m`;
911
+ if (status === "success") {
912
+ dot.className = "status-dot w-3 h-3 rounded-full bg-green-500";
913
+ text.textContent = "Authenticated";
914
+ text.className = "text-2xl font-bold text-green-500";
915
+ } else {
916
+ dot.className = "status-dot w-3 h-3 rounded-full bg-yellow-500";
917
+ text.textContent = "Pending";
918
+ text.className = "text-2xl font-bold text-yellow-500";
609
919
  }
920
+ }
610
921
 
611
- function addEventItem(message, color) {
612
- const list = document.getElementById('events-list');
613
- const colorClass = {
614
- 'blue': 'border-blue-500',
615
- 'green': 'border-green-500',
616
- 'red': 'border-red-500',
617
- 'yellow': 'border-yellow-500',
618
- 'purple': 'border-purple-500'
619
- }[color] || 'border-gray-500';
620
-
621
- const item = document.createElement('div');
622
- item.className = `event-item bg-gray-700 rounded p-2 border-l-4 ${colorClass}`;
623
- item.innerHTML = `
922
+ // Health check
923
+ function startHealthCheck() {
924
+ setInterval(async () => {
925
+ try {
926
+ const response = await fetch("/health");
927
+ const health = await response.json();
928
+
929
+ document.getElementById("health-overall").textContent = health.status;
930
+ document.getElementById("health-overall").className =
931
+ health.status === "healthy" ? "text-green-500" : "text-yellow-500";
932
+ document.getElementById("health-circuit").textContent =
933
+ health.webhook?.circuitState || "N/A";
934
+ document.getElementById("health-uptime").textContent = formatUptime(health.timestamp);
935
+ } catch (error) {
936
+ console.error("Health check failed:", error);
937
+ }
938
+ }, 5000);
939
+ }
940
+
941
+ function formatUptime(timestamp) {
942
+ const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000);
943
+ const hours = Math.floor(seconds / 3600);
944
+ const minutes = Math.floor((seconds % 3600) / 60);
945
+ return `${hours}h ${minutes}m`;
946
+ }
947
+
948
+ function addEventItem(message, color) {
949
+ const list = document.getElementById("events-list");
950
+ const colorClass =
951
+ {
952
+ blue: "border-blue-500",
953
+ green: "border-green-500",
954
+ red: "border-red-500",
955
+ yellow: "border-yellow-500",
956
+ purple: "border-purple-500"
957
+ }[color] || "border-gray-500";
958
+
959
+ const item = document.createElement("div");
960
+ item.className = `event-item bg-gray-700 rounded p-2 border-l-4 ${colorClass}`;
961
+ item.innerHTML = `
624
962
  <div class="flex items-center justify-between">
625
963
  <span class="text-sm">${message}</span>
626
964
  <span class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</span>
627
965
  </div>
628
966
  `;
629
967
 
630
- if (list.firstChild && list.firstChild.textContent.includes('Waiting for events')) {
631
- list.innerHTML = '';
632
- }
968
+ if (list.firstChild && list.firstChild.textContent.includes("Waiting for events")) {
969
+ list.innerHTML = "";
970
+ }
971
+
972
+ list.insertBefore(item, list.firstChild);
973
+
974
+ if (list.children.length > 50) {
975
+ list.removeChild(list.lastChild);
976
+ }
977
+ }
978
+
979
+ // ===========================================
980
+ // V2.0 ROOM MANAGEMENT FUNCTIONS
981
+ // ===========================================
982
+
983
+ async function loadRoomLimitV2() {
984
+ try {
985
+ const response = await fetch("/api/v2/rooms/limit");
986
+ const data = await response.json();
987
+
988
+ const limitText = `${data.count}/${data.limit} ${data.canCreate ? "✅" : "⚠️"}`;
989
+ document.getElementById("room-limit-text").textContent = limitText;
990
+ } catch (error) {
991
+ console.error("Failed to load room limit:", error);
992
+ }
993
+ }
994
+
995
+ async function loadPrivateRoomsV2() {
996
+ try {
997
+ // Fetch both owned and shared rooms
998
+ const [ownedResponse, sharedResponse] = await Promise.all([
999
+ fetch("/api/v2/rooms/owned"),
1000
+ fetch("/api/v2/rooms/shared")
1001
+ ]);
1002
+
1003
+ const ownedRooms = await ownedResponse.json();
1004
+ const sharedRooms = await sharedResponse.json();
1005
+
1006
+ // Filter to only show private rooms (exclude public rooms)
1007
+ // Owned rooms should already be private, but filter shared rooms too
1008
+ const allPrivateRooms = [
1009
+ ...ownedRooms.filter((r) => !r.is_public),
1010
+ ...sharedRooms.filter((r) => !r.is_public)
1011
+ ];
1012
+
1013
+ const list = document.getElementById("private-rooms-list");
1014
+ if (allPrivateRooms.length === 0) {
1015
+ list.innerHTML = '<p class="text-gray-500">No private rooms</p>';
1016
+ return;
1017
+ }
1018
+
1019
+ list.innerHTML = allPrivateRooms
1020
+ .map(
1021
+ (room) => `
1022
+ <div class="bg-gray-800 rounded p-3 border border-gray-600">
1023
+ <div class="flex justify-between items-start">
1024
+ <div class="flex-1">
1025
+ <div class="font-semibold">${room.name}</div>
1026
+ <div class="text-xs text-gray-400">${room.id}</div>
1027
+ ${room.description ? `<div class="text-sm text-gray-400 mt-1">${room.description}</div>` : ""}
1028
+ <div class="text-xs mt-1">
1029
+ <span class="px-2 py-1 rounded bg-purple-900">🔒 Private</span>
1030
+ ${room.is_owner ? '<span class="ml-2 px-2 py-1 rounded bg-yellow-900">👑 Owner</span>' : '<span class="ml-2 px-2 py-1 rounded bg-gray-700">🤝 Shared</span>'}
1031
+ </div>
1032
+ </div>
1033
+ ${
1034
+ room.is_owner
1035
+ ? `
1036
+ <div class="flex gap-2">
1037
+ <button onclick="deleteRoomV2('${room.id}')" class="bg-red-600 hover:bg-red-700 text-white text-xs py-1 px-2 rounded transition">
1038
+ Delete
1039
+ </button>
1040
+ </div>
1041
+ `
1042
+ : ""
1043
+ }
1044
+ </div>
1045
+ </div>
1046
+ `
1047
+ )
1048
+ .join("");
1049
+
1050
+ // Also populate agent-room selector with owned rooms only
1051
+ populateAgentRoomSelector(ownedRooms);
1052
+ } catch (error) {
1053
+ console.error("Failed to load private rooms:", error);
1054
+ }
1055
+ }
1056
+
1057
+ function setupRoomManagementV2() {
1058
+ document.getElementById("create-room-btn").addEventListener("click", async () => {
1059
+ const name = document.getElementById("new-room-name").value.trim();
1060
+ const description = document.getElementById("new-room-desc").value.trim();
1061
+
1062
+ if (!name) {
1063
+ addEventItem("Room name is required", "yellow");
1064
+ return;
1065
+ }
633
1066
 
634
- list.insertBefore(item, list.firstChild);
1067
+ try {
1068
+ const response = await fetch("/api/v2/rooms", {
1069
+ method: "POST",
1070
+ headers: { "Content-Type": "application/json" },
1071
+ body: JSON.stringify({ name, description: description || undefined })
1072
+ });
1073
+
1074
+ const result = await response.json();
1075
+
1076
+ if (response.ok) {
1077
+ document.getElementById("new-room-name").value = "";
1078
+ document.getElementById("new-room-desc").value = "";
635
1079
 
636
- if (list.children.length > 50) {
637
- list.removeChild(list.lastChild);
1080
+ addEventItem(`Created room: ${result.name}`, "green");
1081
+ setTimeout(() => {
1082
+ loadRoomLimitV2();
1083
+ loadPrivateRoomsV2();
1084
+ }, 500);
1085
+ } else {
1086
+ addEventItem(`Failed to create room: ${result.error}`, "red");
638
1087
  }
1088
+ } catch (error) {
1089
+ console.error("Create room error:", error);
1090
+ addEventItem("Error creating room", "red");
1091
+ }
1092
+ });
1093
+ }
1094
+
1095
+ async function deleteRoomV2(roomId) {
1096
+ if (!confirm("Are you sure you want to delete this room?")) return;
1097
+
1098
+ try {
1099
+ const response = await fetch(`/api/v2/rooms/${roomId}`, {
1100
+ method: "DELETE"
1101
+ });
1102
+
1103
+ const result = await response.json();
1104
+
1105
+ if (response.ok) {
1106
+ addEventItem(`Deleted room: ${roomId}`, "green");
1107
+ setTimeout(() => {
1108
+ loadRoomLimitV2();
1109
+ loadPrivateRoomsV2();
1110
+ }, 500);
1111
+ } else {
1112
+ addEventItem(`Failed to delete room: ${result.error}`, "red");
1113
+ }
1114
+ } catch (error) {
1115
+ console.error("Delete room error:", error);
1116
+ addEventItem("Error deleting room", "red");
1117
+ }
1118
+ }
1119
+
1120
+ // ===========================================
1121
+ // V2.0 AGENT-ROOM MANAGEMENT FUNCTIONS
1122
+ // ===========================================
1123
+
1124
+ function populateAgentRoomSelector(ownedRooms) {
1125
+ const select = document.getElementById("agent-room-select");
1126
+ if (ownedRooms.length === 0) {
1127
+ select.innerHTML = '<option value="">No owned rooms - create one first</option>';
1128
+ return;
1129
+ }
1130
+
1131
+ select.innerHTML =
1132
+ '<option value="">Select a room...</option>' +
1133
+ ownedRooms.map((room) => `<option value="${room.id}">${room.name}</option>`).join("");
1134
+ }
1135
+
1136
+ function setupAgentRoomManagement() {
1137
+ document.getElementById("agent-room-select").addEventListener("change", async (e) => {
1138
+ const roomId = e.target.value;
1139
+ if (!roomId) {
1140
+ document.getElementById("room-agents-list").innerHTML =
1141
+ '<p class="text-gray-500">Select a room first</p>';
1142
+ document.getElementById("available-agents-list").innerHTML =
1143
+ '<p class="text-gray-500">Select a room first</p>';
1144
+ document.getElementById("room-agents-count").textContent = "-";
1145
+ document.getElementById("available-agents-count").textContent = "-";
1146
+ return;
1147
+ }
1148
+
1149
+ await Promise.all([loadRoomAgentsV2(roomId), loadAvailableAgentsV2(roomId)]);
1150
+ });
1151
+ }
1152
+
1153
+ async function loadRoomAgentsV2(roomId) {
1154
+ try {
1155
+ const response = await fetch(`/api/v2/rooms/${roomId}/agents`);
1156
+ const data = await response.json();
1157
+
1158
+ document.getElementById("room-agents-count").textContent = `${data.count} agents`;
1159
+
1160
+ const list = document.getElementById("room-agents-list");
1161
+ if (data.agents.length === 0) {
1162
+ list.innerHTML = '<p class="text-gray-500">No agents in this room</p>';
1163
+ return;
1164
+ }
1165
+
1166
+ list.innerHTML = data.agents
1167
+ .map(
1168
+ (agent) => `
1169
+ <div class="bg-gray-800 rounded p-3 border border-gray-600 flex justify-between items-center">
1170
+ <div>
1171
+ <div class="font-semibold">${agent.agent_name || "Unnamed"}</div>
1172
+ <div class="text-xs text-gray-400">${agent.agent_id}</div>
1173
+ ${
1174
+ agent.capabilities
1175
+ ? `
1176
+ <div class="flex flex-wrap gap-1 mt-1">
1177
+ ${agent.capabilities
1178
+ .slice(0, 3)
1179
+ .map(
1180
+ (cap) =>
1181
+ `<span class="text-xs px-2 py-1 bg-blue-900 rounded">${cap.name}</span>`
1182
+ )
1183
+ .join("")}
1184
+ </div>
1185
+ `
1186
+ : ""
1187
+ }
1188
+ </div>
1189
+ <button onclick="removeAgentFromRoomV2('${roomId}', '${agent.agent_id}')"
1190
+ class="bg-red-600 hover:bg-red-700 text-white text-xs py-1 px-3 rounded transition">
1191
+ Remove
1192
+ </button>
1193
+ </div>
1194
+ `
1195
+ )
1196
+ .join("");
1197
+ } catch (error) {
1198
+ console.error("Failed to load room agents:", error);
1199
+ document.getElementById("room-agents-list").innerHTML =
1200
+ '<p class="text-red-500">Error loading agents</p>';
1201
+ }
1202
+ }
1203
+
1204
+ async function loadAvailableAgentsV2(roomId) {
1205
+ try {
1206
+ const response = await fetch(`/api/v2/rooms/${roomId}/available-agents`);
1207
+ const data = await response.json();
1208
+
1209
+ document.getElementById("available-agents-count").textContent = `${data.count} available`;
1210
+
1211
+ const list = document.getElementById("available-agents-list");
1212
+ if (data.agents.length === 0) {
1213
+ list.innerHTML = '<p class="text-gray-500">No more agents available</p>';
1214
+ return;
1215
+ }
1216
+
1217
+ list.innerHTML = data.agents
1218
+ .map(
1219
+ (agent) => `
1220
+ <div class="bg-gray-800 rounded p-3 border border-gray-600 flex justify-between items-center">
1221
+ <div>
1222
+ <div class="font-semibold">${agent.agent_name || "Unnamed"}</div>
1223
+ <div class="text-xs text-gray-400">${agent.agent_id}</div>
1224
+ ${agent.description ? `<div class="text-xs text-gray-400 mt-1">${agent.description.substring(0, 60)}...</div>` : ""}
1225
+ </div>
1226
+ <button onclick="addAgentToRoomV2('${roomId}', '${agent.agent_id}')"
1227
+ class="bg-green-600 hover:bg-green-700 text-white text-xs py-1 px-3 rounded transition">
1228
+ Add
1229
+ </button>
1230
+ </div>
1231
+ `
1232
+ )
1233
+ .join("");
1234
+ } catch (error) {
1235
+ console.error("Failed to load available agents:", error);
1236
+ document.getElementById("available-agents-list").innerHTML =
1237
+ '<p class="text-red-500">Error loading agents</p>';
1238
+ }
1239
+ }
1240
+
1241
+ async function addAgentToRoomV2(roomId, agentId) {
1242
+ try {
1243
+ const response = await fetch(`/api/v2/rooms/${roomId}/agents/${agentId}`, {
1244
+ method: "POST"
1245
+ });
1246
+
1247
+ const result = await response.json();
1248
+
1249
+ if (response.ok) {
1250
+ addEventItem(`Added agent to room`, "green");
1251
+ setTimeout(() => {
1252
+ loadRoomAgentsV2(roomId);
1253
+ loadAvailableAgentsV2(roomId);
1254
+ }, 500);
1255
+ } else {
1256
+ addEventItem(`Failed to add agent: ${result.error}`, "red");
1257
+ }
1258
+ } catch (error) {
1259
+ console.error("Add agent error:", error);
1260
+ addEventItem("Error adding agent", "red");
1261
+ }
1262
+ }
1263
+
1264
+ async function removeAgentFromRoomV2(roomId, agentId) {
1265
+ try {
1266
+ const response = await fetch(`/api/v2/rooms/${roomId}/agents/${agentId}`, {
1267
+ method: "DELETE"
1268
+ });
1269
+
1270
+ const result = await response.json();
1271
+
1272
+ if (response.ok) {
1273
+ addEventItem(`Removed agent from room`, "green");
1274
+ setTimeout(() => {
1275
+ loadRoomAgentsV2(roomId);
1276
+ loadAvailableAgentsV2(roomId);
1277
+ }, 500);
1278
+ } else {
1279
+ addEventItem(`Failed to remove agent: ${result.error}`, "red");
1280
+ }
1281
+ } catch (error) {
1282
+ console.error("Remove agent error:", error);
1283
+ addEventItem("Error removing agent", "red");
639
1284
  }
1285
+ }
640
1286
  </script>
641
- </body>
1287
+ </body>
642
1288
  </html>