opencode-tbot 0.1.30 → 0.1.31
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/README.md +35 -3
- package/README.zh-CN.md +83 -51
- package/dist/assets/{plugin-config-DNeV2Ckw.js → plugin-config-jkAZYbFW.js} +91 -18
- package/dist/assets/plugin-config-jkAZYbFW.js.map +1 -0
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +629 -142
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/plugin-config-DNeV2Ckw.js.map +0 -1
package/dist/plugin.js
CHANGED
|
@@ -1,83 +1,360 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
1
|
+
import { i as OPENCODE_TBOT_VERSION, n as preparePluginConfiguration, o as loadAppConfig } from "./assets/plugin-config-jkAZYbFW.js";
|
|
2
|
+
import { appendFile, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join } from "node:path";
|
|
4
4
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
7
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
8
8
|
import { run } from "@grammyjs/runner";
|
|
9
9
|
import { Bot, GrammyError, HttpError, InlineKeyboard } from "grammy";
|
|
10
10
|
//#region src/infra/utils/redact.ts
|
|
11
|
-
var REDACTED = "[REDACTED]";
|
|
12
|
-
var DEFAULT_PREVIEW_LENGTH = 160;
|
|
11
|
+
var REDACTED$1 = "[REDACTED]";
|
|
13
12
|
var TELEGRAM_TOKEN_PATTERN = /\b\d{6,}:[A-Za-z0-9_-]{20,}\b/g;
|
|
14
13
|
var BEARER_TOKEN_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/-]+=*\b/gi;
|
|
15
14
|
var NAMED_SECRET_PATTERN = /\b(api[_\s-]?key|token|secret|password)\b(\s*[:=]\s*)([^\s,;]+)/gi;
|
|
16
15
|
var API_KEY_LIKE_PATTERN = /\b(?:sk|pk)_[A-Za-z0-9_-]{10,}\b/g;
|
|
17
16
|
function redactSensitiveText(input) {
|
|
18
|
-
return input.replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED}`).replace(TELEGRAM_TOKEN_PATTERN, REDACTED).replace(NAMED_SECRET_PATTERN, (_, name, separator) => `${name}${separator}${REDACTED}`).replace(API_KEY_LIKE_PATTERN, REDACTED);
|
|
19
|
-
}
|
|
20
|
-
function createRedactedPreview(input, maxLength = DEFAULT_PREVIEW_LENGTH) {
|
|
21
|
-
const redacted = redactSensitiveText(input).replace(/\s+/g, " ").trim();
|
|
22
|
-
if (redacted.length <= maxLength) return redacted;
|
|
23
|
-
return `${redacted.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
17
|
+
return input.replace(BEARER_TOKEN_PATTERN, `Bearer ${REDACTED$1}`).replace(TELEGRAM_TOKEN_PATTERN, REDACTED$1).replace(NAMED_SECRET_PATTERN, (_, name, separator) => `${name}${separator}${REDACTED$1}`).replace(API_KEY_LIKE_PATTERN, REDACTED$1);
|
|
24
18
|
}
|
|
25
19
|
//#endregion
|
|
26
20
|
//#region src/infra/logger/index.ts
|
|
21
|
+
var DEFAULT_COMPONENT = "app";
|
|
22
|
+
var DEFAULT_EVENT = "log";
|
|
27
23
|
var DEFAULT_SERVICE_NAME = "opencode-tbot";
|
|
24
|
+
var DEFAULT_MAX_LOG_FILES = 30;
|
|
25
|
+
var DEFAULT_MAX_TOTAL_LOG_BYTES = 314572800;
|
|
26
|
+
var CONTENT_OMITTED = "[OMITTED]";
|
|
27
|
+
var REDACTED = "[REDACTED]";
|
|
28
28
|
var LEVEL_PRIORITY = {
|
|
29
29
|
debug: 10,
|
|
30
30
|
info: 20,
|
|
31
31
|
warn: 30,
|
|
32
32
|
error: 40
|
|
33
33
|
};
|
|
34
|
+
var RESERVED_EVENT_FIELDS = new Set([
|
|
35
|
+
"attempt",
|
|
36
|
+
"callbackData",
|
|
37
|
+
"chatId",
|
|
38
|
+
"command",
|
|
39
|
+
"component",
|
|
40
|
+
"correlationId",
|
|
41
|
+
"durationMs",
|
|
42
|
+
"error",
|
|
43
|
+
"event",
|
|
44
|
+
"operationId",
|
|
45
|
+
"projectId",
|
|
46
|
+
"requestId",
|
|
47
|
+
"runtimeId",
|
|
48
|
+
"sessionId",
|
|
49
|
+
"sizeBytes",
|
|
50
|
+
"status",
|
|
51
|
+
"updateId",
|
|
52
|
+
"worktree"
|
|
53
|
+
]);
|
|
34
54
|
function createOpenCodeAppLogger(client, options = {}) {
|
|
35
55
|
const service = normalizeServiceName(options.service);
|
|
36
56
|
const minimumLevel = normalizeLogLevel(options.level);
|
|
57
|
+
const runtimeId = normalizeString(options.runtimeId) ?? randomUUID();
|
|
58
|
+
const boundRootContext = {
|
|
59
|
+
component: DEFAULT_COMPONENT,
|
|
60
|
+
runtimeId,
|
|
61
|
+
...options.worktree ? { worktree: options.worktree } : {}
|
|
62
|
+
};
|
|
63
|
+
const sinks = createSinks(client, {
|
|
64
|
+
file: {
|
|
65
|
+
dir: options.file?.dir,
|
|
66
|
+
maxFiles: options.file?.retention?.maxFiles,
|
|
67
|
+
maxTotalBytes: options.file?.retention?.maxTotalBytes
|
|
68
|
+
},
|
|
69
|
+
runtimeId,
|
|
70
|
+
service,
|
|
71
|
+
sinks: options.sinks
|
|
72
|
+
});
|
|
37
73
|
let queue = Promise.resolve();
|
|
38
|
-
const enqueue = (
|
|
39
|
-
if (LEVEL_PRIORITY[level] < LEVEL_PRIORITY[minimumLevel]) return;
|
|
40
|
-
const
|
|
41
|
-
const payload = {
|
|
42
|
-
service,
|
|
43
|
-
level,
|
|
44
|
-
message: text,
|
|
45
|
-
...extra ? { extra } : {}
|
|
46
|
-
};
|
|
74
|
+
const enqueue = (event) => {
|
|
75
|
+
if (LEVEL_PRIORITY[event.level] < LEVEL_PRIORITY[minimumLevel]) return;
|
|
76
|
+
const structuredEvent = buildStructuredLogEvent(event.level, event.input, event.message, service, event.context);
|
|
47
77
|
queue = queue.catch(() => void 0).then(async () => {
|
|
48
|
-
|
|
49
|
-
if (client.app.log.length >= 2) {
|
|
50
|
-
await client.app.log(payload, {
|
|
51
|
-
responseStyle: "data",
|
|
52
|
-
throwOnError: true
|
|
53
|
-
});
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
await client.app.log({
|
|
57
|
-
body: payload,
|
|
58
|
-
responseStyle: "data",
|
|
59
|
-
throwOnError: true
|
|
60
|
-
});
|
|
61
|
-
} catch {}
|
|
78
|
+
await Promise.allSettled(sinks.map((sink) => sink.write(structuredEvent)));
|
|
62
79
|
});
|
|
63
80
|
};
|
|
64
|
-
|
|
81
|
+
const createLogger = (context) => ({
|
|
65
82
|
debug(input, message) {
|
|
66
|
-
enqueue(
|
|
83
|
+
enqueue({
|
|
84
|
+
context,
|
|
85
|
+
input,
|
|
86
|
+
level: "debug",
|
|
87
|
+
message
|
|
88
|
+
});
|
|
67
89
|
},
|
|
68
90
|
info(input, message) {
|
|
69
|
-
enqueue(
|
|
91
|
+
enqueue({
|
|
92
|
+
context,
|
|
93
|
+
input,
|
|
94
|
+
level: "info",
|
|
95
|
+
message
|
|
96
|
+
});
|
|
70
97
|
},
|
|
71
98
|
warn(input, message) {
|
|
72
|
-
enqueue(
|
|
99
|
+
enqueue({
|
|
100
|
+
context,
|
|
101
|
+
input,
|
|
102
|
+
level: "warn",
|
|
103
|
+
message
|
|
104
|
+
});
|
|
73
105
|
},
|
|
74
106
|
error(input, message) {
|
|
75
|
-
enqueue(
|
|
107
|
+
enqueue({
|
|
108
|
+
context,
|
|
109
|
+
input,
|
|
110
|
+
level: "error",
|
|
111
|
+
message
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
child(childContext) {
|
|
115
|
+
return createLogger({
|
|
116
|
+
...context,
|
|
117
|
+
...removeUndefinedFields(childContext)
|
|
118
|
+
});
|
|
76
119
|
},
|
|
77
120
|
async flush() {
|
|
78
121
|
await queue.catch(() => void 0);
|
|
122
|
+
await Promise.allSettled(sinks.map(async (sink) => {
|
|
123
|
+
await sink.flush?.();
|
|
124
|
+
}));
|
|
79
125
|
}
|
|
126
|
+
});
|
|
127
|
+
return createLogger(boundRootContext);
|
|
128
|
+
}
|
|
129
|
+
function logTelegramUpdate(logger, input, message) {
|
|
130
|
+
logger.info({
|
|
131
|
+
component: "telegram",
|
|
132
|
+
...input
|
|
133
|
+
}, message);
|
|
134
|
+
}
|
|
135
|
+
function logPromptLifecycle(logger, input, message) {
|
|
136
|
+
logger.info({
|
|
137
|
+
component: "prompt",
|
|
138
|
+
...input
|
|
139
|
+
}, message);
|
|
140
|
+
}
|
|
141
|
+
function logOpenCodeRequest(logger, input, message) {
|
|
142
|
+
logger.info({
|
|
143
|
+
component: "opencode",
|
|
144
|
+
...input
|
|
145
|
+
}, message);
|
|
146
|
+
}
|
|
147
|
+
function logPluginEvent(logger, input, message) {
|
|
148
|
+
logger.info({
|
|
149
|
+
component: "plugin-event",
|
|
150
|
+
...input
|
|
151
|
+
}, message);
|
|
152
|
+
}
|
|
153
|
+
function createSinks(client, options) {
|
|
154
|
+
const sinkOptions = options.sinks ?? {};
|
|
155
|
+
const sinks = [];
|
|
156
|
+
if (sinkOptions.host !== false) sinks.push(createHostSink(client, options.service));
|
|
157
|
+
if (sinkOptions.file !== false && options.file?.dir) sinks.push(createJsonlFileSink({
|
|
158
|
+
dir: options.file.dir,
|
|
159
|
+
maxFiles: options.file.maxFiles,
|
|
160
|
+
maxTotalBytes: options.file.maxTotalBytes,
|
|
161
|
+
runtimeId: options.runtimeId
|
|
162
|
+
}));
|
|
163
|
+
return sinks;
|
|
164
|
+
}
|
|
165
|
+
function createHostSink(client, service) {
|
|
166
|
+
return { async write(event) {
|
|
167
|
+
const payload = {
|
|
168
|
+
service,
|
|
169
|
+
level: event.level,
|
|
170
|
+
message: event.message,
|
|
171
|
+
extra: buildHostLogExtra(event)
|
|
172
|
+
};
|
|
173
|
+
if (client.app.log.length >= 2) {
|
|
174
|
+
await client.app.log(payload, {
|
|
175
|
+
responseStyle: "data",
|
|
176
|
+
throwOnError: true
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await client.app.log({
|
|
181
|
+
body: payload,
|
|
182
|
+
responseStyle: "data",
|
|
183
|
+
throwOnError: true
|
|
184
|
+
});
|
|
185
|
+
} };
|
|
186
|
+
}
|
|
187
|
+
function createJsonlFileSink(options) {
|
|
188
|
+
const maxFiles = options.maxFiles ?? DEFAULT_MAX_LOG_FILES;
|
|
189
|
+
const maxTotalBytes = options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_LOG_BYTES;
|
|
190
|
+
const runtimeSegment = sanitizeFileSegment(options.runtimeId);
|
|
191
|
+
const timestampSegment = (/* @__PURE__ */ new Date()).toISOString().replace(/:/gu, "-");
|
|
192
|
+
const filePath = join(options.dir, `${timestampSegment}.${process.pid}.${runtimeSegment}.jsonl`);
|
|
193
|
+
let initialized = false;
|
|
194
|
+
let writeCount = 0;
|
|
195
|
+
const initialize = async () => {
|
|
196
|
+
if (initialized) return;
|
|
197
|
+
await mkdir(options.dir, { recursive: true });
|
|
198
|
+
await cleanupLogDirectory(options.dir, maxFiles, maxTotalBytes);
|
|
199
|
+
initialized = true;
|
|
80
200
|
};
|
|
201
|
+
return { async write(event) {
|
|
202
|
+
await initialize();
|
|
203
|
+
await appendFile(filePath, `${JSON.stringify(event)}\n`, "utf8");
|
|
204
|
+
writeCount += 1;
|
|
205
|
+
if (writeCount === 1 || writeCount % 100 === 0) await cleanupLogDirectory(options.dir, maxFiles, maxTotalBytes);
|
|
206
|
+
} };
|
|
207
|
+
}
|
|
208
|
+
async function cleanupLogDirectory(directory, maxFiles, maxTotalBytes) {
|
|
209
|
+
let entries = [];
|
|
210
|
+
try {
|
|
211
|
+
const names = await readdir(directory);
|
|
212
|
+
entries = await Promise.all(names.filter((name) => name.endsWith(".jsonl")).map(async (name) => {
|
|
213
|
+
const entryPath = join(directory, name);
|
|
214
|
+
const entryStat = await stat(entryPath);
|
|
215
|
+
return {
|
|
216
|
+
path: entryPath,
|
|
217
|
+
size: entryStat.size,
|
|
218
|
+
time: entryStat.mtimeMs
|
|
219
|
+
};
|
|
220
|
+
}));
|
|
221
|
+
} catch {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
entries.sort((left, right) => right.time - left.time);
|
|
225
|
+
let totalBytes = 0;
|
|
226
|
+
const retained = [];
|
|
227
|
+
const deleted = [];
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
const canKeepByCount = retained.length < maxFiles;
|
|
230
|
+
const canKeepBySize = retained.length === 0 || totalBytes + entry.size <= maxTotalBytes;
|
|
231
|
+
if (canKeepByCount && canKeepBySize) {
|
|
232
|
+
retained.push(entry);
|
|
233
|
+
totalBytes += entry.size;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
deleted.push(entry);
|
|
237
|
+
}
|
|
238
|
+
await Promise.allSettled(deleted.map(async (entry) => {
|
|
239
|
+
await unlink(entry.path);
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
function buildStructuredLogEvent(level, input, message, service, boundContext) {
|
|
243
|
+
const extracted = extractContextAndExtra(input);
|
|
244
|
+
const context = {
|
|
245
|
+
...removeUndefinedFields(boundContext),
|
|
246
|
+
...removeUndefinedFields(extracted.context)
|
|
247
|
+
};
|
|
248
|
+
const resolvedMessage = normalizeLogMessage(input, message);
|
|
249
|
+
return {
|
|
250
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
251
|
+
level,
|
|
252
|
+
service,
|
|
253
|
+
component: normalizeComponent(context.component),
|
|
254
|
+
event: normalizeEventName(context.event),
|
|
255
|
+
message: redactSensitiveText(resolvedMessage),
|
|
256
|
+
runtimeId: context.runtimeId ?? null,
|
|
257
|
+
operationId: context.operationId ?? null,
|
|
258
|
+
correlationId: resolveCorrelationId(context),
|
|
259
|
+
...context.worktree ? { worktree: redactSensitiveText(context.worktree) } : {},
|
|
260
|
+
...typeof context.chatId === "number" ? { chatId: context.chatId } : {},
|
|
261
|
+
...context.sessionId ? { sessionId: context.sessionId } : {},
|
|
262
|
+
...context.projectId ? { projectId: context.projectId } : {},
|
|
263
|
+
...context.requestId ? { requestId: context.requestId } : {},
|
|
264
|
+
...typeof context.updateId === "number" ? { updateId: context.updateId } : {},
|
|
265
|
+
...context.command ? { command: context.command } : {},
|
|
266
|
+
...context.callbackData ? { callbackData: context.callbackData } : {},
|
|
267
|
+
...typeof context.durationMs === "number" ? { durationMs: context.durationMs } : {},
|
|
268
|
+
...typeof context.attempt === "number" ? { attempt: context.attempt } : {},
|
|
269
|
+
...context.status ? { status: context.status } : {},
|
|
270
|
+
...typeof context.sizeBytes === "number" ? { sizeBytes: context.sizeBytes } : {},
|
|
271
|
+
...context.error ? { error: context.error } : {},
|
|
272
|
+
...removeUndefinedFields(extracted.extra)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function buildHostLogExtra(event) {
|
|
276
|
+
const { service: _service, level: _level, message: _message, ...rest } = event;
|
|
277
|
+
return rest;
|
|
278
|
+
}
|
|
279
|
+
function extractContextAndExtra(input) {
|
|
280
|
+
if (input instanceof Error) return {
|
|
281
|
+
context: { error: serializeError(input) },
|
|
282
|
+
extra: {}
|
|
283
|
+
};
|
|
284
|
+
if (Array.isArray(input)) return {
|
|
285
|
+
context: {},
|
|
286
|
+
extra: { items: input.map((item) => sanitizeValue(item)) }
|
|
287
|
+
};
|
|
288
|
+
if (input === null || input === void 0) return {
|
|
289
|
+
context: {},
|
|
290
|
+
extra: {}
|
|
291
|
+
};
|
|
292
|
+
if (typeof input === "string") return {
|
|
293
|
+
context: {},
|
|
294
|
+
extra: {}
|
|
295
|
+
};
|
|
296
|
+
if (typeof input !== "object") return {
|
|
297
|
+
context: {},
|
|
298
|
+
extra: { value: sanitizeValue(input) }
|
|
299
|
+
};
|
|
300
|
+
const record = input;
|
|
301
|
+
const context = {};
|
|
302
|
+
const extra = {};
|
|
303
|
+
for (const [key, value] of Object.entries(record)) {
|
|
304
|
+
if (RESERVED_EVENT_FIELDS.has(key)) {
|
|
305
|
+
assignReservedField(context, key, value);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
extra[key] = sanitizeFieldValue(key, value);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
context,
|
|
312
|
+
extra
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function assignReservedField(context, key, value) {
|
|
316
|
+
switch (key) {
|
|
317
|
+
case "attempt":
|
|
318
|
+
case "durationMs":
|
|
319
|
+
case "sizeBytes":
|
|
320
|
+
if (typeof value === "number") context[key] = value;
|
|
321
|
+
return;
|
|
322
|
+
case "chatId":
|
|
323
|
+
case "updateId":
|
|
324
|
+
if (typeof value === "number") context[key] = value;
|
|
325
|
+
return;
|
|
326
|
+
case "callbackData":
|
|
327
|
+
case "command":
|
|
328
|
+
case "component":
|
|
329
|
+
case "event":
|
|
330
|
+
case "projectId":
|
|
331
|
+
case "requestId":
|
|
332
|
+
case "sessionId":
|
|
333
|
+
case "status":
|
|
334
|
+
case "worktree":
|
|
335
|
+
if (typeof value === "string" && value.trim().length > 0) context[key] = redactSensitiveText(value.trim());
|
|
336
|
+
return;
|
|
337
|
+
case "correlationId":
|
|
338
|
+
case "operationId":
|
|
339
|
+
case "runtimeId":
|
|
340
|
+
if (typeof value === "string" && value.trim().length > 0) context[key] = value.trim();
|
|
341
|
+
else if (value === null) context[key] = null;
|
|
342
|
+
return;
|
|
343
|
+
case "error":
|
|
344
|
+
if (value instanceof Error) context.error = serializeError(value);
|
|
345
|
+
else if (isPlainObject(value)) context.error = normalizeStructuredLogErrorRecord(value);
|
|
346
|
+
return;
|
|
347
|
+
default: return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function normalizeLogMessage(input, message) {
|
|
351
|
+
if (typeof input === "string") {
|
|
352
|
+
const normalizedInput = input.trim();
|
|
353
|
+
return normalizedInput.length > 0 ? normalizedInput : "log";
|
|
354
|
+
}
|
|
355
|
+
if (message && message.trim().length > 0) return message.trim();
|
|
356
|
+
if (input instanceof Error) return input.message.trim() || input.name;
|
|
357
|
+
return "log";
|
|
81
358
|
}
|
|
82
359
|
function normalizeServiceName(value) {
|
|
83
360
|
const normalized = value?.trim();
|
|
@@ -91,59 +368,140 @@ function normalizeLogLevel(value) {
|
|
|
91
368
|
default: return "info";
|
|
92
369
|
}
|
|
93
370
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
371
|
+
function normalizeComponent(value) {
|
|
372
|
+
const normalized = value?.trim();
|
|
373
|
+
return normalized && normalized.length > 0 ? normalized : DEFAULT_COMPONENT;
|
|
374
|
+
}
|
|
375
|
+
function normalizeEventName(value) {
|
|
376
|
+
const normalized = value?.trim();
|
|
377
|
+
return normalized && normalized.length > 0 ? normalized : DEFAULT_EVENT;
|
|
378
|
+
}
|
|
379
|
+
function resolveCorrelationId(context) {
|
|
380
|
+
if (context.correlationId) return context.correlationId;
|
|
381
|
+
if (typeof context.updateId === "number") return String(context.updateId);
|
|
382
|
+
return context.operationId ?? null;
|
|
383
|
+
}
|
|
384
|
+
function serializeError(error) {
|
|
108
385
|
return {
|
|
109
|
-
|
|
110
|
-
|
|
386
|
+
name: error.name,
|
|
387
|
+
message: redactSensitiveText(error.message),
|
|
388
|
+
...error.stack ? { stack: redactSensitiveText(error.stack) } : {},
|
|
389
|
+
..."data" in error && error.data && typeof error.data === "object" ? { data: sanitizeValue(error.data) } : {}
|
|
111
390
|
};
|
|
112
391
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
if (input instanceof Error) return { error: serializeError(input) };
|
|
116
|
-
if (Array.isArray(input)) return { items: input.map((item) => sanitizeValue(item)) };
|
|
117
|
-
if (typeof input !== "object") return { value: sanitizeValue(input) };
|
|
118
|
-
return sanitizeRecord(input);
|
|
392
|
+
function sanitizePlainObject(value) {
|
|
393
|
+
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, sanitizeFieldValue(key, entryValue)]));
|
|
119
394
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
395
|
+
function sanitizeFieldValue(key, value) {
|
|
396
|
+
if (isSensitiveKey(key)) return redactSensitiveFieldValue(value);
|
|
397
|
+
if (isUrlLikeKey(key)) return summarizeUrlValue(value);
|
|
398
|
+
if (isTextContentKey(key)) return summarizeTextValue(value);
|
|
399
|
+
if (isAttachmentCollectionKey(key)) return summarizeAttachmentCollection(value);
|
|
400
|
+
return sanitizeValue(value);
|
|
122
401
|
}
|
|
123
402
|
function sanitizeValue(value) {
|
|
124
403
|
if (value instanceof Error) return serializeError(value);
|
|
125
|
-
if (Array.isArray(value)) return value.map((
|
|
404
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeValue(entry));
|
|
126
405
|
if (typeof value === "string") return redactSensitiveText(value);
|
|
127
406
|
if (!value || typeof value !== "object") return value;
|
|
128
|
-
return
|
|
407
|
+
return sanitizePlainObject(value);
|
|
129
408
|
}
|
|
130
|
-
function
|
|
409
|
+
function summarizeTextValue(value) {
|
|
410
|
+
if (typeof value === "string") return {
|
|
411
|
+
omitted: CONTENT_OMITTED,
|
|
412
|
+
length: value.length
|
|
413
|
+
};
|
|
414
|
+
if (Array.isArray(value)) return {
|
|
415
|
+
omitted: CONTENT_OMITTED,
|
|
416
|
+
count: value.length
|
|
417
|
+
};
|
|
418
|
+
if (value && typeof value === "object") return {
|
|
419
|
+
omitted: CONTENT_OMITTED,
|
|
420
|
+
kind: "object"
|
|
421
|
+
};
|
|
422
|
+
return value;
|
|
423
|
+
}
|
|
424
|
+
function summarizeUrlValue(value) {
|
|
425
|
+
if (typeof value === "string" && value.trim().length > 0) return {
|
|
426
|
+
omitted: CONTENT_OMITTED,
|
|
427
|
+
kind: "url"
|
|
428
|
+
};
|
|
429
|
+
return sanitizeValue(value);
|
|
430
|
+
}
|
|
431
|
+
function summarizeAttachmentCollection(value) {
|
|
432
|
+
if (!Array.isArray(value)) return summarizeTextValue(value);
|
|
131
433
|
return {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
...error.stack ? { stack: redactSensitiveText(error.stack) } : {},
|
|
135
|
-
..."data" in error && error.data && typeof error.data === "object" ? { data: sanitizeValue(error.data) } : {}
|
|
434
|
+
count: value.length,
|
|
435
|
+
items: value.map((entry) => summarizeAttachmentValue(entry))
|
|
136
436
|
};
|
|
137
437
|
}
|
|
438
|
+
function summarizeAttachmentValue(value) {
|
|
439
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return summarizeTextValue(value);
|
|
440
|
+
const record = value;
|
|
441
|
+
return removeUndefinedFields({
|
|
442
|
+
filename: normalizeString(record.filename) ?? normalizeString(record.fileName) ?? void 0,
|
|
443
|
+
mime: normalizeString(record.mime) ?? normalizeString(record.mimeType) ?? void 0,
|
|
444
|
+
sizeBytes: pickNumericValue(record, [
|
|
445
|
+
"sizeBytes",
|
|
446
|
+
"size",
|
|
447
|
+
"fileSize"
|
|
448
|
+
]),
|
|
449
|
+
hasCaption: pickStringValue(record, ["caption"]) !== null,
|
|
450
|
+
textLength: pickStringValue(record, ["text", "prompt"])?.length,
|
|
451
|
+
type: normalizeString(record.type) ?? void 0
|
|
452
|
+
});
|
|
453
|
+
}
|
|
138
454
|
function isSensitiveKey(key) {
|
|
139
|
-
return /token|secret|api[-_]?key|authorization|password/iu.test(key);
|
|
455
|
+
return /token|secret|api[-_]?key|authorization|password|cookie/iu.test(key);
|
|
456
|
+
}
|
|
457
|
+
function isTextContentKey(key) {
|
|
458
|
+
return /(^|[-_])(text|prompt|caption|body|markdown|content|messageText|fallbackText|input|raw)$/iu.test(key) || /bodyMd|bodyText|messageText|fallbackText/iu.test(key);
|
|
459
|
+
}
|
|
460
|
+
function isUrlLikeKey(key) {
|
|
461
|
+
return /(^|[-_])(url|uri|href|filePath|downloadPath|downloadUrl|fileUrl)$/iu.test(key);
|
|
462
|
+
}
|
|
463
|
+
function isAttachmentCollectionKey(key) {
|
|
464
|
+
return /(^|[-_])(files|parts|attachments)$/iu.test(key);
|
|
140
465
|
}
|
|
141
466
|
function redactSensitiveFieldValue(value) {
|
|
142
|
-
if (typeof value === "string" && value.trim().length > 0) return
|
|
143
|
-
if (Array.isArray(value)) return value.map(() =>
|
|
144
|
-
if (value && typeof value === "object") return
|
|
467
|
+
if (typeof value === "string" && value.trim().length > 0) return REDACTED;
|
|
468
|
+
if (Array.isArray(value)) return value.map(() => REDACTED);
|
|
469
|
+
if (value && typeof value === "object") return REDACTED;
|
|
145
470
|
return value;
|
|
146
471
|
}
|
|
472
|
+
function removeUndefinedFields(record) {
|
|
473
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== void 0));
|
|
474
|
+
}
|
|
475
|
+
function isPlainObject(value) {
|
|
476
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
477
|
+
}
|
|
478
|
+
function normalizeString(value) {
|
|
479
|
+
return typeof value === "string" && value.trim().length > 0 ? redactSensitiveText(value.trim()) : null;
|
|
480
|
+
}
|
|
481
|
+
function pickNumericValue(record, keys) {
|
|
482
|
+
for (const key of keys) {
|
|
483
|
+
const value = record[key];
|
|
484
|
+
if (typeof value === "number") return value;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function pickStringValue(record, keys) {
|
|
488
|
+
for (const key of keys) {
|
|
489
|
+
const value = record[key];
|
|
490
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
491
|
+
}
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
function sanitizeFileSegment(value) {
|
|
495
|
+
return value.replace(/[^a-z0-9._-]+/giu, "_");
|
|
496
|
+
}
|
|
497
|
+
function normalizeStructuredLogErrorRecord(value) {
|
|
498
|
+
return {
|
|
499
|
+
name: normalizeString(value.name) ?? "Error",
|
|
500
|
+
message: normalizeString(value.message) ?? "Unknown error",
|
|
501
|
+
...typeof value.stack === "string" ? { stack: redactSensitiveText(value.stack) } : {},
|
|
502
|
+
...value.data !== void 0 ? { data: sanitizeValue(value.data) } : {}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
147
505
|
//#endregion
|
|
148
506
|
//#region src/repositories/pending-action.repo.ts
|
|
149
507
|
var FilePendingActionRepository = class {
|
|
@@ -1979,10 +2337,11 @@ var SendPromptUseCase = class {
|
|
|
1979
2337
|
binding
|
|
1980
2338
|
});
|
|
1981
2339
|
binding = createdSession.binding;
|
|
1982
|
-
this.logger
|
|
2340
|
+
logPromptLifecycle(this.logger, {
|
|
1983
2341
|
chatId: input.chatId,
|
|
1984
|
-
|
|
2342
|
+
event: "prompt.session.created",
|
|
1985
2343
|
projectId: createdSession.session.projectID,
|
|
2344
|
+
sessionId: createdSession.session.id,
|
|
1986
2345
|
directory: createdSession.session.directory
|
|
1987
2346
|
}, "session created");
|
|
1988
2347
|
}
|
|
@@ -1990,11 +2349,15 @@ var SendPromptUseCase = class {
|
|
|
1990
2349
|
const selectedModel = (await this.opencodeClient.listModels()).find((model) => model.providerID === binding?.modelProviderId && model.id === binding?.modelId);
|
|
1991
2350
|
if (!selectedModel) {
|
|
1992
2351
|
binding = await clearStoredModelSelection(this.sessionRepo, binding);
|
|
1993
|
-
this.logger.warn?.({
|
|
2352
|
+
this.logger.warn?.({
|
|
2353
|
+
chatId: input.chatId,
|
|
2354
|
+
event: "prompt.model.unavailable"
|
|
2355
|
+
}, "selected model is no longer available, falling back to OpenCode default");
|
|
1994
2356
|
} else if (binding.modelVariant && !(binding.modelVariant in selectedModel.variants)) {
|
|
1995
2357
|
binding = await clearStoredModelVariant(this.sessionRepo, binding);
|
|
1996
2358
|
this.logger.warn?.({
|
|
1997
2359
|
chatId: input.chatId,
|
|
2360
|
+
event: "prompt.model.variant_unavailable",
|
|
1998
2361
|
providerId: selectedModel.providerID,
|
|
1999
2362
|
modelId: selectedModel.id
|
|
2000
2363
|
}, "selected model variant is no longer available, falling back to default variant");
|
|
@@ -2010,13 +2373,24 @@ var SendPromptUseCase = class {
|
|
|
2010
2373
|
const selectedAgent = resolveSelectedAgent(await this.opencodeClient.listAgents(), activeBinding.agentName);
|
|
2011
2374
|
if (activeBinding.agentName && selectedAgent?.name !== activeBinding.agentName) {
|
|
2012
2375
|
activeBinding = await clearStoredAgentSelection(this.sessionRepo, activeBinding);
|
|
2013
|
-
this.logger.warn?.({
|
|
2376
|
+
this.logger.warn?.({
|
|
2377
|
+
chatId: input.chatId,
|
|
2378
|
+
event: "prompt.agent.unavailable"
|
|
2379
|
+
}, "selected agent is no longer available, falling back to OpenCode default");
|
|
2014
2380
|
}
|
|
2015
2381
|
const temporarySessionId = shouldIsolateImageTurn ? await this.createTemporaryImageSession(input.chatId, activeBinding.sessionId) : null;
|
|
2016
2382
|
const executionSessionId = temporarySessionId ?? activeBinding.sessionId;
|
|
2017
2383
|
input.onExecutionSession?.(executionSessionId);
|
|
2018
2384
|
let result;
|
|
2019
2385
|
try {
|
|
2386
|
+
logOpenCodeRequest(this.logger, {
|
|
2387
|
+
chatId: input.chatId,
|
|
2388
|
+
event: "opencode.prompt.submit",
|
|
2389
|
+
projectId: activeBinding.projectId,
|
|
2390
|
+
sessionId: executionSessionId,
|
|
2391
|
+
fileCount: files.length,
|
|
2392
|
+
status: "started"
|
|
2393
|
+
}, "submitting OpenCode prompt");
|
|
2020
2394
|
result = await this.opencodeClient.promptSession({
|
|
2021
2395
|
sessionId: executionSessionId,
|
|
2022
2396
|
prompt: promptText,
|
|
@@ -2027,6 +2401,13 @@ var SendPromptUseCase = class {
|
|
|
2027
2401
|
...input.signal ? { signal: input.signal } : {},
|
|
2028
2402
|
...activeBinding.modelVariant ? { variant: activeBinding.modelVariant } : {}
|
|
2029
2403
|
});
|
|
2404
|
+
logPromptLifecycle(this.logger, {
|
|
2405
|
+
chatId: input.chatId,
|
|
2406
|
+
event: "prompt.completed",
|
|
2407
|
+
projectId: activeBinding.projectId,
|
|
2408
|
+
sessionId: executionSessionId,
|
|
2409
|
+
status: "completed"
|
|
2410
|
+
}, "prompt completed");
|
|
2030
2411
|
} finally {
|
|
2031
2412
|
if (temporarySessionId) await this.cleanupTemporaryImageSession(input.chatId, activeBinding.sessionId, temporarySessionId);
|
|
2032
2413
|
}
|
|
@@ -2039,14 +2420,18 @@ var SendPromptUseCase = class {
|
|
|
2039
2420
|
}
|
|
2040
2421
|
async clearInvalidSessionContext(chatId, binding, reason) {
|
|
2041
2422
|
const nextBinding = await clearStoredSessionContext(this.sessionRepo, binding);
|
|
2042
|
-
this.logger.warn?.({
|
|
2423
|
+
this.logger.warn?.({
|
|
2424
|
+
chatId,
|
|
2425
|
+
event: "prompt.session.invalid_context"
|
|
2426
|
+
}, `${reason}, falling back to the current OpenCode project`);
|
|
2043
2427
|
return nextBinding;
|
|
2044
2428
|
}
|
|
2045
2429
|
async createTemporaryImageSession(chatId, sessionId) {
|
|
2046
2430
|
const temporarySession = await this.opencodeClient.forkSession(sessionId);
|
|
2047
2431
|
if (!temporarySession.id || temporarySession.id === sessionId) throw new Error("OpenCode did not return a distinct temporary session for the image turn.");
|
|
2048
|
-
this.logger
|
|
2432
|
+
logPromptLifecycle(this.logger, {
|
|
2049
2433
|
chatId,
|
|
2434
|
+
event: "prompt.temporary_session.created",
|
|
2050
2435
|
parentSessionId: sessionId,
|
|
2051
2436
|
sessionId: temporarySession.id
|
|
2052
2437
|
}, "created temporary image session");
|
|
@@ -2056,6 +2441,7 @@ var SendPromptUseCase = class {
|
|
|
2056
2441
|
try {
|
|
2057
2442
|
if (!await this.opencodeClient.deleteSession(sessionId)) this.logger.warn?.({
|
|
2058
2443
|
chatId,
|
|
2444
|
+
event: "prompt.temporary_session.cleanup_failed",
|
|
2059
2445
|
parentSessionId,
|
|
2060
2446
|
sessionId
|
|
2061
2447
|
}, "failed to delete temporary image session");
|
|
@@ -2063,6 +2449,7 @@ var SendPromptUseCase = class {
|
|
|
2063
2449
|
this.logger.warn?.({
|
|
2064
2450
|
error,
|
|
2065
2451
|
chatId,
|
|
2452
|
+
event: "prompt.temporary_session.cleanup_failed",
|
|
2066
2453
|
parentSessionId,
|
|
2067
2454
|
sessionId
|
|
2068
2455
|
}, "failed to delete temporary image session");
|
|
@@ -2263,7 +2650,23 @@ function resolveExtension(mimeType) {
|
|
|
2263
2650
|
//#endregion
|
|
2264
2651
|
//#region src/app/container.ts
|
|
2265
2652
|
function createAppContainer(config, client) {
|
|
2266
|
-
const
|
|
2653
|
+
const runtimeId = randomUUID();
|
|
2654
|
+
const logger = createOpenCodeAppLogger(client, {
|
|
2655
|
+
file: {
|
|
2656
|
+
dir: config.loggingFileDir,
|
|
2657
|
+
retention: {
|
|
2658
|
+
maxFiles: config.loggingRetentionMaxFiles,
|
|
2659
|
+
maxTotalBytes: config.loggingRetentionMaxTotalBytes
|
|
2660
|
+
}
|
|
2661
|
+
},
|
|
2662
|
+
level: config.loggingLevel,
|
|
2663
|
+
runtimeId,
|
|
2664
|
+
sinks: {
|
|
2665
|
+
file: config.loggingFileSinkEnabled,
|
|
2666
|
+
host: config.loggingHostSinkEnabled
|
|
2667
|
+
},
|
|
2668
|
+
worktree: config.worktreePath
|
|
2669
|
+
});
|
|
2267
2670
|
return createContainer(config, createOpenCodeClientFromSdkClient(client, fetch, {
|
|
2268
2671
|
waitTimeoutMs: config.promptWaitTimeoutMs,
|
|
2269
2672
|
pollRequestTimeoutMs: config.promptPollRequestTimeoutMs,
|
|
@@ -2271,6 +2674,9 @@ function createAppContainer(config, client) {
|
|
|
2271
2674
|
}), logger);
|
|
2272
2675
|
}
|
|
2273
2676
|
function createContainer(config, opencodeClient, logger) {
|
|
2677
|
+
const storageLogger = logger.child({ component: "storage" });
|
|
2678
|
+
const opencodeLogger = logger.child({ component: "opencode" });
|
|
2679
|
+
const promptLogger = logger.child({ component: "prompt" });
|
|
2274
2680
|
const stateStore = new JsonStateStore({
|
|
2275
2681
|
filePath: config.stateFilePath,
|
|
2276
2682
|
createDefaultState: createDefaultOpencodeTbotState
|
|
@@ -2285,7 +2691,7 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2285
2691
|
});
|
|
2286
2692
|
const uploadFileUseCase = new UploadFileUseCase(telegramFileClient);
|
|
2287
2693
|
const abortPromptUseCase = new AbortPromptUseCase(sessionRepo, opencodeClient, foregroundSessionTracker);
|
|
2288
|
-
const createSessionUseCase = new CreateSessionUseCase(sessionRepo, opencodeClient,
|
|
2694
|
+
const createSessionUseCase = new CreateSessionUseCase(sessionRepo, opencodeClient, opencodeLogger);
|
|
2289
2695
|
const getHealthUseCase = new GetHealthUseCase(opencodeClient);
|
|
2290
2696
|
const getPathUseCase = new GetPathUseCase(opencodeClient);
|
|
2291
2697
|
const listAgentsUseCase = new ListAgentsUseCase(sessionRepo, opencodeClient);
|
|
@@ -2294,11 +2700,11 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2294
2700
|
const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
|
|
2295
2701
|
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
|
|
2296
2702
|
const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
|
|
2297
|
-
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient,
|
|
2298
|
-
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient,
|
|
2299
|
-
const switchAgentUseCase = new SwitchAgentUseCase(sessionRepo, opencodeClient,
|
|
2300
|
-
const switchModelUseCase = new SwitchModelUseCase(sessionRepo, opencodeClient,
|
|
2301
|
-
const switchSessionUseCase = new SwitchSessionUseCase(sessionRepo, opencodeClient,
|
|
2703
|
+
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, opencodeLogger);
|
|
2704
|
+
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, promptLogger);
|
|
2705
|
+
const switchAgentUseCase = new SwitchAgentUseCase(sessionRepo, opencodeClient, opencodeLogger);
|
|
2706
|
+
const switchModelUseCase = new SwitchModelUseCase(sessionRepo, opencodeClient, opencodeLogger);
|
|
2707
|
+
const switchSessionUseCase = new SwitchSessionUseCase(sessionRepo, opencodeClient, opencodeLogger);
|
|
2302
2708
|
let disposed = false;
|
|
2303
2709
|
return {
|
|
2304
2710
|
abortPromptUseCase,
|
|
@@ -2327,7 +2733,10 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2327
2733
|
async dispose() {
|
|
2328
2734
|
if (disposed) return;
|
|
2329
2735
|
disposed = true;
|
|
2330
|
-
|
|
2736
|
+
storageLogger.info({
|
|
2737
|
+
event: "storage.container.disposed",
|
|
2738
|
+
filePath: config.stateFilePath
|
|
2739
|
+
}, "disposing telegram bot container");
|
|
2331
2740
|
await logger.flush();
|
|
2332
2741
|
}
|
|
2333
2742
|
};
|
|
@@ -2431,6 +2840,11 @@ async function handleTelegramBotPluginEvent(runtime, event) {
|
|
|
2431
2840
|
}
|
|
2432
2841
|
}
|
|
2433
2842
|
async function handlePermissionAsked(runtime, request) {
|
|
2843
|
+
const logger = runtime.container.logger.child({
|
|
2844
|
+
component: "plugin-event",
|
|
2845
|
+
requestId: request.id,
|
|
2846
|
+
sessionId: request.sessionID
|
|
2847
|
+
});
|
|
2434
2848
|
const bindings = await runtime.container.sessionRepo.listBySessionId(request.sessionID);
|
|
2435
2849
|
const chatIds = new Set([...bindings.map((binding) => binding.chatId), ...runtime.container.foregroundSessionTracker.listChatIds(request.sessionID)]);
|
|
2436
2850
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(request.id);
|
|
@@ -2451,35 +2865,45 @@ async function handlePermissionAsked(runtime, request) {
|
|
|
2451
2865
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2452
2866
|
});
|
|
2453
2867
|
} catch (error) {
|
|
2454
|
-
|
|
2868
|
+
logger.error({
|
|
2455
2869
|
error,
|
|
2456
2870
|
chatId,
|
|
2871
|
+
event: "plugin-event.permission.ask.delivery_failed",
|
|
2457
2872
|
requestId: request.id
|
|
2458
2873
|
}, "failed to deliver permission request to Telegram");
|
|
2459
2874
|
}
|
|
2460
2875
|
}
|
|
2461
2876
|
}
|
|
2462
2877
|
async function handlePermissionReplied(runtime, event) {
|
|
2878
|
+
const logger = runtime.container.logger.child({
|
|
2879
|
+
component: "plugin-event",
|
|
2880
|
+
event: "plugin-event.permission.replied",
|
|
2881
|
+
requestId: event.requestId,
|
|
2882
|
+
sessionId: event.sessionId
|
|
2883
|
+
});
|
|
2463
2884
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(event.requestId);
|
|
2464
2885
|
await Promise.all(approvals.map(async (approval) => {
|
|
2465
2886
|
try {
|
|
2466
2887
|
await runtime.bot.api.editMessageText(approval.chatId, approval.messageId, buildPermissionApprovalResolvedMessage(event.requestId, event.reply));
|
|
2467
2888
|
} catch (error) {
|
|
2468
|
-
|
|
2889
|
+
logger.warn({
|
|
2469
2890
|
error,
|
|
2470
2891
|
chatId: approval.chatId,
|
|
2471
|
-
|
|
2472
|
-
sessionId: event.sessionId
|
|
2892
|
+
event: "plugin-event.permission.reply_message_failed"
|
|
2473
2893
|
}, "failed to update Telegram permission message");
|
|
2474
2894
|
}
|
|
2475
2895
|
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, event.reply));
|
|
2476
2896
|
}));
|
|
2477
2897
|
}
|
|
2478
2898
|
async function handleSessionError(runtime, event) {
|
|
2899
|
+
const logger = runtime.container.logger.child({
|
|
2900
|
+
component: "plugin-event",
|
|
2901
|
+
sessionId: event.sessionId
|
|
2902
|
+
});
|
|
2479
2903
|
if (runtime.container.foregroundSessionTracker.fail(event.sessionId, event.error instanceof Error ? event.error : /* @__PURE__ */ new Error("Unknown session error."))) {
|
|
2480
|
-
|
|
2904
|
+
logger.warn({
|
|
2481
2905
|
error: event.error,
|
|
2482
|
-
|
|
2906
|
+
event: "plugin-event.session.error.foreground_suppressed"
|
|
2483
2907
|
}, "session error suppressed for foreground Telegram session");
|
|
2484
2908
|
return;
|
|
2485
2909
|
}
|
|
@@ -2487,8 +2911,12 @@ async function handleSessionError(runtime, event) {
|
|
|
2487
2911
|
await notifyBoundChats(runtime, event.sessionId, `Session failed.\n\nSession: ${event.sessionId}\nError: ${message}`);
|
|
2488
2912
|
}
|
|
2489
2913
|
async function handleSessionIdle(runtime, event) {
|
|
2914
|
+
const logger = runtime.container.logger.child({
|
|
2915
|
+
component: "plugin-event",
|
|
2916
|
+
sessionId: event.sessionId
|
|
2917
|
+
});
|
|
2490
2918
|
if (runtime.container.foregroundSessionTracker.clear(event.sessionId)) {
|
|
2491
|
-
|
|
2919
|
+
logPluginEvent(logger, { event: "plugin-event.session.idle.foreground_suppressed" }, "session idle notification suppressed for foreground Telegram session");
|
|
2492
2920
|
return;
|
|
2493
2921
|
}
|
|
2494
2922
|
await notifyBoundChats(runtime, event.sessionId, `Session finished.\n\nSession: ${event.sessionId}`);
|
|
@@ -2498,16 +2926,20 @@ async function handleSessionStatus(runtime, event) {
|
|
|
2498
2926
|
await handleSessionIdle(runtime, event);
|
|
2499
2927
|
}
|
|
2500
2928
|
async function notifyBoundChats(runtime, sessionId, text) {
|
|
2929
|
+
const logger = runtime.container.logger.child({
|
|
2930
|
+
component: "plugin-event",
|
|
2931
|
+
sessionId
|
|
2932
|
+
});
|
|
2501
2933
|
const bindings = await runtime.container.sessionRepo.listBySessionId(sessionId);
|
|
2502
2934
|
const chatIds = [...new Set(bindings.map((binding) => binding.chatId))];
|
|
2503
2935
|
await Promise.all(chatIds.map(async (chatId) => {
|
|
2504
2936
|
try {
|
|
2505
2937
|
await runtime.bot.api.sendMessage(chatId, text);
|
|
2506
2938
|
} catch (error) {
|
|
2507
|
-
|
|
2939
|
+
logger.warn({
|
|
2508
2940
|
error,
|
|
2509
2941
|
chatId,
|
|
2510
|
-
|
|
2942
|
+
event: "plugin-event.session.notify_failed"
|
|
2511
2943
|
}, "failed to notify Telegram chat about session event");
|
|
2512
2944
|
}
|
|
2513
2945
|
}));
|
|
@@ -3284,7 +3716,9 @@ var TELEGRAM_COMMAND_SYNC_SCOPES = [{ type: "default" }, { type: "all_private_ch
|
|
|
3284
3716
|
async function syncTelegramCommands(bot, logger) {
|
|
3285
3717
|
await Promise.all(TELEGRAM_COMMAND_SYNC_SCOPES.map((scope) => bot.api.setMyCommands(TELEGRAM_COMMANDS, { scope })));
|
|
3286
3718
|
logger.info({
|
|
3719
|
+
component: "runtime",
|
|
3287
3720
|
commands: TELEGRAM_COMMANDS.map((command) => command.command),
|
|
3721
|
+
event: "runtime.commands.synced",
|
|
3288
3722
|
scopes: TELEGRAM_COMMAND_SYNC_SCOPES.map((scope) => scope.type)
|
|
3289
3723
|
}, "telegram commands synced");
|
|
3290
3724
|
}
|
|
@@ -3295,6 +3729,44 @@ async function syncTelegramCommandsForChat(api, chatId, language) {
|
|
|
3295
3729
|
} });
|
|
3296
3730
|
}
|
|
3297
3731
|
//#endregion
|
|
3732
|
+
//#region src/bot/logger-context.ts
|
|
3733
|
+
function buildTelegramLoggerContext(ctx, component = "telegram") {
|
|
3734
|
+
const updateId = typeof ctx.update?.update_id === "number" ? ctx.update.update_id : void 0;
|
|
3735
|
+
const command = extractTelegramCommand(resolveMessageText(ctx));
|
|
3736
|
+
const callbackData = normalizeTelegramString(ctx.callbackQuery?.data);
|
|
3737
|
+
const operationId = typeof updateId === "number" ? `telegram-${updateId}` : null;
|
|
3738
|
+
return {
|
|
3739
|
+
component,
|
|
3740
|
+
...typeof ctx.chat?.id === "number" ? { chatId: ctx.chat.id } : {},
|
|
3741
|
+
...typeof updateId === "number" ? { updateId } : {},
|
|
3742
|
+
...command ? { command } : {},
|
|
3743
|
+
...callbackData ? { callbackData } : {},
|
|
3744
|
+
correlationId: typeof updateId === "number" ? String(updateId) : operationId,
|
|
3745
|
+
operationId
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
function scopeLoggerToTelegramContext(logger, ctx, component = "telegram") {
|
|
3749
|
+
return logger.child(buildTelegramLoggerContext(ctx, component));
|
|
3750
|
+
}
|
|
3751
|
+
function scopeDependenciesToTelegramContext(dependencies, ctx, component = "telegram") {
|
|
3752
|
+
return {
|
|
3753
|
+
...dependencies,
|
|
3754
|
+
logger: scopeLoggerToTelegramContext(dependencies.logger, ctx, component)
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
function resolveMessageText(ctx) {
|
|
3758
|
+
return normalizeTelegramString(ctx.message?.text) ?? normalizeTelegramString(ctx.msg?.text);
|
|
3759
|
+
}
|
|
3760
|
+
function extractTelegramCommand(value) {
|
|
3761
|
+
if (!value || !value.startsWith("/")) return null;
|
|
3762
|
+
const token = value.split(/\s+/u, 1)[0]?.trim();
|
|
3763
|
+
if (!token) return null;
|
|
3764
|
+
return token.replace(/^\/+/u, "").split("@", 1)[0] ?? null;
|
|
3765
|
+
}
|
|
3766
|
+
function normalizeTelegramString(value) {
|
|
3767
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3768
|
+
}
|
|
3769
|
+
//#endregion
|
|
3298
3770
|
//#region src/bot/presenters/error.presenter.ts
|
|
3299
3771
|
function presentError(error, copy = BOT_COPY) {
|
|
3300
3772
|
const presented = normalizeError(error, copy);
|
|
@@ -3411,16 +3883,12 @@ function stringifyUnknown(value) {
|
|
|
3411
3883
|
//#endregion
|
|
3412
3884
|
//#region src/bot/error-boundary.ts
|
|
3413
3885
|
function extractTelegramUpdateContext(ctx) {
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
...typeof chatId === "number" ? { chatId } : {},
|
|
3421
|
-
...typeof messageText === "string" && messageText.trim().length > 0 ? { messageText } : {},
|
|
3422
|
-
...typeof callbackData === "string" && callbackData.trim().length > 0 ? { callbackData } : {}
|
|
3423
|
-
};
|
|
3886
|
+
return buildTelegramLoggerContext({
|
|
3887
|
+
callbackQuery: { data: getNestedString(ctx, ["callbackQuery", "data"]) },
|
|
3888
|
+
chat: { id: getNestedNumber(ctx, ["chat", "id"]) ?? void 0 },
|
|
3889
|
+
message: { text: getNestedString(ctx, ["message", "text"]) },
|
|
3890
|
+
update: { update_id: getNestedNumber(ctx, ["update", "update_id"]) ?? void 0 }
|
|
3891
|
+
});
|
|
3424
3892
|
}
|
|
3425
3893
|
async function replyWithDefaultTelegramError(ctx, logger, error) {
|
|
3426
3894
|
const text = presentError(error, BOT_COPY);
|
|
@@ -3985,7 +4453,7 @@ async function handleAgentsCommand(ctx, dependencies) {
|
|
|
3985
4453
|
}
|
|
3986
4454
|
function registerAgentsCommand(bot, dependencies) {
|
|
3987
4455
|
bot.command(["agents", "agent"], async (ctx) => {
|
|
3988
|
-
await handleAgentsCommand(ctx, dependencies);
|
|
4456
|
+
await handleAgentsCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
3989
4457
|
});
|
|
3990
4458
|
}
|
|
3991
4459
|
//#endregion
|
|
@@ -4128,7 +4596,7 @@ async function handleCancelCommand(ctx, dependencies) {
|
|
|
4128
4596
|
}
|
|
4129
4597
|
function registerCancelCommand(bot, dependencies) {
|
|
4130
4598
|
bot.command("cancel", async (ctx) => {
|
|
4131
|
-
await handleCancelCommand(ctx, dependencies);
|
|
4599
|
+
await handleCancelCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4132
4600
|
});
|
|
4133
4601
|
}
|
|
4134
4602
|
//#endregion
|
|
@@ -4175,7 +4643,7 @@ async function presentLanguageSwitchForChat(chatId, api, language, dependencies)
|
|
|
4175
4643
|
}
|
|
4176
4644
|
function registerLanguageCommand(bot, dependencies) {
|
|
4177
4645
|
bot.command("language", async (ctx) => {
|
|
4178
|
-
await handleLanguageCommand(ctx, dependencies);
|
|
4646
|
+
await handleLanguageCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4179
4647
|
});
|
|
4180
4648
|
}
|
|
4181
4649
|
//#endregion
|
|
@@ -4203,7 +4671,7 @@ async function handleModelsCommand(ctx, dependencies) {
|
|
|
4203
4671
|
}
|
|
4204
4672
|
function registerModelsCommand(bot, dependencies) {
|
|
4205
4673
|
bot.command(["model", "models"], async (ctx) => {
|
|
4206
|
-
await handleModelsCommand(ctx, dependencies);
|
|
4674
|
+
await handleModelsCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4207
4675
|
});
|
|
4208
4676
|
}
|
|
4209
4677
|
//#endregion
|
|
@@ -4224,7 +4692,7 @@ async function handleNewCommand(ctx, dependencies) {
|
|
|
4224
4692
|
}
|
|
4225
4693
|
function registerNewCommand(bot, dependencies) {
|
|
4226
4694
|
bot.command("new", async (ctx) => {
|
|
4227
|
-
await handleNewCommand(ctx, dependencies);
|
|
4695
|
+
await handleNewCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4228
4696
|
});
|
|
4229
4697
|
}
|
|
4230
4698
|
function extractSessionTitle(ctx) {
|
|
@@ -4589,7 +5057,7 @@ async function handleStatusCommand(ctx, dependencies) {
|
|
|
4589
5057
|
}
|
|
4590
5058
|
function registerStatusCommand(bot, dependencies) {
|
|
4591
5059
|
bot.command("status", async (ctx) => {
|
|
4592
|
-
await handleStatusCommand(ctx, dependencies);
|
|
5060
|
+
await handleStatusCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4593
5061
|
});
|
|
4594
5062
|
}
|
|
4595
5063
|
//#endregion
|
|
@@ -4607,7 +5075,7 @@ async function handleSessionsCommand(ctx, dependencies) {
|
|
|
4607
5075
|
}
|
|
4608
5076
|
function registerSessionsCommand(bot, dependencies) {
|
|
4609
5077
|
bot.command("sessions", async (ctx) => {
|
|
4610
|
-
await handleSessionsCommand(ctx, dependencies);
|
|
5078
|
+
await handleSessionsCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4611
5079
|
});
|
|
4612
5080
|
}
|
|
4613
5081
|
//#endregion
|
|
@@ -4634,7 +5102,7 @@ async function handleStartCommand(ctx, dependencies) {
|
|
|
4634
5102
|
}
|
|
4635
5103
|
function registerStartCommand(bot, dependencies) {
|
|
4636
5104
|
bot.command("start", async (ctx) => {
|
|
4637
|
-
await handleStartCommand(ctx, dependencies);
|
|
5105
|
+
await handleStartCommand(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4638
5106
|
});
|
|
4639
5107
|
}
|
|
4640
5108
|
//#endregion
|
|
@@ -4900,19 +5368,19 @@ async function handlePermissionApprovalCallback(ctx, dependencies) {
|
|
|
4900
5368
|
}
|
|
4901
5369
|
function registerCallbackHandler(bot, dependencies) {
|
|
4902
5370
|
bot.callbackQuery(/^agents:/, async (ctx) => {
|
|
4903
|
-
await handleAgentsCallback(ctx, dependencies);
|
|
5371
|
+
await handleAgentsCallback(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4904
5372
|
});
|
|
4905
5373
|
bot.callbackQuery(/^sessions:/, async (ctx) => {
|
|
4906
|
-
await handleSessionsCallback(ctx, dependencies);
|
|
5374
|
+
await handleSessionsCallback(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4907
5375
|
});
|
|
4908
5376
|
bot.callbackQuery(/^model:/, async (ctx) => {
|
|
4909
|
-
await handleModelsCallback(ctx, dependencies);
|
|
5377
|
+
await handleModelsCallback(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4910
5378
|
});
|
|
4911
5379
|
bot.callbackQuery(/^language:/, async (ctx) => {
|
|
4912
|
-
await handleLanguageCallback(ctx, dependencies);
|
|
5380
|
+
await handleLanguageCallback(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4913
5381
|
});
|
|
4914
5382
|
bot.callbackQuery(/^permission:/, async (ctx) => {
|
|
4915
|
-
await handlePermissionApprovalCallback(ctx, dependencies);
|
|
5383
|
+
await handlePermissionApprovalCallback(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
4916
5384
|
});
|
|
4917
5385
|
}
|
|
4918
5386
|
function parseSessionActionTarget(data, prefix) {
|
|
@@ -5014,10 +5482,10 @@ async function handleImageMessage(ctx, dependencies) {
|
|
|
5014
5482
|
}
|
|
5015
5483
|
function registerFileHandler(bot, dependencies) {
|
|
5016
5484
|
bot.on("message:photo", async (ctx) => {
|
|
5017
|
-
await handleImageMessage(ctx, dependencies);
|
|
5485
|
+
await handleImageMessage(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
5018
5486
|
});
|
|
5019
5487
|
bot.on("message:document", async (ctx) => {
|
|
5020
|
-
await handleImageMessage(ctx, dependencies);
|
|
5488
|
+
await handleImageMessage(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
5021
5489
|
});
|
|
5022
5490
|
}
|
|
5023
5491
|
function resolveTelegramImage(message) {
|
|
@@ -5059,7 +5527,7 @@ async function handleTextMessage(ctx, dependencies) {
|
|
|
5059
5527
|
}
|
|
5060
5528
|
function registerMessageHandler(bot, dependencies) {
|
|
5061
5529
|
bot.on("message:text", async (ctx) => {
|
|
5062
|
-
await handleTextMessage(ctx, dependencies);
|
|
5530
|
+
await handleTextMessage(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
5063
5531
|
});
|
|
5064
5532
|
}
|
|
5065
5533
|
//#endregion
|
|
@@ -5072,7 +5540,7 @@ async function handleVoiceMessage(ctx, dependencies) {
|
|
|
5072
5540
|
}
|
|
5073
5541
|
function registerVoiceHandler(bot, dependencies) {
|
|
5074
5542
|
bot.on("message:voice", async (ctx) => {
|
|
5075
|
-
await handleVoiceMessage(ctx, dependencies);
|
|
5543
|
+
await handleVoiceMessage(ctx, scopeDependenciesToTelegramContext(dependencies, ctx, "telegram"));
|
|
5076
5544
|
});
|
|
5077
5545
|
}
|
|
5078
5546
|
//#endregion
|
|
@@ -5093,17 +5561,18 @@ function createAuthMiddleware(allowedChatIds) {
|
|
|
5093
5561
|
function buildIncomingUpdateLogFields(ctx) {
|
|
5094
5562
|
const messageText = ctx.msg && "text" in ctx.msg ? ctx.msg.text : void 0;
|
|
5095
5563
|
return {
|
|
5564
|
+
...buildTelegramLoggerContext(ctx),
|
|
5565
|
+
event: "telegram.update.received",
|
|
5096
5566
|
updateId: ctx.update.update_id,
|
|
5097
5567
|
chatId: ctx.chat?.id,
|
|
5098
5568
|
fromId: ctx.from?.id,
|
|
5099
5569
|
hasText: typeof messageText === "string" && messageText.length > 0,
|
|
5100
|
-
textLength: typeof messageText === "string" ? messageText.length : 0
|
|
5101
|
-
textPreview: typeof messageText === "string" && messageText.length > 0 ? createRedactedPreview(messageText) : void 0
|
|
5570
|
+
textLength: typeof messageText === "string" ? messageText.length : 0
|
|
5102
5571
|
};
|
|
5103
5572
|
}
|
|
5104
5573
|
function createLoggingMiddleware(logger) {
|
|
5105
5574
|
return async (ctx, next) => {
|
|
5106
|
-
logger
|
|
5575
|
+
logTelegramUpdate(logger, { ...buildIncomingUpdateLogFields(ctx) }, "incoming update");
|
|
5107
5576
|
return next();
|
|
5108
5577
|
};
|
|
5109
5578
|
}
|
|
@@ -5113,11 +5582,13 @@ function registerBot(bot, container, options) {
|
|
|
5113
5582
|
bot.use(createLoggingMiddleware(container.logger));
|
|
5114
5583
|
bot.use(createAuthMiddleware(options.telegramAllowedChatIds));
|
|
5115
5584
|
const safeBot = bot.errorBoundary(async (error) => {
|
|
5116
|
-
container.logger.
|
|
5585
|
+
const scopedLogger = scopeLoggerToTelegramContext(container.logger, error.ctx, "telegram");
|
|
5586
|
+
scopedLogger.error({
|
|
5117
5587
|
...extractTelegramUpdateContext(error.ctx),
|
|
5588
|
+
event: "telegram.middleware.failed",
|
|
5118
5589
|
error: error.error
|
|
5119
5590
|
}, "telegram middleware failed");
|
|
5120
|
-
await replyWithDefaultTelegramError(error.ctx,
|
|
5591
|
+
await replyWithDefaultTelegramError(error.ctx, scopedLogger, error.error);
|
|
5121
5592
|
});
|
|
5122
5593
|
registerStartCommand(safeBot, container);
|
|
5123
5594
|
registerStatusCommand(safeBot, container);
|
|
@@ -5144,8 +5615,10 @@ async function startTelegramBotRuntime(input) {
|
|
|
5144
5615
|
const runtimeKey = buildTelegramRuntimeKey(input.config);
|
|
5145
5616
|
const registry = getTelegramBotRuntimeRegistry();
|
|
5146
5617
|
const existingRuntime = registry.activeByKey.get(runtimeKey);
|
|
5618
|
+
const runtimeLogger = input.container.logger.child({ component: "runtime" });
|
|
5147
5619
|
if (existingRuntime) {
|
|
5148
|
-
|
|
5620
|
+
runtimeLogger.warn({
|
|
5621
|
+
event: "runtime.reused",
|
|
5149
5622
|
runtimeKey,
|
|
5150
5623
|
telegramApiRoot: input.config.telegramApiRoot
|
|
5151
5624
|
}, "telegram runtime already active in this process; reusing the existing runner");
|
|
@@ -5163,13 +5636,18 @@ async function startTelegramBotRuntime(input) {
|
|
|
5163
5636
|
}
|
|
5164
5637
|
async function startTelegramBotRuntimeInternal(input, runtimeKey, releaseRuntime) {
|
|
5165
5638
|
const bot = (input.botFactory ?? ((token, options) => new Bot(token, options)))(input.config.telegramBotToken, { client: { apiRoot: input.config.telegramApiRoot } });
|
|
5639
|
+
const runtimeLogger = input.container.logger.child({ component: "runtime" });
|
|
5166
5640
|
wrapTelegramGetUpdates(bot, input.container);
|
|
5167
5641
|
(input.registerBotHandlers ?? registerBot)(bot, input.container, { telegramAllowedChatIds: input.config.telegramAllowedChatIds });
|
|
5168
5642
|
bot.catch((error) => {
|
|
5169
5643
|
const metadata = extractTelegramUpdateContext(error.ctx);
|
|
5644
|
+
const telegramLogger = input.container.logger.child({
|
|
5645
|
+
component: "telegram",
|
|
5646
|
+
...metadata
|
|
5647
|
+
});
|
|
5170
5648
|
if (error.error instanceof GrammyError) {
|
|
5171
|
-
|
|
5172
|
-
|
|
5649
|
+
telegramLogger.error({
|
|
5650
|
+
event: "telegram.api.error",
|
|
5173
5651
|
errorCode: error.error.error_code,
|
|
5174
5652
|
description: error.error.description,
|
|
5175
5653
|
method: error.error.method,
|
|
@@ -5179,24 +5657,28 @@ async function startTelegramBotRuntimeInternal(input, runtimeKey, releaseRuntime
|
|
|
5179
5657
|
return;
|
|
5180
5658
|
}
|
|
5181
5659
|
if (error.error instanceof HttpError) {
|
|
5182
|
-
|
|
5183
|
-
|
|
5660
|
+
telegramLogger.error({
|
|
5661
|
+
event: "telegram.http.error",
|
|
5184
5662
|
error: error.error.error,
|
|
5185
5663
|
message: error.error.message
|
|
5186
5664
|
}, "telegram bot network request failed");
|
|
5187
5665
|
return;
|
|
5188
5666
|
}
|
|
5189
|
-
|
|
5190
|
-
|
|
5667
|
+
telegramLogger.error({
|
|
5668
|
+
event: "telegram.update.failed",
|
|
5191
5669
|
error: error.error
|
|
5192
5670
|
}, "telegram bot update failed");
|
|
5193
5671
|
});
|
|
5194
|
-
|
|
5672
|
+
runtimeLogger.info({
|
|
5673
|
+
event: "runtime.polling.starting",
|
|
5674
|
+
runtimeKey
|
|
5675
|
+
}, "telegram bot polling starting");
|
|
5195
5676
|
const runner = (input.runBot ?? run)(bot, TELEGRAM_RUNNER_OPTIONS);
|
|
5196
5677
|
let stopped = false;
|
|
5197
5678
|
let disposed = false;
|
|
5198
5679
|
if (input.syncCommands ?? true) (input.syncCommandsHandler ?? syncTelegramCommands)(bot, input.container.logger).catch((error) => {
|
|
5199
|
-
|
|
5680
|
+
runtimeLogger.warn({
|
|
5681
|
+
event: "runtime.commands.sync_failed",
|
|
5200
5682
|
error,
|
|
5201
5683
|
runtimeKey
|
|
5202
5684
|
}, "failed to sync telegram commands; polling continues without command registration updates");
|
|
@@ -5206,7 +5688,8 @@ async function startTelegramBotRuntimeInternal(input, runtimeKey, releaseRuntime
|
|
|
5206
5688
|
if (stopped) return;
|
|
5207
5689
|
stopped = true;
|
|
5208
5690
|
stopPromise = runner.stop().catch((error) => {
|
|
5209
|
-
|
|
5691
|
+
runtimeLogger.warn({
|
|
5692
|
+
event: "runtime.stop.failed",
|
|
5210
5693
|
error,
|
|
5211
5694
|
runtimeKey
|
|
5212
5695
|
}, "failed to stop telegram runner cleanly");
|
|
@@ -5235,6 +5718,7 @@ async function startTelegramBotRuntimeInternal(input, runtimeKey, releaseRuntime
|
|
|
5235
5718
|
}
|
|
5236
5719
|
function wrapTelegramGetUpdates(bot, container) {
|
|
5237
5720
|
const originalGetUpdates = bot.api.getUpdates.bind(bot.api);
|
|
5721
|
+
const runtimeLogger = container.logger.child({ component: "runtime" });
|
|
5238
5722
|
bot.api.getUpdates = async (options, signal) => {
|
|
5239
5723
|
const requestOptions = options ?? {
|
|
5240
5724
|
limit: 100,
|
|
@@ -5244,7 +5728,8 @@ function wrapTelegramGetUpdates(bot, container) {
|
|
|
5244
5728
|
try {
|
|
5245
5729
|
return await originalGetUpdates(requestOptions, signal);
|
|
5246
5730
|
} catch (error) {
|
|
5247
|
-
|
|
5731
|
+
runtimeLogger.warn({
|
|
5732
|
+
event: "runtime.telegram.get_updates_failed",
|
|
5248
5733
|
error,
|
|
5249
5734
|
limit: requestOptions.limit,
|
|
5250
5735
|
offset: requestOptions.offset,
|
|
@@ -5308,8 +5793,9 @@ async function startPluginRuntime(options, cwd) {
|
|
|
5308
5793
|
});
|
|
5309
5794
|
const { config, container } = bootstrapApp(options.context.client, preparedConfiguration.config, { cwd: preparedConfiguration.cwd });
|
|
5310
5795
|
try {
|
|
5311
|
-
if (preparedConfiguration.ignoredProjectConfigFilePath) container.logger.warn({
|
|
5796
|
+
if (preparedConfiguration.ignoredProjectConfigFilePath) container.logger.child({ component: "runtime" }).warn({
|
|
5312
5797
|
cwd: preparedConfiguration.cwd,
|
|
5798
|
+
event: "runtime.config.legacy_worktree_ignored",
|
|
5313
5799
|
ignoredProjectConfigFilePath: preparedConfiguration.ignoredProjectConfigFilePath,
|
|
5314
5800
|
globalConfigFilePath: preparedConfiguration.globalConfigFilePath
|
|
5315
5801
|
}, "legacy worktree plugin config is ignored; migrate settings to the global opencode-tbot config");
|
|
@@ -5317,8 +5803,9 @@ async function startPluginRuntime(options, cwd) {
|
|
|
5317
5803
|
config,
|
|
5318
5804
|
container
|
|
5319
5805
|
});
|
|
5320
|
-
container.logger.info({
|
|
5806
|
+
container.logger.child({ component: "runtime" }).info({
|
|
5321
5807
|
cwd: preparedConfiguration.cwd,
|
|
5808
|
+
event: "runtime.plugin.started",
|
|
5322
5809
|
globalConfigFilePath: preparedConfiguration.globalConfigFilePath,
|
|
5323
5810
|
ignoredProjectConfigFilePath: preparedConfiguration.ignoredProjectConfigFilePath,
|
|
5324
5811
|
configFilePath: preparedConfiguration.configFilePath,
|