@deadragdoll/tellymcp 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example.client +42 -52
- package/.env.example.gateway +48 -62
- package/CHANGELOG.md +3 -3
- package/README-ru.md +226 -376
- package/README.md +213 -1184
- package/TOOLS.md +320 -377
- package/VERSION.md +13 -13
- 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 +907 -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 +120 -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/browserOpenTool.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 +306 -26
- 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 +697 -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 +143 -42
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +139 -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 +69 -70
- 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 +444 -7093
- 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 +149 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConstructorWiring.js +642 -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 +65 -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 +108 -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 +665 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportTerminalRuntime.js +226 -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 +409 -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,96 @@ const TelegramMcpGatewaySocketService = {
|
|
|
565
984
|
}
|
|
566
985
|
const sessionId = request.local_session_id.trim();
|
|
567
986
|
const session = await runtime.sessionStore.getSession(sessionId);
|
|
568
|
-
if (!session?.
|
|
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);
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
type: "live_response",
|
|
998
|
+
request_id: request.request_id,
|
|
999
|
+
ok: true,
|
|
1000
|
+
result: {
|
|
1001
|
+
ok: true,
|
|
1002
|
+
},
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
if (request.request_type === "stream_subscribe") {
|
|
1006
|
+
const streamId = typeof request.payload?.stream_id === "string"
|
|
1007
|
+
? request.payload.stream_id.trim()
|
|
1008
|
+
: "";
|
|
1009
|
+
if (!streamId) {
|
|
1010
|
+
throw new Error("stream_id is required");
|
|
1011
|
+
}
|
|
1012
|
+
const sessionId = request.local_session_id.trim();
|
|
1013
|
+
const session = await runtime.sessionStore.getSession(sessionId);
|
|
1014
|
+
if (!session?.terminalTarget) {
|
|
1015
|
+
throw new Error("terminal target is not configured for this session");
|
|
1016
|
+
}
|
|
1017
|
+
if (!(0, client_1.isStreamableTerminalTarget)(session.terminalTarget)) {
|
|
1018
|
+
throw new Error("Live stream is supported only for PTY-backed terminals");
|
|
1019
|
+
}
|
|
1020
|
+
this.localLiveStreamSubscriptions?.get(streamId)?.();
|
|
1021
|
+
this.localLiveStreamSubscriptions?.delete(streamId);
|
|
1022
|
+
const terminalSize = await (0, terminal_1.getTerminalWindowSize)(runtime.config.terminal, session.terminalTarget);
|
|
1023
|
+
const content = await (0, terminal_1.captureVisibleTerminal)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
|
|
1024
|
+
const ansi = await (0, terminal_1.captureVisibleTerminalAnsi)(runtime.config.terminal, session.terminalTarget, runtime.config.terminal.captureLines, runtime.config.webapp.visibleScreens);
|
|
1025
|
+
this.wsClient?.send(JSON.stringify({
|
|
1026
|
+
type: "live_stream_event",
|
|
1027
|
+
stream_id: streamId,
|
|
1028
|
+
event: "snapshot",
|
|
1029
|
+
payload: {
|
|
1030
|
+
session_id: session.sessionId,
|
|
1031
|
+
session_label: session.label ?? null,
|
|
1032
|
+
captured_at: new Date().toISOString(),
|
|
1033
|
+
content,
|
|
1034
|
+
ansi,
|
|
1035
|
+
...(terminalSize ? terminalSize : {}),
|
|
1036
|
+
},
|
|
1037
|
+
}));
|
|
1038
|
+
const unsubscribe = (0, client_1.subscribeForegroundTerminal)(session.terminalTarget, {
|
|
1039
|
+
onData: (data) => {
|
|
1040
|
+
this.wsClient?.send(JSON.stringify({
|
|
1041
|
+
type: "live_stream_event",
|
|
1042
|
+
stream_id: streamId,
|
|
1043
|
+
event: "data",
|
|
1044
|
+
payload: { data },
|
|
1045
|
+
}));
|
|
1046
|
+
},
|
|
1047
|
+
onExit: (info) => {
|
|
1048
|
+
this.wsClient?.send(JSON.stringify({
|
|
1049
|
+
type: "live_stream_event",
|
|
1050
|
+
stream_id: streamId,
|
|
1051
|
+
event: "exit",
|
|
1052
|
+
payload: {
|
|
1053
|
+
exitCode: typeof info.exitCode === "number" ? info.exitCode : null,
|
|
1054
|
+
signal: typeof info.signal === "number" ? info.signal : null,
|
|
1055
|
+
},
|
|
1056
|
+
}));
|
|
1057
|
+
},
|
|
1058
|
+
});
|
|
1059
|
+
this.localLiveStreamSubscriptions?.set(streamId, unsubscribe);
|
|
1060
|
+
return {
|
|
1061
|
+
type: "live_response",
|
|
1062
|
+
request_id: request.request_id,
|
|
1063
|
+
ok: true,
|
|
1064
|
+
result: {
|
|
1065
|
+
ok: true,
|
|
1066
|
+
stream_id: streamId,
|
|
1067
|
+
},
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
if (request.request_type === "stream_unsubscribe") {
|
|
1071
|
+
const streamId = typeof request.payload?.stream_id === "string"
|
|
1072
|
+
? request.payload.stream_id.trim()
|
|
1073
|
+
: "";
|
|
1074
|
+
if (streamId) {
|
|
1075
|
+
this.localLiveStreamSubscriptions?.get(streamId)?.();
|
|
1076
|
+
this.localLiveStreamSubscriptions?.delete(streamId);
|
|
576
1077
|
}
|
|
577
1078
|
return {
|
|
578
1079
|
type: "live_response",
|
|
@@ -580,6 +1081,7 @@ const TelegramMcpGatewaySocketService = {
|
|
|
580
1081
|
ok: true,
|
|
581
1082
|
result: {
|
|
582
1083
|
ok: true,
|
|
1084
|
+
...(streamId ? { stream_id: streamId } : {}),
|
|
583
1085
|
},
|
|
584
1086
|
};
|
|
585
1087
|
}
|
|
@@ -590,7 +1092,7 @@ const TelegramMcpGatewaySocketService = {
|
|
|
590
1092
|
type: "live_response",
|
|
591
1093
|
request_id: request.request_id,
|
|
592
1094
|
ok: false,
|
|
593
|
-
error:
|
|
1095
|
+
error: formatTerminalRelayError(error),
|
|
594
1096
|
};
|
|
595
1097
|
}
|
|
596
1098
|
},
|
|
@@ -607,6 +1109,17 @@ const TelegramMcpGatewaySocketService = {
|
|
|
607
1109
|
...(typeof parsed.client_uuid === "string" && parsed.client_uuid.trim()
|
|
608
1110
|
? { client_uuid: parsed.client_uuid.trim() }
|
|
609
1111
|
: {}),
|
|
1112
|
+
...(typeof parsed.client_label === "string" && parsed.client_label.trim()
|
|
1113
|
+
? { client_label: parsed.client_label.trim() }
|
|
1114
|
+
: {}),
|
|
1115
|
+
...(typeof parsed.gateway_user_uuid === "string" &&
|
|
1116
|
+
parsed.gateway_user_uuid.trim()
|
|
1117
|
+
? { gateway_user_uuid: parsed.gateway_user_uuid.trim() }
|
|
1118
|
+
: {}),
|
|
1119
|
+
...(typeof parsed.system_username === "string" &&
|
|
1120
|
+
parsed.system_username.trim()
|
|
1121
|
+
? { system_username: parsed.system_username.trim() }
|
|
1122
|
+
: {}),
|
|
610
1123
|
...(typeof parsed.project_name === "string" && parsed.project_name.trim()
|
|
611
1124
|
? { project_name: parsed.project_name.trim() }
|
|
612
1125
|
: {}),
|
|
@@ -654,6 +1167,12 @@ const TelegramMcpGatewaySocketService = {
|
|
|
654
1167
|
tools_hash: item.tools_hash.trim(),
|
|
655
1168
|
}
|
|
656
1169
|
: {}),
|
|
1170
|
+
...(typeof item.cwd === "string" &&
|
|
1171
|
+
item.cwd.trim()
|
|
1172
|
+
? {
|
|
1173
|
+
cwd: item.cwd.trim(),
|
|
1174
|
+
}
|
|
1175
|
+
: {}),
|
|
657
1176
|
}
|
|
658
1177
|
: null)
|
|
659
1178
|
.filter((item) => Boolean(item?.local_session_id)),
|
|
@@ -661,6 +1180,12 @@ const TelegramMcpGatewaySocketService = {
|
|
|
661
1180
|
: {}),
|
|
662
1181
|
};
|
|
663
1182
|
const previous = this.connectedClients?.get(socket);
|
|
1183
|
+
const previousByClientUuid = hello.client_uuid
|
|
1184
|
+
? this.connectedClientsByUuid?.get(hello.client_uuid)
|
|
1185
|
+
: null;
|
|
1186
|
+
const previousHelloForClient = previousByClientUuid && previousByClientUuid !== socket
|
|
1187
|
+
? this.connectedClients?.get(previousByClientUuid)
|
|
1188
|
+
: previous;
|
|
664
1189
|
if (previous?.client_uuid) {
|
|
665
1190
|
this.connectedClientsByUuid?.delete(previous.client_uuid);
|
|
666
1191
|
}
|
|
@@ -669,6 +1194,46 @@ const TelegramMcpGatewaySocketService = {
|
|
|
669
1194
|
this.connectedClientsByUuid?.set(hello.client_uuid, socket);
|
|
670
1195
|
}
|
|
671
1196
|
runtime.logger.info("Gateway WS hello received", hello);
|
|
1197
|
+
if (hello.client_uuid) {
|
|
1198
|
+
await this.broker.call("telegramMcp.gateway.syncLiveConsoles", {
|
|
1199
|
+
client_uuid: hello.client_uuid,
|
|
1200
|
+
connection_id: hello.connection_id,
|
|
1201
|
+
...(hello.gateway_user_uuid
|
|
1202
|
+
? { gateway_user_uuid: hello.gateway_user_uuid }
|
|
1203
|
+
: {}),
|
|
1204
|
+
...(hello.client_label ? { client_label: hello.client_label } : {}),
|
|
1205
|
+
...(hello.system_username
|
|
1206
|
+
? { system_username: hello.system_username }
|
|
1207
|
+
: {}),
|
|
1208
|
+
...(hello.namespace ? { namespace: hello.namespace } : {}),
|
|
1209
|
+
...(hello.node_id ? { node_id: hello.node_id } : {}),
|
|
1210
|
+
...(hello.package_version
|
|
1211
|
+
? { package_version: hello.package_version }
|
|
1212
|
+
: {}),
|
|
1213
|
+
...(hello.protocol_version
|
|
1214
|
+
? { protocol_version: hello.protocol_version }
|
|
1215
|
+
: {}),
|
|
1216
|
+
session_tools: Array.isArray(hello.session_tools)
|
|
1217
|
+
? hello.session_tools.map((sessionTool) => ({
|
|
1218
|
+
local_session_id: sessionTool.local_session_id,
|
|
1219
|
+
...(sessionTool.session_label
|
|
1220
|
+
? { session_label: sessionTool.session_label }
|
|
1221
|
+
: {}),
|
|
1222
|
+
...(sessionTool.tools_hash
|
|
1223
|
+
? { tools_hash: sessionTool.tools_hash }
|
|
1224
|
+
: {}),
|
|
1225
|
+
...(sessionTool.cwd ? { cwd: sessionTool.cwd } : {}),
|
|
1226
|
+
}))
|
|
1227
|
+
: [],
|
|
1228
|
+
}, { meta: { internal_call: true } });
|
|
1229
|
+
await runtime.telegramTransport.hydrateGatewayClientOwnerRoute({
|
|
1230
|
+
clientUuid: hello.client_uuid,
|
|
1231
|
+
...(hello.gateway_user_uuid
|
|
1232
|
+
? { gatewayUserUuid: hello.gateway_user_uuid }
|
|
1233
|
+
: {}),
|
|
1234
|
+
});
|
|
1235
|
+
runtime.telegramTransport.ensurePromptScanRunning();
|
|
1236
|
+
}
|
|
672
1237
|
const localVersionInfo = this.getLocalVersionInfo?.() ?? {
|
|
673
1238
|
packageVersion: "0.0.0-unknown",
|
|
674
1239
|
protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
|
|
@@ -718,6 +1283,34 @@ const TelegramMcpGatewaySocketService = {
|
|
|
718
1283
|
}, 50);
|
|
719
1284
|
return;
|
|
720
1285
|
}
|
|
1286
|
+
if (hello.client_uuid) {
|
|
1287
|
+
const previousSessionIds = new Set(Array.isArray(previousHelloForClient?.session_tools)
|
|
1288
|
+
? previousHelloForClient.session_tools
|
|
1289
|
+
.map((session) => session.local_session_id)
|
|
1290
|
+
.filter((sessionId) => typeof sessionId === "string" && sessionId.trim().length > 0)
|
|
1291
|
+
: []);
|
|
1292
|
+
const currentSessions = Array.isArray(hello.session_tools)
|
|
1293
|
+
? hello.session_tools.filter((session) => typeof session?.local_session_id === "string" &&
|
|
1294
|
+
session.local_session_id.trim().length > 0)
|
|
1295
|
+
: [];
|
|
1296
|
+
const newSessions = currentSessions.filter((session) => !previousSessionIds.has(session.local_session_id));
|
|
1297
|
+
const isNewClient = !previousHelloForClient;
|
|
1298
|
+
if (isNewClient || newSessions.length > 0) {
|
|
1299
|
+
await runtime.telegramTransport.sendAdminGatewayRegistrationNotifications({
|
|
1300
|
+
clientUuid: hello.client_uuid,
|
|
1301
|
+
...(hello.gateway_user_uuid
|
|
1302
|
+
? { gatewayUserUuid: hello.gateway_user_uuid }
|
|
1303
|
+
: {}),
|
|
1304
|
+
...(hello.node_id ? { nodeId: hello.node_id } : {}),
|
|
1305
|
+
...(hello.package_version
|
|
1306
|
+
? { packageVersion: hello.package_version }
|
|
1307
|
+
: {}),
|
|
1308
|
+
totalSessions: currentSessions.length,
|
|
1309
|
+
isNewClient,
|
|
1310
|
+
newSessions,
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
721
1314
|
await this.notifyToolsMismatchForSocket?.(socket, hello);
|
|
722
1315
|
if (hello.client_uuid) {
|
|
723
1316
|
const queued = await this.broker.call("telegramMcp.gateway.pollDeliveries", {
|
|
@@ -778,6 +1371,51 @@ const TelegramMcpGatewaySocketService = {
|
|
|
778
1371
|
}
|
|
779
1372
|
return;
|
|
780
1373
|
}
|
|
1374
|
+
if (parsed.type === "live_stream_event") {
|
|
1375
|
+
const streamId = typeof parsed.stream_id === "string" ? parsed.stream_id.trim() : "";
|
|
1376
|
+
if (!streamId) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
const handler = this.liveStreamHandlers?.get(streamId);
|
|
1380
|
+
if (!handler) {
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
handler.onEvent(parsed);
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
if (parsed.type === "action_response") {
|
|
1387
|
+
const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
|
|
1388
|
+
if (!requestId) {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const pending = this.pendingActionRequests?.get(requestId);
|
|
1392
|
+
if (!pending) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
clearTimeout(pending.timeout);
|
|
1396
|
+
this.pendingActionRequests?.delete(requestId);
|
|
1397
|
+
runtime.logger.info("Gateway WS action response received", {
|
|
1398
|
+
requestId,
|
|
1399
|
+
clientUuid: pending.clientUuid,
|
|
1400
|
+
ok: parsed.ok === true,
|
|
1401
|
+
...(parsed.ok === true
|
|
1402
|
+
? {}
|
|
1403
|
+
: {
|
|
1404
|
+
error: typeof parsed.error === "string" && parsed.error.trim()
|
|
1405
|
+
? parsed.error
|
|
1406
|
+
: "Remote action request failed",
|
|
1407
|
+
}),
|
|
1408
|
+
});
|
|
1409
|
+
if (parsed.ok === true) {
|
|
1410
|
+
pending.resolve(parsed.result);
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
pending.reject(new Error(typeof parsed.error === "string" && parsed.error.trim()
|
|
1414
|
+
? parsed.error
|
|
1415
|
+
: "Remote action request failed"));
|
|
1416
|
+
}
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
781
1419
|
if (parsed.type === "delivery_ack" || parsed.type === "delivery_fail") {
|
|
782
1420
|
const hello = this.connectedClients?.get(socket);
|
|
783
1421
|
const clientUuid = hello?.client_uuid?.trim();
|
|
@@ -903,6 +1541,58 @@ const TelegramMcpGatewaySocketService = {
|
|
|
903
1541
|
await runtime.telegramTransport.handleToolsUpdatedEvent(parsed.payload);
|
|
904
1542
|
return;
|
|
905
1543
|
}
|
|
1544
|
+
if (parsed.type === "action_request") {
|
|
1545
|
+
const requestId = typeof parsed.request_id === "string" ? parsed.request_id.trim() : "";
|
|
1546
|
+
const actionName = typeof parsed.action_name === "string" ? parsed.action_name.trim() : "";
|
|
1547
|
+
if (!requestId || !actionName) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
runtime.logger.info("Gateway WS action request received on client", {
|
|
1551
|
+
requestId,
|
|
1552
|
+
actionName,
|
|
1553
|
+
sessionId: typeof parsed.payload?.session_id === "string"
|
|
1554
|
+
? parsed.payload.session_id
|
|
1555
|
+
: null,
|
|
1556
|
+
});
|
|
1557
|
+
try {
|
|
1558
|
+
const result = await this.broker.call(actionName, parsed.payload ?? {}, { meta: { internal_call: true } });
|
|
1559
|
+
if (isBackendErrorLike(result)) {
|
|
1560
|
+
this.wsClient?.send(JSON.stringify({
|
|
1561
|
+
type: "action_response",
|
|
1562
|
+
request_id: requestId,
|
|
1563
|
+
ok: false,
|
|
1564
|
+
error: formatBackendErrorLike(result),
|
|
1565
|
+
}));
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
runtime.logger.info("Gateway WS action request completed on client", {
|
|
1569
|
+
requestId,
|
|
1570
|
+
actionName,
|
|
1571
|
+
});
|
|
1572
|
+
this.wsClient?.send(JSON.stringify({
|
|
1573
|
+
type: "action_response",
|
|
1574
|
+
request_id: requestId,
|
|
1575
|
+
ok: true,
|
|
1576
|
+
result,
|
|
1577
|
+
}));
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
const formattedError = formatRemoteActionError(error);
|
|
1581
|
+
runtime.logger.error("Gateway WS action request failed on client", {
|
|
1582
|
+
requestId,
|
|
1583
|
+
actionName,
|
|
1584
|
+
error: formattedError,
|
|
1585
|
+
payload: parsed.payload ?? {},
|
|
1586
|
+
});
|
|
1587
|
+
this.wsClient?.send(JSON.stringify({
|
|
1588
|
+
type: "action_response",
|
|
1589
|
+
request_id: requestId,
|
|
1590
|
+
ok: false,
|
|
1591
|
+
error: formattedError,
|
|
1592
|
+
}));
|
|
1593
|
+
}
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
906
1596
|
if (parsed.type === "project_event") {
|
|
907
1597
|
if (parsed.event === "member_joined" &&
|
|
908
1598
|
parsed.payload &&
|
|
@@ -937,6 +1627,12 @@ const TelegramMcpGatewaySocketService = {
|
|
|
937
1627
|
return;
|
|
938
1628
|
}
|
|
939
1629
|
}
|
|
1630
|
+
if (parsed.type === "transport_event" && parsed.payload) {
|
|
1631
|
+
if (parsed.event === "request_reply") {
|
|
1632
|
+
await runtime.telegramTransport.handleGatewayTransportReplyEvent(parsed.payload);
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
940
1636
|
if (parsed.type === "delivery_event") {
|
|
941
1637
|
const delivery = parsed.payload;
|
|
942
1638
|
if (!delivery) {
|
|
@@ -979,9 +1675,12 @@ const TelegramMcpGatewaySocketService = {
|
|
|
979
1675
|
},
|
|
980
1676
|
async requestLiveRelay(params) {
|
|
981
1677
|
const runtime = this.getRuntimeOrThrow();
|
|
982
|
-
const socket = this
|
|
1678
|
+
const socket = findConnectedSocketForSession(this, {
|
|
1679
|
+
clientUuid: params.clientUuid,
|
|
1680
|
+
localSessionId: params.localSessionId,
|
|
1681
|
+
});
|
|
983
1682
|
if (!socket || socket.readyState !== 1) {
|
|
984
|
-
throw new Error(`Gateway WS
|
|
1683
|
+
throw new Error(`Gateway WS console '${params.clientUuid}/${params.localSessionId}' is not connected`);
|
|
985
1684
|
}
|
|
986
1685
|
const requestId = (0, node_crypto_1.randomUUID)();
|
|
987
1686
|
const response = await new Promise((resolve, reject) => {
|
|
@@ -1011,6 +1710,83 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1011
1710
|
});
|
|
1012
1711
|
return response;
|
|
1013
1712
|
},
|
|
1713
|
+
async openLiveRelayStream(params) {
|
|
1714
|
+
const streamId = (0, node_crypto_1.randomUUID)();
|
|
1715
|
+
this.liveStreamHandlers?.set(streamId, {
|
|
1716
|
+
clientUuid: params.clientUuid,
|
|
1717
|
+
onEvent: params.onEvent,
|
|
1718
|
+
});
|
|
1719
|
+
try {
|
|
1720
|
+
await this.requestLiveRelay?.({
|
|
1721
|
+
clientUuid: params.clientUuid,
|
|
1722
|
+
localSessionId: params.localSessionId,
|
|
1723
|
+
requestType: "stream_subscribe",
|
|
1724
|
+
payload: { stream_id: streamId },
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
catch (error) {
|
|
1728
|
+
this.liveStreamHandlers?.delete(streamId);
|
|
1729
|
+
throw error;
|
|
1730
|
+
}
|
|
1731
|
+
return {
|
|
1732
|
+
streamId,
|
|
1733
|
+
close: async () => {
|
|
1734
|
+
this.liveStreamHandlers?.delete(streamId);
|
|
1735
|
+
try {
|
|
1736
|
+
await this.requestLiveRelay?.({
|
|
1737
|
+
clientUuid: params.clientUuid,
|
|
1738
|
+
localSessionId: params.localSessionId,
|
|
1739
|
+
requestType: "stream_unsubscribe",
|
|
1740
|
+
payload: { stream_id: streamId },
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
catch {
|
|
1744
|
+
// best-effort unsubscribe during stream shutdown
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
};
|
|
1748
|
+
},
|
|
1749
|
+
async requestClientAction(params) {
|
|
1750
|
+
const localSessionId = typeof params.payload?.session_id === "string" && params.payload.session_id.trim()
|
|
1751
|
+
? params.payload.session_id.trim()
|
|
1752
|
+
: null;
|
|
1753
|
+
const socket = localSessionId
|
|
1754
|
+
? findConnectedSocketForSession(this, {
|
|
1755
|
+
clientUuid: params.clientUuid,
|
|
1756
|
+
localSessionId,
|
|
1757
|
+
})
|
|
1758
|
+
: (this.connectedClientsByUuid?.get(params.clientUuid) ?? null);
|
|
1759
|
+
if (!socket || socket.readyState !== 1) {
|
|
1760
|
+
throw new Error(localSessionId
|
|
1761
|
+
? `Gateway WS console '${params.clientUuid}/${localSessionId}' is not connected`
|
|
1762
|
+
: `Gateway WS client '${params.clientUuid}' is not connected`);
|
|
1763
|
+
}
|
|
1764
|
+
const requestId = (0, node_crypto_1.randomUUID)();
|
|
1765
|
+
return await new Promise((resolve, reject) => {
|
|
1766
|
+
const timeout = setTimeout(() => {
|
|
1767
|
+
this.pendingActionRequests?.delete(requestId);
|
|
1768
|
+
reject(new Error("Remote action WS request timed out"));
|
|
1769
|
+
}, LIVE_REQUEST_TIMEOUT_MS);
|
|
1770
|
+
this.pendingActionRequests?.set(requestId, {
|
|
1771
|
+
clientUuid: params.clientUuid,
|
|
1772
|
+
resolve,
|
|
1773
|
+
reject,
|
|
1774
|
+
timeout,
|
|
1775
|
+
});
|
|
1776
|
+
this.getRuntimeOrThrow().logger.info("Gateway WS action request sent", {
|
|
1777
|
+
requestId,
|
|
1778
|
+
clientUuid: params.clientUuid,
|
|
1779
|
+
actionName: params.actionName,
|
|
1780
|
+
localSessionId,
|
|
1781
|
+
});
|
|
1782
|
+
socket.send(JSON.stringify({
|
|
1783
|
+
type: "action_request",
|
|
1784
|
+
request_id: requestId,
|
|
1785
|
+
action_name: params.actionName,
|
|
1786
|
+
payload: params.payload ?? {},
|
|
1787
|
+
}));
|
|
1788
|
+
});
|
|
1789
|
+
},
|
|
1014
1790
|
async notifyProjectMemberJoined(params) {
|
|
1015
1791
|
const message = {
|
|
1016
1792
|
type: "project_event",
|
|
@@ -1034,12 +1810,14 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1034
1810
|
delivered += 1;
|
|
1035
1811
|
continue;
|
|
1036
1812
|
}
|
|
1037
|
-
const
|
|
1038
|
-
if (
|
|
1813
|
+
const sockets = listConnectedSocketsForClient(this, clientUuid);
|
|
1814
|
+
if (sockets.length === 0) {
|
|
1039
1815
|
continue;
|
|
1040
1816
|
}
|
|
1041
|
-
socket
|
|
1042
|
-
|
|
1817
|
+
for (const socket of sockets) {
|
|
1818
|
+
socket.send(JSON.stringify(message));
|
|
1819
|
+
delivered += 1;
|
|
1820
|
+
}
|
|
1043
1821
|
}
|
|
1044
1822
|
return delivered;
|
|
1045
1823
|
},
|
|
@@ -1066,12 +1844,14 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1066
1844
|
delivered += 1;
|
|
1067
1845
|
continue;
|
|
1068
1846
|
}
|
|
1069
|
-
const
|
|
1070
|
-
if (
|
|
1847
|
+
const sockets = listConnectedSocketsForClient(this, clientUuid);
|
|
1848
|
+
if (sockets.length === 0) {
|
|
1071
1849
|
continue;
|
|
1072
1850
|
}
|
|
1073
|
-
socket
|
|
1074
|
-
|
|
1851
|
+
for (const socket of sockets) {
|
|
1852
|
+
socket.send(JSON.stringify(message));
|
|
1853
|
+
delivered += 1;
|
|
1854
|
+
}
|
|
1075
1855
|
}
|
|
1076
1856
|
return delivered;
|
|
1077
1857
|
},
|
|
@@ -1092,57 +1872,38 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1092
1872
|
delivered += 1;
|
|
1093
1873
|
continue;
|
|
1094
1874
|
}
|
|
1095
|
-
const
|
|
1096
|
-
if (
|
|
1875
|
+
const sockets = listConnectedSocketsForClient(this, clientUuid);
|
|
1876
|
+
if (sockets.length === 0) {
|
|
1097
1877
|
continue;
|
|
1098
1878
|
}
|
|
1099
|
-
socket
|
|
1100
|
-
|
|
1879
|
+
for (const socket of sockets) {
|
|
1880
|
+
socket.send(JSON.stringify(message));
|
|
1881
|
+
delivered += 1;
|
|
1882
|
+
}
|
|
1101
1883
|
}
|
|
1102
1884
|
return delivered;
|
|
1103
1885
|
},
|
|
1104
1886
|
async notifyLiveApprovalRequest(params) {
|
|
1105
|
-
|
|
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
|
-
}));
|
|
1887
|
+
const runtime = this.getRuntimeOrThrow();
|
|
1888
|
+
await runtime.telegramTransport.handleLiveViewApprovalRequestEvent(params.payload);
|
|
1119
1889
|
return true;
|
|
1120
1890
|
},
|
|
1121
1891
|
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
|
-
}));
|
|
1892
|
+
const runtime = this.getRuntimeOrThrow();
|
|
1893
|
+
await runtime.telegramTransport.handleLiveViewApprovalResolvedEvent({
|
|
1894
|
+
approved: params.approved,
|
|
1895
|
+
...params.payload,
|
|
1896
|
+
});
|
|
1139
1897
|
return true;
|
|
1140
1898
|
},
|
|
1141
1899
|
async notifyDeliveryQueued(params) {
|
|
1142
1900
|
if (await this.handleLocalIncomingDelivery?.(params)) {
|
|
1143
1901
|
return true;
|
|
1144
1902
|
}
|
|
1145
|
-
const socket = this
|
|
1903
|
+
const socket = findConnectedSocketForSession(this, {
|
|
1904
|
+
clientUuid: params.clientUuid,
|
|
1905
|
+
localSessionId: params.delivery.target_local_session_id,
|
|
1906
|
+
});
|
|
1146
1907
|
if (!socket || socket.readyState !== 1) {
|
|
1147
1908
|
return false;
|
|
1148
1909
|
}
|
|
@@ -1157,14 +1918,35 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1157
1918
|
if (await this.handleLocalDeliveryStatus?.(params)) {
|
|
1158
1919
|
return true;
|
|
1159
1920
|
}
|
|
1160
|
-
const
|
|
1161
|
-
if (
|
|
1921
|
+
const sockets = listConnectedSocketsForClient(this, params.clientUuid);
|
|
1922
|
+
if (sockets.length === 0) {
|
|
1162
1923
|
return false;
|
|
1163
1924
|
}
|
|
1164
|
-
socket
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1925
|
+
for (const socket of sockets) {
|
|
1926
|
+
socket.send(JSON.stringify({
|
|
1927
|
+
type: "delivery_status_event",
|
|
1928
|
+
payload: params.status,
|
|
1929
|
+
}));
|
|
1930
|
+
}
|
|
1931
|
+
return true;
|
|
1932
|
+
},
|
|
1933
|
+
async notifyTransportReply(params) {
|
|
1934
|
+
if (await this.isLocalGatewayClientUuid?.(params.clientUuid)) {
|
|
1935
|
+
const runtime = this.getRuntimeOrThrow();
|
|
1936
|
+
await runtime.telegramTransport.handleGatewayTransportReplyEvent(params.payload);
|
|
1937
|
+
return true;
|
|
1938
|
+
}
|
|
1939
|
+
const sockets = listConnectedSocketsForClient(this, params.clientUuid);
|
|
1940
|
+
if (sockets.length === 0) {
|
|
1941
|
+
return false;
|
|
1942
|
+
}
|
|
1943
|
+
for (const socket of sockets) {
|
|
1944
|
+
socket.send(JSON.stringify({
|
|
1945
|
+
type: "transport_event",
|
|
1946
|
+
event: "request_reply",
|
|
1947
|
+
payload: params.payload,
|
|
1948
|
+
}));
|
|
1949
|
+
}
|
|
1168
1950
|
return true;
|
|
1169
1951
|
},
|
|
1170
1952
|
async startGatewayWsServer() {
|
|
@@ -1172,7 +1954,7 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1172
1954
|
if (!runtime || this.wsServer) {
|
|
1173
1955
|
return;
|
|
1174
1956
|
}
|
|
1175
|
-
const httpServer = this.
|
|
1957
|
+
const httpServer = await this.waitForHttpServer?.();
|
|
1176
1958
|
const wsPath = runtime.config.distributed.gatewayWsPath.replace(/\/+$/u, "") || "/";
|
|
1177
1959
|
const wsServer = new WebSocketServer({ noServer: true });
|
|
1178
1960
|
wsServer.on("connection", (socket, req) => {
|
|
@@ -1197,8 +1979,38 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1197
1979
|
});
|
|
1198
1980
|
socket.on("close", () => {
|
|
1199
1981
|
const hello = this.connectedClients?.get(socket);
|
|
1982
|
+
if (hello?.connection_id) {
|
|
1983
|
+
void this.broker
|
|
1984
|
+
.call("telegramMcp.gateway.removeLiveConsoles", {
|
|
1985
|
+
connection_id: hello.connection_id,
|
|
1986
|
+
...(hello.client_uuid ? { client_uuid: hello.client_uuid } : {}),
|
|
1987
|
+
}, { meta: { internal_call: true } })
|
|
1988
|
+
.catch((error) => {
|
|
1989
|
+
runtime.logger.warn("Failed to remove gateway live consoles on disconnect", {
|
|
1990
|
+
connectionId: hello.connection_id,
|
|
1991
|
+
clientUuid: hello?.client_uuid ?? null,
|
|
1992
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1993
|
+
});
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1200
1996
|
if (hello?.client_uuid) {
|
|
1201
|
-
this.connectedClientsByUuid?.
|
|
1997
|
+
if (this.connectedClientsByUuid?.get(hello.client_uuid) === socket) {
|
|
1998
|
+
const replacement = listConnectedSocketsForClient(this, hello.client_uuid).find((candidate) => candidate !== socket) ?? null;
|
|
1999
|
+
if (replacement) {
|
|
2000
|
+
this.connectedClientsByUuid?.set(hello.client_uuid, replacement);
|
|
2001
|
+
}
|
|
2002
|
+
else {
|
|
2003
|
+
this.connectedClientsByUuid?.delete(hello.client_uuid);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
for (const [streamId, handler] of this.liveStreamHandlers?.entries() ?? []) {
|
|
2007
|
+
if (handler.clientUuid === hello.client_uuid) {
|
|
2008
|
+
this.liveStreamHandlers?.delete(streamId);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if ((this.connectedClientsByUuid?.size ?? 0) === 0) {
|
|
2013
|
+
runtime.telegramTransport.pausePromptScan();
|
|
1202
2014
|
}
|
|
1203
2015
|
this.connectedClientToolsAlerts?.delete(socket);
|
|
1204
2016
|
this.connectedClients?.delete(socket);
|
|
@@ -1426,6 +2238,16 @@ const TelegramMcpGatewaySocketService = {
|
|
|
1426
2238
|
pending.reject(new Error("Gateway WS transport is shutting down"));
|
|
1427
2239
|
}
|
|
1428
2240
|
this.pendingLiveRequests?.clear();
|
|
2241
|
+
for (const pending of this.pendingActionRequests?.values() ?? []) {
|
|
2242
|
+
clearTimeout(pending.timeout);
|
|
2243
|
+
pending.reject(new Error("Gateway WS transport is shutting down"));
|
|
2244
|
+
}
|
|
2245
|
+
this.pendingActionRequests?.clear();
|
|
2246
|
+
for (const unsubscribe of this.localLiveStreamSubscriptions?.values() ?? []) {
|
|
2247
|
+
unsubscribe();
|
|
2248
|
+
}
|
|
2249
|
+
this.localLiveStreamSubscriptions?.clear();
|
|
2250
|
+
this.liveStreamHandlers?.clear();
|
|
1429
2251
|
if (this.wsClient) {
|
|
1430
2252
|
const socket = this.wsClient;
|
|
1431
2253
|
this.wsClient = null;
|