@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.
- package/.env.example.client +2 -0
- package/.env.example.gateway +2 -1
- package/README-ru.md +106 -0
- package/README.md +91 -48
- package/VERSION.md +29 -0
- package/dist/cli.js +1 -1
- package/dist/lib/mixins/session.errors.js +15 -11
- package/dist/lib/pinoTargets.js +29 -0
- package/dist/moleculer.config.js +20 -66
- package/dist/services/features/telegram-mcp/src/app/config/env.js +12 -1
- package/dist/services/features/telegram-mcp/src/app/http.js +1 -1
- package/dist/services/features/telegram-mcp/src/app/providers/mcp/server.js +2 -2
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +1 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +103 -3
- package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +75 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +7 -26
- package/package.json +2 -4
- package/dist/lib/middlewares/tracer.js +0 -172
- package/dist/lib/trace.js +0 -147
- package/dist/lib/traceContext.js +0 -116
|
@@ -19,6 +19,7 @@ const proxyFetch_1 = require("./proxyFetch");
|
|
|
19
19
|
const client_1 = require("../tmux/client");
|
|
20
20
|
const versionHandshake_1 = require("../../lib/version/versionHandshake");
|
|
21
21
|
const LOCAL_INDEX_FILE_NAME = "LOCAL_INDEX.md";
|
|
22
|
+
const TMUX_NUDGE_FAILURE_NOTICE_COOLDOWN_MS = 5 * 60 * 1000;
|
|
22
23
|
function trimTrailingSlashes(value) {
|
|
23
24
|
return value.replace(/\/+$/u, "");
|
|
24
25
|
}
|
|
@@ -306,6 +307,7 @@ class TelegramTransport {
|
|
|
306
307
|
screenshotMessageMenu;
|
|
307
308
|
waiters = new Map();
|
|
308
309
|
tmuxNudgeDebounceTimers = new Map();
|
|
310
|
+
tmuxNudgeFailureNoticeAt = new Map();
|
|
309
311
|
pendingRenames = new Map();
|
|
310
312
|
pendingBroadcasts = new Map();
|
|
311
313
|
pendingPartnerNotes = new Map();
|
|
@@ -2585,22 +2587,117 @@ class TelegramTransport {
|
|
|
2585
2587
|
return;
|
|
2586
2588
|
}
|
|
2587
2589
|
await this.sendTypingForSession(sessionId);
|
|
2588
|
-
|
|
2590
|
+
let tmuxTarget = session.tmuxTarget;
|
|
2591
|
+
try {
|
|
2592
|
+
await (0, client_1.sendTmuxLiteralLine)(this.config.tmux, tmuxTarget, input.message);
|
|
2593
|
+
}
|
|
2594
|
+
catch (error) {
|
|
2595
|
+
if ((0, client_1.isTmuxTargetInvalidError)(error)) {
|
|
2596
|
+
const recoveredTarget = await this.tryRecoverTmuxTarget(sessionId, session);
|
|
2597
|
+
if (recoveredTarget) {
|
|
2598
|
+
tmuxTarget = recoveredTarget;
|
|
2599
|
+
await (0, client_1.sendTmuxLiteralLine)(this.config.tmux, recoveredTarget, input.message);
|
|
2600
|
+
}
|
|
2601
|
+
else {
|
|
2602
|
+
await this.notifyTmuxTargetInvalid(sessionId, session, error);
|
|
2603
|
+
throw error;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
else {
|
|
2607
|
+
throw error;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2589
2610
|
const lastTmuxNudgeAt = new Date(nowMs).toISOString();
|
|
2590
2611
|
await this.sessionStore.setSession({
|
|
2591
2612
|
...session,
|
|
2613
|
+
tmuxTarget,
|
|
2614
|
+
...(tmuxTarget ? { tmuxPaneId: tmuxTarget } : {}),
|
|
2592
2615
|
lastTmuxNudgeAt,
|
|
2593
2616
|
});
|
|
2617
|
+
this.tmuxNudgeFailureNoticeAt.delete(sessionId);
|
|
2594
2618
|
this.logger.info("tmux nudge sent", {
|
|
2595
2619
|
sessionId,
|
|
2596
2620
|
reason: input.reason,
|
|
2597
2621
|
message: input.message,
|
|
2598
2622
|
tmuxSessionName: session.tmuxSessionName,
|
|
2599
|
-
tmuxTarget
|
|
2623
|
+
tmuxTarget,
|
|
2600
2624
|
inboxCount,
|
|
2601
2625
|
lastTmuxNudgeAt,
|
|
2602
2626
|
});
|
|
2603
2627
|
}
|
|
2628
|
+
async tryRecoverTmuxTarget(sessionId, session) {
|
|
2629
|
+
const recoveredTarget = await (0, client_1.resolveTmuxTargetFromHint)(this.config.tmux, {
|
|
2630
|
+
tmuxSessionName: session.tmuxSessionName,
|
|
2631
|
+
tmuxWindowName: session.tmuxWindowName,
|
|
2632
|
+
tmuxWindowIndex: session.tmuxWindowIndex,
|
|
2633
|
+
tmuxPaneId: session.tmuxPaneId,
|
|
2634
|
+
tmuxPaneIndex: session.tmuxPaneIndex,
|
|
2635
|
+
tmuxTarget: session.tmuxTarget,
|
|
2636
|
+
});
|
|
2637
|
+
if (!recoveredTarget || recoveredTarget === session.tmuxTarget) {
|
|
2638
|
+
return recoveredTarget;
|
|
2639
|
+
}
|
|
2640
|
+
await this.sessionStore.setSession({
|
|
2641
|
+
...session,
|
|
2642
|
+
tmuxTarget: recoveredTarget,
|
|
2643
|
+
tmuxPaneId: recoveredTarget,
|
|
2644
|
+
updatedAt: new Date().toISOString(),
|
|
2645
|
+
});
|
|
2646
|
+
this.logger.warn("tmux target auto-recovered", {
|
|
2647
|
+
sessionId,
|
|
2648
|
+
previousTmuxTarget: session.tmuxTarget,
|
|
2649
|
+
recoveredTmuxTarget: recoveredTarget,
|
|
2650
|
+
tmuxSessionName: session.tmuxSessionName,
|
|
2651
|
+
tmuxWindowName: session.tmuxWindowName,
|
|
2652
|
+
tmuxWindowIndex: session.tmuxWindowIndex,
|
|
2653
|
+
tmuxPaneIndex: session.tmuxPaneIndex,
|
|
2654
|
+
});
|
|
2655
|
+
return recoveredTarget;
|
|
2656
|
+
}
|
|
2657
|
+
async notifyTmuxTargetInvalid(sessionId, session, error) {
|
|
2658
|
+
const binding = await this.bindingStore.getBinding(sessionId);
|
|
2659
|
+
if (!binding) {
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
const nowMs = Date.now();
|
|
2663
|
+
const lastNoticeAt = this.tmuxNudgeFailureNoticeAt.get(sessionId);
|
|
2664
|
+
if (lastNoticeAt &&
|
|
2665
|
+
nowMs - lastNoticeAt < TMUX_NUDGE_FAILURE_NOTICE_COOLDOWN_MS) {
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
this.tmuxNudgeFailureNoticeAt.set(sessionId, nowMs);
|
|
2669
|
+
const sessionLabel = session.label ?? sessionId;
|
|
2670
|
+
const tmuxTarget = session.tmuxTarget ?? "unknown";
|
|
2671
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2672
|
+
try {
|
|
2673
|
+
await this.sendNotification({
|
|
2674
|
+
sessionId,
|
|
2675
|
+
sessionLabel: "TellyMCP",
|
|
2676
|
+
recipient: {
|
|
2677
|
+
telegramChatId: binding.telegramChatId,
|
|
2678
|
+
telegramUserId: binding.telegramUserId,
|
|
2679
|
+
},
|
|
2680
|
+
message: [
|
|
2681
|
+
`⚠️ Автоматический tmux nudge для сессии ${sessionLabel} не сработал.`,
|
|
2682
|
+
`Сохранённый tmux target больше недействителен: ${tmuxTarget}`,
|
|
2683
|
+
`Ошибка: ${errorMessage}`,
|
|
2684
|
+
"Обычно это значит, что pane/window/session был пересоздан.",
|
|
2685
|
+
"Перепривяжи tmux target для этой сессии.",
|
|
2686
|
+
].join("\n"),
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
catch (notifyError) {
|
|
2690
|
+
this.logger.warn("Failed to deliver tmux target failure notification", {
|
|
2691
|
+
sessionId,
|
|
2692
|
+
tmuxTarget,
|
|
2693
|
+
telegramChatId: binding.telegramChatId,
|
|
2694
|
+
telegramUserId: binding.telegramUserId,
|
|
2695
|
+
notifyError: notifyError instanceof Error
|
|
2696
|
+
? (notifyError.stack ?? notifyError.message)
|
|
2697
|
+
: String(notifyError),
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2604
2701
|
async sendTypingForSession(sessionId) {
|
|
2605
2702
|
const binding = await this.bindingStore.getBinding(sessionId);
|
|
2606
2703
|
if (!binding) {
|
|
@@ -6454,7 +6551,7 @@ class TelegramTransport {
|
|
|
6454
6551
|
telegramUserId: input.principal.telegramUserId,
|
|
6455
6552
|
sourceTelegramMessageId: input.sourceTelegramMessageId,
|
|
6456
6553
|
text: [
|
|
6457
|
-
"Пользователь просит текущую сессию
|
|
6554
|
+
"Пользователь просит текущую сессию выполнить работу и отправить результат другой сессии.",
|
|
6458
6555
|
`Маршрут отправки: ${sourceLabel} -> ${targetLabel}`,
|
|
6459
6556
|
`Тип: ${input.kind}`,
|
|
6460
6557
|
`Кратко: ${input.summary}`,
|
|
@@ -6466,6 +6563,9 @@ class TelegramTransport {
|
|
|
6466
6563
|
"Содержимое для отправки:",
|
|
6467
6564
|
input.message,
|
|
6468
6565
|
"",
|
|
6566
|
+
"Не пересылай это как новую задачу в target-сессию.",
|
|
6567
|
+
"Сначала выполни работу в текущей сессии сам.",
|
|
6568
|
+
"Через send_partner_note или send_partner_file отправляй только результат, а не исходное поручение.",
|
|
6469
6569
|
"Не используй linked partner для отправки. Передай target_session_id явно в send_partner_note.",
|
|
6470
6570
|
"После подготовки обязательно используй send_partner_note.",
|
|
6471
6571
|
"Задача не завершена, пока send_partner_note не отработал успешно.",
|
|
@@ -10,6 +10,8 @@ exports.listXchangeFiles = listXchangeFiles;
|
|
|
10
10
|
exports.deleteXchangeFile = deleteXchangeFile;
|
|
11
11
|
exports.readWorkspaceFile = readWorkspaceFile;
|
|
12
12
|
exports.isTmuxUnavailableError = isTmuxUnavailableError;
|
|
13
|
+
exports.isTmuxTargetInvalidError = isTmuxTargetInvalidError;
|
|
14
|
+
exports.resolveTmuxTargetFromHint = resolveTmuxTargetFromHint;
|
|
13
15
|
exports.getTmuxWindowHeight = getTmuxWindowHeight;
|
|
14
16
|
exports.captureTmuxPaneRange = captureTmuxPaneRange;
|
|
15
17
|
exports.captureVisibleTmuxPane = captureVisibleTmuxPane;
|
|
@@ -189,6 +191,79 @@ function isTmuxUnavailableError(error) {
|
|
|
189
191
|
message.includes("ENOENT") ||
|
|
190
192
|
message.includes("tmux is unavailable"));
|
|
191
193
|
}
|
|
194
|
+
function isTmuxTargetInvalidError(error) {
|
|
195
|
+
const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
196
|
+
return (message.includes("can't find pane") ||
|
|
197
|
+
message.includes("can't find window") ||
|
|
198
|
+
message.includes("can't find session"));
|
|
199
|
+
}
|
|
200
|
+
async function listTmuxPanes(config) {
|
|
201
|
+
const { stdout } = await execFileOutputAsync("tmux", buildTmuxArgs(config, [
|
|
202
|
+
"list-panes",
|
|
203
|
+
"-a",
|
|
204
|
+
"-F",
|
|
205
|
+
"#{session_name}\t#{window_name}\t#{window_index}\t#{pane_id}\t#{pane_index}",
|
|
206
|
+
]));
|
|
207
|
+
return stdout
|
|
208
|
+
.split("\n")
|
|
209
|
+
.map((line) => line.trim())
|
|
210
|
+
.filter((line) => line.length > 0)
|
|
211
|
+
.map((line) => {
|
|
212
|
+
const [sessionName = "", windowName = "", windowIndexRaw = "", paneId = "", paneIndexRaw = ""] = line.split("\t");
|
|
213
|
+
return {
|
|
214
|
+
sessionName,
|
|
215
|
+
windowName,
|
|
216
|
+
windowIndex: Number.parseInt(windowIndexRaw, 10),
|
|
217
|
+
paneId,
|
|
218
|
+
paneIndex: Number.parseInt(paneIndexRaw, 10),
|
|
219
|
+
};
|
|
220
|
+
})
|
|
221
|
+
.filter((pane) => pane.sessionName &&
|
|
222
|
+
pane.paneId &&
|
|
223
|
+
Number.isFinite(pane.windowIndex) &&
|
|
224
|
+
Number.isFinite(pane.paneIndex));
|
|
225
|
+
}
|
|
226
|
+
async function resolveTmuxTargetFromHint(config, hint) {
|
|
227
|
+
const panes = await listTmuxPanes(config);
|
|
228
|
+
const byPaneId = hint.tmuxPaneId
|
|
229
|
+
? panes.find((pane) => pane.paneId === hint.tmuxPaneId)
|
|
230
|
+
: null;
|
|
231
|
+
if (byPaneId) {
|
|
232
|
+
return byPaneId.paneId;
|
|
233
|
+
}
|
|
234
|
+
const exactMatch = panes.find((pane) => {
|
|
235
|
+
if (!hint.tmuxSessionName) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (pane.sessionName !== hint.tmuxSessionName) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
if (typeof hint.tmuxWindowIndex === "number" &&
|
|
242
|
+
pane.windowIndex !== hint.tmuxWindowIndex) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
if (typeof hint.tmuxPaneIndex === "number" &&
|
|
246
|
+
pane.paneIndex !== hint.tmuxPaneIndex) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (hint.tmuxWindowName &&
|
|
250
|
+
pane.windowName !== hint.tmuxWindowName) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
if (exactMatch) {
|
|
256
|
+
return exactMatch.paneId;
|
|
257
|
+
}
|
|
258
|
+
const fallbackBySessionAndPane = panes.find((pane) => {
|
|
259
|
+
if (!hint.tmuxSessionName || typeof hint.tmuxPaneIndex !== "number") {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return (pane.sessionName === hint.tmuxSessionName &&
|
|
263
|
+
pane.paneIndex === hint.tmuxPaneIndex);
|
|
264
|
+
});
|
|
265
|
+
return fallbackBySessionAndPane?.paneId ?? null;
|
|
266
|
+
}
|
|
192
267
|
async function getTmuxWindowHeight(config, target) {
|
|
193
268
|
const { stdout: heightRaw } = await execFileOutputAsync("tmux", buildTmuxArgs(config, ["display-message", "-p", "-t", target, "#{window_height}"]));
|
|
194
269
|
const height = Number.parseInt(heightRaw.trim(), 10);
|
|
@@ -4,10 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createLogger = createLogger;
|
|
7
|
-
const node_fs_1 = require("node:fs");
|
|
8
|
-
const node_path_1 = require("node:path");
|
|
9
7
|
const pino_1 = __importDefault(require("pino"));
|
|
10
|
-
const
|
|
8
|
+
const pinoTargets_1 = require("../../../../../../../lib/pinoTargets");
|
|
11
9
|
function write(logger, level, message, meta) {
|
|
12
10
|
if (meta) {
|
|
13
11
|
logger[level](meta, message);
|
|
@@ -16,32 +14,15 @@ function write(logger, level, message, meta) {
|
|
|
16
14
|
logger[level](message);
|
|
17
15
|
}
|
|
18
16
|
function createLogger(config) {
|
|
19
|
-
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(DEFAULT_LOG_FILE_PATH), { recursive: true });
|
|
20
17
|
const transport = pino_1.default.transport({
|
|
21
|
-
targets:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
destination: DEFAULT_LOG_FILE_PATH,
|
|
27
|
-
mkdir: true,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
target: "pino-pretty",
|
|
32
|
-
level: config.logging.level,
|
|
33
|
-
options: {
|
|
34
|
-
destination: 2,
|
|
35
|
-
colorize: true,
|
|
36
|
-
translateTime: "SYS:standard",
|
|
37
|
-
ignore: "pid,hostname",
|
|
38
|
-
singleLine: false,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
],
|
|
18
|
+
targets: (0, pinoTargets_1.createPinoTargets)({
|
|
19
|
+
level: config.logging.level,
|
|
20
|
+
fileEnabled: config.logging.fileEnabled,
|
|
21
|
+
filePath: config.logging.filePath,
|
|
22
|
+
}),
|
|
42
23
|
});
|
|
43
24
|
const baseLogger = (0, pino_1.default)({
|
|
44
|
-
name: "
|
|
25
|
+
name: "tellymcp",
|
|
45
26
|
level: config.logging.level,
|
|
46
27
|
timestamp: pino_1.default.stdTimeFunctions.isoTime,
|
|
47
28
|
}, transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deadragdoll/tellymcp",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "TellyMCP - Telegram
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"description": "TellyMCP - Telegram control plane for MCP-connected coding agents",
|
|
5
5
|
"main": "dist/services/features/telegram-mcp/runtime.service.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tellymcp": "dist/cli.js"
|
|
@@ -75,7 +75,6 @@
|
|
|
75
75
|
"amqplib": "0.10.9",
|
|
76
76
|
"dotenv": "^17.4.2",
|
|
77
77
|
"grammy": "1.43.0",
|
|
78
|
-
"graphql": "^16.14.0",
|
|
79
78
|
"https-proxy-agent": "^7.0.6",
|
|
80
79
|
"ioredis": "5.10.1",
|
|
81
80
|
"knex": "^3.2.10",
|
|
@@ -88,7 +87,6 @@
|
|
|
88
87
|
"pino": "^10.3.1",
|
|
89
88
|
"pino-pretty": "^13.1.3",
|
|
90
89
|
"playwright": "^1.60.0",
|
|
91
|
-
"redis": "5.12.1",
|
|
92
90
|
"socks-proxy-agent": "^8.0.5",
|
|
93
91
|
"ws": "8.20.1",
|
|
94
92
|
"zod": "4.4.3"
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createTracerMiddleware = void 0;
|
|
4
|
-
const traceContext_1 = require("../traceContext");
|
|
5
|
-
const trace_1 = require("../trace");
|
|
6
|
-
const shouldSkipTrace = (actionName, ctx) => Boolean(ctx?.meta?.$traceInternal ||
|
|
7
|
-
!actionName ||
|
|
8
|
-
actionName === "context" ||
|
|
9
|
-
actionName === "graphql.publish" ||
|
|
10
|
-
(actionName === "rest" && ctx?.params?.req?.url === "/api/graphql"));
|
|
11
|
-
const createTracerMiddleware = () => {
|
|
12
|
-
let broker = null;
|
|
13
|
-
const resolveTracer = (action) => {
|
|
14
|
-
const rawName = String(action?.rawName || action?.name || "");
|
|
15
|
-
if (action?.tracer)
|
|
16
|
-
return { tracer: action.tracer, source: "action.tracer" };
|
|
17
|
-
if (action?.schema?.tracer)
|
|
18
|
-
return { tracer: action.schema.tracer, source: "action.schema.tracer" };
|
|
19
|
-
if (action?.service?.schema?.actions?.[rawName]?.tracer) {
|
|
20
|
-
return {
|
|
21
|
-
tracer: action.service.schema.actions[rawName].tracer,
|
|
22
|
-
source: "service.schema.actions[rawName].tracer",
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return {};
|
|
26
|
-
};
|
|
27
|
-
const callTrace = async (ctx, actionName, params, silent = false) => {
|
|
28
|
-
if (!broker) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
return await broker.call(actionName, params, {
|
|
33
|
-
requestID: ctx.requestID,
|
|
34
|
-
meta: {
|
|
35
|
-
user: ctx.meta?.user,
|
|
36
|
-
$traceInternal: true,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
if (!silent) {
|
|
42
|
-
broker.logger.warn("Tracer call failed", {
|
|
43
|
-
actionName,
|
|
44
|
-
sourceAction: ctx.action?.name || null,
|
|
45
|
-
error: error instanceof Error ? error.message : String(error),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
return {
|
|
52
|
-
created(localBroker) {
|
|
53
|
-
broker = localBroker;
|
|
54
|
-
},
|
|
55
|
-
localAction(next, action) {
|
|
56
|
-
const actionName = String(action?.rawName || action?.name || "");
|
|
57
|
-
const serviceName = String(action?.service?.name || "");
|
|
58
|
-
return async function traceAction(ctx) {
|
|
59
|
-
if (serviceName === "trace" || shouldSkipTrace(actionName, ctx)) {
|
|
60
|
-
return next(ctx);
|
|
61
|
-
}
|
|
62
|
-
const resolvedTracer = resolveTracer(ctx?.action || action);
|
|
63
|
-
const tracer = resolvedTracer.tracer;
|
|
64
|
-
let traceMeta = null;
|
|
65
|
-
const startSessionName = tracer?.startSession;
|
|
66
|
-
const tracerTag = tracer?.tag;
|
|
67
|
-
const shouldStartRootSession = Boolean(startSessionName);
|
|
68
|
-
if (!shouldStartRootSession) {
|
|
69
|
-
traceMeta = await (0, traceContext_1.loadTraceContext)();
|
|
70
|
-
}
|
|
71
|
-
let startedSession = false;
|
|
72
|
-
if (shouldStartRootSession) {
|
|
73
|
-
await (0, traceContext_1.deleteTraceContext)().catch(() => null);
|
|
74
|
-
const session = (await callTrace(ctx, "trace.startSession", {
|
|
75
|
-
name: startSessionName,
|
|
76
|
-
tag: tracerTag || null,
|
|
77
|
-
source: actionName,
|
|
78
|
-
meta: (0, trace_1.sanitizeTraceValue)({
|
|
79
|
-
action: actionName,
|
|
80
|
-
params: ctx.params,
|
|
81
|
-
}),
|
|
82
|
-
}, false));
|
|
83
|
-
if (session?.session_id) {
|
|
84
|
-
traceMeta = {
|
|
85
|
-
sessionId: String(session.session_id),
|
|
86
|
-
name: startSessionName ?? null,
|
|
87
|
-
tag: tracerTag || null,
|
|
88
|
-
rootAction: actionName,
|
|
89
|
-
startedBy: actionName,
|
|
90
|
-
};
|
|
91
|
-
await (0, traceContext_1.saveTraceContext)(traceMeta);
|
|
92
|
-
startedSession = true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const sessionId = String(traceMeta?.sessionId || "").trim();
|
|
96
|
-
const hasSession = Boolean(sessionId);
|
|
97
|
-
const startedAt = Date.now();
|
|
98
|
-
if (hasSession) {
|
|
99
|
-
await (0, traceContext_1.touchTraceContext)().catch(() => null);
|
|
100
|
-
await callTrace(ctx, "trace.log", {
|
|
101
|
-
session_id: sessionId,
|
|
102
|
-
level: (0, trace_1.normalizeTraceLevel)(tracer?.level || "debug"),
|
|
103
|
-
action: actionName,
|
|
104
|
-
state: "started",
|
|
105
|
-
marker: tracer?.marker || null,
|
|
106
|
-
step: tracer?.step || "action",
|
|
107
|
-
message: actionName,
|
|
108
|
-
data: (0, trace_1.buildTraceStartData)(ctx, actionName, tracer, startedSession),
|
|
109
|
-
}, false);
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const result = await next(ctx);
|
|
113
|
-
const durationMs = Date.now() - startedAt;
|
|
114
|
-
if (hasSession) {
|
|
115
|
-
await callTrace(ctx, "trace.log", {
|
|
116
|
-
session_id: sessionId,
|
|
117
|
-
level: (0, trace_1.normalizeTraceLevel)(tracer?.level || "debug"),
|
|
118
|
-
action: actionName,
|
|
119
|
-
state: "succeeded",
|
|
120
|
-
marker: tracer?.marker || null,
|
|
121
|
-
step: tracer?.step || "action",
|
|
122
|
-
message: actionName,
|
|
123
|
-
data: (0, trace_1.buildTraceSuccessData)(ctx, actionName, result, durationMs, tracer),
|
|
124
|
-
}, false);
|
|
125
|
-
if (tracer?.stopSession) {
|
|
126
|
-
await callTrace(ctx, "trace.endSession", {
|
|
127
|
-
session_id: sessionId,
|
|
128
|
-
status: "succeeded",
|
|
129
|
-
summary: `${actionName} completed`,
|
|
130
|
-
meta: (0, trace_1.sanitizeTraceValue)({
|
|
131
|
-
action: actionName,
|
|
132
|
-
durationMs,
|
|
133
|
-
}),
|
|
134
|
-
}, false);
|
|
135
|
-
await (0, traceContext_1.deleteTraceContext)();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
const durationMs = Date.now() - startedAt;
|
|
142
|
-
if (hasSession) {
|
|
143
|
-
await callTrace(ctx, "trace.log", {
|
|
144
|
-
session_id: sessionId,
|
|
145
|
-
level: "error",
|
|
146
|
-
action: actionName,
|
|
147
|
-
state: "failed",
|
|
148
|
-
marker: tracer?.marker || null,
|
|
149
|
-
step: tracer?.step || "action",
|
|
150
|
-
message: actionName,
|
|
151
|
-
data: (0, trace_1.buildTraceErrorData)(ctx, actionName, error, durationMs, tracer),
|
|
152
|
-
}, false);
|
|
153
|
-
if (tracer?.stopSession || startedSession) {
|
|
154
|
-
await callTrace(ctx, "trace.endSession", {
|
|
155
|
-
session_id: sessionId,
|
|
156
|
-
status: "failed",
|
|
157
|
-
summary: error instanceof Error ? error.message : `Action failed: ${actionName}`,
|
|
158
|
-
meta: (0, trace_1.sanitizeTraceValue)({
|
|
159
|
-
action: actionName,
|
|
160
|
-
durationMs,
|
|
161
|
-
}),
|
|
162
|
-
}, false);
|
|
163
|
-
await (0, traceContext_1.deleteTraceContext)();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
throw error;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
exports.createTracerMiddleware = createTracerMiddleware;
|
package/dist/lib/trace.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildTraceErrorData = exports.buildTraceSuccessData = exports.buildTraceStartData = exports.buildTraceMetaSummary = exports.sanitizeTraceValue = exports.normalizeTraceLevel = void 0;
|
|
4
|
-
const TRACE_MAX_DEPTH = 4;
|
|
5
|
-
const TRACE_MAX_ARRAY = 25;
|
|
6
|
-
const TRACE_MAX_KEYS = 40;
|
|
7
|
-
const TRACE_MAX_STRING = 1000;
|
|
8
|
-
const truncateString = (value) => value.length > TRACE_MAX_STRING ? `${value.slice(0, TRACE_MAX_STRING)}...` : value;
|
|
9
|
-
const normalizeTraceLevel = (value) => {
|
|
10
|
-
const level = String(value || "info").toLowerCase();
|
|
11
|
-
if (["fatal", "error", "warn", "info", "debug", "trace"].includes(level)) {
|
|
12
|
-
return level;
|
|
13
|
-
}
|
|
14
|
-
return "info";
|
|
15
|
-
};
|
|
16
|
-
exports.normalizeTraceLevel = normalizeTraceLevel;
|
|
17
|
-
const sanitizeError = (error, depth, seen) => {
|
|
18
|
-
if (!(error instanceof Error)) {
|
|
19
|
-
return (0, exports.sanitizeTraceValue)(error, depth, seen);
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
name: error.name,
|
|
23
|
-
message: truncateString(error.message || ""),
|
|
24
|
-
stack: truncateString(error.stack || ""),
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
const sanitizeTraceValue = (value, depth = 0, seen = new WeakSet()) => {
|
|
28
|
-
if (value == null || typeof value === "boolean" || typeof value === "number") {
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
if (typeof value === "string") {
|
|
32
|
-
return truncateString(value);
|
|
33
|
-
}
|
|
34
|
-
if (typeof value === "bigint") {
|
|
35
|
-
return value.toString();
|
|
36
|
-
}
|
|
37
|
-
if (typeof value === "symbol") {
|
|
38
|
-
return value.toString();
|
|
39
|
-
}
|
|
40
|
-
if (typeof value === "function") {
|
|
41
|
-
return `[Function ${value.name || "anonymous"}]`;
|
|
42
|
-
}
|
|
43
|
-
if (value instanceof Date) {
|
|
44
|
-
return value.toISOString();
|
|
45
|
-
}
|
|
46
|
-
if (value instanceof Error) {
|
|
47
|
-
return sanitizeError(value, depth, seen);
|
|
48
|
-
}
|
|
49
|
-
if (Buffer.isBuffer(value)) {
|
|
50
|
-
return `[Buffer ${value.length}]`;
|
|
51
|
-
}
|
|
52
|
-
if (value instanceof Set) {
|
|
53
|
-
return {
|
|
54
|
-
type: "Set",
|
|
55
|
-
size: value.size,
|
|
56
|
-
values: Array.from(value.values())
|
|
57
|
-
.slice(0, TRACE_MAX_ARRAY)
|
|
58
|
-
.map(item => (0, exports.sanitizeTraceValue)(item, depth + 1, seen)),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (value instanceof Map) {
|
|
62
|
-
return {
|
|
63
|
-
type: "Map",
|
|
64
|
-
size: value.size,
|
|
65
|
-
entries: Array.from(value.entries())
|
|
66
|
-
.slice(0, TRACE_MAX_ARRAY)
|
|
67
|
-
.map(([key, item]) => [
|
|
68
|
-
(0, exports.sanitizeTraceValue)(key, depth + 1, seen),
|
|
69
|
-
(0, exports.sanitizeTraceValue)(item, depth + 1, seen),
|
|
70
|
-
]),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(value)) {
|
|
74
|
-
if (depth >= TRACE_MAX_DEPTH) {
|
|
75
|
-
return `[Array(${value.length})]`;
|
|
76
|
-
}
|
|
77
|
-
return value
|
|
78
|
-
.slice(0, TRACE_MAX_ARRAY)
|
|
79
|
-
.map(item => (0, exports.sanitizeTraceValue)(item, depth + 1, seen));
|
|
80
|
-
}
|
|
81
|
-
if (typeof value === "object") {
|
|
82
|
-
const objectValue = value;
|
|
83
|
-
if (seen.has(objectValue)) {
|
|
84
|
-
return "[Circular]";
|
|
85
|
-
}
|
|
86
|
-
seen.add(objectValue);
|
|
87
|
-
if (depth >= TRACE_MAX_DEPTH) {
|
|
88
|
-
return `[${objectValue?.constructor?.name || "Object"}]`;
|
|
89
|
-
}
|
|
90
|
-
const keys = Object.keys(objectValue).slice(0, TRACE_MAX_KEYS);
|
|
91
|
-
const result = {};
|
|
92
|
-
for (const key of keys) {
|
|
93
|
-
result[key] = (0, exports.sanitizeTraceValue)(objectValue[key], depth + 1, seen);
|
|
94
|
-
}
|
|
95
|
-
if (Object.keys(objectValue).length > TRACE_MAX_KEYS) {
|
|
96
|
-
result.__truncatedKeys = Object.keys(objectValue).length - TRACE_MAX_KEYS;
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
return String(value);
|
|
101
|
-
};
|
|
102
|
-
exports.sanitizeTraceValue = sanitizeTraceValue;
|
|
103
|
-
const pickFields = (source, fields) => {
|
|
104
|
-
if (fields === false) {
|
|
105
|
-
return undefined;
|
|
106
|
-
}
|
|
107
|
-
if (fields === true || fields == null) {
|
|
108
|
-
return (0, exports.sanitizeTraceValue)(source);
|
|
109
|
-
}
|
|
110
|
-
if (!source || typeof source !== "object") {
|
|
111
|
-
return (0, exports.sanitizeTraceValue)(source);
|
|
112
|
-
}
|
|
113
|
-
const result = {};
|
|
114
|
-
for (const key of fields) {
|
|
115
|
-
result[key] = (0, exports.sanitizeTraceValue)(source[key]);
|
|
116
|
-
}
|
|
117
|
-
return result;
|
|
118
|
-
};
|
|
119
|
-
const buildTraceMetaSummary = (ctx) => (0, exports.sanitizeTraceValue)({
|
|
120
|
-
requestID: ctx.requestID || null,
|
|
121
|
-
userSub: ctx.meta?.user?.sub || null,
|
|
122
|
-
});
|
|
123
|
-
exports.buildTraceMetaSummary = buildTraceMetaSummary;
|
|
124
|
-
const buildTraceStartData = (ctx, actionName, tracer, startedSession = false) => (0, exports.sanitizeTraceValue)({
|
|
125
|
-
action: actionName,
|
|
126
|
-
startedSession,
|
|
127
|
-
params: pickFields(ctx.params, tracer?.captureParams),
|
|
128
|
-
meta: (0, exports.buildTraceMetaSummary)(ctx),
|
|
129
|
-
});
|
|
130
|
-
exports.buildTraceStartData = buildTraceStartData;
|
|
131
|
-
const buildTraceSuccessData = (ctx, actionName, result, durationMs, tracer) => (0, exports.sanitizeTraceValue)({
|
|
132
|
-
action: actionName,
|
|
133
|
-
durationMs,
|
|
134
|
-
result: tracer?.captureResult === undefined
|
|
135
|
-
? undefined
|
|
136
|
-
: pickFields(result, tracer.captureResult),
|
|
137
|
-
});
|
|
138
|
-
exports.buildTraceSuccessData = buildTraceSuccessData;
|
|
139
|
-
const buildTraceErrorData = (ctx, actionName, error, durationMs, tracer) => (0, exports.sanitizeTraceValue)({
|
|
140
|
-
action: actionName,
|
|
141
|
-
durationMs,
|
|
142
|
-
error: tracer?.captureError === undefined
|
|
143
|
-
? sanitizeError(error, 0, new WeakSet())
|
|
144
|
-
: pickFields(error, tracer.captureError),
|
|
145
|
-
meta: (0, exports.buildTraceMetaSummary)(ctx),
|
|
146
|
-
});
|
|
147
|
-
exports.buildTraceErrorData = buildTraceErrorData;
|