@deadragdoll/tellymcp 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.env.example.client +23 -39
  2. package/.env.example.gateway +52 -61
  3. package/CHANGELOG.md +57 -0
  4. package/README-ru.md +80 -6
  5. package/README.md +80 -6
  6. package/TOOLS.md +219 -10
  7. package/config/templates/env.both.template +19 -7
  8. package/config/templates/env.client.template +9 -19
  9. package/config/templates/env.gateway.template +19 -31
  10. package/dist/cli.js +252 -47
  11. package/dist/configureServer.js +966 -0
  12. package/dist/envMigration.js +316 -0
  13. package/dist/moleculer.config.js +1 -3
  14. package/dist/services/features/telegram-mcp/approval.service.js +1 -1
  15. package/dist/services/features/telegram-mcp/browser.service.js +18 -0
  16. package/dist/services/features/telegram-mcp/collaboration.service.js +2 -2
  17. package/dist/services/features/telegram-mcp/ensuredb.service.js +1 -1
  18. package/dist/services/features/telegram-mcp/file-content.service.js +94 -0
  19. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +5 -1
  20. package/dist/services/features/telegram-mcp/gateway-socket.service.js +43 -11
  21. package/dist/services/features/telegram-mcp/gateway.service.js +1 -1
  22. package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -0
  23. package/dist/services/features/telegram-mcp/mcp-server.service.js +20 -0
  24. package/dist/services/features/telegram-mcp/session-context.service.js +25 -1
  25. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +79 -67
  26. package/dist/services/features/telegram-mcp/src/app/config/env.js +129 -38
  27. package/dist/services/features/telegram-mcp/src/app/config/environmentContract.js +66 -0
  28. package/dist/services/features/telegram-mcp/src/app/http.js +139 -99
  29. package/dist/services/features/telegram-mcp/src/app/oauthFacade.js +642 -0
  30. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +151 -170
  31. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +96 -99
  32. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +88 -19
  33. package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachActiveTabTool.js +28 -0
  34. package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachTabTool.js +28 -0
  35. package/dist/services/features/telegram-mcp/src/features/browser/model/browserDetachTabTool.js +28 -0
  36. package/dist/services/features/telegram-mcp/src/features/browser/model/browserListAttachedInstancesTool.js +1 -1
  37. package/dist/services/features/telegram-mcp/src/features/browser/model/browserListTabsTool.js +1 -1
  38. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +412 -29
  39. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/browserRecordingBundle.js +37 -3
  40. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachRegistry.js +7 -6
  41. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachServer.js +214 -37
  42. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/types.js +186 -0
  43. package/dist/services/features/telegram-mcp/src/features/collaboration/model/collaborationService.js +2 -0
  44. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +5 -5
  45. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +6 -3
  46. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +5 -2
  47. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +4 -4
  48. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +33 -36
  49. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +4 -3
  50. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileListTool.js +33 -0
  51. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileService.js +327 -0
  52. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileTool.js +81 -0
  53. package/dist/services/features/telegram-mcp/src/features/file-content/model/temporaryFileLinkStore.js +307 -0
  54. package/dist/services/features/telegram-mcp/src/features/file-content/model/workspaceFilePolicy.js +115 -0
  55. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +9 -5
  56. package/dist/services/features/telegram-mcp/src/features/session-context/model/getRuntimeDiagnosticsTool.js +30 -0
  57. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +169 -7
  58. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalStateStore.js +260 -0
  59. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +1 -1
  60. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +4 -1
  61. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +2 -2
  62. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +6 -3
  63. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +1 -1
  64. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +2 -2
  65. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +29 -6
  66. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +100 -2
  67. package/dist/services/features/telegram-mcp/src/shared/lib/bodyLimits.js +63 -0
  68. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayAuth.js +13 -0
  69. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +5 -5
  70. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +10 -0
  71. package/dist/services/features/telegram-mcp/src/shared/lib/time/localTimestamp.js +21 -0
  72. package/docs/CHAT_CONNECTOR.md +134 -0
  73. package/docs/STANDALONE-ru.md +41 -3
  74. package/docs/STANDALONE.md +41 -3
  75. package/package.json +5 -3
  76. package/packages/chrome-attach-extension/dist/background.js +572 -163
  77. package/packages/chrome-attach-extension/dist/manifest.json +2 -1
  78. package/packages/chrome-attach-extension/dist/options.js +15 -2
  79. package/packages/chrome-attach-extension/dist/recorder-content.js +14 -1
  80. package/packages/chrome-attach-extension/dist/recorder-page.js +34 -18
  81. package/packages/firefox-attach-extension/dist/background.js +413 -33
  82. package/packages/firefox-attach-extension/dist/manifest.json +0 -12
  83. package/packages/firefox-attach-extension/dist/options.js +14 -1
  84. package/packages/firefox-attach-extension/dist/recorder-content.js +14 -1
  85. package/packages/firefox-attach-extension/dist/recorder-page.js +34 -18
  86. package/scripts/postinstall.js +33 -1
@@ -9,16 +9,174 @@ class SessionContextService {
9
9
  logger;
10
10
  projectIdentityResolver;
11
11
  remoteConsoleInvoker;
12
- constructor(sessionStore, bindingStore, logger, projectIdentityResolver, remoteConsoleInvoker) {
12
+ runtimeDiagnostics;
13
+ constructor(sessionStore, bindingStore, logger, projectIdentityResolver, remoteConsoleInvoker, runtimeDiagnostics) {
13
14
  this.sessionStore = sessionStore;
14
15
  this.bindingStore = bindingStore;
15
16
  this.logger = logger;
16
17
  this.projectIdentityResolver = projectIdentityResolver;
17
18
  this.remoteConsoleInvoker = remoteConsoleInvoker;
19
+ this.runtimeDiagnostics = runtimeDiagnostics;
20
+ }
21
+ getRemoteConsoleInvoker() {
22
+ return this.runtimeDiagnostics?.mode === "client"
23
+ ? undefined
24
+ : this.remoteConsoleInvoker;
25
+ }
26
+ formatDiagnosticError(error) {
27
+ const message = error instanceof Error ? error.message : String(error);
28
+ const redacted = (0, redactSecrets_1.redactSecrets)(message).replace(/\s+/gu, " ").trim();
29
+ return redacted.length > 500
30
+ ? `${redacted.slice(0, 497)}...`
31
+ : redacted || "Unknown error";
32
+ }
33
+ finalizeDiagnostics(output) {
34
+ const degraded = Object.values(output.checks).some((check) => check.status !== "ok");
35
+ return {
36
+ ...output,
37
+ status: degraded ? "degraded" : "ok",
38
+ };
39
+ }
40
+ async collectLocalDiagnostics(sessionId) {
41
+ const runtime = this.runtimeDiagnostics;
42
+ let session = null;
43
+ let sessionStoreCheck;
44
+ try {
45
+ session = await this.sessionStore.getSession(sessionId);
46
+ await this.bindingStore.getBinding(sessionId);
47
+ sessionStoreCheck = {
48
+ status: "ok",
49
+ message: session
50
+ ? "Session and route stores are readable; session metadata exists."
51
+ : "Session and route stores are readable; no saved metadata exists for this id.",
52
+ };
53
+ }
54
+ catch (error) {
55
+ sessionStoreCheck = {
56
+ status: "error",
57
+ message: `Session store check failed: ${this.formatDiagnosticError(error)}`,
58
+ };
59
+ }
60
+ let redisCheck;
61
+ if (runtime?.mode === "client") {
62
+ redisCheck = {
63
+ status: "ok",
64
+ message: "Redis is not required in client mode; process-local state is active.",
65
+ };
66
+ }
67
+ else if (!runtime?.pingRedis) {
68
+ redisCheck = {
69
+ status: "warn",
70
+ message: "Runtime Redis probe is unavailable.",
71
+ };
72
+ }
73
+ else {
74
+ try {
75
+ const reply = await runtime.pingRedis();
76
+ redisCheck = {
77
+ status: reply.trim().toUpperCase() === "PONG" ? "ok" : "warn",
78
+ message: `Redis probe returned ${reply.trim() || "an empty response"}.`,
79
+ };
80
+ }
81
+ catch (error) {
82
+ redisCheck = {
83
+ status: "error",
84
+ message: `Redis probe failed: ${this.formatDiagnosticError(error)}`,
85
+ };
86
+ }
87
+ }
88
+ const mode = runtime?.mode ?? "client";
89
+ const gatewayConfigured = Boolean(runtime?.gatewayWsUrlConfigured);
90
+ const gatewayConfiguration = mode === "gateway"
91
+ ? {
92
+ status: "ok",
93
+ message: "Gateway runtime does not require an outbound gateway WebSocket URL.",
94
+ }
95
+ : gatewayConfigured
96
+ ? {
97
+ status: "ok",
98
+ message: runtime?.gatewayAuthConfigured
99
+ ? "Gateway WebSocket URL and authentication are configured."
100
+ : "Gateway WebSocket URL is configured; authentication is not configured.",
101
+ }
102
+ : {
103
+ status: "error",
104
+ message: "Gateway WebSocket URL is not configured for this client runtime.",
105
+ };
106
+ return this.finalizeDiagnostics({
107
+ checked_at: new Date().toISOString(),
108
+ session_id: sessionId,
109
+ runtime: {
110
+ mode,
111
+ package_version: runtime?.packageVersion ?? "unknown",
112
+ protocol_version: runtime?.protocolVersion ?? "unknown",
113
+ ...(runtime?.nodeId ? { node_id: runtime.nodeId } : {}),
114
+ },
115
+ checks: {
116
+ configuration: runtime
117
+ ? {
118
+ status: "ok",
119
+ message: "Normalized environment schema was accepted at startup.",
120
+ }
121
+ : {
122
+ status: "warn",
123
+ message: "Runtime configuration metadata is unavailable.",
124
+ },
125
+ redis: redisCheck,
126
+ session_store: sessionStoreCheck,
127
+ terminal: session?.terminalTarget
128
+ ? {
129
+ status: "ok",
130
+ message: "A PTY terminal target is configured for this session.",
131
+ }
132
+ : {
133
+ status: "warn",
134
+ message: "No PTY terminal target is configured for this session.",
135
+ },
136
+ gateway_configuration: gatewayConfiguration,
137
+ relay: {
138
+ status: "ok",
139
+ message: "Local diagnostic action completed.",
140
+ },
141
+ },
142
+ });
143
+ }
144
+ async getRuntimeDiagnostics(input) {
145
+ const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
146
+ const remoteConsoleInvoker = this.getRemoteConsoleInvoker();
147
+ if (!remoteConsoleInvoker) {
148
+ return await this.collectLocalDiagnostics(resolved.sessionId);
149
+ }
150
+ try {
151
+ const remote = await remoteConsoleInvoker.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.getRuntimeDiagnosticsRemote", input);
152
+ return this.finalizeDiagnostics({
153
+ ...remote,
154
+ checks: {
155
+ ...remote.checks,
156
+ relay: {
157
+ status: "ok",
158
+ message: "Gateway-to-client relay completed successfully.",
159
+ },
160
+ },
161
+ });
162
+ }
163
+ catch (error) {
164
+ const local = await this.collectLocalDiagnostics(resolved.sessionId);
165
+ return this.finalizeDiagnostics({
166
+ ...local,
167
+ checks: {
168
+ ...local.checks,
169
+ relay: {
170
+ status: "error",
171
+ message: `Gateway-to-client relay failed: ${this.formatDiagnosticError(error)}`,
172
+ },
173
+ },
174
+ });
175
+ }
18
176
  }
19
177
  async setContext(input) {
20
178
  const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
21
- const remote = await this.remoteConsoleInvoker?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.setContextRemote", input);
179
+ const remote = await this.getRemoteConsoleInvoker()?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.setContextRemote", input);
22
180
  if (remote) {
23
181
  return remote;
24
182
  }
@@ -62,7 +220,9 @@ class SessionContextService {
62
220
  : existing?.risks
63
221
  ? { risks: existing.risks }
64
222
  : {}),
65
- ...(existing?.terminalTarget ? { terminalTarget: existing.terminalTarget } : {}),
223
+ ...(existing?.terminalTarget
224
+ ? { terminalTarget: existing.terminalTarget }
225
+ : {}),
66
226
  ...(existing?.lastTerminalNudgeAt
67
227
  ? { lastTerminalNudgeAt: existing.lastTerminalNudgeAt }
68
228
  : {}),
@@ -98,7 +258,7 @@ class SessionContextService {
98
258
  }
99
259
  async renameSession(input) {
100
260
  const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
101
- const remote = await this.remoteConsoleInvoker?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.renameSessionRemote", input);
261
+ const remote = await this.getRemoteConsoleInvoker()?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.renameSessionRemote", input);
102
262
  if (remote) {
103
263
  return remote;
104
264
  }
@@ -120,7 +280,9 @@ class SessionContextService {
120
280
  ...(existing?.files ? { files: existing.files } : {}),
121
281
  ...(existing?.decisions ? { decisions: existing.decisions } : {}),
122
282
  ...(existing?.risks ? { risks: existing.risks } : {}),
123
- ...(existing?.terminalTarget ? { terminalTarget: existing.terminalTarget } : {}),
283
+ ...(existing?.terminalTarget
284
+ ? { terminalTarget: existing.terminalTarget }
285
+ : {}),
124
286
  ...(existing?.lastTerminalNudgeAt
125
287
  ? { lastTerminalNudgeAt: existing.lastTerminalNudgeAt }
126
288
  : {}),
@@ -151,7 +313,7 @@ class SessionContextService {
151
313
  }
152
314
  async getContext(input) {
153
315
  const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
154
- const remote = await this.remoteConsoleInvoker?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.getContextRemote", input);
316
+ const remote = await this.getRemoteConsoleInvoker()?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.getContextRemote", input);
155
317
  if (remote) {
156
318
  return remote;
157
319
  }
@@ -224,7 +386,7 @@ class SessionContextService {
224
386
  }
225
387
  async clearContext(input) {
226
388
  const resolved = this.projectIdentityResolver.resolveSessionDefaults(input);
227
- const remote = await this.remoteConsoleInvoker?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.clearContextRemote", input);
389
+ const remote = await this.getRemoteConsoleInvoker()?.invokeForRelaySession(resolved.sessionId, "telegramMcp.sessionContext.clearContextRemote", input);
228
390
  if (remote) {
229
391
  return remote;
230
392
  }
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProcessLocalStateStore = void 0;
4
+ const redactSecrets_1 = require("../../lib/redact-secrets/redactSecrets");
5
+ function principalKey(principal) {
6
+ return `${principal.telegramChatId}:${principal.telegramUserId}`;
7
+ }
8
+ function xchangeKey(sessionId, filePath) {
9
+ return `${sessionId}\u0000${filePath}`;
10
+ }
11
+ class ProcessLocalStateStore {
12
+ pairCodes = new Map();
13
+ bindings = new Map();
14
+ principalSessions = new Map();
15
+ principalActiveSessions = new Map();
16
+ locales = new Map();
17
+ authorizedPrincipals = new Map();
18
+ requests = new Map();
19
+ requestQueue = [];
20
+ activeRequestId = null;
21
+ menuPayloads = new Map();
22
+ xchangeFileMetas = new Map();
23
+ browserAttachments = new Map();
24
+ browserRecordings = new Map();
25
+ projectMenuViews = new Map();
26
+ outgoingNotices = new Map();
27
+ gatewayClientUuid;
28
+ constructor(input = {}) {
29
+ this.gatewayClientUuid = input.gatewayClientUuid?.trim() || null;
30
+ this.onGatewayClientUuidChange = input.onGatewayClientUuidChange;
31
+ }
32
+ onGatewayClientUuidChange;
33
+ async createPairCode(record, ttlSeconds) {
34
+ const existing = this.readExpiring(this.pairCodes, record.code);
35
+ if (existing)
36
+ return false;
37
+ this.pairCodes.set(record.code, { value: record, expiresAt: Date.now() + ttlSeconds * 1000 });
38
+ return true;
39
+ }
40
+ async consumePairCode(code) {
41
+ const record = this.readExpiring(this.pairCodes, code);
42
+ this.pairCodes.delete(code);
43
+ return record;
44
+ }
45
+ async getBinding(sessionId) {
46
+ return this.bindings.get(sessionId) ?? null;
47
+ }
48
+ async setBinding(binding) {
49
+ const previous = this.bindings.get(binding.sessionId);
50
+ if (previous)
51
+ this.detachSession(previous, binding.sessionId);
52
+ this.bindings.set(binding.sessionId, binding);
53
+ const key = principalKey(binding);
54
+ const sessions = this.principalSessions.get(key) ?? new Set();
55
+ sessions.add(binding.sessionId);
56
+ this.principalSessions.set(key, sessions);
57
+ this.principalActiveSessions.set(key, binding.sessionId);
58
+ }
59
+ async clearBinding(sessionId) {
60
+ const binding = this.bindings.get(sessionId);
61
+ this.bindings.delete(sessionId);
62
+ if (binding)
63
+ this.detachSession(binding, sessionId);
64
+ }
65
+ async getActiveSessionIdForPrincipal(principal) {
66
+ return this.principalActiveSessions.get(principalKey(principal)) ?? null;
67
+ }
68
+ async getActiveSessionIdForTelegramUser(telegramUserId) {
69
+ for (const [sessionId, binding] of this.bindings) {
70
+ if (binding.telegramUserId === telegramUserId) {
71
+ return this.principalActiveSessions.get(principalKey(binding)) ?? sessionId;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ async clearActiveSessionIdForPrincipal(principal) {
77
+ this.principalActiveSessions.delete(principalKey(principal));
78
+ }
79
+ async setActiveSessionIdForPrincipal(principal, sessionId) {
80
+ const key = principalKey(principal);
81
+ const sessions = this.principalSessions.get(key) ?? new Set();
82
+ sessions.add(sessionId);
83
+ this.principalSessions.set(key, sessions);
84
+ this.principalActiveSessions.set(key, sessionId);
85
+ }
86
+ async listBoundSessionIdsForPrincipal(principal) {
87
+ return Array.from(this.principalSessions.get(principalKey(principal)) ?? []);
88
+ }
89
+ async listBoundPrincipals() {
90
+ const principals = new Map();
91
+ for (const binding of this.bindings.values()) {
92
+ principals.set(principalKey(binding), {
93
+ telegramChatId: binding.telegramChatId,
94
+ telegramUserId: binding.telegramUserId,
95
+ });
96
+ }
97
+ return Array.from(principals.values());
98
+ }
99
+ async resetRuntimeState() {
100
+ this.activeRequestId = null;
101
+ this.requestQueue.length = 0;
102
+ }
103
+ async getActive() {
104
+ return this.activeRequestId ? this.requests.get(this.activeRequestId) ?? null : null;
105
+ }
106
+ async createPending(request) {
107
+ this.requests.set(request.requestId, request);
108
+ this.activeRequestId = request.requestId;
109
+ }
110
+ async updatePending(request) {
111
+ this.requests.set(request.requestId, request);
112
+ }
113
+ async resolvePending(requestId, resolution) {
114
+ const current = this.requests.get(requestId);
115
+ if (current) {
116
+ this.requests.set(requestId, {
117
+ ...current,
118
+ status: resolution.status,
119
+ ...(resolution.answer ? { answer: (0, redactSecrets_1.redactSecrets)(resolution.answer) } : {}),
120
+ ...(resolution.receivedAt ? { receivedAt: resolution.receivedAt } : {}),
121
+ ...(resolution.fallbackUsed ? { fallbackIfTimeout: resolution.fallbackUsed } : {}),
122
+ });
123
+ }
124
+ this.activeRequestId = null;
125
+ }
126
+ async enqueue(request) {
127
+ this.requests.set(request.requestId, request);
128
+ this.requestQueue.push(request.requestId);
129
+ }
130
+ async dequeueNext() {
131
+ const requestId = this.requestQueue.shift();
132
+ return requestId ? this.requests.get(requestId) ?? null : null;
133
+ }
134
+ async createMenuPayload(record, ttlSeconds) {
135
+ this.menuPayloads.set(record.key, { value: record, expiresAt: Date.now() + ttlSeconds * 1000 });
136
+ }
137
+ async getMenuPayload(key) {
138
+ return this.readExpiring(this.menuPayloads, key);
139
+ }
140
+ async getUserLocale(telegramUserId) {
141
+ return this.locales.get(telegramUserId) ?? null;
142
+ }
143
+ async setUserLocale(telegramUserId, locale) {
144
+ this.locales.set(telegramUserId, locale);
145
+ }
146
+ async isAdminAuthorized(principal) {
147
+ return this.authorizedPrincipals.has(principalKey(principal));
148
+ }
149
+ async setAdminAuthorized(principal) {
150
+ this.authorizedPrincipals.set(principalKey(principal), principal);
151
+ }
152
+ async clearAdminAuthorized(principal) {
153
+ this.authorizedPrincipals.delete(principalKey(principal));
154
+ }
155
+ async listAdminAuthorizedPrincipals() {
156
+ return Array.from(this.authorizedPrincipals.values());
157
+ }
158
+ async setXchangeFileMeta(meta) {
159
+ this.xchangeFileMetas.set(xchangeKey(meta.sessionId, meta.filePath), meta);
160
+ }
161
+ async listXchangeFileMetas(sessionId) {
162
+ return Array.from(this.xchangeFileMetas.values())
163
+ .filter((meta) => meta.sessionId === sessionId)
164
+ .sort((left, right) => right.uploadedAt.localeCompare(left.uploadedAt));
165
+ }
166
+ async getXchangeFileMeta(sessionId, filePath) {
167
+ return this.xchangeFileMetas.get(xchangeKey(sessionId, filePath)) ?? null;
168
+ }
169
+ async deleteXchangeFileMeta(sessionId, filePath) {
170
+ return this.xchangeFileMetas.delete(xchangeKey(sessionId, filePath));
171
+ }
172
+ async pruneAll() {
173
+ const deletedKeys = this.countEntries();
174
+ this.pairCodes.clear();
175
+ this.bindings.clear();
176
+ this.principalSessions.clear();
177
+ this.principalActiveSessions.clear();
178
+ this.locales.clear();
179
+ this.authorizedPrincipals.clear();
180
+ this.requests.clear();
181
+ this.requestQueue.length = 0;
182
+ this.activeRequestId = null;
183
+ this.menuPayloads.clear();
184
+ this.xchangeFileMetas.clear();
185
+ this.browserAttachments.clear();
186
+ this.browserRecordings.clear();
187
+ this.projectMenuViews.clear();
188
+ this.outgoingNotices.clear();
189
+ this.gatewayClientUuid = null;
190
+ return { deletedKeys };
191
+ }
192
+ async getGatewayClientUuid() { return this.gatewayClientUuid; }
193
+ async setGatewayClientUuid(clientUuid) {
194
+ this.gatewayClientUuid = clientUuid;
195
+ this.onGatewayClientUuidChange?.(clientUuid);
196
+ }
197
+ async getBrowserAttachment(sessionId) {
198
+ return this.browserAttachments.get(sessionId) ?? null;
199
+ }
200
+ async setBrowserAttachment(record) {
201
+ this.browserAttachments.set(record.sessionId, record);
202
+ }
203
+ async clearBrowserAttachment(sessionId) { this.browserAttachments.delete(sessionId); }
204
+ async getBrowserRecording(sessionId) {
205
+ return this.browserRecordings.get(sessionId) ?? null;
206
+ }
207
+ async setBrowserRecording(record) {
208
+ this.browserRecordings.set(record.sessionId, record);
209
+ }
210
+ async clearBrowserRecording(sessionId) { this.browserRecordings.delete(sessionId); }
211
+ async setProjectMenuViewState(state) {
212
+ this.projectMenuViews.set(xchangeKey(state.projectUuid, state.sessionId), state);
213
+ }
214
+ async listProjectMenuViewStates(projectUuid) {
215
+ return Array.from(this.projectMenuViews.values()).filter((state) => state.projectUuid === projectUuid);
216
+ }
217
+ async deleteProjectMenuViewState(sessionId, projectUuid) {
218
+ return this.projectMenuViews.delete(xchangeKey(projectUuid, sessionId));
219
+ }
220
+ async setOutgoingDeliveryNotice(notice) {
221
+ this.outgoingNotices.set(notice.deliveryUuid, notice);
222
+ }
223
+ async listOutgoingDeliveryNotices() {
224
+ return Array.from(this.outgoingNotices.values());
225
+ }
226
+ async deleteOutgoingDeliveryNotice(deliveryUuid) {
227
+ return this.outgoingNotices.delete(deliveryUuid);
228
+ }
229
+ readExpiring(store, key) {
230
+ const record = store.get(key);
231
+ if (!record)
232
+ return null;
233
+ if (record.expiresAt <= Date.now()) {
234
+ store.delete(key);
235
+ return null;
236
+ }
237
+ return record.value;
238
+ }
239
+ detachSession(principal, sessionId) {
240
+ const key = principalKey(principal);
241
+ const sessions = this.principalSessions.get(key);
242
+ sessions?.delete(sessionId);
243
+ if (!sessions?.size)
244
+ this.principalSessions.delete(key);
245
+ if (this.principalActiveSessions.get(key) === sessionId) {
246
+ const next = sessions?.values().next().value;
247
+ if (next)
248
+ this.principalActiveSessions.set(key, next);
249
+ else
250
+ this.principalActiveSessions.delete(key);
251
+ }
252
+ }
253
+ countEntries() {
254
+ return this.pairCodes.size + this.bindings.size + this.locales.size +
255
+ this.authorizedPrincipals.size + this.requests.size + this.menuPayloads.size +
256
+ this.xchangeFileMetas.size + this.browserAttachments.size + this.browserRecordings.size +
257
+ this.projectMenuViews.size + this.outgoingNotices.size + (this.gatewayClientUuid ? 1 : 0);
258
+ }
259
+ }
260
+ exports.ProcessLocalStateStore = ProcessLocalStateStore;
@@ -29,7 +29,7 @@ class MinioExchangeStore {
29
29
  terminalConfig;
30
30
  exchangeDirName;
31
31
  logger;
32
- constructor(_callBroker, _bindingStore, terminalConfig, exchangeDirName, _vfsScope, logger, _distributedMode = "client", _gatewayPublicUrl, _gatewayAuthToken) {
32
+ constructor(terminalConfig, exchangeDirName, logger) {
33
33
  this.terminalConfig = terminalConfig;
34
34
  this.exchangeDirName = exchangeDirName;
35
35
  this.logger = logger;
@@ -6,6 +6,7 @@ const promises_1 = require("node:fs/promises");
6
6
  const grammy_1 = require("grammy");
7
7
  const telegramXchangeRecords_1 = require("../../lib/telegramXchangeRecords");
8
8
  const collabSemantics_1 = require("./collabSemantics");
9
+ const bodyLimits_1 = require("../../lib/bodyLimits");
9
10
  const transportFormatting_1 = require("./transportFormatting");
10
11
  const transportConstructorWiring_1 = require("./transportConstructorWiring");
11
12
  const transportUtils_1 = require("./transportUtils");
@@ -217,7 +218,7 @@ class TelegramTransport {
217
218
  }
218
219
  isAdminAuthEnabled() {
219
220
  return (this.isAdminBotProfile() &&
220
- Boolean(this.config.distributed.gatewayToken?.trim()));
221
+ Boolean(this.config.distributed.gatewayScopeToken?.trim()));
221
222
  }
222
223
  isAdminBotProfile() {
223
224
  return this.config.distributed.mode === "gateway";
@@ -383,6 +384,8 @@ class TelegramTransport {
383
384
  }
384
385
  async sendDocumentToChat(telegramChatId, filePath, caption) {
385
386
  this.ensureTelegramEnabledFor("send Telegram documents");
387
+ const fileStats = await (0, promises_1.stat)(filePath);
388
+ (0, bodyLimits_1.assertBodySize)(fileStats.size);
386
389
  const fileBuffer = await (0, promises_1.readFile)(filePath);
387
390
  const response = await this.bot.api.sendDocument(telegramChatId, new grammy_1.InputFile(fileBuffer, (0, node_path_1.basename)(filePath)), caption?.trim()
388
391
  ? {
@@ -26,8 +26,8 @@ class TransportConsoleRegistry {
26
26
  ...(input?.principal
27
27
  ? { telegram_user_id: input.principal.telegramUserId }
28
28
  : {}),
29
- ...(this.host.config.distributed.gatewayToken
30
- ? { gateway_token: this.host.config.distributed.gatewayToken }
29
+ ...(this.host.config.distributed.gatewayScopeToken
30
+ ? { gateway_token: this.host.config.distributed.gatewayScopeToken }
31
31
  : {}),
32
32
  });
33
33
  return Array.isArray(response.sessions) ? response.sessions : [];
@@ -11,6 +11,7 @@ const xchangeRecordHints_1 = require("../../lib/xchangeRecordHints");
11
11
  const sqliteRecordStore_1 = require("../xchange/sqliteRecordStore");
12
12
  const client_1 = require("../terminal/client");
13
13
  const transportUtils_1 = require("./transportUtils");
14
+ const bodyLimits_1 = require("../../lib/bodyLimits");
14
15
  class TransportFileHandoffActions {
15
16
  host;
16
17
  constructor(host) {
@@ -316,12 +317,12 @@ class TransportFileHandoffActions {
316
317
  ...(meta?.relativePath ? { relativePath: meta.relativePath } : {}),
317
318
  ...(meta?.storageRef ? { storageRef: meta.storageRef } : {}),
318
319
  });
319
- const fileContent = await (0, client_1.readWorkspaceFile)(this.host.config.terminal, this.host.objectStore.resolveWorkspaceDir(session), localFilePath);
320
+ const fileContent = await (0, client_1.readWorkspaceFile)(this.host.config.terminal, this.host.objectStore.resolveWorkspaceDir(session), localFilePath, bodyLimits_1.MAX_BASE64_SOURCE_SIZE_BYTES);
320
321
  const handoffSummary = input.description
321
322
  .split(/\r?\n/u)
322
323
  .map((line) => line.trim())
323
324
  .find(Boolean) ?? `Передача файла: ${fileName}`;
324
- return this.host.sendPartnerNote({
325
+ const note = {
325
326
  session_id: input.sessionId,
326
327
  ...(input.targetSessionId ? { target_session_id: input.targetSessionId } : {}),
327
328
  ...(input.projectUuid ? { project_uuid: input.projectUuid } : {}),
@@ -346,7 +347,9 @@ class TransportFileHandoffActions {
346
347
  content_base64: node_buffer_1.Buffer.from(fileContent).toString("base64"),
347
348
  },
348
349
  ],
349
- });
350
+ };
351
+ (0, bodyLimits_1.assertSerializedBodySize)(note);
352
+ return this.host.sendPartnerNote(note);
350
353
  }
351
354
  }
352
355
  exports.TransportFileHandoffActions = TransportFileHandoffActions;
@@ -73,7 +73,7 @@ class TransportMessageFlow {
73
73
  await this.host.replyText(ctx, await this.host.tForContext(ctx, "menu:admin.auth.disabled"), { sessionId: "gateway-auth", kind: "transport" });
74
74
  return true;
75
75
  }
76
- if (authToken !== this.host.config.distributed.gatewayToken) {
76
+ if (authToken !== this.host.config.distributed.gatewayScopeToken) {
77
77
  await this.host.replyText(ctx, await this.host.tForContext(ctx, "menu:admin.auth.invalid"), { sessionId: "gateway-auth", kind: "transport" });
78
78
  return true;
79
79
  }
@@ -46,8 +46,8 @@ class TransportProjectState {
46
46
  this.host.config.telegram.botUsername ||
47
47
  "telegram-mcp client",
48
48
  bot_username: this.host.config.telegram.botUsername,
49
- ...(this.host.config.distributed.gatewayToken
50
- ? { gateway_token: this.host.config.distributed.gatewayToken }
49
+ ...(this.host.config.distributed.gatewayScopeToken
50
+ ? { gateway_token: this.host.config.distributed.gatewayScopeToken }
51
51
  : {}),
52
52
  meta: {
53
53
  telegram_chat_id: principal.telegramChatId,
@@ -9,6 +9,7 @@ exports.writeXchangeRelativeFile = writeXchangeRelativeFile;
9
9
  exports.listXchangeFiles = listXchangeFiles;
10
10
  exports.deleteXchangeFile = deleteXchangeFile;
11
11
  exports.readWorkspaceFile = readWorkspaceFile;
12
+ exports.resolveWorkspaceFileForRead = resolveWorkspaceFileForRead;
12
13
  exports.isTerminalUnavailableError = isTerminalUnavailableError;
13
14
  exports.isTerminalTargetInvalidError = isTerminalTargetInvalidError;
14
15
  exports.getConfiguredTerminalShell = getConfiguredTerminalShell;
@@ -31,6 +32,7 @@ exports.subscribeForegroundTerminal = subscribeForegroundTerminal;
31
32
  exports.stopAllForegroundTerminals = stopAllForegroundTerminals;
32
33
  const promises_1 = require("node:fs/promises");
33
34
  const node_path_1 = __importDefault(require("node:path"));
35
+ const bodyLimits_1 = require("../../lib/bodyLimits");
34
36
  const ptyRegistry_1 = require("../terminal/ptyRegistry");
35
37
  const ENTER_AFTER_PASTE_DELAY_MS = 75;
36
38
  function toPtyConfig(config) {
@@ -93,9 +95,7 @@ async function allocateAvailableFilePath(dir, fileName) {
93
95
  const extension = node_path_1.default.extname(safeFileName);
94
96
  const baseName = node_path_1.default.basename(safeFileName, extension);
95
97
  for (let attempt = 0; attempt < 1000; attempt += 1) {
96
- const candidateName = attempt === 0
97
- ? safeFileName
98
- : `${baseName}--${attempt}${extension}`;
98
+ const candidateName = attempt === 0 ? safeFileName : `${baseName}--${attempt}${extension}`;
99
99
  const candidatePath = node_path_1.default.join(dir, candidateName);
100
100
  try {
101
101
  await (0, promises_1.access)(candidatePath);
@@ -172,9 +172,32 @@ async function deleteXchangeFile(config, workspaceDir, exchangeDirName, filePath
172
172
  await (0, promises_1.rm)(resolvedFilePath, { force: true });
173
173
  return true;
174
174
  }
175
- async function readWorkspaceFile(config, workspaceDir, filePath) {
175
+ async function readWorkspaceFile(config, workspaceDir, filePath, maxBytes = bodyLimits_1.MAX_BODY_SIZE_BYTES) {
176
+ const resolved = await resolveWorkspaceFileForRead(config, workspaceDir, filePath, maxBytes);
177
+ return (0, promises_1.readFile)(resolved.filePath);
178
+ }
179
+ async function resolveWorkspaceFileForRead(config, workspaceDir, filePath, maxBytes = bodyLimits_1.MAX_BODY_SIZE_BYTES) {
180
+ void config;
176
181
  const resolvedFilePath = resolvePathInsideWorkspace(workspaceDir, filePath);
177
- return (0, promises_1.readFile)(resolvedFilePath);
182
+ const [realWorkspaceDir, realFilePath] = await Promise.all([
183
+ (0, promises_1.realpath)(node_path_1.default.resolve(workspaceDir)),
184
+ (0, promises_1.realpath)(resolvedFilePath),
185
+ ]);
186
+ const relative = node_path_1.default.relative(realWorkspaceDir, realFilePath);
187
+ if (relative.startsWith("..") ||
188
+ node_path_1.default.isAbsolute(relative) ||
189
+ relative.trim() === "") {
190
+ throw new Error("File path is outside the workspace directory.");
191
+ }
192
+ const fileStats = await (0, promises_1.stat)(realFilePath);
193
+ if (!fileStats.isFile()) {
194
+ throw new Error("File path does not point to a regular file.");
195
+ }
196
+ (0, bodyLimits_1.assertBodySize)(fileStats.size, maxBytes);
197
+ return {
198
+ filePath: realFilePath,
199
+ sizeBytes: fileStats.size,
200
+ };
178
201
  }
179
202
  function isTerminalUnavailableError(error) {
180
203
  const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
@@ -202,7 +225,7 @@ function ensureTerminalTargetForSession(config, input) {
202
225
  }
203
226
  async function resolveTerminalTargetFromHint(config, hint) {
204
227
  const target = hint.terminalTarget?.trim() || (0, ptyRegistry_1.buildPtyTarget)("default");
205
- return (0, ptyRegistry_1.hasPtyTarget)(target) ? target : hint.terminalTarget?.trim() ?? null;
228
+ return (0, ptyRegistry_1.hasPtyTarget)(target) ? target : (hint.terminalTarget?.trim() ?? null);
206
229
  }
207
230
  async function getTerminalWindowHeight(config, target) {
208
231
  return (0, ptyRegistry_1.getPtyWindowHeight)(target);