@deadragdoll/tellymcp 0.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 (124) hide show
  1. package/.env.example.client +93 -0
  2. package/.env.example.gateway +120 -0
  3. package/CHANGELOG.md +155 -0
  4. package/LICENSE +21 -0
  5. package/README-ru.md +338 -0
  6. package/README.md +1262 -0
  7. package/STANDALONE-ru.md +266 -0
  8. package/STANDALONE.md +266 -0
  9. package/TOOLS.md +1296 -0
  10. package/config/templates/env.both.template +83 -0
  11. package/config/templates/env.client.template +60 -0
  12. package/config/templates/env.gateway.template +82 -0
  13. package/dist/cli.js +636 -0
  14. package/dist/index.js +17 -0
  15. package/dist/lib/logfeed/store.js +52 -0
  16. package/dist/lib/middlewares/tracer.js +172 -0
  17. package/dist/lib/mixins/db.js +267 -0
  18. package/dist/lib/mixins/logfeed.js +34 -0
  19. package/dist/lib/mixins/session.errors.js +142 -0
  20. package/dist/lib/moleculer.js +2 -0
  21. package/dist/lib/trace.js +147 -0
  22. package/dist/lib/traceContext.js +116 -0
  23. package/dist/moleculer.config.js +274 -0
  24. package/dist/services/features/telegram-mcp/approval.service.js +33 -0
  25. package/dist/services/features/telegram-mcp/browser.service.js +42 -0
  26. package/dist/services/features/telegram-mcp/collaboration.service.js +53 -0
  27. package/dist/services/features/telegram-mcp/ensuredb.service.js +337 -0
  28. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +378 -0
  29. package/dist/services/features/telegram-mcp/gateway-loopback.js +10 -0
  30. package/dist/services/features/telegram-mcp/gateway-rmq.service.js +294 -0
  31. package/dist/services/features/telegram-mcp/gateway-socket.service.js +1463 -0
  32. package/dist/services/features/telegram-mcp/gateway.service.js +1141 -0
  33. package/dist/services/features/telegram-mcp/inbox.service.js +33 -0
  34. package/dist/services/features/telegram-mcp/mcp-http.service.js +76 -0
  35. package/dist/services/features/telegram-mcp/mcp-server.service.js +127 -0
  36. package/dist/services/features/telegram-mcp/notify.service.js +33 -0
  37. package/dist/services/features/telegram-mcp/pair.service.js +33 -0
  38. package/dist/services/features/telegram-mcp/runtime.service.js +36 -0
  39. package/dist/services/features/telegram-mcp/session-context.service.js +33 -0
  40. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +103 -0
  41. package/dist/services/features/telegram-mcp/src/app/config/env.js +317 -0
  42. package/dist/services/features/telegram-mcp/src/app/http.js +774 -0
  43. package/dist/services/features/telegram-mcp/src/app/index.js +2 -0
  44. package/dist/services/features/telegram-mcp/src/app/providers/mcp/server.js +13 -0
  45. package/dist/services/features/telegram-mcp/src/app/providers/redis/client.js +18 -0
  46. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +740 -0
  47. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +267 -0
  48. package/dist/services/features/telegram-mcp/src/app/webapp/relay.js +69 -0
  49. package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +9 -0
  50. package/dist/services/features/telegram-mcp/src/entities/auth/model/types.js +2 -0
  51. package/dist/services/features/telegram-mcp/src/entities/browser/model/types.js +2 -0
  52. package/dist/services/features/telegram-mcp/src/entities/collaboration/model/types.js +2 -0
  53. package/dist/services/features/telegram-mcp/src/entities/inbox/model/types.js +2 -0
  54. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +545 -0
  55. package/dist/services/features/telegram-mcp/src/entities/request/model/types.js +2 -0
  56. package/dist/services/features/telegram-mcp/src/entities/session/model/types.js +2 -0
  57. package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +33 -0
  58. package/dist/services/features/telegram-mcp/src/features/browser/model/browserClearLogsTool.js +28 -0
  59. package/dist/services/features/telegram-mcp/src/features/browser/model/browserClickTool.js +28 -0
  60. package/dist/services/features/telegram-mcp/src/features/browser/model/browserCloseTool.js +28 -0
  61. package/dist/services/features/telegram-mcp/src/features/browser/model/browserComputedStyleTool.js +28 -0
  62. package/dist/services/features/telegram-mcp/src/features/browser/model/browserConsoleTool.js +28 -0
  63. package/dist/services/features/telegram-mcp/src/features/browser/model/browserDomTool.js +28 -0
  64. package/dist/services/features/telegram-mcp/src/features/browser/model/browserErrorsTool.js +28 -0
  65. package/dist/services/features/telegram-mcp/src/features/browser/model/browserFillTool.js +28 -0
  66. package/dist/services/features/telegram-mcp/src/features/browser/model/browserNetworkFailuresTool.js +28 -0
  67. package/dist/services/features/telegram-mcp/src/features/browser/model/browserOpenTool.js +33 -0
  68. package/dist/services/features/telegram-mcp/src/features/browser/model/browserPressTool.js +28 -0
  69. package/dist/services/features/telegram-mcp/src/features/browser/model/browserReloadTool.js +28 -0
  70. package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +28 -0
  71. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +689 -0
  72. package/dist/services/features/telegram-mcp/src/features/browser/model/browserWaitForTool.js +28 -0
  73. package/dist/services/features/telegram-mcp/src/features/browser/model/browserWaitForUrlTool.js +28 -0
  74. package/dist/services/features/telegram-mcp/src/features/collaboration/model/backend.js +2 -0
  75. package/dist/services/features/telegram-mcp/src/features/collaboration/model/collaborationService.js +26 -0
  76. package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +390 -0
  77. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +102 -0
  78. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +33 -0
  79. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +33 -0
  80. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +69 -0
  81. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +657 -0
  82. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayReplyResolution.js +17 -0
  83. package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +33 -0
  84. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +33 -0
  85. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +33 -0
  86. package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +77 -0
  87. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +93 -0
  88. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +33 -0
  89. package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +33 -0
  90. package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +33 -0
  91. package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +202 -0
  92. package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +33 -0
  93. package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +33 -0
  94. package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +33 -0
  95. package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +33 -0
  96. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +409 -0
  97. package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +33 -0
  98. package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +33 -0
  99. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +123 -0
  100. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +33 -0
  101. package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +243 -0
  102. package/dist/services/features/telegram-mcp/src/shared/api/storage/contract.js +2 -0
  103. package/dist/services/features/telegram-mcp/src/shared/api/tool-registry/registry.js +8 -0
  104. package/dist/services/features/telegram-mcp/src/shared/api/tool-registry/types.js +2 -0
  105. package/dist/services/features/telegram-mcp/src/shared/api/transport/contract.js +2 -0
  106. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +86 -0
  107. package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +436 -0
  108. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/collabSemantics.js +21 -0
  109. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/collabUi.js +87 -0
  110. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/messageFormat.js +60 -0
  111. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +46 -0
  112. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +6534 -0
  113. package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +280 -0
  114. package/dist/services/features/telegram-mcp/src/shared/lib/ids/ids.js +34 -0
  115. package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +68 -0
  116. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +223 -0
  117. package/dist/services/features/telegram-mcp/src/shared/lib/redact-secrets/redactSecrets.js +22 -0
  118. package/dist/services/features/telegram-mcp/src/shared/lib/truncate/truncate.js +12 -0
  119. package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +124 -0
  120. package/dist/services/features/telegram-mcp/src/shared/types/common.js +2 -0
  121. package/dist/services/features/telegram-mcp/standalone-http.service.js +113 -0
  122. package/dist/services/features/telegram-mcp/tools-sync.service.js +33 -0
  123. package/package.json +110 -0
  124. package/scripts/postinstall.js +60 -0
@@ -0,0 +1,1463 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TELEGRAM_MCP_GATEWAY_SOCKET_SERVICE_NAME = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const runtime_service_1 = require("./runtime.service");
8
+ const standalone_http_service_1 = require("./standalone-http.service");
9
+ const auth_1 = require("./src/app/webapp/auth");
10
+ const tmux_1 = require("./src/app/webapp/tmux");
11
+ const gateway_loopback_1 = require("./gateway-loopback");
12
+ const versionHandshake_1 = require("./src/shared/lib/version/versionHandshake");
13
+ const wsLib = require("ws");
14
+ const WebSocketServer = wsLib.WebSocketServer;
15
+ exports.TELEGRAM_MCP_GATEWAY_SOCKET_SERVICE_NAME = "telegramMcp.gatewaySocket";
16
+ const CLIENT_RECONNECT_DELAY_MS = 3000;
17
+ const LIVE_REQUEST_TIMEOUT_MS = 20000;
18
+ const TOOLS_SYNC_CHECK_INTERVAL_MS = 15000;
19
+ const WS_HEARTBEAT_INTERVAL_MS = 10000;
20
+ function normalizeWebSocketUrl(value, defaultPath) {
21
+ const url = new URL(value);
22
+ if (url.protocol === "https:") {
23
+ url.protocol = "wss:";
24
+ }
25
+ else if (url.protocol === "http:") {
26
+ url.protocol = "ws:";
27
+ }
28
+ if (!url.pathname || url.pathname === "/") {
29
+ url.pathname = defaultPath;
30
+ }
31
+ return url.toString();
32
+ }
33
+ function formatTmuxRelayError(error) {
34
+ if ((0, tmux_1.isTmuxUnavailableError)(error)) {
35
+ return "tmux is unavailable";
36
+ }
37
+ return error instanceof Error ? error.message : String(error);
38
+ }
39
+ function computeToolsHashForDir(workspaceDir) {
40
+ const toolsPath = (0, node_path_1.join)((0, node_path_1.resolve)(workspaceDir), "TOOLS.md");
41
+ if (!(0, node_fs_1.existsSync)(toolsPath)) {
42
+ return null;
43
+ }
44
+ const content = (0, node_fs_1.readFileSync)(toolsPath, "utf8");
45
+ return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
46
+ }
47
+ function normalizeGatewayBaseUrl(value) {
48
+ const url = new URL(value);
49
+ url.pathname = url.pathname.replace(/\/+$/u, "");
50
+ if (!url.pathname.endsWith("/gateway")) {
51
+ url.pathname = `${url.pathname}/gateway`.replace(/\/{2,}/gu, "/");
52
+ }
53
+ return url;
54
+ }
55
+ const TelegramMcpGatewaySocketService = {
56
+ name: exports.TELEGRAM_MCP_GATEWAY_SOCKET_SERVICE_NAME,
57
+ dependencies: [
58
+ runtime_service_1.TELEGRAM_MCP_RUNTIME_SERVICE_NAME,
59
+ standalone_http_service_1.TELEGRAM_MCP_STANDALONE_HTTP_SERVICE_NAME,
60
+ ],
61
+ actions: {
62
+ requestLiveRelay: {
63
+ params: {
64
+ clientUuid: "string",
65
+ localSessionId: "string",
66
+ requestType: {
67
+ type: "enum",
68
+ values: ["bootstrap", "bootstrap_validate", "view", "action"],
69
+ },
70
+ payload: { type: "object", optional: true },
71
+ },
72
+ async handler(ctx) {
73
+ return await this.requestLiveRelay?.({
74
+ clientUuid: ctx.params.clientUuid,
75
+ localSessionId: ctx.params.localSessionId,
76
+ requestType: ctx.params.requestType,
77
+ payload: ctx.params.payload ?? {},
78
+ });
79
+ },
80
+ },
81
+ notifyProjectMemberJoined: {
82
+ params: {
83
+ clientUuids: { type: "array", items: "string" },
84
+ projectUuid: "string",
85
+ projectName: "string",
86
+ memberDisplayName: { type: "string", optional: true },
87
+ memberTelegramUsername: { type: "string", optional: true },
88
+ },
89
+ async handler(ctx) {
90
+ return await this.notifyProjectMemberJoined?.(ctx.params);
91
+ },
92
+ },
93
+ notifyProjectMemberLeft: {
94
+ params: {
95
+ clientUuids: { type: "array", items: "string" },
96
+ projectUuid: "string",
97
+ projectName: "string",
98
+ memberDisplayName: { type: "string", optional: true },
99
+ memberTelegramUsername: { type: "string", optional: true },
100
+ },
101
+ async handler(ctx) {
102
+ return await this.notifyProjectMemberLeft?.(ctx.params);
103
+ },
104
+ },
105
+ notifyProjectDeleted: {
106
+ params: {
107
+ clientUuids: { type: "array", items: "string" },
108
+ projectUuid: "string",
109
+ projectName: "string",
110
+ },
111
+ async handler(ctx) {
112
+ return await this.notifyProjectDeleted?.(ctx.params);
113
+ },
114
+ },
115
+ notifyLiveApprovalRequest: {
116
+ params: {
117
+ clientUuid: "string",
118
+ payload: { type: "object" },
119
+ },
120
+ async handler(ctx) {
121
+ return {
122
+ delivered: await this.notifyLiveApprovalRequest?.(ctx.params),
123
+ };
124
+ },
125
+ },
126
+ notifyLiveApprovalResolved: {
127
+ params: {
128
+ clientUuid: "string",
129
+ approved: "boolean",
130
+ payload: { type: "object" },
131
+ },
132
+ async handler(ctx) {
133
+ return {
134
+ delivered: await this.notifyLiveApprovalResolved?.(ctx.params),
135
+ };
136
+ },
137
+ },
138
+ notifyDeliveryQueued: {
139
+ params: {
140
+ clientUuid: "string",
141
+ delivery: { type: "object" },
142
+ },
143
+ async handler(ctx) {
144
+ return await this.notifyDeliveryQueued?.(ctx.params);
145
+ },
146
+ },
147
+ notifyDeliveryStatus: {
148
+ params: {
149
+ clientUuid: "string",
150
+ status: { type: "object" },
151
+ },
152
+ async handler(ctx) {
153
+ return await this.notifyDeliveryStatus?.(ctx.params);
154
+ },
155
+ },
156
+ },
157
+ created() {
158
+ this.runtimeService = null;
159
+ this.standaloneHttpService = null;
160
+ this.wsServer = null;
161
+ this.wsClient = null;
162
+ this.wsReconnectTimer = null;
163
+ this.wsIdentityRefreshTimer = null;
164
+ this.wsToolsSyncTimer = null;
165
+ this.wsHeartbeatTimer = null;
166
+ this.wsAwaitingPong = false;
167
+ this.wsConnectionId = null;
168
+ this.wsHelloClientUuid = null;
169
+ this.wsHelloSessionToolsSnapshot = null;
170
+ this.wsClientHasConnectedOnce = false;
171
+ this.wsUpgradeHandler = null;
172
+ this.stopRequested = false;
173
+ this.connectedClients = new Map();
174
+ this.connectedClientsByUuid = new Map();
175
+ this.connectedClientToolsAlerts = new Map();
176
+ this.pendingLiveRequests = new Map();
177
+ },
178
+ methods: {
179
+ getRuntimeOrThrow() {
180
+ const runtimeService = this.runtimeService ??
181
+ this.broker.getLocalService(runtime_service_1.TELEGRAM_MCP_RUNTIME_SERVICE_NAME);
182
+ if (!runtimeService) {
183
+ throw new Error(`Local Moleculer service '${runtime_service_1.TELEGRAM_MCP_RUNTIME_SERVICE_NAME}' is unavailable`);
184
+ }
185
+ this.runtimeService = runtimeService;
186
+ return runtimeService.getRuntime();
187
+ },
188
+ getHttpServerOrThrow() {
189
+ const standaloneHttpService = this.standaloneHttpService ??
190
+ this.broker.getLocalService(standalone_http_service_1.TELEGRAM_MCP_STANDALONE_HTTP_SERVICE_NAME);
191
+ if (!standaloneHttpService?.httpServer) {
192
+ throw new Error(`Local Moleculer service '${standalone_http_service_1.TELEGRAM_MCP_STANDALONE_HTTP_SERVICE_NAME}' HTTP server is unavailable`);
193
+ }
194
+ this.standaloneHttpService = standaloneHttpService;
195
+ return standaloneHttpService.httpServer;
196
+ },
197
+ async collectSessionTools() {
198
+ const runtime = this.getRuntimeOrThrow();
199
+ const sessions = await runtime.sessionStore.listSessions();
200
+ const boundSessions = (await Promise.all(sessions.map(async (session) => (await runtime.bindingStore.getBinding(session.sessionId))
201
+ ? session
202
+ : null))).filter((session) => Boolean(session));
203
+ const sessionTools = boundSessions
204
+ .map((session) => {
205
+ const toolsHash = session.cwd
206
+ ? computeToolsHashForDir(session.cwd)
207
+ : null;
208
+ return {
209
+ local_session_id: session.sessionId,
210
+ ...(session.label ? { session_label: session.label } : {}),
211
+ ...(toolsHash ? { tools_hash: toolsHash } : {}),
212
+ };
213
+ })
214
+ .sort((left, right) => left.local_session_id.localeCompare(right.local_session_id));
215
+ return {
216
+ sessionTools,
217
+ snapshot: JSON.stringify(sessionTools),
218
+ };
219
+ },
220
+ getGatewayToolsHash() {
221
+ return computeToolsHashForDir(process.cwd());
222
+ },
223
+ getLocalVersionInfo() {
224
+ return {
225
+ packageVersion: (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname),
226
+ protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
227
+ capabilities: [...versionHandshake_1.TELLYMCP_CAPABILITIES],
228
+ };
229
+ },
230
+ async fetchGatewayToolsHashForClient() {
231
+ const runtime = this.getRuntimeOrThrow();
232
+ if (runtime.config.distributed.mode === "gateway" ||
233
+ runtime.config.distributed.mode === "both") {
234
+ return this.getGatewayToolsHash?.() ?? null;
235
+ }
236
+ const gatewayPublicUrl = runtime.config.distributed.gatewayPublicUrl;
237
+ if (!gatewayPublicUrl) {
238
+ return null;
239
+ }
240
+ const url = normalizeGatewayBaseUrl(gatewayPublicUrl);
241
+ url.pathname = `${url.pathname}/tools-md`.replace(/\/{2,}/gu, "/");
242
+ try {
243
+ const response = await fetch(url, {
244
+ method: "GET",
245
+ headers: {
246
+ ...(runtime.config.distributed.gatewayAuthToken
247
+ ? { authorization: `Bearer ${runtime.config.distributed.gatewayAuthToken}` }
248
+ : {}),
249
+ },
250
+ });
251
+ if (!response.ok) {
252
+ const message = await response.text();
253
+ throw new Error(`Gateway TOOLS.md request failed with status ${response.status}: ${message || response.statusText}`);
254
+ }
255
+ return (0, node_crypto_1.createHash)("sha256").update(await response.text()).digest("hex");
256
+ }
257
+ catch (error) {
258
+ runtime.logger.debug("Gateway TOOLS.md self-check skipped", {
259
+ gatewayPublicUrl,
260
+ error: error instanceof Error ? error.message : String(error),
261
+ });
262
+ return null;
263
+ }
264
+ },
265
+ async syncLocalToolsAgainstGateway(sessionId) {
266
+ const runtime = this.getRuntimeOrThrow();
267
+ const gatewayToolsHash = await this.fetchGatewayToolsHashForClient?.();
268
+ if (!gatewayToolsHash) {
269
+ return 0;
270
+ }
271
+ const sessions = sessionId
272
+ ? [await runtime.sessionStore.getSession(sessionId)].filter((item) => Boolean(item))
273
+ : await runtime.sessionStore.listSessions();
274
+ const boundSessions = (await Promise.all(sessions.map(async (session) => (await runtime.bindingStore.getBinding(session.sessionId))
275
+ ? session
276
+ : null))).filter((session) => Boolean(session));
277
+ let delivered = 0;
278
+ for (const session of boundSessions) {
279
+ const localHash = session.cwd
280
+ ? computeToolsHashForDir(session.cwd)
281
+ : null;
282
+ if (localHash === gatewayToolsHash) {
283
+ continue;
284
+ }
285
+ if (session.lastSeenToolsHash?.trim() === gatewayToolsHash ||
286
+ session.lastNotifiedToolsHash?.trim() === gatewayToolsHash) {
287
+ continue;
288
+ }
289
+ await runtime.telegramTransport.handleToolsUpdatedEvent({
290
+ local_session_id: session.sessionId,
291
+ ...(session.label ? { session_label: session.label } : {}),
292
+ ...(localHash ? { client_tools_hash: localHash } : {}),
293
+ gateway_tools_hash: gatewayToolsHash,
294
+ reason: localHash ? "outdated" : "missing",
295
+ instruction: "Call refresh_tools_markdown for this session, then re-read the local TOOLS.md and apply it before continuing.",
296
+ });
297
+ delivered += 1;
298
+ }
299
+ return delivered;
300
+ },
301
+ async notifyToolsMismatchForSocket(socket, hello) {
302
+ const gatewayToolsHash = this.getGatewayToolsHash?.();
303
+ if (!gatewayToolsHash || !socket || socket.readyState !== 1) {
304
+ return 0;
305
+ }
306
+ const sessionTools = Array.isArray(hello.session_tools)
307
+ ? hello.session_tools
308
+ : [];
309
+ if (sessionTools.length === 0) {
310
+ return 0;
311
+ }
312
+ const alerted = this.connectedClientToolsAlerts?.get(socket) ?? new Map();
313
+ this.connectedClientToolsAlerts?.set(socket, alerted);
314
+ let delivered = 0;
315
+ for (const sessionTool of sessionTools) {
316
+ const clientToolsHash = typeof sessionTool.tools_hash === "string" && sessionTool.tools_hash.trim()
317
+ ? sessionTool.tools_hash.trim()
318
+ : null;
319
+ if (clientToolsHash === gatewayToolsHash) {
320
+ alerted.delete(sessionTool.local_session_id);
321
+ continue;
322
+ }
323
+ if (alerted.get(sessionTool.local_session_id) === gatewayToolsHash) {
324
+ continue;
325
+ }
326
+ socket.send(JSON.stringify({
327
+ type: "tools_event",
328
+ payload: {
329
+ local_session_id: sessionTool.local_session_id,
330
+ ...(sessionTool.session_label
331
+ ? { session_label: sessionTool.session_label }
332
+ : {}),
333
+ ...(clientToolsHash ? { client_tools_hash: clientToolsHash } : {}),
334
+ gateway_tools_hash: gatewayToolsHash,
335
+ reason: clientToolsHash ? "outdated" : "missing",
336
+ instruction: "Call refresh_tools_markdown for this session, then re-read the local TOOLS.md and apply it before continuing.",
337
+ },
338
+ }));
339
+ alerted.set(sessionTool.local_session_id, gatewayToolsHash);
340
+ delivered += 1;
341
+ }
342
+ return delivered;
343
+ },
344
+ async sendClientHello(socket) {
345
+ const runtime = this.getRuntimeOrThrow();
346
+ if (!runtime || !socket || socket.readyState !== 1) {
347
+ return;
348
+ }
349
+ const clientUuid = await runtime.maintenanceStore.getGatewayClientUuid();
350
+ const { sessionTools, snapshot } = await this.collectSessionTools?.() ?? {
351
+ sessionTools: [],
352
+ snapshot: "[]",
353
+ };
354
+ const versionInfo = this.getLocalVersionInfo?.() ?? {
355
+ packageVersion: "0.0.0-unknown",
356
+ protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
357
+ capabilities: [...versionHandshake_1.TELLYMCP_CAPABILITIES],
358
+ };
359
+ const hello = {
360
+ type: "hello",
361
+ connection_id: this.wsConnectionId || (0, node_crypto_1.randomUUID)(),
362
+ role: "client",
363
+ ...(clientUuid ? { client_uuid: clientUuid } : {}),
364
+ ...(runtime.config.project.name
365
+ ? { project_name: runtime.config.project.name }
366
+ : {}),
367
+ ...(this.broker.nodeID ? { node_id: this.broker.nodeID } : {}),
368
+ package_version: versionInfo.packageVersion,
369
+ protocol_version: versionInfo.protocolVersion,
370
+ capabilities: versionInfo.capabilities,
371
+ ...(sessionTools.length > 0 ? { session_tools: sessionTools } : {}),
372
+ };
373
+ this.wsHelloClientUuid = clientUuid ?? null;
374
+ this.wsHelloSessionTools = sessionTools;
375
+ this.wsHelloSessionToolsSnapshot = snapshot;
376
+ socket.send(JSON.stringify(hello));
377
+ },
378
+ async isLocalGatewayClientUuid(clientUuid) {
379
+ const runtime = this.getRuntimeOrThrow();
380
+ const localClientUuid = await runtime.maintenanceStore.getGatewayClientUuid();
381
+ return Boolean(localClientUuid && localClientUuid === clientUuid);
382
+ },
383
+ async handleLocalIncomingDelivery(params) {
384
+ const runtime = this.getRuntimeOrThrow();
385
+ const localTargetSession = await runtime.sessionStore.getSession(params.delivery.target_local_session_id);
386
+ if (!(0, gateway_loopback_1.hasLocalTargetSession)(localTargetSession)) {
387
+ return false;
388
+ }
389
+ try {
390
+ await this.broker.call("telegramMcp.gatewayDelivery.materializeIncomingDelivery", {
391
+ delivery: params.delivery,
392
+ }, { meta: { internal_call: true } });
393
+ }
394
+ catch (error) {
395
+ const result = await this.broker.call("telegramMcp.gateway.failDeliveries", {
396
+ client_uuid: params.clientUuid,
397
+ delivery_ids: [params.delivery.delivery_uuid],
398
+ error_text: error instanceof Error ? error.message : String(error),
399
+ }, { meta: { internal_call: true } });
400
+ for (const status of Array.isArray(result.deliveries)
401
+ ? result.deliveries
402
+ : []) {
403
+ if (!status.source_client_uuid) {
404
+ continue;
405
+ }
406
+ const published = await this.notifyDeliveryStatus?.({
407
+ clientUuid: status.source_client_uuid,
408
+ status,
409
+ });
410
+ if (!published && (await this.isLocalGatewayClientUuid?.(status.source_client_uuid))) {
411
+ await this.handleLocalDeliveryStatus?.({
412
+ clientUuid: status.source_client_uuid,
413
+ status,
414
+ });
415
+ }
416
+ }
417
+ throw error;
418
+ }
419
+ const result = await this.broker.call("telegramMcp.gateway.ackDeliveries", {
420
+ client_uuid: params.clientUuid,
421
+ delivery_ids: [params.delivery.delivery_uuid],
422
+ }, { meta: { internal_call: true } });
423
+ for (const status of Array.isArray(result.deliveries)
424
+ ? result.deliveries
425
+ : []) {
426
+ if (!status.source_client_uuid) {
427
+ continue;
428
+ }
429
+ const published = await this.notifyDeliveryStatus?.({
430
+ clientUuid: status.source_client_uuid,
431
+ status,
432
+ });
433
+ if (!published && (await this.isLocalGatewayClientUuid?.(status.source_client_uuid))) {
434
+ await this.handleLocalDeliveryStatus?.({
435
+ clientUuid: status.source_client_uuid,
436
+ status,
437
+ });
438
+ }
439
+ }
440
+ return true;
441
+ },
442
+ async handleLocalDeliveryStatus(params) {
443
+ const runtime = this.getRuntimeOrThrow();
444
+ const notices = await runtime.maintenanceStore.listOutgoingDeliveryNotices();
445
+ if (!(0, gateway_loopback_1.hasOutgoingDeliveryNotice)(notices, params.status.delivery_uuid)) {
446
+ return false;
447
+ }
448
+ await this.broker.call("telegramMcp.gatewayDelivery.applyOutgoingDeliveryStatus", {
449
+ status: params.status,
450
+ }, { meta: { internal_call: true } });
451
+ return true;
452
+ },
453
+ async processLiveRequest(request) {
454
+ const runtime = this.getRuntimeOrThrow();
455
+ try {
456
+ if (request.request_type === "bootstrap_validate") {
457
+ const payload = request.payload ?? {};
458
+ const initDataRaw = typeof payload.initDataRaw === "string" ? payload.initDataRaw : "";
459
+ const initDataUnsafe = payload.initDataUnsafe && typeof payload.initDataUnsafe === "object"
460
+ ? payload.initDataUnsafe
461
+ : null;
462
+ if (!initDataRaw || !initDataUnsafe) {
463
+ throw new Error("initDataRaw and initDataUnsafe are required");
464
+ }
465
+ const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, runtime.config.telegram.botToken, runtime.config.webapp.initDataTtlSeconds);
466
+ const launchRecord = runtime.webAppLaunchRegistry.getByUserId(validated.user.id);
467
+ if (!launchRecord) {
468
+ throw new Error("No pending Telegram WebApp launch was found");
469
+ }
470
+ runtime.webAppLaunchRegistry.deleteByUserId(validated.user.id);
471
+ if (launchRecord?.telegramChatId !== undefined &&
472
+ launchRecord?.telegramMessageId !== undefined) {
473
+ void runtime.telegramTransport
474
+ .deleteMessage(launchRecord.telegramChatId, launchRecord.telegramMessageId)
475
+ .catch(() => undefined);
476
+ }
477
+ return {
478
+ type: "live_response",
479
+ request_id: request.request_id,
480
+ ok: true,
481
+ result: {
482
+ telegram_user_id: validated.user.id,
483
+ },
484
+ };
485
+ }
486
+ if (request.request_type === "bootstrap") {
487
+ const payload = request.payload ?? {};
488
+ const allowForeignBinding = payload.allowForeignBinding === true;
489
+ const trustedTelegramUserId = typeof payload.telegramUserId === "number"
490
+ ? payload.telegramUserId
491
+ : typeof payload.telegramUserId === "string" &&
492
+ payload.telegramUserId.trim()
493
+ ? Number(payload.telegramUserId)
494
+ : null;
495
+ let telegramUserId = trustedTelegramUserId;
496
+ if (telegramUserId !== null &&
497
+ (!Number.isFinite(telegramUserId) || telegramUserId <= 0)) {
498
+ telegramUserId = null;
499
+ }
500
+ if (telegramUserId === null) {
501
+ const initDataRaw = typeof payload.initDataRaw === "string" ? payload.initDataRaw : "";
502
+ const initDataUnsafe = payload.initDataUnsafe && typeof payload.initDataUnsafe === "object"
503
+ ? payload.initDataUnsafe
504
+ : null;
505
+ if (!initDataRaw || !initDataUnsafe) {
506
+ throw new Error("initDataRaw and initDataUnsafe are required");
507
+ }
508
+ const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, runtime.config.telegram.botToken, runtime.config.webapp.initDataTtlSeconds);
509
+ telegramUserId = validated.user.id;
510
+ }
511
+ const sessionId = request.local_session_id.trim();
512
+ if (!sessionId) {
513
+ throw new Error("sessionId is missing for relay bootstrap");
514
+ }
515
+ const binding = await runtime.bindingStore.getBinding(sessionId);
516
+ if (!allowForeignBinding &&
517
+ (!binding || binding.telegramUserId !== telegramUserId)) {
518
+ throw new Error("This Telegram user is not bound to the requested session.");
519
+ }
520
+ const session = await runtime.sessionStore.getSession(sessionId);
521
+ return {
522
+ type: "live_response",
523
+ request_id: request.request_id,
524
+ ok: true,
525
+ result: {
526
+ session_id: sessionId,
527
+ session_label: session?.label ?? null,
528
+ tmux_target: Boolean(session?.tmuxTarget),
529
+ poll_interval_ms: runtime.config.webapp.pollIntervalMs,
530
+ telegram_user_id: telegramUserId,
531
+ },
532
+ };
533
+ }
534
+ if (request.request_type === "view") {
535
+ const sessionId = request.local_session_id.trim();
536
+ const session = await runtime.sessionStore.getSession(sessionId);
537
+ if (!session?.tmuxTarget) {
538
+ throw new Error("tmux target is not configured for this session");
539
+ }
540
+ const content = await (0, tmux_1.captureVisibleTmuxPane)(runtime.config.tmux, session.tmuxTarget, runtime.config.tmux.captureLines, runtime.config.webapp.visibleScreens);
541
+ return {
542
+ type: "live_response",
543
+ request_id: request.request_id,
544
+ ok: true,
545
+ result: {
546
+ session_id: session.sessionId,
547
+ session_label: session.label ?? null,
548
+ captured_at: new Date().toISOString(),
549
+ content,
550
+ },
551
+ };
552
+ }
553
+ if (request.request_type === "action") {
554
+ const action = typeof request.payload?.action === "string"
555
+ ? request.payload.action
556
+ : "";
557
+ if (!["up", "down", "enter", "slash", "delete", "tab", "escape"].includes(action)) {
558
+ throw new Error("Unsupported action");
559
+ }
560
+ const sessionId = request.local_session_id.trim();
561
+ const session = await runtime.sessionStore.getSession(sessionId);
562
+ if (!session?.tmuxTarget) {
563
+ throw new Error("tmux target is not configured for this session");
564
+ }
565
+ await (0, tmux_1.sendAllowedTmuxAction)(runtime.config.tmux, session.tmuxTarget, action);
566
+ return {
567
+ type: "live_response",
568
+ request_id: request.request_id,
569
+ ok: true,
570
+ result: {
571
+ ok: true,
572
+ },
573
+ };
574
+ }
575
+ throw new Error(`Unsupported live request type '${request.request_type}'`);
576
+ }
577
+ catch (error) {
578
+ return {
579
+ type: "live_response",
580
+ request_id: request.request_id,
581
+ ok: false,
582
+ error: formatTmuxRelayError(error),
583
+ };
584
+ }
585
+ },
586
+ async handleGatewayWsServerMessage(socket, raw) {
587
+ const runtime = this.getRuntimeOrThrow();
588
+ const parsed = JSON.parse(String(raw));
589
+ if (parsed.type === "hello") {
590
+ const hello = {
591
+ type: "hello",
592
+ connection_id: typeof parsed.connection_id === "string" && parsed.connection_id.trim()
593
+ ? parsed.connection_id.trim()
594
+ : (0, node_crypto_1.randomUUID)(),
595
+ role: parsed.role === "gateway" ? "gateway" : "client",
596
+ ...(typeof parsed.client_uuid === "string" && parsed.client_uuid.trim()
597
+ ? { client_uuid: parsed.client_uuid.trim() }
598
+ : {}),
599
+ ...(typeof parsed.project_name === "string" && parsed.project_name.trim()
600
+ ? { project_name: parsed.project_name.trim() }
601
+ : {}),
602
+ ...(typeof parsed.node_id === "string" && parsed.node_id.trim()
603
+ ? { node_id: parsed.node_id.trim() }
604
+ : {}),
605
+ ...(typeof parsed.package_version === "string" &&
606
+ parsed.package_version.trim()
607
+ ? { package_version: parsed.package_version.trim() }
608
+ : {}),
609
+ ...(typeof parsed.protocol_version === "string" &&
610
+ parsed.protocol_version.trim()
611
+ ? { protocol_version: parsed.protocol_version.trim() }
612
+ : {}),
613
+ ...(Array.isArray(parsed.capabilities)
614
+ ? {
615
+ capabilities: parsed.capabilities
616
+ .map((item) => typeof item === "string" && item.trim()
617
+ ? item.trim()
618
+ : null)
619
+ .filter((item) => Boolean(item)),
620
+ }
621
+ : {}),
622
+ ...(Array.isArray(parsed.session_tools)
623
+ ? {
624
+ session_tools: parsed.session_tools
625
+ .map((item) => item && typeof item === "object"
626
+ ? {
627
+ local_session_id: typeof item.local_session_id ===
628
+ "string" &&
629
+ item.local_session_id.trim()
630
+ ? item.local_session_id.trim()
631
+ : null,
632
+ ...(typeof item.session_label ===
633
+ "string" &&
634
+ item.session_label.trim()
635
+ ? {
636
+ session_label: item.session_label.trim(),
637
+ }
638
+ : {}),
639
+ ...(typeof item.tools_hash ===
640
+ "string" &&
641
+ item.tools_hash.trim()
642
+ ? {
643
+ tools_hash: item.tools_hash.trim(),
644
+ }
645
+ : {}),
646
+ }
647
+ : null)
648
+ .filter((item) => Boolean(item?.local_session_id)),
649
+ }
650
+ : {}),
651
+ };
652
+ const previous = this.connectedClients?.get(socket);
653
+ if (previous?.client_uuid) {
654
+ this.connectedClientsByUuid?.delete(previous.client_uuid);
655
+ }
656
+ this.connectedClients?.set(socket, hello);
657
+ if (hello.client_uuid) {
658
+ this.connectedClientsByUuid?.set(hello.client_uuid, socket);
659
+ }
660
+ runtime.logger.info("Gateway WS hello received", hello);
661
+ const localVersionInfo = this.getLocalVersionInfo?.() ?? {
662
+ packageVersion: "0.0.0-unknown",
663
+ protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
664
+ capabilities: [...versionHandshake_1.TELLYMCP_CAPABILITIES],
665
+ };
666
+ const compatibility = (0, versionHandshake_1.evaluateVersionCompatibility)({
667
+ gatewayPackageVersion: localVersionInfo.packageVersion,
668
+ gatewayProtocolVersion: localVersionInfo.protocolVersion,
669
+ ...(hello.package_version
670
+ ? { clientPackageVersion: hello.package_version }
671
+ : {}),
672
+ ...(hello.protocol_version
673
+ ? { clientProtocolVersion: hello.protocol_version }
674
+ : {}),
675
+ });
676
+ const ackInstruction = compatibility.compatibility === "reject"
677
+ ? "Upgrade this client before continuing. Gateway transport is blocked until protocol major versions match."
678
+ : compatibility.compatibility === "warn"
679
+ ? "Client and gateway versions differ. Upgrade the older side and verify TOOLS.md before continuing sensitive work."
680
+ : "Version handshake passed.";
681
+ socket.send(JSON.stringify({
682
+ type: "hello_ack",
683
+ connection_id: hello.connection_id,
684
+ package_version: localVersionInfo.packageVersion,
685
+ protocol_version: localVersionInfo.protocolVersion,
686
+ capabilities: localVersionInfo.capabilities,
687
+ compatibility: compatibility.compatibility,
688
+ reasons: compatibility.reasons,
689
+ instruction: ackInstruction,
690
+ }));
691
+ if (compatibility.compatibility === "reject") {
692
+ runtime.logger.warn("Rejecting gateway WS client due to protocol incompatibility", {
693
+ clientUuid: hello.client_uuid,
694
+ clientPackageVersion: hello.package_version ?? null,
695
+ clientProtocolVersion: hello.protocol_version ?? null,
696
+ gatewayPackageVersion: localVersionInfo.packageVersion,
697
+ gatewayProtocolVersion: localVersionInfo.protocolVersion,
698
+ reasons: compatibility.reasons,
699
+ });
700
+ setTimeout(() => {
701
+ try {
702
+ socket.close?.(4002, "version_incompatible");
703
+ }
704
+ catch {
705
+ socket.terminate?.();
706
+ }
707
+ }, 50);
708
+ return;
709
+ }
710
+ await this.notifyToolsMismatchForSocket?.(socket, hello);
711
+ if (hello.client_uuid) {
712
+ const queued = await this.broker.call("telegramMcp.gateway.pollDeliveries", {
713
+ client_uuid: hello.client_uuid,
714
+ limit: 50,
715
+ }, { meta: { internal_call: true } });
716
+ for (const delivery of Array.isArray(queued.deliveries)
717
+ ? queued.deliveries
718
+ : []) {
719
+ socket.send(JSON.stringify({
720
+ type: "delivery_event",
721
+ event: "incoming_delivery",
722
+ payload: delivery,
723
+ }));
724
+ }
725
+ const statuses = await this.broker.call("telegramMcp.gateway.listSenderDeliveryStatuses", {
726
+ client_uuid: hello.client_uuid,
727
+ limit: 100,
728
+ }, { meta: { internal_call: true } });
729
+ for (const status of Array.isArray(statuses.deliveries)
730
+ ? statuses.deliveries
731
+ : []) {
732
+ socket.send(JSON.stringify({
733
+ type: "delivery_status_event",
734
+ payload: status,
735
+ }));
736
+ }
737
+ }
738
+ return;
739
+ }
740
+ if (parsed.type === "heartbeat_ping") {
741
+ socket.send(JSON.stringify({
742
+ type: "heartbeat_pong",
743
+ ts: typeof parsed.ts === "string" && parsed.ts.trim()
744
+ ? parsed.ts.trim()
745
+ : new Date().toISOString(),
746
+ }));
747
+ return;
748
+ }
749
+ if (parsed.type === "live_response") {
750
+ const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
751
+ if (!requestId) {
752
+ return;
753
+ }
754
+ const pending = this.pendingLiveRequests?.get(requestId);
755
+ if (!pending) {
756
+ return;
757
+ }
758
+ clearTimeout(pending.timeout);
759
+ this.pendingLiveRequests?.delete(requestId);
760
+ if (parsed.ok === true) {
761
+ pending.resolve(parsed.result);
762
+ }
763
+ else {
764
+ pending.reject(new Error(typeof parsed.error === "string" && parsed.error.trim()
765
+ ? parsed.error
766
+ : "Live relay request failed"));
767
+ }
768
+ return;
769
+ }
770
+ if (parsed.type === "delivery_ack" || parsed.type === "delivery_fail") {
771
+ const hello = this.connectedClients?.get(socket);
772
+ const clientUuid = hello?.client_uuid?.trim();
773
+ if (!clientUuid) {
774
+ throw new Error("Gateway WS delivery update requires hello client_uuid");
775
+ }
776
+ const deliveryIds = Array.isArray(parsed.delivery_ids)
777
+ ? parsed.delivery_ids
778
+ .map((item) => typeof item === "string" && item.trim() ? item.trim() : null)
779
+ .filter((item) => Boolean(item))
780
+ : [];
781
+ if (deliveryIds.length === 0) {
782
+ return;
783
+ }
784
+ const result = await this.broker.call(parsed.type === "delivery_ack"
785
+ ? "telegramMcp.gateway.ackDeliveries"
786
+ : "telegramMcp.gateway.failDeliveries", {
787
+ client_uuid: clientUuid,
788
+ delivery_ids: deliveryIds,
789
+ ...(parsed.type === "delivery_fail" &&
790
+ typeof parsed.error_text === "string" &&
791
+ parsed.error_text.trim()
792
+ ? { error_text: parsed.error_text.trim() }
793
+ : {}),
794
+ }, { meta: { internal_call: true } });
795
+ for (const status of Array.isArray(result.deliveries)
796
+ ? result.deliveries
797
+ : []) {
798
+ if (!status.source_client_uuid) {
799
+ continue;
800
+ }
801
+ const publishResult = await this.broker.call("telegramMcp.gatewayRmq.publishDeliveryStatus", {
802
+ clientUuid: status.source_client_uuid,
803
+ status,
804
+ }, { meta: { internal_call: true } });
805
+ if (!publishResult?.published) {
806
+ await this.notifyDeliveryStatus?.({
807
+ clientUuid: status.source_client_uuid,
808
+ status,
809
+ });
810
+ }
811
+ }
812
+ }
813
+ },
814
+ async handleGatewayWsClientMessage(raw) {
815
+ const runtime = this.getRuntimeOrThrow();
816
+ const parsed = JSON.parse(String(raw));
817
+ if (parsed.type === "hello_ack") {
818
+ const compatibility = parsed.compatibility === "warn" || parsed.compatibility === "reject"
819
+ ? parsed.compatibility
820
+ : "ok";
821
+ const gatewayPackageVersion = typeof parsed.package_version === "string" && parsed.package_version.trim()
822
+ ? parsed.package_version.trim()
823
+ : "0.0.0-unknown";
824
+ const gatewayProtocolVersion = typeof parsed.protocol_version === "string" && parsed.protocol_version.trim()
825
+ ? parsed.protocol_version.trim()
826
+ : versionHandshake_1.TELLYMCP_PROTOCOL_VERSION;
827
+ const reasons = Array.isArray(parsed.reasons)
828
+ ? parsed.reasons
829
+ .map((item) => typeof item === "string" && item.trim() ? item.trim() : null)
830
+ .filter((item) => Boolean(item))
831
+ : [];
832
+ const gatewayCapabilities = Array.isArray(parsed.capabilities)
833
+ ? parsed.capabilities
834
+ .map((item) => typeof item === "string" && item.trim() ? item.trim() : null)
835
+ .filter((item) => Boolean(item))
836
+ : [];
837
+ const localVersionInfo = this.getLocalVersionInfo?.() ?? {
838
+ packageVersion: "0.0.0-unknown",
839
+ protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
840
+ capabilities: [...versionHandshake_1.TELLYMCP_CAPABILITIES],
841
+ };
842
+ runtime.logger.info("Gateway WS hello acknowledged", {
843
+ connectionId: typeof parsed.connection_id === "string" ? parsed.connection_id : null,
844
+ clientUuid: this.wsHelloClientUuid,
845
+ compatibility,
846
+ gatewayPackageVersion,
847
+ gatewayProtocolVersion,
848
+ });
849
+ if (compatibility !== "ok") {
850
+ const sessionTools = Array.isArray(this.wsHelloSessionTools)
851
+ ? this.wsHelloSessionTools
852
+ : [];
853
+ for (const sessionTool of sessionTools) {
854
+ await runtime.telegramTransport.handleGatewayVersionCompatibilityEvent({
855
+ local_session_id: sessionTool.local_session_id,
856
+ ...(sessionTool.session_label
857
+ ? { session_label: sessionTool.session_label }
858
+ : {}),
859
+ compatibility,
860
+ gateway_package_version: gatewayPackageVersion,
861
+ gateway_protocol_version: gatewayProtocolVersion,
862
+ gateway_capabilities: gatewayCapabilities,
863
+ client_package_version: localVersionInfo.packageVersion,
864
+ client_protocol_version: localVersionInfo.protocolVersion,
865
+ client_capabilities: localVersionInfo.capabilities,
866
+ reasons,
867
+ instruction: typeof parsed.instruction === "string" && parsed.instruction.trim()
868
+ ? parsed.instruction.trim()
869
+ : compatibility === "reject"
870
+ ? "Upgrade this client before continuing. Gateway transport is blocked until protocol major versions match."
871
+ : "Client and gateway versions differ. Upgrade the older side and verify TOOLS.md before continuing sensitive work.",
872
+ });
873
+ }
874
+ }
875
+ if (compatibility === "reject") {
876
+ try {
877
+ this.wsClient?.close?.(4002, "version_incompatible");
878
+ }
879
+ catch {
880
+ this.wsClient?.terminate?.();
881
+ }
882
+ return;
883
+ }
884
+ await this.syncLocalToolsAgainstGateway?.();
885
+ return;
886
+ }
887
+ if (parsed.type === "heartbeat_pong") {
888
+ this.wsAwaitingPong = false;
889
+ return;
890
+ }
891
+ if (parsed.type === "tools_event" && parsed.payload) {
892
+ await runtime.telegramTransport.handleToolsUpdatedEvent(parsed.payload);
893
+ return;
894
+ }
895
+ if (parsed.type === "project_event") {
896
+ if (parsed.event === "member_joined" &&
897
+ parsed.payload &&
898
+ typeof parsed.payload === "object") {
899
+ await runtime.telegramTransport.handleProjectMemberJoinedEvent(parsed.payload);
900
+ return;
901
+ }
902
+ if (parsed.event === "member_left" &&
903
+ parsed.payload &&
904
+ typeof parsed.payload === "object") {
905
+ await runtime.telegramTransport.handleProjectMemberLeftEvent(parsed.payload);
906
+ return;
907
+ }
908
+ if (parsed.event === "project_deleted" &&
909
+ parsed.payload &&
910
+ typeof parsed.payload === "object") {
911
+ await runtime.telegramTransport.handleProjectDeletedEvent(parsed.payload);
912
+ return;
913
+ }
914
+ }
915
+ if (parsed.type === "live_event" && parsed.payload) {
916
+ if (parsed.event === "approval_request") {
917
+ await runtime.telegramTransport.handleLiveViewApprovalRequestEvent(parsed.payload);
918
+ return;
919
+ }
920
+ if (parsed.event === "approval_granted" ||
921
+ parsed.event === "approval_denied") {
922
+ await runtime.telegramTransport.handleLiveViewApprovalResolvedEvent({
923
+ approved: parsed.event === "approval_granted",
924
+ ...parsed.payload,
925
+ });
926
+ return;
927
+ }
928
+ }
929
+ if (parsed.type === "delivery_event") {
930
+ const delivery = parsed.payload;
931
+ if (!delivery) {
932
+ return;
933
+ }
934
+ try {
935
+ await this.broker.call("telegramMcp.gatewayDelivery.materializeIncomingDelivery", {
936
+ delivery,
937
+ }, { meta: { internal_call: true } });
938
+ this.wsClient?.send(JSON.stringify({
939
+ type: "delivery_ack",
940
+ delivery_ids: [delivery.delivery_uuid],
941
+ }));
942
+ }
943
+ catch (error) {
944
+ this.wsClient?.send(JSON.stringify({
945
+ type: "delivery_fail",
946
+ delivery_ids: [delivery.delivery_uuid],
947
+ error_text: error instanceof Error ? error.message : String(error),
948
+ }));
949
+ throw error;
950
+ }
951
+ return;
952
+ }
953
+ if (parsed.type === "delivery_status_event") {
954
+ await this.broker.call("telegramMcp.gatewayDelivery.applyOutgoingDeliveryStatus", {
955
+ status: parsed.payload,
956
+ }, { meta: { internal_call: true } });
957
+ return;
958
+ }
959
+ if (parsed.type === "live_request") {
960
+ const request = parsed;
961
+ const response = await this.processLiveRequest?.(request);
962
+ this.wsClient?.send(JSON.stringify(response));
963
+ return;
964
+ }
965
+ runtime.logger.debug("Gateway WS client message received", {
966
+ payload: String(raw),
967
+ });
968
+ },
969
+ async requestLiveRelay(params) {
970
+ const runtime = this.getRuntimeOrThrow();
971
+ const socket = this.connectedClientsByUuid?.get(params.clientUuid);
972
+ if (!socket || socket.readyState !== 1) {
973
+ throw new Error(`Gateway WS client '${params.clientUuid}' is not connected`);
974
+ }
975
+ const requestId = (0, node_crypto_1.randomUUID)();
976
+ const response = await new Promise((resolve, reject) => {
977
+ const timeout = setTimeout(() => {
978
+ this.pendingLiveRequests?.delete(requestId);
979
+ reject(new Error("Live relay WS request timed out"));
980
+ }, LIVE_REQUEST_TIMEOUT_MS);
981
+ this.pendingLiveRequests?.set(requestId, {
982
+ clientUuid: params.clientUuid,
983
+ resolve,
984
+ reject,
985
+ timeout,
986
+ });
987
+ const request = {
988
+ type: "live_request",
989
+ request_id: requestId,
990
+ request_type: params.requestType,
991
+ local_session_id: params.localSessionId,
992
+ payload: params.payload,
993
+ };
994
+ socket.send(JSON.stringify(request));
995
+ });
996
+ runtime.logger.debug("Gateway WS live relay completed", {
997
+ clientUuid: params.clientUuid,
998
+ localSessionId: params.localSessionId,
999
+ requestType: params.requestType,
1000
+ });
1001
+ return response;
1002
+ },
1003
+ async notifyProjectMemberJoined(params) {
1004
+ const message = {
1005
+ type: "project_event",
1006
+ event: "member_joined",
1007
+ payload: {
1008
+ project_uuid: params.projectUuid,
1009
+ project_name: params.projectName,
1010
+ ...(params.memberDisplayName
1011
+ ? { member_display_name: params.memberDisplayName }
1012
+ : {}),
1013
+ ...(params.memberTelegramUsername
1014
+ ? { member_telegram_username: params.memberTelegramUsername }
1015
+ : {}),
1016
+ },
1017
+ };
1018
+ let delivered = 0;
1019
+ for (const clientUuid of params.clientUuids) {
1020
+ if (await this.isLocalGatewayClientUuid?.(clientUuid)) {
1021
+ const runtime = this.getRuntimeOrThrow();
1022
+ await runtime.telegramTransport.handleProjectMemberJoinedEvent(message.payload);
1023
+ delivered += 1;
1024
+ continue;
1025
+ }
1026
+ const socket = this.connectedClientsByUuid?.get(clientUuid);
1027
+ if (!socket || socket.readyState !== 1) {
1028
+ continue;
1029
+ }
1030
+ socket.send(JSON.stringify(message));
1031
+ delivered += 1;
1032
+ }
1033
+ return delivered;
1034
+ },
1035
+ async notifyProjectMemberLeft(params) {
1036
+ const message = {
1037
+ type: "project_event",
1038
+ event: "member_left",
1039
+ payload: {
1040
+ project_uuid: params.projectUuid,
1041
+ project_name: params.projectName,
1042
+ ...(params.memberDisplayName
1043
+ ? { member_display_name: params.memberDisplayName }
1044
+ : {}),
1045
+ ...(params.memberTelegramUsername
1046
+ ? { member_telegram_username: params.memberTelegramUsername }
1047
+ : {}),
1048
+ },
1049
+ };
1050
+ let delivered = 0;
1051
+ for (const clientUuid of params.clientUuids) {
1052
+ if (await this.isLocalGatewayClientUuid?.(clientUuid)) {
1053
+ const runtime = this.getRuntimeOrThrow();
1054
+ await runtime.telegramTransport.handleProjectMemberLeftEvent(message.payload);
1055
+ delivered += 1;
1056
+ continue;
1057
+ }
1058
+ const socket = this.connectedClientsByUuid?.get(clientUuid);
1059
+ if (!socket || socket.readyState !== 1) {
1060
+ continue;
1061
+ }
1062
+ socket.send(JSON.stringify(message));
1063
+ delivered += 1;
1064
+ }
1065
+ return delivered;
1066
+ },
1067
+ async notifyProjectDeleted(params) {
1068
+ const message = {
1069
+ type: "project_event",
1070
+ event: "project_deleted",
1071
+ payload: {
1072
+ project_uuid: params.projectUuid,
1073
+ project_name: params.projectName,
1074
+ },
1075
+ };
1076
+ let delivered = 0;
1077
+ for (const clientUuid of params.clientUuids) {
1078
+ if (await this.isLocalGatewayClientUuid?.(clientUuid)) {
1079
+ const runtime = this.getRuntimeOrThrow();
1080
+ await runtime.telegramTransport.handleProjectDeletedEvent(message.payload);
1081
+ delivered += 1;
1082
+ continue;
1083
+ }
1084
+ const socket = this.connectedClientsByUuid?.get(clientUuid);
1085
+ if (!socket || socket.readyState !== 1) {
1086
+ continue;
1087
+ }
1088
+ socket.send(JSON.stringify(message));
1089
+ delivered += 1;
1090
+ }
1091
+ return delivered;
1092
+ },
1093
+ async notifyLiveApprovalRequest(params) {
1094
+ if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
1095
+ const runtime = this.getRuntimeOrThrow();
1096
+ await runtime.telegramTransport.handleLiveViewApprovalRequestEvent(params.payload);
1097
+ return true;
1098
+ }
1099
+ const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1100
+ if (!socket || socket.readyState !== 1) {
1101
+ return false;
1102
+ }
1103
+ socket.send(JSON.stringify({
1104
+ type: "live_event",
1105
+ event: "approval_request",
1106
+ payload: params.payload,
1107
+ }));
1108
+ return true;
1109
+ },
1110
+ async notifyLiveApprovalResolved(params) {
1111
+ if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
1112
+ const runtime = this.getRuntimeOrThrow();
1113
+ await runtime.telegramTransport.handleLiveViewApprovalResolvedEvent({
1114
+ approved: params.approved,
1115
+ ...params.payload,
1116
+ });
1117
+ return true;
1118
+ }
1119
+ const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1120
+ if (!socket || socket.readyState !== 1) {
1121
+ return false;
1122
+ }
1123
+ socket.send(JSON.stringify({
1124
+ type: "live_event",
1125
+ event: params.approved ? "approval_granted" : "approval_denied",
1126
+ payload: params.payload,
1127
+ }));
1128
+ return true;
1129
+ },
1130
+ async notifyDeliveryQueued(params) {
1131
+ if (await this.handleLocalIncomingDelivery?.(params)) {
1132
+ return true;
1133
+ }
1134
+ const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1135
+ if (!socket || socket.readyState !== 1) {
1136
+ return false;
1137
+ }
1138
+ socket.send(JSON.stringify({
1139
+ type: "delivery_event",
1140
+ event: "incoming_delivery",
1141
+ payload: params.delivery,
1142
+ }));
1143
+ return true;
1144
+ },
1145
+ async notifyDeliveryStatus(params) {
1146
+ if (await this.handleLocalDeliveryStatus?.(params)) {
1147
+ return true;
1148
+ }
1149
+ const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1150
+ if (!socket || socket.readyState !== 1) {
1151
+ return false;
1152
+ }
1153
+ socket.send(JSON.stringify({
1154
+ type: "delivery_status_event",
1155
+ payload: params.status,
1156
+ }));
1157
+ return true;
1158
+ },
1159
+ async startGatewayWsServer() {
1160
+ const runtime = this.getRuntimeOrThrow();
1161
+ if (!runtime || this.wsServer) {
1162
+ return;
1163
+ }
1164
+ const httpServer = this.getHttpServerOrThrow?.();
1165
+ const wsPath = runtime.config.distributed.gatewayWsPath.replace(/\/+$/u, "") || "/";
1166
+ const wsServer = new WebSocketServer({ noServer: true });
1167
+ wsServer.on("connection", (socket, req) => {
1168
+ if (runtime.config.distributed.gatewayAuthToken) {
1169
+ const authorization = req.headers?.authorization;
1170
+ if (authorization !==
1171
+ `Bearer ${runtime.config.distributed.gatewayAuthToken}`) {
1172
+ socket.close(1008, "Unauthorized");
1173
+ return;
1174
+ }
1175
+ }
1176
+ runtime.logger.warn("Gateway WS client connected", {
1177
+ remoteAddress: req.socket.remoteAddress,
1178
+ path: req.url,
1179
+ });
1180
+ socket.on("message", (raw) => {
1181
+ void this.handleGatewayWsServerMessage?.(socket, raw).catch((error) => {
1182
+ runtime.logger.warn("Gateway WS server message parse failed", {
1183
+ error: error instanceof Error ? (error.stack ?? error.message) : String(error),
1184
+ });
1185
+ });
1186
+ });
1187
+ socket.on("close", () => {
1188
+ const hello = this.connectedClients?.get(socket);
1189
+ if (hello?.client_uuid) {
1190
+ this.connectedClientsByUuid?.delete(hello.client_uuid);
1191
+ }
1192
+ this.connectedClientToolsAlerts?.delete(socket);
1193
+ this.connectedClients?.delete(socket);
1194
+ runtime.logger.warn("Gateway WS client disconnected", {
1195
+ connectionId: hello?.connection_id ?? null,
1196
+ clientUuid: hello?.client_uuid ?? null,
1197
+ });
1198
+ });
1199
+ });
1200
+ const upgradeHandler = (req, socket, head) => {
1201
+ const requestUrl = new URL(req.url ?? "/", "http://gateway.local");
1202
+ const requestPath = requestUrl.pathname.replace(/\/+$/u, "") || "/";
1203
+ if (requestPath !== wsPath) {
1204
+ return;
1205
+ }
1206
+ wsServer.handleUpgrade(req, socket, head, (clientSocket) => {
1207
+ wsServer.emit("connection", clientSocket, req);
1208
+ });
1209
+ };
1210
+ httpServer?.on("upgrade", upgradeHandler);
1211
+ this.wsServer = wsServer;
1212
+ this.wsUpgradeHandler = upgradeHandler;
1213
+ runtime.logger.warn("Gateway WS server attached", {
1214
+ path: runtime.config.distributed.gatewayWsPath,
1215
+ });
1216
+ },
1217
+ ensureGatewayWsClientIsReusable() {
1218
+ if (!this.wsClient) {
1219
+ return true;
1220
+ }
1221
+ const readyState = typeof this.wsClient.readyState === "number"
1222
+ ? this.wsClient.readyState
1223
+ : null;
1224
+ // OPEN=1, CONNECTING=0 are still active; CLOSING=2 and CLOSED=3 are stale.
1225
+ if (readyState === 0 || readyState === 1) {
1226
+ return false;
1227
+ }
1228
+ this.wsClient = null;
1229
+ return true;
1230
+ },
1231
+ scheduleGatewayWsReconnect() {
1232
+ if (this.stopRequested || this.wsReconnectTimer) {
1233
+ return;
1234
+ }
1235
+ const runtime = this.getRuntimeOrThrow?.();
1236
+ runtime?.logger.warn("Gateway WS reconnect scheduled", {
1237
+ delayMs: CLIENT_RECONNECT_DELAY_MS,
1238
+ });
1239
+ this.wsReconnectTimer = setTimeout(() => {
1240
+ this.wsReconnectTimer = null;
1241
+ void this.startGatewayWsClient?.();
1242
+ }, CLIENT_RECONNECT_DELAY_MS);
1243
+ },
1244
+ async startGatewayWsClient() {
1245
+ const runtime = this.getRuntimeOrThrow?.();
1246
+ if (!runtime) {
1247
+ return;
1248
+ }
1249
+ if (!this.ensureGatewayWsClientIsReusable?.()) {
1250
+ return;
1251
+ }
1252
+ const wsUrl = runtime.config.distributed.gatewayWsUrl;
1253
+ if (!wsUrl) {
1254
+ runtime.logger.info("Gateway WS client is disabled", {
1255
+ reason: "GATEWAY_WS_URL is not configured",
1256
+ });
1257
+ return;
1258
+ }
1259
+ const normalizedUrl = normalizeWebSocketUrl(wsUrl, runtime.config.distributed.gatewayWsPath);
1260
+ this.wsConnectionId = (0, node_crypto_1.randomUUID)();
1261
+ const socket = new wsLib.WebSocket(normalizedUrl, {
1262
+ headers: runtime.config.distributed.gatewayAuthToken
1263
+ ? {
1264
+ authorization: `Bearer ${runtime.config.distributed.gatewayAuthToken}`,
1265
+ }
1266
+ : undefined,
1267
+ });
1268
+ socket.on("open", () => {
1269
+ runtime.logger.warn(this.wsClientHasConnectedOnce
1270
+ ? "Gateway WS connected to gateway again"
1271
+ : "Gateway WS connected to gateway", {
1272
+ url: normalizedUrl,
1273
+ });
1274
+ this.wsClientHasConnectedOnce = true;
1275
+ this.wsAwaitingPong = false;
1276
+ void this.sendClientHello?.(socket);
1277
+ });
1278
+ socket.on("message", (raw) => {
1279
+ void this.handleGatewayWsClientMessage?.(raw).catch((error) => {
1280
+ runtime.logger.warn("Gateway WS client message handling failed", {
1281
+ error: error instanceof Error ? (error.stack ?? error.message) : String(error),
1282
+ });
1283
+ });
1284
+ });
1285
+ socket.on("error", (error) => {
1286
+ runtime.logger.warn("Gateway WS client error", {
1287
+ url: normalizedUrl,
1288
+ error: error instanceof Error ? (error.stack ?? error.message) : String(error),
1289
+ });
1290
+ this.wsAwaitingPong = false;
1291
+ if (this.wsClient === socket && socket.readyState !== 1) {
1292
+ this.wsClient = null;
1293
+ }
1294
+ this.scheduleGatewayWsReconnect?.();
1295
+ });
1296
+ socket.on("close", () => {
1297
+ runtime.logger.warn("Gateway WS connection to gateway closed", {
1298
+ url: normalizedUrl,
1299
+ clientUuid: this.wsHelloClientUuid,
1300
+ });
1301
+ this.wsAwaitingPong = false;
1302
+ if (this.wsClient === socket) {
1303
+ this.wsClient = null;
1304
+ }
1305
+ this.scheduleGatewayWsReconnect?.();
1306
+ });
1307
+ this.wsClient = socket;
1308
+ if (!this.wsIdentityRefreshTimer) {
1309
+ this.wsIdentityRefreshTimer = setInterval(() => {
1310
+ if (this.stopRequested || !this.wsClient || this.wsClient.readyState !== 1) {
1311
+ return;
1312
+ }
1313
+ void (async () => {
1314
+ const currentClientUuid = await runtime.maintenanceStore.getGatewayClientUuid();
1315
+ const currentSessionTools = await this.collectSessionTools?.() ?? {
1316
+ sessionTools: [],
1317
+ snapshot: "[]",
1318
+ };
1319
+ if ((currentClientUuid ?? null) === this.wsHelloClientUuid &&
1320
+ currentSessionTools.snapshot === this.wsHelloSessionToolsSnapshot) {
1321
+ return;
1322
+ }
1323
+ await this.sendClientHello?.(this.wsClient);
1324
+ })().catch((error) => {
1325
+ runtime.logger.warn("Gateway WS hello refresh failed", {
1326
+ error: error instanceof Error ? (error.stack ?? error.message) : String(error),
1327
+ });
1328
+ });
1329
+ }, 5000);
1330
+ }
1331
+ if (!this.wsToolsSyncTimer) {
1332
+ this.wsToolsSyncTimer = setInterval(() => {
1333
+ if (this.stopRequested) {
1334
+ return;
1335
+ }
1336
+ void (async () => {
1337
+ for (const [clientSocket, hello] of this.connectedClients?.entries() ?? []) {
1338
+ if (!clientSocket || clientSocket.readyState !== 1) {
1339
+ continue;
1340
+ }
1341
+ await this.notifyToolsMismatchForSocket?.(clientSocket, hello);
1342
+ }
1343
+ })().catch((error) => {
1344
+ runtime.logger.warn("Gateway WS tools sync check failed", {
1345
+ error: error instanceof Error ? (error.stack ?? error.message) : String(error),
1346
+ });
1347
+ });
1348
+ }, TOOLS_SYNC_CHECK_INTERVAL_MS);
1349
+ }
1350
+ if (!this.wsHeartbeatTimer) {
1351
+ this.wsHeartbeatTimer = setInterval(() => {
1352
+ if (this.stopRequested) {
1353
+ return;
1354
+ }
1355
+ const activeSocket = this.wsClient;
1356
+ if (!activeSocket || activeSocket.readyState !== 1) {
1357
+ this.wsAwaitingPong = false;
1358
+ return;
1359
+ }
1360
+ if (this.wsAwaitingPong) {
1361
+ runtime.logger.warn("Gateway WS heartbeat timed out; terminating stale client socket", {
1362
+ url: normalizedUrl,
1363
+ clientUuid: this.wsHelloClientUuid,
1364
+ });
1365
+ this.wsAwaitingPong = false;
1366
+ if (this.wsClient === activeSocket) {
1367
+ this.wsClient = null;
1368
+ }
1369
+ activeSocket.terminate?.();
1370
+ this.scheduleGatewayWsReconnect?.();
1371
+ return;
1372
+ }
1373
+ this.wsAwaitingPong = true;
1374
+ try {
1375
+ activeSocket.send?.(JSON.stringify({
1376
+ type: "heartbeat_ping",
1377
+ ts: new Date().toISOString(),
1378
+ }));
1379
+ }
1380
+ catch (error) {
1381
+ this.wsAwaitingPong = false;
1382
+ runtime.logger.warn("Gateway WS heartbeat ping failed", {
1383
+ url: normalizedUrl,
1384
+ error: error instanceof Error ? (error.stack ?? error.message) : String(error),
1385
+ });
1386
+ if (this.wsClient === activeSocket) {
1387
+ this.wsClient = null;
1388
+ }
1389
+ activeSocket.terminate?.();
1390
+ this.scheduleGatewayWsReconnect?.();
1391
+ }
1392
+ }, WS_HEARTBEAT_INTERVAL_MS);
1393
+ }
1394
+ },
1395
+ async closeGatewayWsResources() {
1396
+ if (this.wsReconnectTimer) {
1397
+ clearTimeout(this.wsReconnectTimer);
1398
+ this.wsReconnectTimer = null;
1399
+ }
1400
+ if (this.wsIdentityRefreshTimer) {
1401
+ clearInterval(this.wsIdentityRefreshTimer);
1402
+ this.wsIdentityRefreshTimer = null;
1403
+ }
1404
+ if (this.wsToolsSyncTimer) {
1405
+ clearInterval(this.wsToolsSyncTimer);
1406
+ this.wsToolsSyncTimer = null;
1407
+ }
1408
+ if (this.wsHeartbeatTimer) {
1409
+ clearInterval(this.wsHeartbeatTimer);
1410
+ this.wsHeartbeatTimer = null;
1411
+ }
1412
+ this.wsAwaitingPong = false;
1413
+ for (const pending of this.pendingLiveRequests?.values() ?? []) {
1414
+ clearTimeout(pending.timeout);
1415
+ pending.reject(new Error("Gateway WS transport is shutting down"));
1416
+ }
1417
+ this.pendingLiveRequests?.clear();
1418
+ if (this.wsClient) {
1419
+ const socket = this.wsClient;
1420
+ this.wsClient = null;
1421
+ socket.removeAllListeners();
1422
+ socket.close();
1423
+ }
1424
+ this.wsHelloSessionToolsSnapshot = null;
1425
+ if (this.wsServer) {
1426
+ const server = this.wsServer;
1427
+ this.wsServer = null;
1428
+ const httpServer = this.standaloneHttpService?.httpServer;
1429
+ if (httpServer && this.wsUpgradeHandler) {
1430
+ httpServer.removeListener("upgrade", this.wsUpgradeHandler);
1431
+ }
1432
+ this.wsUpgradeHandler = null;
1433
+ await new Promise((resolve, reject) => {
1434
+ server.close((error) => {
1435
+ if (error) {
1436
+ reject(error);
1437
+ return;
1438
+ }
1439
+ resolve();
1440
+ });
1441
+ });
1442
+ }
1443
+ this.connectedClientToolsAlerts?.clear();
1444
+ },
1445
+ },
1446
+ async started() {
1447
+ const runtime = this.getRuntimeOrThrow?.();
1448
+ const mode = runtime?.config.distributed.mode;
1449
+ const gatewayEnabled = mode === "gateway" || mode === "both";
1450
+ const clientEnabled = mode === "client" || mode === "both";
1451
+ if (gatewayEnabled) {
1452
+ await this.startGatewayWsServer?.();
1453
+ }
1454
+ if (clientEnabled) {
1455
+ await this.startGatewayWsClient?.();
1456
+ }
1457
+ },
1458
+ async stopped() {
1459
+ this.stopRequested = true;
1460
+ await this.closeGatewayWsResources?.();
1461
+ },
1462
+ };
1463
+ exports.default = TelegramMcpGatewaySocketService;