@deadragdoll/tellymcp 0.0.10 → 0.0.12

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 (154) hide show
  1. package/.env.example.client +42 -52
  2. package/.env.example.gateway +48 -62
  3. package/CHANGELOG.md +3 -3
  4. package/README-ru.md +226 -376
  5. package/README.md +213 -1184
  6. package/TOOLS.md +320 -377
  7. package/VERSION.md +13 -13
  8. package/config/codex/plugins/telly-workflows/.codex-plugin/plugin.json +18 -0
  9. package/config/codex/plugins/telly-workflows/references/invariants.md +10 -0
  10. package/config/codex/plugins/telly-workflows/skills/telly-browser-screenshot/SKILL.md +27 -0
  11. package/config/codex/plugins/telly-workflows/skills/telly-collab-artifact/SKILL.md +22 -0
  12. package/config/codex/plugins/telly-workflows/skills/telly-human-telegram/SKILL.md +31 -0
  13. package/config/codex/plugins/telly-workflows/skills/telly-partner-note/SKILL.md +36 -0
  14. package/config/templates/env.both.template +39 -20
  15. package/config/templates/env.client.template +34 -19
  16. package/config/templates/env.gateway.template +37 -22
  17. package/dist/cli.js +322 -75
  18. package/dist/codexPluginInstaller.js +215 -0
  19. package/dist/lib/mixins/session.errors.js +34 -1
  20. package/dist/lib/pinoTargets.js +2 -2
  21. package/dist/moleculer.config.js +7 -5
  22. package/dist/services/features/telegram-mcp/browser.service.js +94 -1
  23. package/dist/services/features/telegram-mcp/collaboration.service.js +40 -3
  24. package/dist/services/features/telegram-mcp/ensuredb.service.js +145 -26
  25. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +285 -103
  26. package/dist/services/features/telegram-mcp/gateway-socket.service.js +907 -85
  27. package/dist/services/features/telegram-mcp/gateway.service.js +876 -81
  28. package/dist/services/features/telegram-mcp/mcp-http.service.js +8 -0
  29. package/dist/services/features/telegram-mcp/mcp-server.service.js +17 -26
  30. package/dist/services/features/telegram-mcp/notify.service.js +127 -1
  31. package/dist/services/features/telegram-mcp/session-context.service.js +28 -1
  32. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +132 -10
  33. package/dist/services/features/telegram-mcp/src/app/config/env.js +158 -52
  34. package/dist/services/features/telegram-mcp/src/app/http.js +375 -42
  35. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +386 -58
  36. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +3 -0
  37. package/dist/services/features/telegram-mcp/src/app/webapp/terminal.js +12 -0
  38. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +120 -104
  39. package/dist/services/features/telegram-mcp/src/entities/xchange/model/types.js +2 -0
  40. package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +1 -1
  41. package/dist/services/features/telegram-mcp/src/features/browser/model/browserOpenTool.js +1 -1
  42. package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
  43. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +306 -26
  44. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +71 -0
  45. package/dist/services/features/telegram-mcp/src/features/collaboration/model/listGatewaySessionsTool.js +33 -0
  46. package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +93 -82
  47. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +47 -5
  48. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +1 -1
  49. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +1 -1
  50. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +82 -0
  51. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +22 -14
  52. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +697 -75
  53. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +76 -0
  54. package/dist/services/features/telegram-mcp/src/features/embedded-runtime/model/embeddedRuntimeBroker.js +92 -0
  55. package/dist/services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js +192 -0
  56. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +382 -3
  57. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +1 -1
  58. package/dist/services/features/telegram-mcp/src/features/notify/model/sendFileToTelegramTool.js +33 -0
  59. package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +1 -1
  60. package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +1 -1
  61. package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +1 -1
  62. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +42 -200
  63. package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +1 -1
  64. package/dist/services/features/telegram-mcp/src/features/terminal-buffer/model/terminalBufferService.js +96 -0
  65. package/dist/services/features/telegram-mcp/src/features/terminal-input/model/terminalInputService.js +97 -0
  66. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +107 -58
  67. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +1 -1
  68. package/dist/services/features/telegram-mcp/src/features/xchange/model/getXchangeRecordTool.js +28 -0
  69. package/dist/services/features/telegram-mcp/src/features/xchange/model/listXchangeRecordsTool.js +28 -0
  70. package/dist/services/features/telegram-mcp/src/features/xchange/model/markXchangeRecordReadTool.js +28 -0
  71. package/dist/services/features/telegram-mcp/src/features/xchange/model/xchangeService.js +169 -0
  72. package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +5 -4
  73. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +143 -42
  74. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +139 -38
  75. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalSessionStore.js +27 -0
  76. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +11 -8
  77. package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +69 -70
  78. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +21 -22
  79. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +444 -7093
  80. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportAttachmentStore.js +93 -0
  81. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportBroadcastActions.js +385 -0
  82. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +149 -0
  83. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +642 -0
  84. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContent.js +84 -0
  85. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContext.js +78 -0
  86. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportDocumentActions.js +36 -0
  87. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportEventActions.js +292 -0
  88. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +352 -0
  89. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFormatting.js +75 -0
  90. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportGatewayActions.js +44 -0
  91. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLifecycleActions.js +161 -0
  92. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLive.js +56 -0
  93. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLiveActions.js +77 -0
  94. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuCallbacks.js +254 -0
  95. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFactories.js +538 -0
  96. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFingerprints.js +93 -0
  97. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFlow.js +344 -0
  98. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +65 -0
  99. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuState.js +408 -0
  100. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuText.js +216 -0
  101. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +452 -0
  102. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportOutputActions.js +189 -0
  103. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPartnerActions.js +286 -0
  104. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +108 -0
  105. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectActions.js +463 -0
  106. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEntryActions.js +202 -0
  107. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEvents.js +99 -0
  108. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectMenus.js +138 -0
  109. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +308 -0
  110. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectView.js +426 -0
  111. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportRequestFlow.js +278 -0
  112. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportSessionActions.js +143 -0
  113. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +665 -0
  114. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +226 -0
  115. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTypes.js +2 -0
  116. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportUtils.js +330 -0
  117. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportXchangeState.js +107 -0
  118. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +255 -0
  119. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +543 -0
  120. package/dist/services/features/telegram-mcp/src/shared/integrations/xchange/sqliteRecordStore.js +223 -0
  121. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +24 -0
  122. package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +6 -0
  123. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +147 -76
  124. package/dist/services/features/telegram-mcp/src/shared/lib/telegramXchangeRecords.js +72 -0
  125. package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +409 -0
  126. package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +129 -1
  127. package/dist/services/features/telegram-mcp/src/shared/lib/xchangeRecordHints.js +98 -0
  128. package/dist/services/features/telegram-mcp/standalone-http.service.js +12 -0
  129. package/dist/services/features/telegram-mcp/terminal-buffer.service.js +42 -0
  130. package/dist/services/features/telegram-mcp/terminal-input.service.js +41 -0
  131. package/dist/services/features/telegram-mcp/tools-sync.service.js +15 -1
  132. package/dist/services/features/telegram-mcp/xchange.service.js +123 -0
  133. package/docs/STANDALONE-ru.md +172 -0
  134. package/docs/STANDALONE.md +172 -0
  135. package/package.json +10 -5
  136. package/scripts/build-package-artifact.sh +27 -0
  137. package/scripts/deploy-gateway.sh +64 -0
  138. package/scripts/deploy-onebot-branch.sh +27 -0
  139. package/scripts/postinstall.js +11 -36
  140. package/STANDALONE-ru.md +0 -274
  141. package/STANDALONE.md +0 -274
  142. package/dist/services/features/telegram-mcp/inbox.service.js +0 -33
  143. package/dist/services/features/telegram-mcp/pair.service.js +0 -33
  144. package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +0 -10
  145. package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +0 -33
  146. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +0 -33
  147. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +0 -33
  148. package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +0 -77
  149. package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +0 -33
  150. package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +0 -33
  151. package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +0 -202
  152. package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +0 -33
  153. package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +0 -33
  154. package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +0 -363
@@ -4,10 +4,12 @@ exports.TELEGRAM_MCP_GATEWAY_SOCKET_SERVICE_NAME = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_path_1 = require("node:path");
7
+ const node_util_1 = require("node:util");
7
8
  const runtime_service_1 = require("./runtime.service");
8
9
  const standalone_http_service_1 = require("./standalone-http.service");
9
10
  const auth_1 = require("./src/app/webapp/auth");
10
- const tmux_1 = require("./src/app/webapp/tmux");
11
+ const terminal_1 = require("./src/app/webapp/terminal");
12
+ const client_1 = require("./src/shared/integrations/terminal/client");
11
13
  const gateway_loopback_1 = require("./gateway-loopback");
12
14
  const versionHandshake_1 = require("./src/shared/lib/version/versionHandshake");
13
15
  const wsLib = require("ws");
@@ -17,6 +19,39 @@ const CLIENT_RECONNECT_DELAY_MS = 3000;
17
19
  const LIVE_REQUEST_TIMEOUT_MS = 20000;
18
20
  const TOOLS_SYNC_CHECK_INTERVAL_MS = 15000;
19
21
  const WS_HEARTBEAT_INTERVAL_MS = 10000;
22
+ const HTTP_SERVER_WAIT_TIMEOUT_MS = 15000;
23
+ const HTTP_SERVER_WAIT_STEP_MS = 100;
24
+ function requireTelegramBotToken(runtime, purpose) {
25
+ const token = runtime.config.telegram.botToken?.trim();
26
+ if (!token) {
27
+ throw new Error(`Telegram bot token is unavailable on this node; cannot ${purpose}.`);
28
+ }
29
+ return token;
30
+ }
31
+ function sanitizeArtifactName(value) {
32
+ const withoutControlChars = Array.from(value)
33
+ .map((char) => (char.charCodeAt(0) < 32 ? "-" : char))
34
+ .join("");
35
+ return withoutControlChars
36
+ .trim()
37
+ .replace(/[/\\]+/gu, "-")
38
+ .replace(/\s+/gu, " ")
39
+ .replace(/^\.+$/u, "file")
40
+ .slice(0, 180) || "file";
41
+ }
42
+ function allocateArtifactRelativePath(shareId, preferredName, usedNames) {
43
+ const sanitized = sanitizeArtifactName(preferredName);
44
+ const ext = (0, node_path_1.extname)(sanitized);
45
+ const base = ext ? sanitized.slice(0, -ext.length) : sanitized;
46
+ let candidate = sanitized;
47
+ let index = 1;
48
+ while (usedNames.has(candidate.toLowerCase())) {
49
+ candidate = `${base}--${index}${ext}`;
50
+ index += 1;
51
+ }
52
+ usedNames.add(candidate.toLowerCase());
53
+ return `shares/files/${shareId}/${candidate}`;
54
+ }
20
55
  function normalizeWebSocketUrl(value, defaultPath) {
21
56
  const url = new URL(value);
22
57
  if (url.protocol === "https:") {
@@ -30,9 +65,9 @@ function normalizeWebSocketUrl(value, defaultPath) {
30
65
  }
31
66
  return url.toString();
32
67
  }
33
- function formatTmuxRelayError(error) {
34
- if ((0, tmux_1.isTmuxUnavailableError)(error)) {
35
- return "tmux is unavailable";
68
+ function formatTerminalRelayError(error) {
69
+ if ((0, terminal_1.isTerminalUnavailableError)(error)) {
70
+ return "terminal runtime is unavailable";
36
71
  }
37
72
  return error instanceof Error ? error.message : String(error);
38
73
  }
@@ -44,6 +79,90 @@ function computeToolsHashForDir(workspaceDir) {
44
79
  const content = (0, node_fs_1.readFileSync)(toolsPath, "utf8");
45
80
  return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
46
81
  }
82
+ function computePackageToolsHash(currentDir) {
83
+ const packageRoot = (0, versionHandshake_1.getTellyMcpPackageRoot)(currentDir);
84
+ if (!packageRoot) {
85
+ return null;
86
+ }
87
+ return computeToolsHashForDir(packageRoot);
88
+ }
89
+ function isBackendErrorLike(value) {
90
+ return Boolean(value &&
91
+ typeof value === "object" &&
92
+ typeof value.statusCode === "number" &&
93
+ typeof value.code === "string" &&
94
+ (typeof value.name === "string" ||
95
+ typeof value.message === "string"));
96
+ }
97
+ function formatBackendErrorLike(value) {
98
+ const details = [];
99
+ if (typeof value.code === "string" && value.code.trim()) {
100
+ details.push(`code=${value.code.trim()}`);
101
+ }
102
+ if (typeof value.statusCode === "number") {
103
+ details.push(`statusCode=${value.statusCode}`);
104
+ }
105
+ if (value.data !== undefined) {
106
+ try {
107
+ details.push(`data=${JSON.stringify(value.data)}`);
108
+ }
109
+ catch {
110
+ details.push(`data=${String(value.data)}`);
111
+ }
112
+ }
113
+ const base = typeof value.message === "string" && value.message.trim()
114
+ ? value.message.trim()
115
+ : `${value.name ?? "BackendError"} (${value.code})`;
116
+ return details.length > 0 ? `${base}\n${details.join("\n")}` : base;
117
+ }
118
+ function formatRemoteActionError(error) {
119
+ if (isBackendErrorLike(error)) {
120
+ return formatBackendErrorLike(error);
121
+ }
122
+ if (!(error instanceof Error)) {
123
+ return (0, node_util_1.inspect)(error, { depth: 6, breakLength: 140 });
124
+ }
125
+ const details = [];
126
+ const named = error;
127
+ const ownProps = Object.fromEntries(Object.getOwnPropertyNames(error).map((key) => [
128
+ key,
129
+ error[key],
130
+ ]));
131
+ if (typeof named.code === "string" && named.code.trim()) {
132
+ details.push(`code=${named.code.trim()}`);
133
+ }
134
+ if (typeof named.type === "string" && named.type.trim()) {
135
+ details.push(`type=${named.type.trim()}`);
136
+ }
137
+ if (named.data !== undefined) {
138
+ try {
139
+ details.push(`data=${JSON.stringify(named.data)}`);
140
+ }
141
+ catch {
142
+ details.push(`data=${String(named.data)}`);
143
+ }
144
+ }
145
+ if (named.fields !== undefined) {
146
+ try {
147
+ details.push(`fields=${JSON.stringify(named.fields)}`);
148
+ }
149
+ catch {
150
+ details.push(`fields=${String(named.fields)}`);
151
+ }
152
+ }
153
+ if (Object.keys(ownProps).length > 0) {
154
+ try {
155
+ details.push(`props=${JSON.stringify(ownProps)}`);
156
+ }
157
+ catch {
158
+ details.push(`props=${(0, node_util_1.inspect)(ownProps, { depth: 6, breakLength: 140 })}`);
159
+ }
160
+ }
161
+ const baseMessage = error.stack ?? error.message;
162
+ return details.length > 0
163
+ ? `${baseMessage}\n${details.join("\n")}`
164
+ : baseMessage;
165
+ }
47
166
  function normalizeGatewayBaseUrl(value) {
48
167
  const url = new URL(value);
49
168
  url.pathname = url.pathname.replace(/\/+$/u, "");
@@ -52,6 +171,34 @@ function normalizeGatewayBaseUrl(value) {
52
171
  }
53
172
  return url;
54
173
  }
174
+ function listConnectedSocketsForClient(carrier, clientUuid) {
175
+ const sockets = [];
176
+ for (const [socket, hello] of carrier.connectedClients?.entries() ?? []) {
177
+ if (hello?.client_uuid !== clientUuid) {
178
+ continue;
179
+ }
180
+ if (socket?.readyState !== undefined && socket.readyState !== 1) {
181
+ continue;
182
+ }
183
+ sockets.push(socket);
184
+ }
185
+ return sockets;
186
+ }
187
+ function findConnectedSocketForSession(carrier, params) {
188
+ for (const [socket, hello] of carrier.connectedClients?.entries() ?? []) {
189
+ if (hello?.client_uuid !== params.clientUuid ||
190
+ (socket?.readyState !== undefined && socket.readyState !== 1)) {
191
+ continue;
192
+ }
193
+ const hasSession = Array.isArray(hello.session_tools)
194
+ ? hello.session_tools.some((item) => item.local_session_id === params.localSessionId)
195
+ : false;
196
+ if (hasSession) {
197
+ return socket;
198
+ }
199
+ }
200
+ return null;
201
+ }
55
202
  const TelegramMcpGatewaySocketService = {
56
203
  name: exports.TELEGRAM_MCP_GATEWAY_SOCKET_SERVICE_NAME,
57
204
  dependencies: [
@@ -59,13 +206,29 @@ const TelegramMcpGatewaySocketService = {
59
206
  standalone_http_service_1.TELEGRAM_MCP_STANDALONE_HTTP_SERVICE_NAME,
60
207
  ],
61
208
  actions: {
209
+ refreshClientHello: {
210
+ async handler() {
211
+ if (!this.wsClient || this.wsClient.readyState !== 1) {
212
+ return { sent: false };
213
+ }
214
+ await this.sendClientHello?.(this.wsClient);
215
+ return { sent: true };
216
+ },
217
+ },
62
218
  requestLiveRelay: {
63
219
  params: {
64
220
  clientUuid: "string",
65
221
  localSessionId: "string",
66
222
  requestType: {
67
223
  type: "enum",
68
- values: ["bootstrap", "bootstrap_validate", "view", "action"],
224
+ values: [
225
+ "bootstrap",
226
+ "bootstrap_validate",
227
+ "view",
228
+ "action",
229
+ "stream_subscribe",
230
+ "stream_unsubscribe",
231
+ ],
69
232
  },
70
233
  payload: { type: "object", optional: true },
71
234
  },
@@ -78,6 +241,32 @@ const TelegramMcpGatewaySocketService = {
78
241
  });
79
242
  },
80
243
  },
244
+ requestClientAction: {
245
+ params: {
246
+ clientUuid: "string",
247
+ actionName: "string",
248
+ params: { type: "object", optional: true },
249
+ },
250
+ async handler(ctx) {
251
+ return await this.requestClientAction?.({
252
+ clientUuid: String(ctx.params.clientUuid),
253
+ actionName: String(ctx.params.actionName),
254
+ payload: ctx.params.params && typeof ctx.params.params === "object"
255
+ ? ctx.params.params
256
+ : {},
257
+ });
258
+ },
259
+ },
260
+ resolveConnectedSessionTarget: {
261
+ params: {
262
+ sessionId: "string",
263
+ },
264
+ async handler(ctx) {
265
+ return await this.resolveConnectedSessionTarget?.({
266
+ sessionId: String(ctx.params.sessionId),
267
+ });
268
+ },
269
+ },
81
270
  notifyProjectMemberJoined: {
82
271
  params: {
83
272
  clientUuids: { type: "array", items: "string" },
@@ -153,6 +342,45 @@ const TelegramMcpGatewaySocketService = {
153
342
  return await this.notifyDeliveryStatus?.(ctx.params);
154
343
  },
155
344
  },
345
+ notifyTransportReply: {
346
+ params: {
347
+ client_uuid: "string",
348
+ request_id: "string",
349
+ answer: "string",
350
+ received_at: "string",
351
+ },
352
+ async handler(ctx) {
353
+ return {
354
+ delivered: await this.notifyTransportReply?.({
355
+ clientUuid: String(ctx.params.client_uuid),
356
+ payload: {
357
+ request_id: String(ctx.params.request_id),
358
+ answer: String(ctx.params.answer),
359
+ received_at: String(ctx.params.received_at),
360
+ },
361
+ }),
362
+ };
363
+ },
364
+ },
365
+ sendDirectPartnerNote: {
366
+ params: {
367
+ clientUuid: "string",
368
+ localSessionId: "string",
369
+ sourceActorLabel: { type: "string", optional: true },
370
+ targetClientUuid: "string",
371
+ targetLocalSessionId: "string",
372
+ kind: "string",
373
+ summary: "string",
374
+ message: "string",
375
+ expectedReply: { type: "string", optional: true },
376
+ requiresReply: { type: "boolean", optional: true },
377
+ inReplyTo: { type: "string", optional: true },
378
+ artifactRefs: { type: "array", optional: true, items: "object" },
379
+ },
380
+ async handler(ctx) {
381
+ return await this.sendDirectPartnerNote?.(ctx.params);
382
+ },
383
+ },
156
384
  },
157
385
  created() {
158
386
  this.runtimeService = null;
@@ -174,6 +402,9 @@ const TelegramMcpGatewaySocketService = {
174
402
  this.connectedClientsByUuid = new Map();
175
403
  this.connectedClientToolsAlerts = new Map();
176
404
  this.pendingLiveRequests = new Map();
405
+ this.pendingActionRequests = new Map();
406
+ this.liveStreamHandlers = new Map();
407
+ this.localLiveStreamSubscriptions = new Map();
177
408
  },
178
409
  methods: {
179
410
  getRuntimeOrThrow() {
@@ -194,21 +425,43 @@ const TelegramMcpGatewaySocketService = {
194
425
  this.standaloneHttpService = standaloneHttpService;
195
426
  return standaloneHttpService.httpServer;
196
427
  },
428
+ async waitForHttpServer() {
429
+ const startedAt = Date.now();
430
+ while (Date.now() - startedAt < HTTP_SERVER_WAIT_TIMEOUT_MS) {
431
+ const standaloneHttpService = this.standaloneHttpService ??
432
+ this.broker.getLocalService(standalone_http_service_1.TELEGRAM_MCP_STANDALONE_HTTP_SERVICE_NAME);
433
+ if (standaloneHttpService?.httpServer) {
434
+ this.standaloneHttpService = standaloneHttpService;
435
+ return standaloneHttpService.httpServer;
436
+ }
437
+ await new Promise((resolve) => setTimeout(resolve, HTTP_SERVER_WAIT_STEP_MS));
438
+ }
439
+ throw new Error(`Local Moleculer service '${standalone_http_service_1.TELEGRAM_MCP_STANDALONE_HTTP_SERVICE_NAME}' HTTP server is unavailable`);
440
+ },
197
441
  async collectSessionTools() {
198
442
  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
443
+ const sessionsForHello = runtime.config.distributed.mode === "client"
444
+ ? await (async () => {
445
+ const resolved = runtime.projectIdentityResolver.resolveSessionDefaults({
446
+ cwd: process.cwd(),
447
+ });
448
+ const session = await runtime.sessionStore.getSession(resolved.sessionId);
449
+ if (!session) {
450
+ throw new Error(`Current console '${resolved.sessionId}' is missing from session store during gateway hello.`);
451
+ }
452
+ return [session];
453
+ })()
454
+ : (await Promise.all((await runtime.sessionStore.listSessions()).map(async (session) => (await runtime.bindingStore.getBinding(session.sessionId))
455
+ ? session
456
+ : null))).filter((session) => Boolean(session));
457
+ const sessionTools = sessionsForHello
204
458
  .map((session) => {
205
- const toolsHash = session.cwd
206
- ? computeToolsHashForDir(session.cwd)
207
- : null;
459
+ const effectiveHash = session.lastSeenToolsHash?.trim() ?? null;
208
460
  return {
209
461
  local_session_id: session.sessionId,
210
462
  ...(session.label ? { session_label: session.label } : {}),
211
- ...(toolsHash ? { tools_hash: toolsHash } : {}),
463
+ ...(effectiveHash ? { tools_hash: effectiveHash } : {}),
464
+ ...(session.cwd ? { cwd: session.cwd } : {}),
212
465
  };
213
466
  })
214
467
  .sort((left, right) => left.local_session_id.localeCompare(right.local_session_id));
@@ -218,7 +471,7 @@ const TelegramMcpGatewaySocketService = {
218
471
  };
219
472
  },
220
473
  getGatewayToolsHash() {
221
- return computeToolsHashForDir(process.cwd());
474
+ return computePackageToolsHash(__dirname);
222
475
  },
223
476
  getLocalVersionInfo() {
224
477
  return {
@@ -227,6 +480,147 @@ const TelegramMcpGatewaySocketService = {
227
480
  capabilities: [...versionHandshake_1.TELLYMCP_CAPABILITIES],
228
481
  };
229
482
  },
483
+ findConnectedSessionTool(params) {
484
+ for (const hello of this.connectedClients?.values() ?? []) {
485
+ if (hello?.client_uuid !== params.clientUuid) {
486
+ continue;
487
+ }
488
+ const sessionTool = Array.isArray(hello.session_tools)
489
+ ? hello.session_tools.find((item) => item.local_session_id === params.localSessionId)
490
+ : null;
491
+ if (!sessionTool) {
492
+ continue;
493
+ }
494
+ return {
495
+ ...(sessionTool.session_label
496
+ ? { session_label: sessionTool.session_label }
497
+ : {}),
498
+ ...(hello.node_id ? { node_id: hello.node_id } : {}),
499
+ ...(hello.package_version
500
+ ? { package_version: hello.package_version }
501
+ : {}),
502
+ };
503
+ }
504
+ return null;
505
+ },
506
+ async resolveConnectedSessionTarget(params) {
507
+ const sessionId = params.sessionId.trim();
508
+ if (!sessionId || sessionId.startsWith("relay~")) {
509
+ return null;
510
+ }
511
+ const resolved = await this.broker.call("telegramMcp.gateway.resolveLiveConsole", { sessionId }, { meta: { internal_call: true } });
512
+ if (!resolved) {
513
+ return null;
514
+ }
515
+ return {
516
+ client_uuid: resolved.client_uuid,
517
+ local_session_id: resolved.local_session_id,
518
+ ...(resolved.session_label ? { session_label: resolved.session_label } : {}),
519
+ };
520
+ },
521
+ async sendDirectPartnerNote(params) {
522
+ const sourceInfo = this.findConnectedSessionTool?.({
523
+ clientUuid: params.clientUuid,
524
+ localSessionId: params.localSessionId,
525
+ });
526
+ const targetInfo = this.findConnectedSessionTool?.({
527
+ clientUuid: params.targetClientUuid,
528
+ localSessionId: params.targetLocalSessionId,
529
+ });
530
+ const targetIsLocal = await this.isLocalGatewayClientUuid?.(params.targetClientUuid);
531
+ if (!targetInfo && !targetIsLocal) {
532
+ throw new Error(`Target gateway session '${params.targetClientUuid}/${params.targetLocalSessionId}' is not connected.`);
533
+ }
534
+ const requiresReply = typeof params.requiresReply === "boolean"
535
+ ? params.requiresReply
536
+ : params.kind === "question" || params.kind === "request";
537
+ const shareId = (0, node_crypto_1.randomUUID)();
538
+ const messageUuid = (0, node_crypto_1.randomUUID)();
539
+ const deliveryUuid = (0, node_crypto_1.randomUUID)();
540
+ const createdAt = new Date().toISOString();
541
+ const usedArtifactNames = new Set();
542
+ const artifacts = (Array.isArray(params.artifactRefs)
543
+ ? params.artifactRefs
544
+ : []).map((artifact) => {
545
+ const originalName = typeof artifact.original_name === "string" && artifact.original_name.trim()
546
+ ? artifact.original_name.trim()
547
+ : typeof artifact.relative_path === "string" &&
548
+ artifact.relative_path.trim()
549
+ ? artifact.relative_path.trim()
550
+ : typeof artifact.file_path === "string" && artifact.file_path.trim()
551
+ ? artifact.file_path.trim()
552
+ : "file";
553
+ return {
554
+ artifact_uuid: (0, node_crypto_1.randomUUID)(),
555
+ original_name: (0, node_path_1.basename)(originalName),
556
+ ...(typeof artifact.mime_type === "string" && artifact.mime_type.trim()
557
+ ? { mime_type: artifact.mime_type.trim() }
558
+ : {}),
559
+ ...(typeof artifact.size_bytes === "number"
560
+ ? { size_bytes: artifact.size_bytes }
561
+ : {}),
562
+ ...(typeof artifact.storage_ref === "string" && artifact.storage_ref.trim()
563
+ ? { storage_ref: artifact.storage_ref.trim() }
564
+ : {}),
565
+ relative_path: allocateArtifactRelativePath(shareId, (0, node_path_1.basename)(originalName), usedArtifactNames),
566
+ ...(typeof artifact.content_base64 === "string" &&
567
+ artifact.content_base64.trim()
568
+ ? { content_base64: artifact.content_base64.trim() }
569
+ : {}),
570
+ };
571
+ });
572
+ const delivery = {
573
+ delivery_uuid: deliveryUuid,
574
+ message_uuid: messageUuid,
575
+ share_id: shareId,
576
+ route_mode: "direct",
577
+ source_actor_label: params.sourceActorLabel?.trim() ||
578
+ sourceInfo?.session_label ||
579
+ params.localSessionId,
580
+ source_client_uuid: params.clientUuid,
581
+ target_client_uuid: params.targetClientUuid,
582
+ kind: params.kind,
583
+ summary: params.summary,
584
+ message: params.message,
585
+ ...(params.expectedReply ? { expected_reply: params.expectedReply } : {}),
586
+ requires_reply: requiresReply,
587
+ ...(params.inReplyTo ? { in_reply_to: params.inReplyTo } : {}),
588
+ source_session_uuid: `${params.clientUuid}:${params.localSessionId}`,
589
+ source_session_label: params.sourceActorLabel?.trim() ||
590
+ sourceInfo?.session_label ||
591
+ params.localSessionId,
592
+ source_local_session_id: params.localSessionId,
593
+ target_session_uuid: `${params.targetClientUuid}:${params.targetLocalSessionId}`,
594
+ target_local_session_id: params.targetLocalSessionId,
595
+ target_session_label: targetInfo?.session_label ?? params.targetLocalSessionId,
596
+ created_at: createdAt,
597
+ note_relative_path: `shares/${shareId}.md`,
598
+ artifacts,
599
+ };
600
+ const delivered = await this.notifyDeliveryQueued?.({
601
+ clientUuid: params.targetClientUuid,
602
+ delivery,
603
+ });
604
+ if (!delivered) {
605
+ throw new Error(`Target gateway session '${params.targetClientUuid}/${params.targetLocalSessionId}' is not connected.`);
606
+ }
607
+ return {
608
+ session_id: params.localSessionId,
609
+ partner_session_id: `${params.targetClientUuid}:${params.targetLocalSessionId}`,
610
+ target_client_uuid: params.targetClientUuid,
611
+ target_local_session_id: params.targetLocalSessionId,
612
+ target_session_label: targetInfo?.session_label ?? params.targetLocalSessionId,
613
+ kind: params.kind,
614
+ share_id: shareId,
615
+ delivery_status: "delivered",
616
+ note_path: `gateway://shares/${shareId}.md`,
617
+ xchange_record_id: shareId,
618
+ copied_artifacts: artifacts.map((artifact) => artifact.original_name),
619
+ inbox_message_id: deliveryUuid,
620
+ requires_reply: requiresReply,
621
+ delivery_uuid: deliveryUuid,
622
+ };
623
+ },
230
624
  async fetchGatewayToolsHashForClient() {
231
625
  const runtime = this.getRuntimeOrThrow();
232
626
  if (runtime.config.distributed.mode === "gateway" ||
@@ -276,9 +670,7 @@ const TelegramMcpGatewaySocketService = {
276
670
  : null))).filter((session) => Boolean(session));
277
671
  let delivered = 0;
278
672
  for (const session of boundSessions) {
279
- const localHash = session.cwd
280
- ? computeToolsHashForDir(session.cwd)
281
- : null;
673
+ const localHash = session.lastSeenToolsHash?.trim() ?? null;
282
674
  if (localHash === gatewayToolsHash) {
283
675
  continue;
284
676
  }
@@ -292,7 +684,7 @@ const TelegramMcpGatewaySocketService = {
292
684
  ...(localHash ? { client_tools_hash: localHash } : {}),
293
685
  gateway_tools_hash: gatewayToolsHash,
294
686
  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.",
687
+ instruction: "Call refresh_tools_markdown with the current known_hash for this session. If changed=true, read and apply the returned content before continuing.",
296
688
  });
297
689
  delivered += 1;
298
690
  }
@@ -333,7 +725,7 @@ const TelegramMcpGatewaySocketService = {
333
725
  ...(clientToolsHash ? { client_tools_hash: clientToolsHash } : {}),
334
726
  gateway_tools_hash: gatewayToolsHash,
335
727
  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.",
728
+ instruction: "Call refresh_tools_markdown with the current known_hash for this session. If changed=true, read and apply the returned content before continuing.",
337
729
  },
338
730
  }));
339
731
  alerted.set(sessionTool.local_session_id, gatewayToolsHash);
@@ -361,9 +753,21 @@ const TelegramMcpGatewaySocketService = {
361
753
  connection_id: this.wsConnectionId || (0, node_crypto_1.randomUUID)(),
362
754
  role: "client",
363
755
  ...(clientUuid ? { client_uuid: clientUuid } : {}),
756
+ ...(runtime.config.distributed.gatewayUserUuid
757
+ ? { gateway_user_uuid: runtime.config.distributed.gatewayUserUuid }
758
+ : {}),
759
+ ...(runtime.config.project.name
760
+ ? { client_label: runtime.config.project.name }
761
+ : {}),
762
+ ...(process.env.USER?.trim()
763
+ ? { system_username: process.env.USER.trim() }
764
+ : process.env.LOGNAME?.trim()
765
+ ? { system_username: process.env.LOGNAME.trim() }
766
+ : {}),
364
767
  ...(runtime.config.project.name
365
768
  ? { project_name: runtime.config.project.name }
366
769
  : {}),
770
+ ...(this.broker.namespace ? { namespace: this.broker.namespace } : {}),
367
771
  ...(this.broker.nodeID ? { node_id: this.broker.nodeID } : {}),
368
772
  package_version: versionInfo.packageVersion,
369
773
  protocol_version: versionInfo.protocolVersion,
@@ -462,7 +866,7 @@ const TelegramMcpGatewaySocketService = {
462
866
  if (!initDataRaw || !initDataUnsafe) {
463
867
  throw new Error("initDataRaw and initDataUnsafe are required");
464
868
  }
465
- const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, runtime.config.telegram.botToken, runtime.config.webapp.initDataTtlSeconds);
869
+ const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, requireTelegramBotToken(runtime, "validate Telegram WebApp relay bootstrap"), runtime.config.webapp.initDataTtlSeconds);
466
870
  const launchRecord = runtime.webAppLaunchRegistry.getByUserId(validated.user.id);
467
871
  if (!launchRecord) {
468
872
  throw new Error("No pending Telegram WebApp launch was found");
@@ -492,6 +896,7 @@ const TelegramMcpGatewaySocketService = {
492
896
  payload.telegramUserId.trim()
493
897
  ? Number(payload.telegramUserId)
494
898
  : null;
899
+ let launchRecord = null;
495
900
  let telegramUserId = trustedTelegramUserId;
496
901
  if (telegramUserId !== null &&
497
902
  (!Number.isFinite(telegramUserId) || telegramUserId <= 0)) {
@@ -505,8 +910,9 @@ const TelegramMcpGatewaySocketService = {
505
910
  if (!initDataRaw || !initDataUnsafe) {
506
911
  throw new Error("initDataRaw and initDataUnsafe are required");
507
912
  }
508
- const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, runtime.config.telegram.botToken, runtime.config.webapp.initDataTtlSeconds);
913
+ const validated = (0, auth_1.validateTelegramWebAppInitData)(initDataRaw, initDataUnsafe, requireTelegramBotToken(runtime, "validate Telegram WebApp relay bootstrap"), runtime.config.webapp.initDataTtlSeconds);
509
914
  telegramUserId = validated.user.id;
915
+ launchRecord = runtime.webAppLaunchRegistry.getByUserId(validated.user.id);
510
916
  }
511
917
  const sessionId = request.local_session_id.trim();
512
918
  if (!sessionId) {
@@ -518,6 +924,15 @@ const TelegramMcpGatewaySocketService = {
518
924
  throw new Error("This Telegram user is not bound to the requested session.");
519
925
  }
520
926
  const session = await runtime.sessionStore.getSession(sessionId);
927
+ if (trustedTelegramUserId === null && telegramUserId !== null) {
928
+ runtime.webAppLaunchRegistry.deleteByUserId(telegramUserId);
929
+ }
930
+ if (launchRecord?.telegramChatId !== undefined &&
931
+ launchRecord?.telegramMessageId !== undefined) {
932
+ void runtime.telegramTransport
933
+ .deleteMessage(launchRecord.telegramChatId, launchRecord.telegramMessageId)
934
+ .catch(() => undefined);
935
+ }
521
936
  return {
522
937
  type: "live_response",
523
938
  request_id: request.request_id,
@@ -525,7 +940,7 @@ const TelegramMcpGatewaySocketService = {
525
940
  result: {
526
941
  session_id: sessionId,
527
942
  session_label: session?.label ?? null,
528
- tmux_target: Boolean(session?.tmuxTarget),
943
+ terminal_target: Boolean(session?.terminalTarget),
529
944
  poll_interval_ms: runtime.config.webapp.pollIntervalMs,
530
945
  telegram_user_id: telegramUserId,
531
946
  },
@@ -534,10 +949,12 @@ const TelegramMcpGatewaySocketService = {
534
949
  if (request.request_type === "view") {
535
950
  const sessionId = request.local_session_id.trim();
536
951
  const session = await runtime.sessionStore.getSession(sessionId);
537
- if (!session?.tmuxTarget) {
538
- throw new Error("tmux target is not configured for this session");
952
+ if (!session?.terminalTarget) {
953
+ throw new Error("terminal target is not configured for this session");
539
954
  }
540
- const content = await (0, tmux_1.captureVisibleTmuxPane)(runtime.config.tmux, session.tmuxTarget, runtime.config.tmux.captureLines, runtime.config.webapp.visibleScreens);
955
+ const terminalSize = await (0, terminal_1.getTerminalWindowSize)(runtime.config.terminal, session.terminalTarget);
956
+ const content = await (0, terminal_1.captureVisibleTerminal)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
957
+ const ansi = await (0, terminal_1.captureVisibleTerminalAnsi)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
541
958
  return {
542
959
  type: "live_response",
543
960
  request_id: request.request_id,
@@ -547,6 +964,8 @@ const TelegramMcpGatewaySocketService = {
547
964
  session_label: session.label ?? null,
548
965
  captured_at: new Date().toISOString(),
549
966
  content,
967
+ ansi,
968
+ ...(terminalSize ? terminalSize : {}),
550
969
  },
551
970
  };
552
971
  }
@@ -565,14 +984,96 @@ const TelegramMcpGatewaySocketService = {
565
984
  }
566
985
  const sessionId = request.local_session_id.trim();
567
986
  const session = await runtime.sessionStore.getSession(sessionId);
568
- if (!session?.tmuxTarget) {
569
- throw new Error("tmux target is not configured for this session");
987
+ if (!session?.terminalTarget) {
988
+ throw new Error("terminal target is not configured for this session");
570
989
  }
571
990
  if (action === "text") {
572
- await (0, tmux_1.sendTmuxLiteralText)(runtime.config.tmux, session.tmuxTarget, text);
991
+ await (0, terminal_1.sendTerminalLiteralText)(runtime.config.terminal, session.terminalTarget, text);
573
992
  }
574
993
  else {
575
- await (0, tmux_1.sendAllowedTmuxAction)(runtime.config.tmux, session.tmuxTarget, action);
994
+ await (0, terminal_1.sendAllowedTerminalAction)(runtime.config.terminal, session.terminalTarget, action);
995
+ }
996
+ return {
997
+ type: "live_response",
998
+ request_id: request.request_id,
999
+ ok: true,
1000
+ result: {
1001
+ ok: true,
1002
+ },
1003
+ };
1004
+ }
1005
+ if (request.request_type === "stream_subscribe") {
1006
+ const streamId = typeof request.payload?.stream_id === "string"
1007
+ ? request.payload.stream_id.trim()
1008
+ : "";
1009
+ if (!streamId) {
1010
+ throw new Error("stream_id is required");
1011
+ }
1012
+ const sessionId = request.local_session_id.trim();
1013
+ const session = await runtime.sessionStore.getSession(sessionId);
1014
+ if (!session?.terminalTarget) {
1015
+ throw new Error("terminal target is not configured for this session");
1016
+ }
1017
+ if (!(0, client_1.isStreamableTerminalTarget)(session.terminalTarget)) {
1018
+ throw new Error("Live stream is supported only for PTY-backed terminals");
1019
+ }
1020
+ this.localLiveStreamSubscriptions?.get(streamId)?.();
1021
+ this.localLiveStreamSubscriptions?.delete(streamId);
1022
+ const terminalSize = await (0, terminal_1.getTerminalWindowSize)(runtime.config.terminal, session.terminalTarget);
1023
+ const content = await (0, terminal_1.captureVisibleTerminal)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
1024
+ const ansi = await (0, terminal_1.captureVisibleTerminalAnsi)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
1025
+ this.wsClient?.send(JSON.stringify({
1026
+ type: "live_stream_event",
1027
+ stream_id: streamId,
1028
+ event: "snapshot",
1029
+ payload: {
1030
+ session_id: session.sessionId,
1031
+ session_label: session.label ?? null,
1032
+ captured_at: new Date().toISOString(),
1033
+ content,
1034
+ ansi,
1035
+ ...(terminalSize ? terminalSize : {}),
1036
+ },
1037
+ }));
1038
+ const unsubscribe = (0, client_1.subscribeForegroundTerminal)(session.terminalTarget, {
1039
+ onData: (data) => {
1040
+ this.wsClient?.send(JSON.stringify({
1041
+ type: "live_stream_event",
1042
+ stream_id: streamId,
1043
+ event: "data",
1044
+ payload: { data },
1045
+ }));
1046
+ },
1047
+ onExit: (info) => {
1048
+ this.wsClient?.send(JSON.stringify({
1049
+ type: "live_stream_event",
1050
+ stream_id: streamId,
1051
+ event: "exit",
1052
+ payload: {
1053
+ exitCode: typeof info.exitCode === "number" ? info.exitCode : null,
1054
+ signal: typeof info.signal === "number" ? info.signal : null,
1055
+ },
1056
+ }));
1057
+ },
1058
+ });
1059
+ this.localLiveStreamSubscriptions?.set(streamId, unsubscribe);
1060
+ return {
1061
+ type: "live_response",
1062
+ request_id: request.request_id,
1063
+ ok: true,
1064
+ result: {
1065
+ ok: true,
1066
+ stream_id: streamId,
1067
+ },
1068
+ };
1069
+ }
1070
+ if (request.request_type === "stream_unsubscribe") {
1071
+ const streamId = typeof request.payload?.stream_id === "string"
1072
+ ? request.payload.stream_id.trim()
1073
+ : "";
1074
+ if (streamId) {
1075
+ this.localLiveStreamSubscriptions?.get(streamId)?.();
1076
+ this.localLiveStreamSubscriptions?.delete(streamId);
576
1077
  }
577
1078
  return {
578
1079
  type: "live_response",
@@ -580,6 +1081,7 @@ const TelegramMcpGatewaySocketService = {
580
1081
  ok: true,
581
1082
  result: {
582
1083
  ok: true,
1084
+ ...(streamId ? { stream_id: streamId } : {}),
583
1085
  },
584
1086
  };
585
1087
  }
@@ -590,7 +1092,7 @@ const TelegramMcpGatewaySocketService = {
590
1092
  type: "live_response",
591
1093
  request_id: request.request_id,
592
1094
  ok: false,
593
- error: formatTmuxRelayError(error),
1095
+ error: formatTerminalRelayError(error),
594
1096
  };
595
1097
  }
596
1098
  },
@@ -607,6 +1109,17 @@ const TelegramMcpGatewaySocketService = {
607
1109
  ...(typeof parsed.client_uuid === "string" && parsed.client_uuid.trim()
608
1110
  ? { client_uuid: parsed.client_uuid.trim() }
609
1111
  : {}),
1112
+ ...(typeof parsed.client_label === "string" && parsed.client_label.trim()
1113
+ ? { client_label: parsed.client_label.trim() }
1114
+ : {}),
1115
+ ...(typeof parsed.gateway_user_uuid === "string" &&
1116
+ parsed.gateway_user_uuid.trim()
1117
+ ? { gateway_user_uuid: parsed.gateway_user_uuid.trim() }
1118
+ : {}),
1119
+ ...(typeof parsed.system_username === "string" &&
1120
+ parsed.system_username.trim()
1121
+ ? { system_username: parsed.system_username.trim() }
1122
+ : {}),
610
1123
  ...(typeof parsed.project_name === "string" && parsed.project_name.trim()
611
1124
  ? { project_name: parsed.project_name.trim() }
612
1125
  : {}),
@@ -654,6 +1167,12 @@ const TelegramMcpGatewaySocketService = {
654
1167
  tools_hash: item.tools_hash.trim(),
655
1168
  }
656
1169
  : {}),
1170
+ ...(typeof item.cwd === "string" &&
1171
+ item.cwd.trim()
1172
+ ? {
1173
+ cwd: item.cwd.trim(),
1174
+ }
1175
+ : {}),
657
1176
  }
658
1177
  : null)
659
1178
  .filter((item) => Boolean(item?.local_session_id)),
@@ -661,6 +1180,12 @@ const TelegramMcpGatewaySocketService = {
661
1180
  : {}),
662
1181
  };
663
1182
  const previous = this.connectedClients?.get(socket);
1183
+ const previousByClientUuid = hello.client_uuid
1184
+ ? this.connectedClientsByUuid?.get(hello.client_uuid)
1185
+ : null;
1186
+ const previousHelloForClient = previousByClientUuid && previousByClientUuid !== socket
1187
+ ? this.connectedClients?.get(previousByClientUuid)
1188
+ : previous;
664
1189
  if (previous?.client_uuid) {
665
1190
  this.connectedClientsByUuid?.delete(previous.client_uuid);
666
1191
  }
@@ -669,6 +1194,46 @@ const TelegramMcpGatewaySocketService = {
669
1194
  this.connectedClientsByUuid?.set(hello.client_uuid, socket);
670
1195
  }
671
1196
  runtime.logger.info("Gateway WS hello received", hello);
1197
+ if (hello.client_uuid) {
1198
+ await this.broker.call("telegramMcp.gateway.syncLiveConsoles", {
1199
+ client_uuid: hello.client_uuid,
1200
+ connection_id: hello.connection_id,
1201
+ ...(hello.gateway_user_uuid
1202
+ ? { gateway_user_uuid: hello.gateway_user_uuid }
1203
+ : {}),
1204
+ ...(hello.client_label ? { client_label: hello.client_label } : {}),
1205
+ ...(hello.system_username
1206
+ ? { system_username: hello.system_username }
1207
+ : {}),
1208
+ ...(hello.namespace ? { namespace: hello.namespace } : {}),
1209
+ ...(hello.node_id ? { node_id: hello.node_id } : {}),
1210
+ ...(hello.package_version
1211
+ ? { package_version: hello.package_version }
1212
+ : {}),
1213
+ ...(hello.protocol_version
1214
+ ? { protocol_version: hello.protocol_version }
1215
+ : {}),
1216
+ session_tools: Array.isArray(hello.session_tools)
1217
+ ? hello.session_tools.map((sessionTool) => ({
1218
+ local_session_id: sessionTool.local_session_id,
1219
+ ...(sessionTool.session_label
1220
+ ? { session_label: sessionTool.session_label }
1221
+ : {}),
1222
+ ...(sessionTool.tools_hash
1223
+ ? { tools_hash: sessionTool.tools_hash }
1224
+ : {}),
1225
+ ...(sessionTool.cwd ? { cwd: sessionTool.cwd } : {}),
1226
+ }))
1227
+ : [],
1228
+ }, { meta: { internal_call: true } });
1229
+ await runtime.telegramTransport.hydrateGatewayClientOwnerRoute({
1230
+ clientUuid: hello.client_uuid,
1231
+ ...(hello.gateway_user_uuid
1232
+ ? { gatewayUserUuid: hello.gateway_user_uuid }
1233
+ : {}),
1234
+ });
1235
+ runtime.telegramTransport.ensurePromptScanRunning();
1236
+ }
672
1237
  const localVersionInfo = this.getLocalVersionInfo?.() ?? {
673
1238
  packageVersion: "0.0.0-unknown",
674
1239
  protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
@@ -718,6 +1283,34 @@ const TelegramMcpGatewaySocketService = {
718
1283
  }, 50);
719
1284
  return;
720
1285
  }
1286
+ if (hello.client_uuid) {
1287
+ const previousSessionIds = new Set(Array.isArray(previousHelloForClient?.session_tools)
1288
+ ? previousHelloForClient.session_tools
1289
+ .map((session) => session.local_session_id)
1290
+ .filter((sessionId) => typeof sessionId === "string" && sessionId.trim().length > 0)
1291
+ : []);
1292
+ const currentSessions = Array.isArray(hello.session_tools)
1293
+ ? hello.session_tools.filter((session) => typeof session?.local_session_id === "string" &&
1294
+ session.local_session_id.trim().length > 0)
1295
+ : [];
1296
+ const newSessions = currentSessions.filter((session) => !previousSessionIds.has(session.local_session_id));
1297
+ const isNewClient = !previousHelloForClient;
1298
+ if (isNewClient || newSessions.length > 0) {
1299
+ await runtime.telegramTransport.sendAdminGatewayRegistrationNotifications({
1300
+ clientUuid: hello.client_uuid,
1301
+ ...(hello.gateway_user_uuid
1302
+ ? { gatewayUserUuid: hello.gateway_user_uuid }
1303
+ : {}),
1304
+ ...(hello.node_id ? { nodeId: hello.node_id } : {}),
1305
+ ...(hello.package_version
1306
+ ? { packageVersion: hello.package_version }
1307
+ : {}),
1308
+ totalSessions: currentSessions.length,
1309
+ isNewClient,
1310
+ newSessions,
1311
+ });
1312
+ }
1313
+ }
721
1314
  await this.notifyToolsMismatchForSocket?.(socket, hello);
722
1315
  if (hello.client_uuid) {
723
1316
  const queued = await this.broker.call("telegramMcp.gateway.pollDeliveries", {
@@ -778,6 +1371,51 @@ const TelegramMcpGatewaySocketService = {
778
1371
  }
779
1372
  return;
780
1373
  }
1374
+ if (parsed.type === "live_stream_event") {
1375
+ const streamId = typeof parsed.stream_id === "string" ? parsed.stream_id.trim() : "";
1376
+ if (!streamId) {
1377
+ return;
1378
+ }
1379
+ const handler = this.liveStreamHandlers?.get(streamId);
1380
+ if (!handler) {
1381
+ return;
1382
+ }
1383
+ handler.onEvent(parsed);
1384
+ return;
1385
+ }
1386
+ if (parsed.type === "action_response") {
1387
+ const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
1388
+ if (!requestId) {
1389
+ return;
1390
+ }
1391
+ const pending = this.pendingActionRequests?.get(requestId);
1392
+ if (!pending) {
1393
+ return;
1394
+ }
1395
+ clearTimeout(pending.timeout);
1396
+ this.pendingActionRequests?.delete(requestId);
1397
+ runtime.logger.info("Gateway WS action response received", {
1398
+ requestId,
1399
+ clientUuid: pending.clientUuid,
1400
+ ok: parsed.ok === true,
1401
+ ...(parsed.ok === true
1402
+ ? {}
1403
+ : {
1404
+ error: typeof parsed.error === "string" && parsed.error.trim()
1405
+ ? parsed.error
1406
+ : "Remote action request failed",
1407
+ }),
1408
+ });
1409
+ if (parsed.ok === true) {
1410
+ pending.resolve(parsed.result);
1411
+ }
1412
+ else {
1413
+ pending.reject(new Error(typeof parsed.error === "string" && parsed.error.trim()
1414
+ ? parsed.error
1415
+ : "Remote action request failed"));
1416
+ }
1417
+ return;
1418
+ }
781
1419
  if (parsed.type === "delivery_ack" || parsed.type === "delivery_fail") {
782
1420
  const hello = this.connectedClients?.get(socket);
783
1421
  const clientUuid = hello?.client_uuid?.trim();
@@ -903,6 +1541,58 @@ const TelegramMcpGatewaySocketService = {
903
1541
  await runtime.telegramTransport.handleToolsUpdatedEvent(parsed.payload);
904
1542
  return;
905
1543
  }
1544
+ if (parsed.type === "action_request") {
1545
+ const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
1546
+ const actionName = typeof parsed.action_name === "string" ? parsed.action_name.trim() : "";
1547
+ if (!requestId || !actionName) {
1548
+ return;
1549
+ }
1550
+ runtime.logger.info("Gateway WS action request received on client", {
1551
+ requestId,
1552
+ actionName,
1553
+ sessionId: typeof parsed.payload?.session_id === "string"
1554
+ ? parsed.payload.session_id
1555
+ : null,
1556
+ });
1557
+ try {
1558
+ const result = await this.broker.call(actionName, parsed.payload ?? {}, { meta: { internal_call: true } });
1559
+ if (isBackendErrorLike(result)) {
1560
+ this.wsClient?.send(JSON.stringify({
1561
+ type: "action_response",
1562
+ request_id: requestId,
1563
+ ok: false,
1564
+ error: formatBackendErrorLike(result),
1565
+ }));
1566
+ return;
1567
+ }
1568
+ runtime.logger.info("Gateway WS action request completed on client", {
1569
+ requestId,
1570
+ actionName,
1571
+ });
1572
+ this.wsClient?.send(JSON.stringify({
1573
+ type: "action_response",
1574
+ request_id: requestId,
1575
+ ok: true,
1576
+ result,
1577
+ }));
1578
+ }
1579
+ catch (error) {
1580
+ const formattedError = formatRemoteActionError(error);
1581
+ runtime.logger.error("Gateway WS action request failed on client", {
1582
+ requestId,
1583
+ actionName,
1584
+ error: formattedError,
1585
+ payload: parsed.payload ?? {},
1586
+ });
1587
+ this.wsClient?.send(JSON.stringify({
1588
+ type: "action_response",
1589
+ request_id: requestId,
1590
+ ok: false,
1591
+ error: formattedError,
1592
+ }));
1593
+ }
1594
+ return;
1595
+ }
906
1596
  if (parsed.type === "project_event") {
907
1597
  if (parsed.event === "member_joined" &&
908
1598
  parsed.payload &&
@@ -937,6 +1627,12 @@ const TelegramMcpGatewaySocketService = {
937
1627
  return;
938
1628
  }
939
1629
  }
1630
+ if (parsed.type === "transport_event" && parsed.payload) {
1631
+ if (parsed.event === "request_reply") {
1632
+ await runtime.telegramTransport.handleGatewayTransportReplyEvent(parsed.payload);
1633
+ return;
1634
+ }
1635
+ }
940
1636
  if (parsed.type === "delivery_event") {
941
1637
  const delivery = parsed.payload;
942
1638
  if (!delivery) {
@@ -979,9 +1675,12 @@ const TelegramMcpGatewaySocketService = {
979
1675
  },
980
1676
  async requestLiveRelay(params) {
981
1677
  const runtime = this.getRuntimeOrThrow();
982
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1678
+ const socket = findConnectedSocketForSession(this, {
1679
+ clientUuid: params.clientUuid,
1680
+ localSessionId: params.localSessionId,
1681
+ });
983
1682
  if (!socket || socket.readyState !== 1) {
984
- throw new Error(`Gateway WS client '${params.clientUuid}' is not connected`);
1683
+ throw new Error(`Gateway WS console '${params.clientUuid}/${params.localSessionId}' is not connected`);
985
1684
  }
986
1685
  const requestId = (0, node_crypto_1.randomUUID)();
987
1686
  const response = await new Promise((resolve, reject) => {
@@ -1011,6 +1710,83 @@ const TelegramMcpGatewaySocketService = {
1011
1710
  });
1012
1711
  return response;
1013
1712
  },
1713
+ async openLiveRelayStream(params) {
1714
+ const streamId = (0, node_crypto_1.randomUUID)();
1715
+ this.liveStreamHandlers?.set(streamId, {
1716
+ clientUuid: params.clientUuid,
1717
+ onEvent: params.onEvent,
1718
+ });
1719
+ try {
1720
+ await this.requestLiveRelay?.({
1721
+ clientUuid: params.clientUuid,
1722
+ localSessionId: params.localSessionId,
1723
+ requestType: "stream_subscribe",
1724
+ payload: { stream_id: streamId },
1725
+ });
1726
+ }
1727
+ catch (error) {
1728
+ this.liveStreamHandlers?.delete(streamId);
1729
+ throw error;
1730
+ }
1731
+ return {
1732
+ streamId,
1733
+ close: async () => {
1734
+ this.liveStreamHandlers?.delete(streamId);
1735
+ try {
1736
+ await this.requestLiveRelay?.({
1737
+ clientUuid: params.clientUuid,
1738
+ localSessionId: params.localSessionId,
1739
+ requestType: "stream_unsubscribe",
1740
+ payload: { stream_id: streamId },
1741
+ });
1742
+ }
1743
+ catch {
1744
+ // best-effort unsubscribe during stream shutdown
1745
+ }
1746
+ },
1747
+ };
1748
+ },
1749
+ async requestClientAction(params) {
1750
+ const localSessionId = typeof params.payload?.session_id === "string" && params.payload.session_id.trim()
1751
+ ? params.payload.session_id.trim()
1752
+ : null;
1753
+ const socket = localSessionId
1754
+ ? findConnectedSocketForSession(this, {
1755
+ clientUuid: params.clientUuid,
1756
+ localSessionId,
1757
+ })
1758
+ : (this.connectedClientsByUuid?.get(params.clientUuid) ?? null);
1759
+ if (!socket || socket.readyState !== 1) {
1760
+ throw new Error(localSessionId
1761
+ ? `Gateway WS console '${params.clientUuid}/${localSessionId}' is not connected`
1762
+ : `Gateway WS client '${params.clientUuid}' is not connected`);
1763
+ }
1764
+ const requestId = (0, node_crypto_1.randomUUID)();
1765
+ return await new Promise((resolve, reject) => {
1766
+ const timeout = setTimeout(() => {
1767
+ this.pendingActionRequests?.delete(requestId);
1768
+ reject(new Error("Remote action WS request timed out"));
1769
+ }, LIVE_REQUEST_TIMEOUT_MS);
1770
+ this.pendingActionRequests?.set(requestId, {
1771
+ clientUuid: params.clientUuid,
1772
+ resolve,
1773
+ reject,
1774
+ timeout,
1775
+ });
1776
+ this.getRuntimeOrThrow().logger.info("Gateway WS action request sent", {
1777
+ requestId,
1778
+ clientUuid: params.clientUuid,
1779
+ actionName: params.actionName,
1780
+ localSessionId,
1781
+ });
1782
+ socket.send(JSON.stringify({
1783
+ type: "action_request",
1784
+ request_id: requestId,
1785
+ action_name: params.actionName,
1786
+ payload: params.payload ?? {},
1787
+ }));
1788
+ });
1789
+ },
1014
1790
  async notifyProjectMemberJoined(params) {
1015
1791
  const message = {
1016
1792
  type: "project_event",
@@ -1034,12 +1810,14 @@ const TelegramMcpGatewaySocketService = {
1034
1810
  delivered += 1;
1035
1811
  continue;
1036
1812
  }
1037
- const socket = this.connectedClientsByUuid?.get(clientUuid);
1038
- if (!socket || socket.readyState !== 1) {
1813
+ const sockets = listConnectedSocketsForClient(this, clientUuid);
1814
+ if (sockets.length === 0) {
1039
1815
  continue;
1040
1816
  }
1041
- socket.send(JSON.stringify(message));
1042
- delivered += 1;
1817
+ for (const socket of sockets) {
1818
+ socket.send(JSON.stringify(message));
1819
+ delivered += 1;
1820
+ }
1043
1821
  }
1044
1822
  return delivered;
1045
1823
  },
@@ -1066,12 +1844,14 @@ const TelegramMcpGatewaySocketService = {
1066
1844
  delivered += 1;
1067
1845
  continue;
1068
1846
  }
1069
- const socket = this.connectedClientsByUuid?.get(clientUuid);
1070
- if (!socket || socket.readyState !== 1) {
1847
+ const sockets = listConnectedSocketsForClient(this, clientUuid);
1848
+ if (sockets.length === 0) {
1071
1849
  continue;
1072
1850
  }
1073
- socket.send(JSON.stringify(message));
1074
- delivered += 1;
1851
+ for (const socket of sockets) {
1852
+ socket.send(JSON.stringify(message));
1853
+ delivered += 1;
1854
+ }
1075
1855
  }
1076
1856
  return delivered;
1077
1857
  },
@@ -1092,57 +1872,38 @@ const TelegramMcpGatewaySocketService = {
1092
1872
  delivered += 1;
1093
1873
  continue;
1094
1874
  }
1095
- const socket = this.connectedClientsByUuid?.get(clientUuid);
1096
- if (!socket || socket.readyState !== 1) {
1875
+ const sockets = listConnectedSocketsForClient(this, clientUuid);
1876
+ if (sockets.length === 0) {
1097
1877
  continue;
1098
1878
  }
1099
- socket.send(JSON.stringify(message));
1100
- delivered += 1;
1879
+ for (const socket of sockets) {
1880
+ socket.send(JSON.stringify(message));
1881
+ delivered += 1;
1882
+ }
1101
1883
  }
1102
1884
  return delivered;
1103
1885
  },
1104
1886
  async notifyLiveApprovalRequest(params) {
1105
- if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
1106
- const runtime = this.getRuntimeOrThrow();
1107
- await runtime.telegramTransport.handleLiveViewApprovalRequestEvent(params.payload);
1108
- return true;
1109
- }
1110
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1111
- if (!socket || socket.readyState !== 1) {
1112
- return false;
1113
- }
1114
- socket.send(JSON.stringify({
1115
- type: "live_event",
1116
- event: "approval_request",
1117
- payload: params.payload,
1118
- }));
1887
+ const runtime = this.getRuntimeOrThrow();
1888
+ await runtime.telegramTransport.handleLiveViewApprovalRequestEvent(params.payload);
1119
1889
  return true;
1120
1890
  },
1121
1891
  async notifyLiveApprovalResolved(params) {
1122
- if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
1123
- const runtime = this.getRuntimeOrThrow();
1124
- await runtime.telegramTransport.handleLiveViewApprovalResolvedEvent({
1125
- approved: params.approved,
1126
- ...params.payload,
1127
- });
1128
- return true;
1129
- }
1130
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1131
- if (!socket || socket.readyState !== 1) {
1132
- return false;
1133
- }
1134
- socket.send(JSON.stringify({
1135
- type: "live_event",
1136
- event: params.approved ? "approval_granted" : "approval_denied",
1137
- payload: params.payload,
1138
- }));
1892
+ const runtime = this.getRuntimeOrThrow();
1893
+ await runtime.telegramTransport.handleLiveViewApprovalResolvedEvent({
1894
+ approved: params.approved,
1895
+ ...params.payload,
1896
+ });
1139
1897
  return true;
1140
1898
  },
1141
1899
  async notifyDeliveryQueued(params) {
1142
1900
  if (await this.handleLocalIncomingDelivery?.(params)) {
1143
1901
  return true;
1144
1902
  }
1145
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1903
+ const socket = findConnectedSocketForSession(this, {
1904
+ clientUuid: params.clientUuid,
1905
+ localSessionId: params.delivery.target_local_session_id,
1906
+ });
1146
1907
  if (!socket || socket.readyState !== 1) {
1147
1908
  return false;
1148
1909
  }
@@ -1157,14 +1918,35 @@ const TelegramMcpGatewaySocketService = {
1157
1918
  if (await this.handleLocalDeliveryStatus?.(params)) {
1158
1919
  return true;
1159
1920
  }
1160
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1161
- if (!socket || socket.readyState !== 1) {
1921
+ const sockets = listConnectedSocketsForClient(this, params.clientUuid);
1922
+ if (sockets.length === 0) {
1162
1923
  return false;
1163
1924
  }
1164
- socket.send(JSON.stringify({
1165
- type: "delivery_status_event",
1166
- payload: params.status,
1167
- }));
1925
+ for (const socket of sockets) {
1926
+ socket.send(JSON.stringify({
1927
+ type: "delivery_status_event",
1928
+ payload: params.status,
1929
+ }));
1930
+ }
1931
+ return true;
1932
+ },
1933
+ async notifyTransportReply(params) {
1934
+ if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
1935
+ const runtime = this.getRuntimeOrThrow();
1936
+ await runtime.telegramTransport.handleGatewayTransportReplyEvent(params.payload);
1937
+ return true;
1938
+ }
1939
+ const sockets = listConnectedSocketsForClient(this, params.clientUuid);
1940
+ if (sockets.length === 0) {
1941
+ return false;
1942
+ }
1943
+ for (const socket of sockets) {
1944
+ socket.send(JSON.stringify({
1945
+ type: "transport_event",
1946
+ event: "request_reply",
1947
+ payload: params.payload,
1948
+ }));
1949
+ }
1168
1950
  return true;
1169
1951
  },
1170
1952
  async startGatewayWsServer() {
@@ -1172,7 +1954,7 @@ const TelegramMcpGatewaySocketService = {
1172
1954
  if (!runtime || this.wsServer) {
1173
1955
  return;
1174
1956
  }
1175
- const httpServer = this.getHttpServerOrThrow?.();
1957
+ const httpServer = await this.waitForHttpServer?.();
1176
1958
  const wsPath = runtime.config.distributed.gatewayWsPath.replace(/\/+$/u, "") || "/";
1177
1959
  const wsServer = new WebSocketServer({ noServer: true });
1178
1960
  wsServer.on("connection", (socket, req) => {
@@ -1197,8 +1979,38 @@ const TelegramMcpGatewaySocketService = {
1197
1979
  });
1198
1980
  socket.on("close", () => {
1199
1981
  const hello = this.connectedClients?.get(socket);
1982
+ if (hello?.connection_id) {
1983
+ void this.broker
1984
+ .call("telegramMcp.gateway.removeLiveConsoles", {
1985
+ connection_id: hello.connection_id,
1986
+ ...(hello.client_uuid ? { client_uuid: hello.client_uuid } : {}),
1987
+ }, { meta: { internal_call: true } })
1988
+ .catch((error) => {
1989
+ runtime.logger.warn("Failed to remove gateway live consoles on disconnect", {
1990
+ connectionId: hello.connection_id,
1991
+ clientUuid: hello?.client_uuid ?? null,
1992
+ error: error instanceof Error ? error.message : String(error),
1993
+ });
1994
+ });
1995
+ }
1200
1996
  if (hello?.client_uuid) {
1201
- this.connectedClientsByUuid?.delete(hello.client_uuid);
1997
+ if (this.connectedClientsByUuid?.get(hello.client_uuid) === socket) {
1998
+ const replacement = listConnectedSocketsForClient(this, hello.client_uuid).find((candidate) => candidate !== socket) ?? null;
1999
+ if (replacement) {
2000
+ this.connectedClientsByUuid?.set(hello.client_uuid, replacement);
2001
+ }
2002
+ else {
2003
+ this.connectedClientsByUuid?.delete(hello.client_uuid);
2004
+ }
2005
+ }
2006
+ for (const [streamId, handler] of this.liveStreamHandlers?.entries() ?? []) {
2007
+ if (handler.clientUuid === hello.client_uuid) {
2008
+ this.liveStreamHandlers?.delete(streamId);
2009
+ }
2010
+ }
2011
+ }
2012
+ if ((this.connectedClientsByUuid?.size ?? 0) === 0) {
2013
+ runtime.telegramTransport.pausePromptScan();
1202
2014
  }
1203
2015
  this.connectedClientToolsAlerts?.delete(socket);
1204
2016
  this.connectedClients?.delete(socket);
@@ -1426,6 +2238,16 @@ const TelegramMcpGatewaySocketService = {
1426
2238
  pending.reject(new Error("Gateway WS transport is shutting down"));
1427
2239
  }
1428
2240
  this.pendingLiveRequests?.clear();
2241
+ for (const pending of this.pendingActionRequests?.values() ?? []) {
2242
+ clearTimeout(pending.timeout);
2243
+ pending.reject(new Error("Gateway WS transport is shutting down"));
2244
+ }
2245
+ this.pendingActionRequests?.clear();
2246
+ for (const unsubscribe of this.localLiveStreamSubscriptions?.values() ?? []) {
2247
+ unsubscribe();
2248
+ }
2249
+ this.localLiveStreamSubscriptions?.clear();
2250
+ this.liveStreamHandlers?.clear();
1429
2251
  if (this.wsClient) {
1430
2252
  const socket = this.wsClient;
1431
2253
  this.wsClient = null;