@deadragdoll/tellymcp 0.0.6 → 0.0.8

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.
@@ -90,4 +90,6 @@ SOCKS5_PROXY=
90
90
 
91
91
  # Логирование.
92
92
  LOG_LEVEL=info
93
+ LOG_FILE_ENABLED=false
94
+ LOG_FILE_PATH=.tellymcp/log.jsonl
93
95
  ENABLE_LOGFEED=0
@@ -112,8 +112,9 @@ SOCKS5_PROXY=
112
112
 
113
113
  # Логирование.
114
114
  LOG_LEVEL=info
115
- ENABLE_LOGFEED=0
116
115
  LOG_FILE_ENABLED=false
116
+ LOG_FILE_PATH=.tellymcp/log.jsonl
117
+ ENABLE_LOGFEED=0
117
118
 
118
119
  # Секреты шлюза.
119
120
  SESSION_SECRET=change_me_session_secret
package/README-ru.md CHANGED
@@ -73,6 +73,12 @@ Telegram HITL здесь тоже есть, но он не исчерпывае
73
73
  - partner notes и partner files
74
74
  - tools sync и version checks
75
75
 
76
+ Важно для `Share`:
77
+
78
+ - текущая сессия должна выполнить работу сама
79
+ - target-сессии отправляется только результат
80
+ - исходное поручение не должно пересылаться дальше как новая задача
81
+
76
82
  Полный список MCP tools лучше держать ниже по README и в самом MCP server, а не на первом экране.
77
83
 
78
84
  ## Prerequisites
@@ -121,6 +127,10 @@ tmux attach -t backend
121
127
 
122
128
  Если агентов несколько, лучше запускать каждого в своей tmux-сессии или pane и привязывать отдельно.
123
129
 
130
+ Если tmux pane пересоздан и его `pane_id` изменился, TellyMCP теперь пытается автоматически восстановить актуальный target по сохранённым tmux session/window/pane hints.
131
+
132
+ Если авто-восстановление не удалось, в Telegram придёт operational warning, а не только запись в backend log.
133
+
124
134
  ## Быстрый старт
125
135
 
126
136
  ### 1. Standalone client без шлюза
@@ -229,6 +239,99 @@ tellymcp run --env .env
229
239
 
230
240
  - `https://your-host.example/api/mcp`
231
241
 
242
+ ## Docker: только для инфраструктуры или для `gateway`-only
243
+
244
+ Docker больше не является основным способом запуска TellyMCP, но один container path поддерживается:
245
+
246
+ - запуск только `gateway`-ноды в контейнере
247
+
248
+ Это вариант именно для чистого control-plane узла:
249
+
250
+ - без локальных agent sessions
251
+ - без локального `tmux`
252
+ - не для `client`
253
+ - не для `both`
254
+
255
+ Плюс `docker-compose.yml` по-прежнему может поднимать локальную инфраструктуру:
256
+
257
+ - `redis` для всех режимов
258
+ - `postgres` для `gateway` / `both`
259
+ - `rabbitmq` только если нужен durable fanout на шлюзе
260
+
261
+ Только Redis, для `standalone` или `client`:
262
+
263
+ ```bash
264
+ docker compose up -d redis
265
+ ```
266
+
267
+ Redis + Postgres, для `gateway` или `both`:
268
+
269
+ ```bash
270
+ docker compose --profile gateway up -d
271
+ ```
272
+
273
+ Добавить RabbitMQ при необходимости:
274
+
275
+ ```bash
276
+ docker compose --profile gateway --profile rmq up -d
277
+ ```
278
+
279
+ Полный gateway stack в Docker:
280
+
281
+ 1. Скопировать пример:
282
+
283
+ ```bash
284
+ cp .env.example.gateway .env-gateway
285
+ ```
286
+
287
+ 2. Отредактировать `.env-gateway` и задать минимум:
288
+
289
+ - `TELEGRAM_BOT_TOKEN`
290
+ - `TELEGRAM_BOT_USERNAME`
291
+ - `WEBAPP_PUBLIC_URL`
292
+ - `GATEWAY_PUBLIC_URL`
293
+ - `GATEWAY_WS_URL`
294
+ - `MCP_HTTP_BEARER_TOKEN`
295
+
296
+ 3. Запустить:
297
+
298
+ ```bash
299
+ docker compose up -d
300
+ ```
301
+
302
+ Это поднимет:
303
+
304
+ - `redis`
305
+ - `postgres`
306
+ - `tellymcp-gateway`
307
+
308
+ Внутри Docker compose сам переопределяет:
309
+
310
+ - `MCP_HTTP_HOST=0.0.0.0`
311
+ - `REDIS_HOST=redis`
312
+ - `DB_HOST=postgres`
313
+
314
+ Ожидаемые endpoint'ы:
315
+
316
+ - `http://127.0.0.1:8080/api/healthz`
317
+ - `http://127.0.0.1:8080/api/mcp`
318
+ - `http://127.0.0.1:8080/api/webapp`
319
+ - `http://127.0.0.1:8080/api/gateway`
320
+
321
+ Остановить всё:
322
+
323
+ ```bash
324
+ docker compose down
325
+ ```
326
+
327
+ Сам TellyMCP при этом запускается напрямую на хосте:
328
+
329
+ ```bash
330
+ tellymcp run --env .env
331
+ ```
332
+
333
+ Для `client` и `both` по-прежнему рекомендуется именно хостовый запуск.
334
+
232
335
  ## Как начать работу с ботом изнутри агента
233
336
 
234
337
  После подключения MCP можно просто написать агенту обычной фразой, что нужно привязаться к Telegram.
@@ -332,6 +435,9 @@ tellymcp mcp --help
332
435
  - `PROXY_USE=http|socks5`
333
436
  - `HTTP_PROXY`
334
437
  - `SOCKS5_PROXY`
438
+ - `LOG_LEVEL`
439
+ - `LOG_FILE_ENABLED`
440
+ - `LOG_FILE_PATH`
335
441
 
336
442
  Только client:
337
443
 
package/README.md CHANGED
@@ -122,6 +122,10 @@ Use short, meaningful names such as:
122
122
 
123
123
  If you run multiple agents, put each one in its own tmux session or pane and pair them separately.
124
124
 
125
+ If a tmux pane is recreated and its pane id changes, TellyMCP now tries to recover the live pane target automatically from saved tmux session, window, and pane hints.
126
+
127
+ If auto-recovery fails, Telegram sends an operational warning so the problem is visible to the human user, not only in backend logs.
128
+
125
129
  ## Quick start
126
130
 
127
131
  ### Standalone client node
@@ -412,10 +416,11 @@ Canonical instructions:
412
416
  - the `TOOLS.md` version marker
413
417
  - the file content itself
414
418
 
415
- Logs are written in two places at the same time:
419
+ Logs use one runtime model:
416
420
 
417
- - pretty console output to `stderr`
418
- - JSONL file at `.telegram-human-mcp/log.jsonl`
421
+ - `pino-pretty` console output to `stderr`
422
+ - optional JSONL file sink via `LOG_FILE_ENABLED=true` and `LOG_FILE_PATH=...`
423
+ - optional in-app `LogFeed` buffer for Telegram/UI diagnostics when `ENABLE_LOGFEED=1`
419
424
 
420
425
  If Telegram access requires a proxy, the bot transport can use:
421
426
 
@@ -489,6 +494,8 @@ Current file model:
489
494
  - `vfs/minio` are no longer part of the active Telegram file exchange path
490
495
  - if an agent must send a real local file to a partner, prefer `send_partner_file`
491
496
  over plain `send_partner_note`
497
+ - for `Share`, the current session must do the work itself and send only the result
498
+ - `Share` must not forward the original task into the target session as a new assignment
492
499
 
493
500
  Current presence model:
494
501
 
@@ -552,10 +559,10 @@ Recommended local dev settings:
552
559
  - install browser binaries once with `npx playwright install chromium`
553
560
  - install browser binaries once with `tellymcp browser install`
554
561
 
555
- Recommended Docker settings:
562
+ Recommended headless server settings:
556
563
 
557
564
  - `BROWSER_HEADLESS=true`
558
- - target the host dev server through `http://host.docker.internal:3000`
565
+ - target the app through a reachable host or LAN address, for example `http://127.0.0.1:3000` or `http://192.168.x.x:3000`
559
566
 
560
567
  Current browser tools:
561
568
 
@@ -829,75 +836,111 @@ If `MCP_HTTP_BEARER_TOKEN` is configured:
829
836
  `yarn dev:gw:telegram` is still available, but it only starts the `telegram_mcp` feature node.
830
837
  It does not expose HTTP by itself anymore. `/mcp`, `/webapp`, and `/healthz` are now served only through the Moleculer API gateway aliases in the full `dev:gw` / `start:gw` runtime, or through a separate gateway node in the same namespace.
831
838
 
832
- ## Optional Docker deployment
839
+ ## Optional Docker infrastructure
840
+
841
+ Docker is no longer the default way to run TellyMCP, but there is one supported container path:
833
842
 
834
- Docker is no longer required for the default product install flow.
843
+ - `gateway`-only container deployment
835
844
 
836
- This repository still includes a single-container deployment path without an internal nginx layer for ops/self-hosted scenarios.
845
+ This is intended for a pure control-plane node:
837
846
 
838
- Inside the container:
847
+ - no local agent sessions
848
+ - no local `tmux`
849
+ - no `client` mode
850
+ - no `both` mode
839
851
 
840
- - `node` runs the MCP HTTP service on `0.0.0.0:8787`
841
- - `redis-server` runs on `127.0.0.1:6379`
842
- - the application itself serves:
843
- - `/mcp`
844
- - `/webapp`
845
- - `/healthz`
846
- - `/sessions`
847
- - `/prune`
852
+ The repository also keeps Docker for local infrastructure:
848
853
 
849
- This means an external reverse proxy can forward directly to container port `8787`, while all app routing stays inside the Node service.
854
+ - `redis` for all modes
855
+ - `postgres` for `gateway` / `both`
856
+ - `rabbitmq` only if you want durable fanout on the gateway
850
857
 
851
- Build the image fully inside Docker:
858
+ Start Redis only, for `standalone` or `client` mode:
852
859
 
853
860
  ```bash
854
- docker compose build
861
+ docker compose up -d redis
855
862
  ```
856
863
 
857
- Run it:
864
+ Start Redis + Postgres, for `gateway` or `both` mode:
858
865
 
859
866
  ```bash
860
- docker compose up -d
867
+ docker compose --profile gateway up -d
861
868
  ```
862
869
 
863
- Stop it:
870
+ Add RabbitMQ only when you need it:
864
871
 
865
872
  ```bash
866
- docker compose down
873
+ docker compose --profile gateway --profile rmq up -d
867
874
  ```
868
875
 
869
- The compose file:
876
+ Run a full gateway container stack with Redis and Postgres:
870
877
 
871
- - builds the image from this repository
872
- - injects `.env`
873
- - overrides runtime networking so the app talks to local in-container Redis and listens on `0.0.0.0:8787`
874
- - publishes only `8787:8787`
875
- - keeps `host.docker.internal` available for optional host-side development integrations
876
- - persists Redis state in `./data/redis`
878
+ 1. Copy the example:
877
879
 
878
- After startup:
880
+ ```bash
881
+ cp .env.example.gateway .env-gateway
882
+ ```
879
883
 
880
- - MCP is reachable at `http://<host>:8787/mcp`
881
- - Mini App static/API routes are reachable under `http://<host>:8787/webapp/`
882
- - health check is at `http://<host>:8787/healthz`
884
+ 2. Edit `.env-gateway` and set at minimum:
885
+
886
+ - `TELEGRAM_BOT_TOKEN`
887
+ - `TELEGRAM_BOT_USERNAME`
888
+ - `WEBAPP_PUBLIC_URL`
889
+ - `GATEWAY_PUBLIC_URL`
890
+ - `GATEWAY_WS_URL`
891
+ - `MCP_HTTP_BEARER_TOKEN`
883
892
 
884
- Recommended external reverse proxy pattern:
893
+ 3. Start the stack:
885
894
 
886
- - external proxy forwards `/mcp` to `http://<container-host>:8787/mcp`
887
- - external proxy forwards `/webapp/` to `http://<container-host>:8787/webapp/`
888
- - or, if you prefer, the external proxy can forward a wider prefix directly to `http://<container-host>:8787`
889
- - no direct external access is needed to in-container Redis
890
- - `tmux` access is expected to be direct from the running `tellymcp` process
895
+ ```bash
896
+ docker compose up -d
897
+ ```
891
898
 
892
- Important:
899
+ This starts:
900
+
901
+ - `redis`
902
+ - `postgres`
903
+ - `tellymcp-gateway`
904
+
905
+ Inside Docker, compose overrides:
906
+
907
+ - `MCP_HTTP_HOST=0.0.0.0`
908
+ - `REDIS_HOST=redis`
909
+ - `DB_HOST=postgres`
910
+
911
+ Public endpoint expectations stay the same:
912
+
913
+ - `http://127.0.0.1:8080/api/healthz`
914
+ - `http://127.0.0.1:8080/api/mcp`
915
+ - `http://127.0.0.1:8080/api/webapp`
916
+ - `http://127.0.0.1:8080/api/gateway`
917
+
918
+ Stop everything:
919
+
920
+ ```bash
921
+ docker compose down
922
+ ```
923
+
924
+ Default published ports:
925
+
926
+ - Redis: `6379`
927
+ - Postgres: `5432`
928
+ - RabbitMQ AMQP: `5672`
929
+ - RabbitMQ UI: `15672`
930
+
931
+ The TellyMCP process itself should run directly on the host:
932
+
933
+ ```bash
934
+ tellymcp run --env .env
935
+ ```
936
+
937
+ This keeps:
893
938
 
894
- - pairing state
895
- - active session bindings
896
- - inbox messages
897
- - menu payload buffers
898
- - WebApp launch/session state
939
+ - direct `tmux` access
940
+ - simpler debugging
941
+ - the same runtime model for `standalone`, `client`, `gateway`, and `both`
899
942
 
900
- are all stored in Redis. In the Docker deployment they survive restarts because `./data/redis` is mounted into the container and Redis AOF is enabled.
943
+ For `client` and `both`, host execution is still the recommended model.
901
944
 
902
945
  Optional if the local tmux server uses a non-default socket:
903
946
 
package/VERSION.md CHANGED
@@ -4,6 +4,35 @@ Public, user-facing release notes for published versions of `@deadragdoll/tellym
4
4
 
5
5
  For detailed engineering history, refactors, and internal development notes, see [CHANGELOG.md](CHANGELOG.md).
6
6
 
7
+ ## 0.0.8
8
+
9
+ ### Added
10
+ - Unified logging model based on `pino`:
11
+ - pretty console output by default
12
+ - optional JSONL file sink for Alloy or other collectors
13
+ - `LOG_FILE_ENABLED=true`
14
+ - `LOG_FILE_PATH=.tellymcp/log.jsonl`
15
+ - Better tmux recovery behavior:
16
+ - when a saved pane target becomes stale after tmux recreation, TellyMCP now tries to recover the live pane automatically from stored tmux session/window/pane hints
17
+ - if auto-recovery fails, Telegram sends a clear operational warning instead of leaving the problem only in logs
18
+ - Stronger `Share` execution guidance:
19
+ - the current session must do the work itself
20
+ - it must send only the result
21
+ - it must not forward the original task to the target session as a new assignment
22
+
23
+ ### Changed
24
+ - Runtime identity and service labels now use `tellymcp` naming consistently instead of older `telegram-human-mcp` tags.
25
+ - MCP server metadata now reports the current package version and `tellymcp` service name.
26
+ - Logging config is now simpler:
27
+ - one console logging model
28
+ - one optional JSON file sink
29
+ - optional `LogFeed` buffer for UI diagnostics
30
+
31
+ ### Fixed
32
+ - Stale tmux pane ids like `%1 -> %2` no longer require manual user understanding before the service can try to wake the session again.
33
+ - Broken tmux nudge targets are now visible to the user in Telegram, not only in backend logs.
34
+ - `Share` inbox instructions are now explicit enough to reduce the chance that one agent re-delegates the task back into the collaboration graph.
35
+
7
36
  ## 0.0.3
8
37
 
9
38
  ### Added
package/dist/cli.js CHANGED
@@ -82,7 +82,7 @@ async function getPlaywrightBrowserStatus(browserEnabled) {
82
82
  }
83
83
  function printHelp() {
84
84
  const tmux = getTmuxStatus();
85
- printBanner("CLI", "Telegram Human-in-the-Loop MCP server");
85
+ printBanner("CLI", "Telegram control plane for MCP-connected coding agents");
86
86
  printSection("Usage", [
87
87
  " tellymcp init <client|gateway|both> [directory]",
88
88
  " tellymcp run [--env <file>]",
@@ -1,19 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PlaygroundDisabledError = exports.SessionIdleTimeoutExceededError = exports.TokenInvalidError = exports.TokenNotFoundError = exports.SessionTokenSignatureInvalidError = exports.SessionTokenInvalidFormatError = exports.SessionTokenMismatchError = exports.SessionTokenNotFoundError = exports.SessionTokenRevokedError = exports.SessionTokenNotActiveError = exports.SessionTokenExpiredError = exports.SessionTokenInvalidError = exports.SessionValidationError = exports.SessionRefreshError = exports.SessionMaxLifetimeExceededError = exports.SessionStealedError = exports.SessionInvalidError = exports.SessionExpiredError = exports.SessionNotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BackendError = void 0;
4
- const graphql_1 = require("graphql");
5
- class BackendError extends graphql_1.GraphQLError {
6
- constructor(message, code, type, data) {
7
- super(message, {
8
- extensions: {
9
- code: code,
10
- type: type || code,
11
- data,
12
- },
13
- });
3
+ exports.PlaygroundDisabledError = exports.SessionIdleTimeoutExceededError = exports.TokenInvalidError = exports.TokenNotFoundError = exports.SessionTokenSignatureInvalidError = exports.SessionTokenInvalidFormatError = exports.SessionTokenMismatchError = exports.SessionTokenNotFoundError = exports.SessionTokenRevokedError = exports.SessionTokenNotActiveError = exports.SessionTokenExpiredError = exports.SessionTokenInvalidError = exports.SessionValidationError = exports.SessionRefreshError = exports.SessionMaxLifetimeExceededError = exports.SessionStealedError = exports.SessionInvalidError = exports.SessionExpiredError = exports.SessionNotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.wrapUnhandledBackendError = exports.buildUnhandledBackendErrorCode = exports.BackendError = void 0;
4
+ class BackendError extends Error {
5
+ statusCode;
6
+ code;
7
+ data;
8
+ constructor(message, statusCode = 500, code, data) {
9
+ super(message);
10
+ this.name = "BackendError";
11
+ this.statusCode = statusCode;
12
+ this.code = code || String(statusCode);
13
+ this.data = data;
14
14
  }
15
15
  }
16
16
  exports.BackendError = BackendError;
17
+ const buildUnhandledBackendErrorCode = (rawName) => `XC_${rawName.toUpperCase()}`;
18
+ exports.buildUnhandledBackendErrorCode = buildUnhandledBackendErrorCode;
19
+ const wrapUnhandledBackendError = (err, rawName) => new BackendError(err.message, 502, (0, exports.buildUnhandledBackendErrorCode)(rawName));
20
+ exports.wrapUnhandledBackendError = wrapUnhandledBackendError;
17
21
  class UnauthorizedError extends BackendError {
18
22
  constructor(message = "Unauthorized", data) {
19
23
  super(message, 401, "UNAUTHORIZED", data);
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPinoTargets = createPinoTargets;
4
+ function createPinoTargets(config) {
5
+ const targets = [
6
+ {
7
+ target: "pino-pretty",
8
+ level: config.level,
9
+ options: {
10
+ destination: 2,
11
+ colorize: true,
12
+ translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l",
13
+ ignore: "pid,hostname",
14
+ singleLine: false,
15
+ },
16
+ },
17
+ ];
18
+ if (config.fileEnabled) {
19
+ targets.unshift({
20
+ target: "pino/file",
21
+ level: config.level,
22
+ options: {
23
+ destination: config.filePath,
24
+ mkdir: true,
25
+ },
26
+ });
27
+ }
28
+ return targets;
29
+ }
@@ -8,11 +8,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  const moleculer_1 = require("moleculer");
9
9
  const dotenv_1 = __importDefault(require("dotenv"));
10
10
  require("module-alias/register");
11
- const promises_1 = __importDefault(require("node:fs/promises"));
12
- const node_path_1 = __importDefault(require("node:path"));
11
+ const pino_1 = __importDefault(require("pino"));
13
12
  const logfeed_1 = require("./lib/mixins/logfeed");
14
13
  const session_errors_1 = require("./lib/mixins/session.errors");
15
- const tracer_1 = require("./lib/middlewares/tracer");
14
+ const pinoTargets_1 = require("./lib/pinoTargets");
16
15
  dotenv_1.default.config({ path: process.env.ENV_FILE ?? ".env" });
17
16
  /**
18
17
  * Moleculer ServiceBroker configuration file 1
@@ -40,72 +39,34 @@ dotenv_1.default.config({ path: process.env.ENV_FILE ?? ".env" });
40
39
  * }
41
40
  */
42
41
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
42
+ const pinoTransport = pino_1.default.transport({
43
+ targets: (0, pinoTargets_1.createPinoTargets)({
44
+ level: process.env.LOG_LEVEL || "info",
45
+ fileEnabled: process.env.LOG_FILE_ENABLED === "true",
46
+ filePath: process.env.LOG_FILE_PATH || ".tellymcp/log.jsonl",
47
+ }),
48
+ });
43
49
  const logger = [
44
50
  {
45
- type: "Console",
51
+ type: "Pino",
46
52
  options: {
47
- colors: true,
48
- moduleColors: true,
49
- formatter: process.env.LOGFORMATTER ?? "short",
50
- objectPrinter: null,
51
- autoPadding: false,
53
+ pino: {
54
+ options: {
55
+ name: "tellymcp-broker",
56
+ level: process.env.LOG_LEVEL || "info",
57
+ timestamp: pino_1.default.stdTimeFunctions.isoTime,
58
+ },
59
+ destination: pinoTransport,
60
+ },
52
61
  },
53
62
  },
54
63
  ];
55
64
  const metricsEnabled = process.env.MOLECULER_METRICS === "true";
56
65
  const metricsPort = +(process.env.METRICS_PORT || 3030);
57
66
  const metricsPath = process.env.METRICS_PATH || "/metrics";
58
- const logFileEnabled = process.env.LOG_FILE_ENABLED === "true";
59
67
  const logFeedEnabled = process.env.ENABLE_LOGFEED != null
60
68
  ? !["0", "false", "no", "off"].includes(process.env.ENABLE_LOGFEED.toLowerCase())
61
69
  : process.env.LOGFEED_ENABLED !== "false";
62
- const logFileFolder = process.env.LOG_FILE_FOLDER || "./logs";
63
- const logFileName = process.env.LOG_FILE_NAME || "moleculer-{date}.log";
64
- const logFileRetentionDays = +(process.env.LOG_FILE_RETENTION_DAYS || 14);
65
- const cleanupLogFiles = async () => {
66
- if (!logFileEnabled || !Number.isFinite(logFileRetentionDays) || logFileRetentionDays <= 0) {
67
- return;
68
- }
69
- const logFolder = node_path_1.default.resolve(logFileFolder);
70
- const now = Date.now();
71
- const maxAgeMs = logFileRetentionDays * 24 * 60 * 60 * 1000;
72
- const filenamePattern = new RegExp(`^${logFileName
73
- .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
74
- .replace(/\\\{date\\\}/g, "\\d{4}-\\d{2}-\\d{2}")
75
- .replace(/\\\{nodeID\\\}/g, "[^/]+")
76
- .replace(/\\\{namespace\\\}/g, "[^/]+")}$`);
77
- try {
78
- const entries = await promises_1.default.readdir(logFolder, { withFileTypes: true });
79
- await Promise.all(entries
80
- .filter(entry => entry.isFile() && filenamePattern.test(entry.name))
81
- .map(async (entry) => {
82
- const filePath = node_path_1.default.join(logFolder, entry.name);
83
- const stats = await promises_1.default.stat(filePath);
84
- if (now - stats.mtimeMs > maxAgeMs) {
85
- await promises_1.default.unlink(filePath);
86
- }
87
- }));
88
- }
89
- catch (error) {
90
- if (error.code !== "ENOENT") {
91
- console.warn("LOG_FILE cleanup failed:", error);
92
- }
93
- }
94
- };
95
- if (logFileEnabled) {
96
- void cleanupLogFiles();
97
- logger.push({
98
- type: "File",
99
- options: {
100
- level: process.env.LOG_FILE_LEVEL || process.env.LOG_LEVEL || "info",
101
- folder: logFileFolder,
102
- filename: logFileName,
103
- formatter: process.env.LOG_FILE_FORMATTER || "json",
104
- eol: "\n",
105
- interval: +(process.env.LOG_FILE_INTERVAL_MS || 1000),
106
- },
107
- });
108
- }
109
70
  if (logFeedEnabled) {
110
71
  logger.push(new logfeed_1.LogFeedLogger({
111
72
  level: process.env.LOGFEED_LEVEL || process.env.LOG_LEVEL || "info",
@@ -206,18 +167,15 @@ const brokerConfig = {
206
167
  // Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html
207
168
  validator: true,
208
169
  errorHandler: (err, { ctx, service }) => {
209
- // ctx.service.logger.error("errorHandler", err);
210
170
  if (err instanceof session_errors_1.BackendError) {
211
- // ctx.meta.$statusCode = err.extensions.code;
212
171
  return err;
213
172
  }
214
173
  else if (err instanceof Error) {
215
- // ctx.meta.$statusCode = 502;
216
174
  const rawName = ctx?.action?.rawName ||
217
175
  ctx?.action?.name ||
218
176
  service?.name ||
219
177
  "UNKNOWN";
220
- return new session_errors_1.BackendError(err.message, 502, `XC_${String(rawName).toUpperCase()}`);
178
+ return (0, session_errors_1.wrapUnhandledBackendError)(err, String(rawName));
221
179
  }
222
180
  return err;
223
181
  },
@@ -259,15 +217,11 @@ const brokerConfig = {
259
217
  },
260
218
  },
261
219
  // Register custom middlewares
262
- middlewares: [(0, tracer_1.createTracerMiddleware)()],
220
+ middlewares: [],
263
221
  // Register custom REPL commands.
264
222
  replCommands: null,
265
223
  // Called after broker created.
266
224
  // created(broker: ServiceBroker): void {},
267
- // Called after broker started.
268
- started() {
269
- console.log("ALLOWED ORIGINS", (process.env.ORIGINS ?? "").split(","));
270
- },
271
225
  // Called after broker stopped.
272
226
  // async stopped(broker: ServiceBroker): Promise<void> {},
273
227
  };
@@ -165,9 +165,18 @@ const envSchema = z.object({
165
165
  LOG_LEVEL: z
166
166
  .enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"])
167
167
  .default("info"),
168
+ LOG_FILE_ENABLED: z
169
+ .string()
170
+ .optional()
171
+ .transform((value) => value === "true"),
172
+ LOG_FILE_PATH: z.string().min(1).default(".tellymcp/log.jsonl"),
168
173
  });
169
174
  function loadConfig() {
170
- if ((0, node_fs_1.existsSync)(".env")) {
175
+ const explicitEnvFile = process.env.ENV_FILE?.trim();
176
+ if (explicitEnvFile) {
177
+ process.loadEnvFile(explicitEnvFile);
178
+ }
179
+ else if ((0, node_fs_1.existsSync)(".env")) {
171
180
  process.loadEnvFile(".env");
172
181
  }
173
182
  const parsed = envSchema.parse(process.env);
@@ -312,6 +321,8 @@ function loadConfig() {
312
321
  },
313
322
  logging: {
314
323
  level: parsed.LOG_LEVEL,
324
+ fileEnabled: parsed.LOG_FILE_ENABLED,
325
+ filePath: parsed.LOG_FILE_PATH,
315
326
  },
316
327
  };
317
328
  }
@@ -136,7 +136,7 @@ function createMcpHttpHandler(runtime, input) {
136
136
  if (requestUrl.pathname === "/healthz") {
137
137
  writeJson(res, 200, {
138
138
  ok: true,
139
- service: "telegram-human-mcp",
139
+ service: "tellymcp",
140
140
  transport: "streamable-http",
141
141
  });
142
142
  return;
@@ -5,8 +5,8 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
5
  const registry_1 = require("../../../shared/api/tool-registry/registry");
6
6
  function createMcpServer(tools) {
7
7
  const server = new mcp_js_1.McpServer({
8
- name: "telegram-human-mcp",
9
- version: "1.0.0",
8
+ name: "tellymcp",
9
+ version: "0.0.6",
10
10
  });
11
11
  (0, registry_1.registerTools)(server, tools);
12
12
  return server;
@@ -241,7 +241,7 @@ class GatewayHttpService {
241
241
  if (pathname === "/gateway/healthz") {
242
242
  writeJson(res, 200, {
243
243
  ok: true,
244
- service: "telegram-human-mcp-gateway",
244
+ service: "tellymcp-gateway",
245
245
  mode: this.config.distributed.mode,
246
246
  databaseConfigured: Boolean(process.env.DB_HOST && process.env.DB_NAME),
247
247
  s3Configured: Boolean(this.config.distributed.gatewayS3Bucket),