@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
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TEMPORARY_FILE_LINK_MAX_BYTES = exports.TemporaryFileLinkStore = void 0;
7
+ exports.resolvePublicFilesBaseUrl = resolvePublicFilesBaseUrl;
8
+ const node_crypto_1 = require("node:crypto");
9
+ const node_fs_1 = require("node:fs");
10
+ const promises_1 = require("node:fs/promises");
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const node_stream_1 = require("node:stream");
13
+ const promises_2 = require("node:stream/promises");
14
+ const FILE_LINK_TTL_MS = 10 * 60 * 1000;
15
+ const FILE_LINK_MAX_BYTES = 32 * 1024 * 1024;
16
+ const FILE_LINK_MAX_DOWNLOADS = 3;
17
+ const FILE_LINK_CLEANUP_INTERVAL_MS = 60 * 1000;
18
+ function sanitizeFilename(value) {
19
+ const basename = node_path_1.default.basename(value?.trim() || "file.bin");
20
+ const withoutControlChars = Array.from(basename)
21
+ .map((character) => {
22
+ const code = character.charCodeAt(0);
23
+ return code < 32 || code === 127 ? "-" : character;
24
+ })
25
+ .join("");
26
+ const safe = withoutControlChars.replace(/[/\\<>:"|?*]/gu, "-").trim();
27
+ return safe || "file.bin";
28
+ }
29
+ function decodeFilenameHeader(value) {
30
+ if (!value) {
31
+ return undefined;
32
+ }
33
+ try {
34
+ return decodeURIComponent(value);
35
+ }
36
+ catch {
37
+ return value;
38
+ }
39
+ }
40
+ function encodeContentDispositionFilename(filename) {
41
+ return encodeURIComponent(filename).replace(/[!'()*]/gu, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
42
+ }
43
+ function resolvePublicFilesBaseUrl(gatewayPublicUrl) {
44
+ if (!gatewayPublicUrl?.trim()) {
45
+ throw new Error("URL file delivery requires GATEWAY_PUBLIC_URL.");
46
+ }
47
+ const url = new URL(gatewayPublicUrl);
48
+ url.search = "";
49
+ url.hash = "";
50
+ url.pathname = url.pathname.replace(/\/+$/u, "");
51
+ if (url.pathname.endsWith("/gateway")) {
52
+ url.pathname = url.pathname.slice(0, -"/gateway".length) || "/";
53
+ }
54
+ url.pathname = `${url.pathname.replace(/\/+$/u, "")}/files`.replace(/\/{2,}/gu, "/");
55
+ return url;
56
+ }
57
+ function readSingleHeader(req, name) {
58
+ const value = req.headers[name];
59
+ return Array.isArray(value) ? value[0] : value;
60
+ }
61
+ class TemporaryFileLinkStore {
62
+ logger;
63
+ recordsByUploadToken = new Map();
64
+ recordsByDownloadToken = new Map();
65
+ recordsByCacheKey = new Map();
66
+ cleanupTimer = null;
67
+ baseUrl;
68
+ rootDir;
69
+ constructor(gatewayPublicUrl, logger, rootDir = node_path_1.default.resolve(".tellymcp", "tmp", "file-links")) {
70
+ this.logger = logger;
71
+ this.baseUrl = resolvePublicFilesBaseUrl(gatewayPublicUrl);
72
+ this.rootDir = rootDir;
73
+ }
74
+ async start() {
75
+ await (0, promises_1.mkdir)(this.rootDir, { recursive: true });
76
+ const entries = await (0, promises_1.readdir)(this.rootDir).catch(() => []);
77
+ await Promise.all(entries.map((entry) => (0, promises_1.rm)(node_path_1.default.join(this.rootDir, entry), { force: true, recursive: true })));
78
+ this.cleanupTimer = setInterval(() => {
79
+ void this.cleanupExpired();
80
+ }, FILE_LINK_CLEANUP_INTERVAL_MS);
81
+ this.cleanupTimer.unref();
82
+ }
83
+ async stop() {
84
+ if (this.cleanupTimer) {
85
+ clearInterval(this.cleanupTimer);
86
+ this.cleanupTimer = null;
87
+ }
88
+ await this.cleanupExpired(true);
89
+ }
90
+ createTicket(cacheKey) {
91
+ const id = (0, node_crypto_1.randomUUID)();
92
+ const uploadToken = (0, node_crypto_1.randomBytes)(32).toString("base64url");
93
+ const downloadToken = (0, node_crypto_1.randomBytes)(32).toString("base64url");
94
+ const expiresAtMs = Date.now() + FILE_LINK_TTL_MS;
95
+ const record = {
96
+ id,
97
+ uploadToken,
98
+ downloadToken,
99
+ cacheKey,
100
+ filePath: node_path_1.default.join(this.rootDir, `${id}.bin`),
101
+ partialPath: node_path_1.default.join(this.rootDir, `${id}.upload`),
102
+ expiresAtMs,
103
+ status: "pending",
104
+ downloadCount: 0,
105
+ };
106
+ this.recordsByUploadToken.set(uploadToken, record);
107
+ this.recordsByDownloadToken.set(downloadToken, record);
108
+ this.recordsByCacheKey.set(cacheKey, record);
109
+ const uploadUrl = new URL(this.baseUrl);
110
+ uploadUrl.pathname = `${this.baseUrl.pathname}/upload/${uploadToken}`;
111
+ return {
112
+ upload_url: uploadUrl.toString(),
113
+ download_token: downloadToken,
114
+ expires_at: new Date(expiresAtMs).toISOString(),
115
+ };
116
+ }
117
+ async discard(downloadToken) {
118
+ const record = this.recordsByDownloadToken.get(downloadToken);
119
+ if (record) {
120
+ await this.deleteRecord(record);
121
+ }
122
+ }
123
+ getReadyLink(downloadToken) {
124
+ const record = this.recordsByDownloadToken.get(downloadToken);
125
+ if (!record ||
126
+ record.expiresAtMs <= Date.now() ||
127
+ record.status !== "ready") {
128
+ throw new Error("Temporary file upload did not complete.");
129
+ }
130
+ if (!record.filename ||
131
+ !record.mimetype ||
132
+ record.sizeBytes === undefined) {
133
+ throw new Error("Temporary file metadata is incomplete.");
134
+ }
135
+ const downloadUrl = new URL(this.baseUrl);
136
+ downloadUrl.pathname = `${this.baseUrl.pathname}/download/${record.downloadToken}/${encodeURIComponent(record.filename)}`;
137
+ return {
138
+ url: downloadUrl.toString(),
139
+ filename: record.filename,
140
+ mimetype: record.mimetype,
141
+ size_bytes: record.sizeBytes,
142
+ expires_at: new Date(record.expiresAtMs).toISOString(),
143
+ };
144
+ }
145
+ async readCachedBase64(cacheKey, maxBytes) {
146
+ const record = this.recordsByCacheKey.get(cacheKey);
147
+ if (!record ||
148
+ record.status !== "ready" ||
149
+ record.expiresAtMs <= Date.now() ||
150
+ !record.filename ||
151
+ !record.mimetype ||
152
+ record.sizeBytes === undefined ||
153
+ record.sizeBytes > maxBytes) {
154
+ return null;
155
+ }
156
+ const content = await (0, promises_1.readFile)(record.filePath);
157
+ return {
158
+ data: content.toString("base64"),
159
+ filename: record.filename,
160
+ mimetype: record.mimetype,
161
+ size_bytes: record.sizeBytes,
162
+ };
163
+ }
164
+ matches(pathname) {
165
+ return pathname.startsWith("/files/");
166
+ }
167
+ async handleRequest(req, res, pathname) {
168
+ if (!this.matches(pathname)) {
169
+ return false;
170
+ }
171
+ const uploadMatch = /^\/files\/upload\/([^/]+)$/u.exec(pathname);
172
+ if (uploadMatch) {
173
+ await this.handleUpload(req, res, uploadMatch[1] ?? "");
174
+ return true;
175
+ }
176
+ const downloadMatch = /^\/files\/download\/([^/]+)(?:\/[^/]*)?$/u.exec(pathname);
177
+ if (downloadMatch) {
178
+ await this.handleDownload(req, res, downloadMatch[1] ?? "");
179
+ return true;
180
+ }
181
+ res.statusCode = 404;
182
+ res.end("Not found");
183
+ return true;
184
+ }
185
+ async handleUpload(req, res, uploadToken) {
186
+ if (req.method !== "PUT") {
187
+ res.statusCode = 405;
188
+ res.end("Method not allowed");
189
+ return;
190
+ }
191
+ const record = this.recordsByUploadToken.get(uploadToken);
192
+ if (!record ||
193
+ record.expiresAtMs <= Date.now() ||
194
+ record.status !== "pending") {
195
+ res.statusCode = 404;
196
+ res.end("Upload ticket not found");
197
+ return;
198
+ }
199
+ const declaredLength = Number(readSingleHeader(req, "content-length") ?? "0");
200
+ if (!Number.isFinite(declaredLength) ||
201
+ declaredLength < 0 ||
202
+ declaredLength > FILE_LINK_MAX_BYTES) {
203
+ res.statusCode = 413;
204
+ res.end("Invalid or oversized content-length");
205
+ return;
206
+ }
207
+ let receivedBytes = 0;
208
+ const limiter = new node_stream_1.Transform({
209
+ transform(chunk, _encoding, callback) {
210
+ receivedBytes += chunk.byteLength;
211
+ if (receivedBytes > FILE_LINK_MAX_BYTES) {
212
+ callback(new Error("Temporary file exceeds the 32 MiB limit."));
213
+ return;
214
+ }
215
+ callback(null, chunk);
216
+ },
217
+ });
218
+ try {
219
+ await (0, promises_2.pipeline)(req, limiter, (0, node_fs_1.createWriteStream)(record.partialPath, { flags: "wx" }));
220
+ if (receivedBytes !== declaredLength) {
221
+ throw new Error("Uploaded file size does not match content-length.");
222
+ }
223
+ await (0, promises_1.rename)(record.partialPath, record.filePath);
224
+ record.filename = sanitizeFilename(decodeFilenameHeader(readSingleHeader(req, "x-telly-filename")));
225
+ record.mimetype =
226
+ readSingleHeader(req, "content-type")?.split(";", 1)[0]?.trim() ||
227
+ "application/octet-stream";
228
+ record.sizeBytes = receivedBytes;
229
+ record.status = "ready";
230
+ this.recordsByUploadToken.delete(uploadToken);
231
+ res.statusCode = 204;
232
+ res.end();
233
+ }
234
+ catch (error) {
235
+ await (0, promises_1.rm)(record.partialPath, { force: true });
236
+ this.logger.warn("Temporary file upload failed", {
237
+ transferId: record.id,
238
+ error: error instanceof Error ? error.message : String(error),
239
+ });
240
+ res.statusCode = 400;
241
+ res.end("Upload failed");
242
+ }
243
+ }
244
+ async handleDownload(req, res, downloadToken) {
245
+ if (req.method !== "GET" && req.method !== "HEAD") {
246
+ res.statusCode = 405;
247
+ res.end("Method not allowed");
248
+ return;
249
+ }
250
+ const record = this.recordsByDownloadToken.get(downloadToken);
251
+ if (!record ||
252
+ record.status !== "ready" ||
253
+ record.expiresAtMs <= Date.now() ||
254
+ !record.filename ||
255
+ !record.mimetype ||
256
+ record.sizeBytes === undefined ||
257
+ record.downloadCount >= FILE_LINK_MAX_DOWNLOADS) {
258
+ res.statusCode = 404;
259
+ res.end("File link expired or unavailable");
260
+ return;
261
+ }
262
+ res.statusCode = 200;
263
+ res.setHeader("content-type", record.mimetype);
264
+ res.setHeader("content-length", record.sizeBytes);
265
+ res.setHeader("content-disposition", `attachment; filename*=UTF-8''${encodeContentDispositionFilename(record.filename)}`);
266
+ res.setHeader("cache-control", "private, no-store, max-age=0");
267
+ res.setHeader("x-content-type-options", "nosniff");
268
+ if (req.method === "HEAD") {
269
+ res.end();
270
+ return;
271
+ }
272
+ record.downloadCount += 1;
273
+ try {
274
+ await (0, promises_2.pipeline)((0, node_fs_1.createReadStream)(record.filePath), res);
275
+ }
276
+ catch (error) {
277
+ record.downloadCount -= 1;
278
+ this.logger.warn("Temporary file download failed", {
279
+ transferId: record.id,
280
+ error: error instanceof Error ? error.message : String(error),
281
+ });
282
+ if (!res.writableEnded) {
283
+ res.destroy(error instanceof Error ? error : undefined);
284
+ }
285
+ }
286
+ }
287
+ async cleanupExpired(force = false) {
288
+ const now = Date.now();
289
+ const records = [...this.recordsByDownloadToken.values()];
290
+ await Promise.all(records
291
+ .filter((record) => force || record.expiresAtMs <= now)
292
+ .map((record) => this.deleteRecord(record)));
293
+ }
294
+ async deleteRecord(record) {
295
+ this.recordsByUploadToken.delete(record.uploadToken);
296
+ this.recordsByDownloadToken.delete(record.downloadToken);
297
+ if (this.recordsByCacheKey.get(record.cacheKey) === record) {
298
+ this.recordsByCacheKey.delete(record.cacheKey);
299
+ }
300
+ await Promise.all([
301
+ (0, promises_1.rm)(record.filePath, { force: true }),
302
+ (0, promises_1.rm)(record.partialPath, { force: true }),
303
+ ]);
304
+ }
305
+ }
306
+ exports.TemporaryFileLinkStore = TemporaryFileLinkStore;
307
+ exports.TEMPORARY_FILE_LINK_MAX_BYTES = FILE_LINK_MAX_BYTES;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertWorkspaceFilePathAllowed = assertWorkspaceFilePathAllowed;
7
+ exports.resolveWorkspaceFileMimeType = resolveWorkspaceFileMimeType;
8
+ exports.decodeWorkspaceTextContent = decodeWorkspaceTextContent;
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_util_1 = require("node:util");
11
+ const mime_types_1 = require("mime-types");
12
+ const BLOCKED_DIRECTORY_NAMES = new Set([
13
+ ".aws",
14
+ ".azure",
15
+ ".docker",
16
+ ".git",
17
+ ".gnupg",
18
+ ".kube",
19
+ ".ssh",
20
+ ".tellymcp",
21
+ ]);
22
+ const BLOCKED_FILE_NAMES = new Set([
23
+ ".dockerconfigjson",
24
+ ".git-credentials",
25
+ ".netrc",
26
+ ".npmrc",
27
+ ".pypirc",
28
+ ".yarnrc",
29
+ ".yarnrc.yml",
30
+ "_netrc",
31
+ "credentials",
32
+ "credentials.json",
33
+ "id_dsa",
34
+ "id_ecdsa",
35
+ "id_ed25519",
36
+ "id_rsa",
37
+ "secrets.json",
38
+ "service-account-key.json",
39
+ "service-account.json",
40
+ ]);
41
+ const BLOCKED_FILE_EXTENSIONS = new Set([
42
+ ".jks",
43
+ ".kdbx",
44
+ ".key",
45
+ ".keystore",
46
+ ".p12",
47
+ ".pem",
48
+ ".pfx",
49
+ ]);
50
+ const SAFE_ENV_TEMPLATE_MARKERS = new Set([
51
+ "dist",
52
+ "example",
53
+ "sample",
54
+ "template",
55
+ ]);
56
+ const SOURCE_MIME_TYPES = new Map([
57
+ [".astro", "text/plain"],
58
+ [".cts", "text/typescript"],
59
+ [".gql", "application/graphql"],
60
+ [".graphql", "application/graphql"],
61
+ [".jsx", "text/javascript"],
62
+ [".mdx", "text/markdown"],
63
+ [".mts", "text/typescript"],
64
+ [".prisma", "text/plain"],
65
+ [".svelte", "text/plain"],
66
+ [".toml", "application/toml"],
67
+ [".ts", "text/typescript"],
68
+ [".tsx", "text/typescript"],
69
+ [".vue", "text/plain"],
70
+ ]);
71
+ const SOURCE_FILE_MIME_TYPES = new Map([
72
+ ["dockerfile", "text/plain"],
73
+ ["makefile", "text/plain"],
74
+ ]);
75
+ function isBlockedEnvFile(fileName) {
76
+ if (fileName === ".env") {
77
+ return true;
78
+ }
79
+ if (!fileName.startsWith(".env.")) {
80
+ return false;
81
+ }
82
+ const suffixSegments = fileName.slice(".env.".length).split(".");
83
+ return !suffixSegments.some((segment) => SAFE_ENV_TEMPLATE_MARKERS.has(segment));
84
+ }
85
+ function assertWorkspaceFilePathAllowed(filePath) {
86
+ const normalized = filePath.replaceAll("\\", "/").toLowerCase();
87
+ const segments = normalized.split("/").filter(Boolean);
88
+ const fileName = segments.at(-1) ?? "";
89
+ if (segments.some((segment) => BLOCKED_DIRECTORY_NAMES.has(segment)) ||
90
+ BLOCKED_FILE_NAMES.has(fileName) ||
91
+ BLOCKED_FILE_EXTENSIONS.has(node_path_1.default.posix.extname(fileName)) ||
92
+ isBlockedEnvFile(fileName)) {
93
+ throw new Error("Access to sensitive workspace files is blocked.");
94
+ }
95
+ }
96
+ function resolveWorkspaceFileMimeType(fileName) {
97
+ const normalized = fileName.toLowerCase();
98
+ return (SOURCE_FILE_MIME_TYPES.get(normalized) ||
99
+ SOURCE_MIME_TYPES.get(node_path_1.default.extname(normalized)) ||
100
+ (0, mime_types_1.lookup)(fileName) ||
101
+ "application/octet-stream");
102
+ }
103
+ function decodeWorkspaceTextContent(content) {
104
+ let text;
105
+ try {
106
+ text = new node_util_1.TextDecoder("utf-8", { fatal: true }).decode(content);
107
+ }
108
+ catch {
109
+ throw new Error("type='text' requires valid UTF-8 content; use type='url' or type='base64' for binary files.");
110
+ }
111
+ if (text.includes("\0")) {
112
+ throw new Error("type='text' does not accept binary content; use type='url' or type='base64'.");
113
+ }
114
+ return text;
115
+ }
@@ -8,6 +8,7 @@ const node_path_1 = __importDefault(require("node:path"));
8
8
  const schema_1 = require("../../../entities/request/model/schema");
9
9
  const redactSecrets_1 = require("../../../shared/lib/redact-secrets/redactSecrets");
10
10
  const client_1 = require("../../../shared/integrations/terminal/client");
11
+ const bodyLimits_1 = require("../../../shared/lib/bodyLimits");
11
12
  const relay_1 = require("../../../app/webapp/relay");
12
13
  const gatewayClientAccess_1 = require("../../distributed-client/model/gatewayClientAccess");
13
14
  function mergeSavedContext(input, session) {
@@ -300,7 +301,10 @@ class NotifyService {
300
301
  if (!binding) {
301
302
  throw new Error("Gateway relay session has no active Telegram route yet.");
302
303
  }
303
- const sendResult = await this.transport.sendDocumentBufferToChat?.(binding.telegramChatId, input.fileName, Buffer.from(input.contentBase64, "base64"), input.caption);
304
+ (0, bodyLimits_1.assertStringBodySize)(input.contentBase64);
305
+ const decodedContent = Buffer.from(input.contentBase64, "base64");
306
+ (0, bodyLimits_1.assertBodySize)(decodedContent.byteLength);
307
+ const sendResult = await this.transport.sendDocumentBufferToChat?.(binding.telegramChatId, input.fileName, decodedContent, input.caption);
304
308
  if (!sendResult) {
305
309
  throw new Error("Gateway transport document proxy is unavailable.");
306
310
  }
@@ -321,8 +325,8 @@ class NotifyService {
321
325
  ...(this._config.distributed.gatewayAuthToken
322
326
  ? { gatewayAuthToken: this._config.distributed.gatewayAuthToken }
323
327
  : {}),
324
- ...(this._config.distributed.gatewayToken
325
- ? { gatewayToken: this._config.distributed.gatewayToken }
328
+ ...(this._config.distributed.gatewayScopeToken
329
+ ? { gatewayScopeToken: this._config.distributed.gatewayScopeToken }
326
330
  : {}),
327
331
  ...(this._config.project.name
328
332
  ? { projectName: this._config.project.name }
@@ -378,8 +382,8 @@ class NotifyService {
378
382
  ...(this._config.distributed.gatewayAuthToken
379
383
  ? { gatewayAuthToken: this._config.distributed.gatewayAuthToken }
380
384
  : {}),
381
- ...(this._config.distributed.gatewayToken
382
- ? { gatewayToken: this._config.distributed.gatewayToken }
385
+ ...(this._config.distributed.gatewayScopeToken
386
+ ? { gatewayScopeToken: this._config.distributed.gatewayScopeToken }
383
387
  : {}),
384
388
  ...(this._config.project.name
385
389
  ? { projectName: this._config.project.name }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GetRuntimeDiagnosticsTool = void 0;
4
+ const schema_1 = require("../../../entities/request/model/schema");
5
+ class GetRuntimeDiagnosticsTool {
6
+ sessionContextService;
7
+ constructor(sessionContextService) {
8
+ this.sessionContextService = sessionContextService;
9
+ }
10
+ register(server) {
11
+ server.registerTool("get_runtime_diagnostics", {
12
+ title: "Get Runtime Diagnostics",
13
+ description: "Run safe, read-only health checks for a selected console: environment schema, package/protocol version, runtime state store, PTY state, gateway configuration, and gateway-to-client relay. Redis is probed only for gateway runtimes. Secrets and raw connection strings are never returned. In gateway mode, pass session_id exactly as returned by list_gateway_sessions.",
14
+ inputSchema: schema_1.getRuntimeDiagnosticsInputSchema,
15
+ outputSchema: schema_1.getRuntimeDiagnosticsOutputSchema,
16
+ }, async (args) => {
17
+ const output = await this.sessionContextService.getRuntimeDiagnostics(args);
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text",
22
+ text: JSON.stringify(output, null, 2),
23
+ },
24
+ ],
25
+ structuredContent: output,
26
+ };
27
+ });
28
+ }
29
+ }
30
+ exports.GetRuntimeDiagnosticsTool = GetRuntimeDiagnosticsTool;