@deadragdoll/tellymcp 0.0.10 → 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.
- package/.env.example.client +42 -52
- package/.env.example.gateway +48 -62
- package/CHANGELOG.md +3 -3
- package/README-ru.md +205 -384
- package/README.md +195 -1194
- package/TOOLS.md +294 -377
- package/VERSION.md +11 -11
- package/config/codex/plugins/telly-workflows/.codex-plugin/plugin.json +18 -0
- package/config/codex/plugins/telly-workflows/references/invariants.md +10 -0
- package/config/codex/plugins/telly-workflows/skills/telly-browser-screenshot/SKILL.md +27 -0
- package/config/codex/plugins/telly-workflows/skills/telly-collab-artifact/SKILL.md +22 -0
- package/config/codex/plugins/telly-workflows/skills/telly-human-telegram/SKILL.md +31 -0
- package/config/codex/plugins/telly-workflows/skills/telly-partner-note/SKILL.md +36 -0
- package/config/templates/env.both.template +39 -20
- package/config/templates/env.client.template +34 -19
- package/config/templates/env.gateway.template +37 -22
- package/dist/cli.js +322 -75
- package/dist/codexPluginInstaller.js +215 -0
- package/dist/lib/mixins/session.errors.js +34 -1
- package/dist/lib/pinoTargets.js +2 -2
- package/dist/moleculer.config.js +7 -5
- package/dist/services/features/telegram-mcp/browser.service.js +94 -1
- package/dist/services/features/telegram-mcp/collaboration.service.js +40 -3
- package/dist/services/features/telegram-mcp/ensuredb.service.js +145 -26
- package/dist/services/features/telegram-mcp/gateway-delivery.service.js +285 -103
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +897 -85
- package/dist/services/features/telegram-mcp/gateway.service.js +876 -81
- package/dist/services/features/telegram-mcp/mcp-http.service.js +8 -0
- package/dist/services/features/telegram-mcp/mcp-server.service.js +17 -26
- package/dist/services/features/telegram-mcp/notify.service.js +127 -1
- package/dist/services/features/telegram-mcp/session-context.service.js +28 -1
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +132 -10
- package/dist/services/features/telegram-mcp/src/app/config/env.js +158 -52
- package/dist/services/features/telegram-mcp/src/app/http.js +375 -42
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +386 -58
- package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +3 -0
- package/dist/services/features/telegram-mcp/src/app/webapp/terminal.js +12 -0
- package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +116 -104
- package/dist/services/features/telegram-mcp/src/entities/xchange/model/types.js +2 -0
- package/dist/services/features/telegram-mcp/src/features/ask-user/model/askUserTelegram.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserScreenshotTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +249 -19
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +71 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/listGatewaySessionsTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/localCollaborationBackend.js +93 -82
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +47 -5
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerNoteTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +82 -0
- package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +22 -14
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +649 -75
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +76 -0
- package/dist/services/features/telegram-mcp/src/features/embedded-runtime/model/embeddedRuntimeBroker.js +92 -0
- package/dist/services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js +192 -0
- package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +382 -3
- package/dist/services/features/telegram-mcp/src/features/notify/model/notifyTelegramTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/notify/model/sendFileToTelegramTool.js +33 -0
- package/dist/services/features/telegram-mcp/src/features/session-context/model/clearSessionContextTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/session-context/model/getSessionContextTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/session-context/model/renameSessionTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +42 -200
- package/dist/services/features/telegram-mcp/src/features/session-context/model/setSessionContextTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/terminal-buffer/model/terminalBufferService.js +96 -0
- package/dist/services/features/telegram-mcp/src/features/terminal-input/model/terminalInputService.js +97 -0
- package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownService.js +107 -58
- package/dist/services/features/telegram-mcp/src/features/tools-sync/model/refreshToolsMarkdownTool.js +1 -1
- package/dist/services/features/telegram-mcp/src/features/xchange/model/getXchangeRecordTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/xchange/model/listXchangeRecordsTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/xchange/model/markXchangeRecordReadTool.js +28 -0
- package/dist/services/features/telegram-mcp/src/features/xchange/model/xchangeService.js +169 -0
- package/dist/services/features/telegram-mcp/src/processes/human-approval/model/orchestrator.js +5 -4
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +139 -42
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +135 -38
- package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalSessionStore.js +27 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +11 -8
- package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +42 -71
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/proxyFetch.js +21 -22
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +415 -7101
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportAttachmentStore.js +93 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportBroadcastActions.js +385 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +143 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +633 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContent.js +84 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportContext.js +78 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportDocumentActions.js +36 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportEventActions.js +292 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +352 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFormatting.js +75 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportGatewayActions.js +44 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLifecycleActions.js +161 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLive.js +56 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportLiveActions.js +77 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuCallbacks.js +254 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFactories.js +538 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFingerprints.js +93 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuFlow.js +344 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuShell.js +62 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuState.js +408 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMenuText.js +216 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +452 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportOutputActions.js +189 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPartnerActions.js +286 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportPayloadState.js +101 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectActions.js +463 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEntryActions.js +202 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectEvents.js +99 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectMenus.js +138 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +308 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectView.js +426 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportRequestFlow.js +278 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportSessionActions.js +143 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalActions.js +468 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +171 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTypes.js +2 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportUtils.js +330 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportXchangeState.js +107 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +255 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +543 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/xchange/sqliteRecordStore.js +223 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +24 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +6 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +147 -76
- package/dist/services/features/telegram-mcp/src/shared/lib/telegramXchangeRecords.js +72 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/terminalPromptDetection.js +237 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/version/versionHandshake.js +129 -1
- package/dist/services/features/telegram-mcp/src/shared/lib/xchangeRecordHints.js +98 -0
- package/dist/services/features/telegram-mcp/standalone-http.service.js +12 -0
- package/dist/services/features/telegram-mcp/terminal-buffer.service.js +42 -0
- package/dist/services/features/telegram-mcp/terminal-input.service.js +41 -0
- package/dist/services/features/telegram-mcp/tools-sync.service.js +15 -1
- package/dist/services/features/telegram-mcp/xchange.service.js +123 -0
- package/docs/STANDALONE-ru.md +172 -0
- package/docs/STANDALONE.md +172 -0
- package/package.json +10 -5
- package/scripts/build-package-artifact.sh +27 -0
- package/scripts/deploy-gateway.sh +64 -0
- package/scripts/deploy-onebot-branch.sh +27 -0
- package/scripts/postinstall.js +11 -36
- package/STANDALONE-ru.md +0 -274
- package/STANDALONE.md +0 -274
- package/dist/services/features/telegram-mcp/inbox.service.js +0 -33
- package/dist/services/features/telegram-mcp/pair.service.js +0 -33
- package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +0 -10
- package/dist/services/features/telegram-mcp/src/features/inbox/model/deleteTelegramInboxMessageTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxCountTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/inbox/model/getTelegramInboxTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/inbox/model/inboxService.js +0 -77
- package/dist/services/features/telegram-mcp/src/features/pair-session/model/clearSessionPairingTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/pair-session/model/createSessionPairCodeTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/pair-session/model/generatePairCode.js +0 -202
- package/dist/services/features/telegram-mcp/src/features/session-context/model/getTmuxTargetTool.js +0 -33
- package/dist/services/features/telegram-mcp/src/features/session-context/model/setTmuxTargetTool.js +0 -33
- 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
|
|
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
|
|
34
|
-
if ((0,
|
|
35
|
-
return "
|
|
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: [
|
|
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
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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
|
-
...(
|
|
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
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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?.
|
|
538
|
-
throw new Error("
|
|
952
|
+
if (!session?.terminalTarget) {
|
|
953
|
+
throw new Error("terminal target is not configured for this session");
|
|
539
954
|
}
|
|
540
|
-
const
|
|
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?.
|
|
569
|
-
throw new Error("
|
|
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,
|
|
991
|
+
await (0, terminal_1.sendTerminalLiteralText)(runtime.config.terminal, session.terminalTarget, text);
|
|
573
992
|
}
|
|
574
993
|
else {
|
|
575
|
-
await (0,
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
1038
|
-
if (
|
|
1806
|
+
const sockets = listConnectedSocketsForClient(this, clientUuid);
|
|
1807
|
+
if (sockets.length === 0) {
|
|
1039
1808
|
continue;
|
|
1040
1809
|
}
|
|
1041
|
-
socket
|
|
1042
|
-
|
|
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
|
|
1070
|
-
if (
|
|
1840
|
+
const sockets = listConnectedSocketsForClient(this, clientUuid);
|
|
1841
|
+
if (sockets.length === 0) {
|
|
1071
1842
|
continue;
|
|
1072
1843
|
}
|
|
1073
|
-
socket
|
|
1074
|
-
|
|
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
|
|
1096
|
-
if (
|
|
1868
|
+
const sockets = listConnectedSocketsForClient(this, clientUuid);
|
|
1869
|
+
if (sockets.length === 0) {
|
|
1097
1870
|
continue;
|
|
1098
1871
|
}
|
|
1099
|
-
socket
|
|
1100
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
|
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
|
|
1161
|
-
if (
|
|
1914
|
+
const sockets = listConnectedSocketsForClient(this, params.clientUuid);
|
|
1915
|
+
if (sockets.length === 0) {
|
|
1162
1916
|
return false;
|
|
1163
1917
|
}
|
|
1164
|
-
socket
|
|
1165
|
-
|
|
1166
|
-
|
|
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.
|
|
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?.
|
|
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;
|