@deadragdoll/tellymcp 0.0.9 → 0.0.11

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 (157) hide show
  1. package/.env.example.client +42 -50
  2. package/.env.example.gateway +49 -61
  3. package/CHANGELOG.md +3 -3
  4. package/README-ru.md +205 -384
  5. package/README.md +195 -1194
  6. package/TOOLS.md +294 -377
  7. package/VERSION.md +36 -10
  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 +40 -20
  15. package/config/templates/env.client.template +34 -18
  16. package/config/templates/env.gateway.template +38 -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/approval.service.js +1 -1
  23. package/dist/services/features/telegram-mcp/browser.service.js +95 -2
  24. package/dist/services/features/telegram-mcp/collaboration.service.js +41 -4
  25. package/dist/services/features/telegram-mcp/ensuredb.service.js +145 -26
  26. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +285 -103
  27. package/dist/services/features/telegram-mcp/gateway-rmq.service.js +3 -2
  28. package/dist/services/features/telegram-mcp/gateway-socket.service.js +905 -87
  29. package/dist/services/features/telegram-mcp/gateway.service.js +876 -81
  30. package/dist/services/features/telegram-mcp/mcp-http.service.js +9 -1
  31. package/dist/services/features/telegram-mcp/mcp-server.service.js +17 -26
  32. package/dist/services/features/telegram-mcp/notify.service.js +128 -2
  33. package/dist/services/features/telegram-mcp/runtime.service.js +27 -7
  34. package/dist/services/features/telegram-mcp/session-context.service.js +29 -2
  35. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +133 -10
  36. package/dist/services/features/telegram-mcp/src/app/config/env.js +161 -51
  37. package/dist/services/features/telegram-mcp/src/app/http.js +375 -42
  38. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +386 -58
  39. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +3 -0
  40. package/dist/services/features/telegram-mcp/src/app/webapp/terminal.js +12 -0
  41. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +116 -104
  42. package/dist/services/features/telegram-mcp/src/entities/xchange/model/types.js +2 -0
  43. package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +1 -1
  44. package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
  45. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +249 -19
  46. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +71 -0
  47. package/dist/services/features/telegram-mcp/src/features/collaboration/model/listGatewaySessionsTool.js +33 -0
  48. package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +93 -82
  49. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +47 -5
  50. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +1 -1
  51. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +1 -1
  52. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +82 -0
  53. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +22 -14
  54. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +649 -75
  55. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +76 -0
  56. package/dist/services/features/telegram-mcp/src/features/embedded-runtime/model/embeddedRuntimeBroker.js +92 -0
  57. package/dist/services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js +192 -0
  58. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +382 -3
  59. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +1 -1
  60. package/dist/services/features/telegram-mcp/src/features/notify/model/sendFileToTelegramTool.js +33 -0
  61. package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +1 -1
  62. package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +1 -1
  63. package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +1 -1
  64. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +42 -200
  65. package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +1 -1
  66. package/dist/services/features/telegram-mcp/src/features/terminal-buffer/model/terminalBufferService.js +96 -0
  67. package/dist/services/features/telegram-mcp/src/features/terminal-input/model/terminalInputService.js +97 -0
  68. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +107 -58
  69. package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +1 -1
  70. package/dist/services/features/telegram-mcp/src/features/xchange/model/getXchangeRecordTool.js +28 -0
  71. package/dist/services/features/telegram-mcp/src/features/xchange/model/listXchangeRecordsTool.js +28 -0
  72. package/dist/services/features/telegram-mcp/src/features/xchange/model/markXchangeRecordReadTool.js +28 -0
  73. package/dist/services/features/telegram-mcp/src/features/xchange/model/xchangeService.js +169 -0
  74. package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +5 -4
  75. package/dist/services/features/telegram-mcp/src/shared/i18n/index.js +46 -0
  76. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +652 -0
  77. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +652 -0
  78. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalSessionStore.js +27 -0
  79. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +11 -8
  80. package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +51 -71
  81. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +21 -22
  82. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +411 -6524
  83. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportAttachmentStore.js +93 -0
  84. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportBroadcastActions.js +385 -0
  85. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +143 -0
  86. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +633 -0
  87. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContent.js +84 -0
  88. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContext.js +78 -0
  89. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportDocumentActions.js +36 -0
  90. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportEventActions.js +292 -0
  91. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +352 -0
  92. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFormatting.js +75 -0
  93. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportGatewayActions.js +44 -0
  94. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLifecycleActions.js +161 -0
  95. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLive.js +56 -0
  96. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLiveActions.js +77 -0
  97. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuCallbacks.js +254 -0
  98. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFactories.js +538 -0
  99. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFingerprints.js +93 -0
  100. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFlow.js +344 -0
  101. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +62 -0
  102. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuState.js +408 -0
  103. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuText.js +216 -0
  104. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +452 -0
  105. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportOutputActions.js +189 -0
  106. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPartnerActions.js +286 -0
  107. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +101 -0
  108. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectActions.js +463 -0
  109. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEntryActions.js +202 -0
  110. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEvents.js +99 -0
  111. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectMenus.js +138 -0
  112. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +308 -0
  113. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectView.js +426 -0
  114. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportRequestFlow.js +278 -0
  115. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportSessionActions.js +143 -0
  116. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +468 -0
  117. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +171 -0
  118. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTypes.js +2 -0
  119. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportUtils.js +330 -0
  120. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportXchangeState.js +107 -0
  121. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +255 -0
  122. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +543 -0
  123. package/dist/services/features/telegram-mcp/src/shared/integrations/xchange/sqliteRecordStore.js +223 -0
  124. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +24 -0
  125. package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +6 -0
  126. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +147 -76
  127. package/dist/services/features/telegram-mcp/src/shared/lib/telegramXchangeRecords.js +72 -0
  128. package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +237 -0
  129. package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +129 -1
  130. package/dist/services/features/telegram-mcp/src/shared/lib/xchangeRecordHints.js +98 -0
  131. package/dist/services/features/telegram-mcp/standalone-http.service.js +13 -1
  132. package/dist/services/features/telegram-mcp/terminal-buffer.service.js +42 -0
  133. package/dist/services/features/telegram-mcp/terminal-input.service.js +41 -0
  134. package/dist/services/features/telegram-mcp/tools-sync.service.js +16 -2
  135. package/dist/services/features/telegram-mcp/xchange.service.js +123 -0
  136. package/docs/STANDALONE-ru.md +172 -0
  137. package/docs/STANDALONE.md +172 -0
  138. package/package.json +11 -5
  139. package/scripts/build-package-artifact.sh +27 -0
  140. package/scripts/deploy-gateway.sh +64 -0
  141. package/scripts/deploy-onebot-branch.sh +27 -0
  142. package/scripts/postinstall.js +11 -36
  143. package/STANDALONE-ru.md +0 -274
  144. package/STANDALONE.md +0 -274
  145. package/dist/services/features/telegram-mcp/inbox.service.js +0 -33
  146. package/dist/services/features/telegram-mcp/pair.service.js +0 -33
  147. package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +0 -10
  148. package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +0 -33
  149. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +0 -33
  150. package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +0 -33
  151. package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +0 -77
  152. package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +0 -33
  153. package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +0 -33
  154. package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +0 -202
  155. package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +0 -33
  156. package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +0 -33
  157. 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,14 @@ 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);
576
995
  }
577
996
  return {
578
997
  type: "live_response",
@@ -583,6 +1002,89 @@ const TelegramMcpGatewaySocketService = {
583
1002
  },
584
1003
  };
585
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);
1077
+ }
1078
+ return {
1079
+ type: "live_response",
1080
+ request_id: request.request_id,
1081
+ ok: true,
1082
+ result: {
1083
+ ok: true,
1084
+ ...(streamId ? { stream_id: streamId } : {}),
1085
+ },
1086
+ };
1087
+ }
586
1088
  throw new Error(`Unsupported live request type '${request.request_type}'`);
587
1089
  }
588
1090
  catch (error) {
@@ -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,39 @@ 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
+ }
672
1230
  const localVersionInfo = this.getLocalVersionInfo?.() ?? {
673
1231
  packageVersion: "0.0.0-unknown",
674
1232
  protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
@@ -718,6 +1276,34 @@ const TelegramMcpGatewaySocketService = {
718
1276
  }, 50);
719
1277
  return;
720
1278
  }
1279
+ if (hello.client_uuid) {
1280
+ const previousSessionIds = new Set(Array.isArray(previousHelloForClient?.session_tools)
1281
+ ? previousHelloForClient.session_tools
1282
+ .map((session) => session.local_session_id)
1283
+ .filter((sessionId) => typeof sessionId === "string" && sessionId.trim().length > 0)
1284
+ : []);
1285
+ const currentSessions = Array.isArray(hello.session_tools)
1286
+ ? hello.session_tools.filter((session) => typeof session?.local_session_id === "string" &&
1287
+ session.local_session_id.trim().length > 0)
1288
+ : [];
1289
+ const newSessions = currentSessions.filter((session) => !previousSessionIds.has(session.local_session_id));
1290
+ const isNewClient = !previousHelloForClient;
1291
+ if (isNewClient || newSessions.length > 0) {
1292
+ await runtime.telegramTransport.sendAdminGatewayRegistrationNotifications({
1293
+ clientUuid: hello.client_uuid,
1294
+ ...(hello.gateway_user_uuid
1295
+ ? { gatewayUserUuid: hello.gateway_user_uuid }
1296
+ : {}),
1297
+ ...(hello.node_id ? { nodeId: hello.node_id } : {}),
1298
+ ...(hello.package_version
1299
+ ? { packageVersion: hello.package_version }
1300
+ : {}),
1301
+ totalSessions: currentSessions.length,
1302
+ isNewClient,
1303
+ newSessions,
1304
+ });
1305
+ }
1306
+ }
721
1307
  await this.notifyToolsMismatchForSocket?.(socket, hello);
722
1308
  if (hello.client_uuid) {
723
1309
  const queued = await this.broker.call("telegramMcp.gateway.pollDeliveries", {
@@ -778,6 +1364,51 @@ const TelegramMcpGatewaySocketService = {
778
1364
  }
779
1365
  return;
780
1366
  }
1367
+ if (parsed.type === "live_stream_event") {
1368
+ const streamId = typeof parsed.stream_id === "string" ? parsed.stream_id.trim() : "";
1369
+ if (!streamId) {
1370
+ return;
1371
+ }
1372
+ const handler = this.liveStreamHandlers?.get(streamId);
1373
+ if (!handler) {
1374
+ return;
1375
+ }
1376
+ handler.onEvent(parsed);
1377
+ return;
1378
+ }
1379
+ if (parsed.type === "action_response") {
1380
+ const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
1381
+ if (!requestId) {
1382
+ return;
1383
+ }
1384
+ const pending = this.pendingActionRequests?.get(requestId);
1385
+ if (!pending) {
1386
+ return;
1387
+ }
1388
+ clearTimeout(pending.timeout);
1389
+ this.pendingActionRequests?.delete(requestId);
1390
+ runtime.logger.info("Gateway WS action response received", {
1391
+ requestId,
1392
+ clientUuid: pending.clientUuid,
1393
+ ok: parsed.ok === true,
1394
+ ...(parsed.ok === true
1395
+ ? {}
1396
+ : {
1397
+ error: typeof parsed.error === "string" && parsed.error.trim()
1398
+ ? parsed.error
1399
+ : "Remote action request failed",
1400
+ }),
1401
+ });
1402
+ if (parsed.ok === true) {
1403
+ pending.resolve(parsed.result);
1404
+ }
1405
+ else {
1406
+ pending.reject(new Error(typeof parsed.error === "string" && parsed.error.trim()
1407
+ ? parsed.error
1408
+ : "Remote action request failed"));
1409
+ }
1410
+ return;
1411
+ }
781
1412
  if (parsed.type === "delivery_ack" || parsed.type === "delivery_fail") {
782
1413
  const hello = this.connectedClients?.get(socket);
783
1414
  const clientUuid = hello?.client_uuid?.trim();
@@ -903,6 +1534,58 @@ const TelegramMcpGatewaySocketService = {
903
1534
  await runtime.telegramTransport.handleToolsUpdatedEvent(parsed.payload);
904
1535
  return;
905
1536
  }
1537
+ if (parsed.type === "action_request") {
1538
+ const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
1539
+ const actionName = typeof parsed.action_name === "string" ? parsed.action_name.trim() : "";
1540
+ if (!requestId || !actionName) {
1541
+ return;
1542
+ }
1543
+ runtime.logger.info("Gateway WS action request received on client", {
1544
+ requestId,
1545
+ actionName,
1546
+ sessionId: typeof parsed.payload?.session_id === "string"
1547
+ ? parsed.payload.session_id
1548
+ : null,
1549
+ });
1550
+ try {
1551
+ const result = await this.broker.call(actionName, parsed.payload ?? {}, { meta: { internal_call: true } });
1552
+ if (isBackendErrorLike(result)) {
1553
+ this.wsClient?.send(JSON.stringify({
1554
+ type: "action_response",
1555
+ request_id: requestId,
1556
+ ok: false,
1557
+ error: formatBackendErrorLike(result),
1558
+ }));
1559
+ return;
1560
+ }
1561
+ runtime.logger.info("Gateway WS action request completed on client", {
1562
+ requestId,
1563
+ actionName,
1564
+ });
1565
+ this.wsClient?.send(JSON.stringify({
1566
+ type: "action_response",
1567
+ request_id: requestId,
1568
+ ok: true,
1569
+ result,
1570
+ }));
1571
+ }
1572
+ catch (error) {
1573
+ const formattedError = formatRemoteActionError(error);
1574
+ runtime.logger.error("Gateway WS action request failed on client", {
1575
+ requestId,
1576
+ actionName,
1577
+ error: formattedError,
1578
+ payload: parsed.payload ?? {},
1579
+ });
1580
+ this.wsClient?.send(JSON.stringify({
1581
+ type: "action_response",
1582
+ request_id: requestId,
1583
+ ok: false,
1584
+ error: formattedError,
1585
+ }));
1586
+ }
1587
+ return;
1588
+ }
906
1589
  if (parsed.type === "project_event") {
907
1590
  if (parsed.event === "member_joined" &&
908
1591
  parsed.payload &&
@@ -937,6 +1620,12 @@ const TelegramMcpGatewaySocketService = {
937
1620
  return;
938
1621
  }
939
1622
  }
1623
+ if (parsed.type === "transport_event" && parsed.payload) {
1624
+ if (parsed.event === "request_reply") {
1625
+ await runtime.telegramTransport.handleGatewayTransportReplyEvent(parsed.payload);
1626
+ return;
1627
+ }
1628
+ }
940
1629
  if (parsed.type === "delivery_event") {
941
1630
  const delivery = parsed.payload;
942
1631
  if (!delivery) {
@@ -979,9 +1668,12 @@ const TelegramMcpGatewaySocketService = {
979
1668
  },
980
1669
  async requestLiveRelay(params) {
981
1670
  const runtime = this.getRuntimeOrThrow();
982
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1671
+ const socket = findConnectedSocketForSession(this, {
1672
+ clientUuid: params.clientUuid,
1673
+ localSessionId: params.localSessionId,
1674
+ });
983
1675
  if (!socket || socket.readyState !== 1) {
984
- throw new Error(`Gateway WS client '${params.clientUuid}' is not connected`);
1676
+ throw new Error(`Gateway WS console '${params.clientUuid}/${params.localSessionId}' is not connected`);
985
1677
  }
986
1678
  const requestId = (0, node_crypto_1.randomUUID)();
987
1679
  const response = await new Promise((resolve, reject) => {
@@ -1011,6 +1703,83 @@ const TelegramMcpGatewaySocketService = {
1011
1703
  });
1012
1704
  return response;
1013
1705
  },
1706
+ async openLiveRelayStream(params) {
1707
+ const streamId = (0, node_crypto_1.randomUUID)();
1708
+ this.liveStreamHandlers?.set(streamId, {
1709
+ clientUuid: params.clientUuid,
1710
+ onEvent: params.onEvent,
1711
+ });
1712
+ try {
1713
+ await this.requestLiveRelay?.({
1714
+ clientUuid: params.clientUuid,
1715
+ localSessionId: params.localSessionId,
1716
+ requestType: "stream_subscribe",
1717
+ payload: { stream_id: streamId },
1718
+ });
1719
+ }
1720
+ catch (error) {
1721
+ this.liveStreamHandlers?.delete(streamId);
1722
+ throw error;
1723
+ }
1724
+ return {
1725
+ streamId,
1726
+ close: async () => {
1727
+ this.liveStreamHandlers?.delete(streamId);
1728
+ try {
1729
+ await this.requestLiveRelay?.({
1730
+ clientUuid: params.clientUuid,
1731
+ localSessionId: params.localSessionId,
1732
+ requestType: "stream_unsubscribe",
1733
+ payload: { stream_id: streamId },
1734
+ });
1735
+ }
1736
+ catch {
1737
+ // best-effort unsubscribe during stream shutdown
1738
+ }
1739
+ },
1740
+ };
1741
+ },
1742
+ async requestClientAction(params) {
1743
+ const localSessionId = typeof params.payload?.session_id === "string" && params.payload.session_id.trim()
1744
+ ? params.payload.session_id.trim()
1745
+ : null;
1746
+ const socket = localSessionId
1747
+ ? findConnectedSocketForSession(this, {
1748
+ clientUuid: params.clientUuid,
1749
+ localSessionId,
1750
+ })
1751
+ : (this.connectedClientsByUuid?.get(params.clientUuid) ?? null);
1752
+ if (!socket || socket.readyState !== 1) {
1753
+ throw new Error(localSessionId
1754
+ ? `Gateway WS console '${params.clientUuid}/${localSessionId}' is not connected`
1755
+ : `Gateway WS client '${params.clientUuid}' is not connected`);
1756
+ }
1757
+ const requestId = (0, node_crypto_1.randomUUID)();
1758
+ return await new Promise((resolve, reject) => {
1759
+ const timeout = setTimeout(() => {
1760
+ this.pendingActionRequests?.delete(requestId);
1761
+ reject(new Error("Remote action WS request timed out"));
1762
+ }, LIVE_REQUEST_TIMEOUT_MS);
1763
+ this.pendingActionRequests?.set(requestId, {
1764
+ clientUuid: params.clientUuid,
1765
+ resolve,
1766
+ reject,
1767
+ timeout,
1768
+ });
1769
+ this.getRuntimeOrThrow().logger.info("Gateway WS action request sent", {
1770
+ requestId,
1771
+ clientUuid: params.clientUuid,
1772
+ actionName: params.actionName,
1773
+ localSessionId,
1774
+ });
1775
+ socket.send(JSON.stringify({
1776
+ type: "action_request",
1777
+ request_id: requestId,
1778
+ action_name: params.actionName,
1779
+ payload: params.payload ?? {},
1780
+ }));
1781
+ });
1782
+ },
1014
1783
  async notifyProjectMemberJoined(params) {
1015
1784
  const message = {
1016
1785
  type: "project_event",
@@ -1034,12 +1803,14 @@ const TelegramMcpGatewaySocketService = {
1034
1803
  delivered += 1;
1035
1804
  continue;
1036
1805
  }
1037
- const socket = this.connectedClientsByUuid?.get(clientUuid);
1038
- if (!socket || socket.readyState !== 1) {
1806
+ const sockets = listConnectedSocketsForClient(this, clientUuid);
1807
+ if (sockets.length === 0) {
1039
1808
  continue;
1040
1809
  }
1041
- socket.send(JSON.stringify(message));
1042
- delivered += 1;
1810
+ for (const socket of sockets) {
1811
+ socket.send(JSON.stringify(message));
1812
+ delivered += 1;
1813
+ }
1043
1814
  }
1044
1815
  return delivered;
1045
1816
  },
@@ -1066,12 +1837,14 @@ const TelegramMcpGatewaySocketService = {
1066
1837
  delivered += 1;
1067
1838
  continue;
1068
1839
  }
1069
- const socket = this.connectedClientsByUuid?.get(clientUuid);
1070
- if (!socket || socket.readyState !== 1) {
1840
+ const sockets = listConnectedSocketsForClient(this, clientUuid);
1841
+ if (sockets.length === 0) {
1071
1842
  continue;
1072
1843
  }
1073
- socket.send(JSON.stringify(message));
1074
- delivered += 1;
1844
+ for (const socket of sockets) {
1845
+ socket.send(JSON.stringify(message));
1846
+ delivered += 1;
1847
+ }
1075
1848
  }
1076
1849
  return delivered;
1077
1850
  },
@@ -1092,57 +1865,38 @@ const TelegramMcpGatewaySocketService = {
1092
1865
  delivered += 1;
1093
1866
  continue;
1094
1867
  }
1095
- const socket = this.connectedClientsByUuid?.get(clientUuid);
1096
- if (!socket || socket.readyState !== 1) {
1868
+ const sockets = listConnectedSocketsForClient(this, clientUuid);
1869
+ if (sockets.length === 0) {
1097
1870
  continue;
1098
1871
  }
1099
- socket.send(JSON.stringify(message));
1100
- delivered += 1;
1872
+ for (const socket of sockets) {
1873
+ socket.send(JSON.stringify(message));
1874
+ delivered += 1;
1875
+ }
1101
1876
  }
1102
1877
  return delivered;
1103
1878
  },
1104
1879
  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
- }));
1880
+ const runtime = this.getRuntimeOrThrow();
1881
+ await runtime.telegramTransport.handleLiveViewApprovalRequestEvent(params.payload);
1119
1882
  return true;
1120
1883
  },
1121
1884
  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
- }));
1885
+ const runtime = this.getRuntimeOrThrow();
1886
+ await runtime.telegramTransport.handleLiveViewApprovalResolvedEvent({
1887
+ approved: params.approved,
1888
+ ...params.payload,
1889
+ });
1139
1890
  return true;
1140
1891
  },
1141
1892
  async notifyDeliveryQueued(params) {
1142
1893
  if (await this.handleLocalIncomingDelivery?.(params)) {
1143
1894
  return true;
1144
1895
  }
1145
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1896
+ const socket = findConnectedSocketForSession(this, {
1897
+ clientUuid: params.clientUuid,
1898
+ localSessionId: params.delivery.target_local_session_id,
1899
+ });
1146
1900
  if (!socket || socket.readyState !== 1) {
1147
1901
  return false;
1148
1902
  }
@@ -1157,14 +1911,35 @@ const TelegramMcpGatewaySocketService = {
1157
1911
  if (await this.handleLocalDeliveryStatus?.(params)) {
1158
1912
  return true;
1159
1913
  }
1160
- const socket = this.connectedClientsByUuid?.get(params.clientUuid);
1161
- if (!socket || socket.readyState !== 1) {
1914
+ const sockets = listConnectedSocketsForClient(this, params.clientUuid);
1915
+ if (sockets.length === 0) {
1162
1916
  return false;
1163
1917
  }
1164
- socket.send(JSON.stringify({
1165
- type: "delivery_status_event",
1166
- payload: params.status,
1167
- }));
1918
+ for (const socket of sockets) {
1919
+ socket.send(JSON.stringify({
1920
+ type: "delivery_status_event",
1921
+ payload: params.status,
1922
+ }));
1923
+ }
1924
+ return true;
1925
+ },
1926
+ async notifyTransportReply(params) {
1927
+ if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
1928
+ const runtime = this.getRuntimeOrThrow();
1929
+ await runtime.telegramTransport.handleGatewayTransportReplyEvent(params.payload);
1930
+ return true;
1931
+ }
1932
+ const sockets = listConnectedSocketsForClient(this, params.clientUuid);
1933
+ if (sockets.length === 0) {
1934
+ return false;
1935
+ }
1936
+ for (const socket of sockets) {
1937
+ socket.send(JSON.stringify({
1938
+ type: "transport_event",
1939
+ event: "request_reply",
1940
+ payload: params.payload,
1941
+ }));
1942
+ }
1168
1943
  return true;
1169
1944
  },
1170
1945
  async startGatewayWsServer() {
@@ -1172,7 +1947,7 @@ const TelegramMcpGatewaySocketService = {
1172
1947
  if (!runtime || this.wsServer) {
1173
1948
  return;
1174
1949
  }
1175
- const httpServer = this.getHttpServerOrThrow?.();
1950
+ const httpServer = await this.waitForHttpServer?.();
1176
1951
  const wsPath = runtime.config.distributed.gatewayWsPath.replace(/\/+$/u, "") || "/";
1177
1952
  const wsServer = new WebSocketServer({ noServer: true });
1178
1953
  wsServer.on("connection", (socket, req) => {
@@ -1197,8 +1972,35 @@ const TelegramMcpGatewaySocketService = {
1197
1972
  });
1198
1973
  socket.on("close", () => {
1199
1974
  const hello = this.connectedClients?.get(socket);
1975
+ if (hello?.connection_id) {
1976
+ void this.broker
1977
+ .call("telegramMcp.gateway.removeLiveConsoles", {
1978
+ connection_id: hello.connection_id,
1979
+ ...(hello.client_uuid ? { client_uuid: hello.client_uuid } : {}),
1980
+ }, { meta: { internal_call: true } })
1981
+ .catch((error) => {
1982
+ runtime.logger.warn("Failed to remove gateway live consoles on disconnect", {
1983
+ connectionId: hello.connection_id,
1984
+ clientUuid: hello?.client_uuid ?? null,
1985
+ error: error instanceof Error ? error.message : String(error),
1986
+ });
1987
+ });
1988
+ }
1200
1989
  if (hello?.client_uuid) {
1201
- this.connectedClientsByUuid?.delete(hello.client_uuid);
1990
+ if (this.connectedClientsByUuid?.get(hello.client_uuid) === socket) {
1991
+ const replacement = listConnectedSocketsForClient(this, hello.client_uuid).find((candidate) => candidate !== socket) ?? null;
1992
+ if (replacement) {
1993
+ this.connectedClientsByUuid?.set(hello.client_uuid, replacement);
1994
+ }
1995
+ else {
1996
+ this.connectedClientsByUuid?.delete(hello.client_uuid);
1997
+ }
1998
+ }
1999
+ for (const [streamId, handler] of this.liveStreamHandlers?.entries() ?? []) {
2000
+ if (handler.clientUuid === hello.client_uuid) {
2001
+ this.liveStreamHandlers?.delete(streamId);
2002
+ }
2003
+ }
1202
2004
  }
1203
2005
  this.connectedClientToolsAlerts?.delete(socket);
1204
2006
  this.connectedClients?.delete(socket);
@@ -1426,6 +2228,16 @@ const TelegramMcpGatewaySocketService = {
1426
2228
  pending.reject(new Error("Gateway WS transport is shutting down"));
1427
2229
  }
1428
2230
  this.pendingLiveRequests?.clear();
2231
+ for (const pending of this.pendingActionRequests?.values() ?? []) {
2232
+ clearTimeout(pending.timeout);
2233
+ pending.reject(new Error("Gateway WS transport is shutting down"));
2234
+ }
2235
+ this.pendingActionRequests?.clear();
2236
+ for (const unsubscribe of this.localLiveStreamSubscriptions?.values() ?? []) {
2237
+ unsubscribe();
2238
+ }
2239
+ this.localLiveStreamSubscriptions?.clear();
2240
+ this.liveStreamHandlers?.clear();
1429
2241
  if (this.wsClient) {
1430
2242
  const socket = this.wsClient;
1431
2243
  this.wsClient = null;
@@ -1455,8 +2267,14 @@ const TelegramMcpGatewaySocketService = {
1455
2267
  },
1456
2268
  },
1457
2269
  async started() {
1458
- const runtime = this.getRuntimeOrThrow?.();
1459
- const mode = runtime?.config.distributed.mode;
2270
+ await this.broker.waitForServices([runtime_service_1.TELEGRAM_MCP_RUNTIME_SERVICE_NAME]);
2271
+ const runtimeService = this.broker.getLocalService(runtime_service_1.TELEGRAM_MCP_RUNTIME_SERVICE_NAME);
2272
+ if (!runtimeService) {
2273
+ throw new Error(`Local Moleculer service '${runtime_service_1.TELEGRAM_MCP_RUNTIME_SERVICE_NAME}' is unavailable`);
2274
+ }
2275
+ this.runtimeService = runtimeService;
2276
+ const runtime = await runtimeService.waitUntilReady();
2277
+ const mode = runtime.config.distributed.mode;
1460
2278
  const gatewayEnabled = mode === "gateway" || mode === "both";
1461
2279
  const clientEnabled = mode === "client" || mode === "both";
1462
2280
  if (gatewayEnabled) {