opencode-tbot 0.1.27 → 0.1.30
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 +3 -0
- package/README.zh-CN.md +3 -1
- package/dist/plugin.js +629 -247
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -3,10 +3,10 @@ import { mkdir, readFile, rename, stat, 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
|
|
6
|
+
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { run } from "@grammyjs/runner";
|
|
9
|
-
import { Bot, InlineKeyboard } from "grammy";
|
|
9
|
+
import { Bot, GrammyError, HttpError, InlineKeyboard } from "grammy";
|
|
10
10
|
//#region src/infra/utils/redact.ts
|
|
11
11
|
var REDACTED = "[REDACTED]";
|
|
12
12
|
var DEFAULT_PREVIEW_LENGTH = 160;
|
|
@@ -46,7 +46,18 @@ function createOpenCodeAppLogger(client, options = {}) {
|
|
|
46
46
|
};
|
|
47
47
|
queue = queue.catch(() => void 0).then(async () => {
|
|
48
48
|
try {
|
|
49
|
-
|
|
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
|
+
});
|
|
50
61
|
} catch {}
|
|
51
62
|
});
|
|
52
63
|
};
|
|
@@ -292,31 +303,57 @@ var OpenCodeClient = class {
|
|
|
292
303
|
...SDK_OPTIONS,
|
|
293
304
|
...input.signal ? { signal: input.signal } : {}
|
|
294
305
|
};
|
|
295
|
-
return unwrapSdkData(
|
|
306
|
+
return unwrapSdkData(handler.length >= 2 ? await handler.call(target, input.legacyParameters, options) : await handler.call(target, input.parameters === void 0 ? options : {
|
|
307
|
+
...input.parameters,
|
|
308
|
+
...options
|
|
309
|
+
}));
|
|
296
310
|
}
|
|
297
311
|
async getHealth() {
|
|
298
|
-
|
|
312
|
+
try {
|
|
313
|
+
return await this.callScopedSdkMethod("global", "health", {});
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (!isMissingScopedSdkMethodError(error, "global", "health")) throw error;
|
|
316
|
+
return this.callRawSdkGet("/global/health");
|
|
317
|
+
}
|
|
299
318
|
}
|
|
300
319
|
async abortSession(sessionId) {
|
|
301
|
-
return this.callScopedSdkMethod("session", "abort", {
|
|
320
|
+
return this.callScopedSdkMethod("session", "abort", {
|
|
321
|
+
legacyParameters: { sessionID: sessionId },
|
|
322
|
+
parameters: { path: { id: sessionId } }
|
|
323
|
+
});
|
|
302
324
|
}
|
|
303
325
|
async deleteSession(sessionId) {
|
|
304
|
-
return this.callScopedSdkMethod("session", "delete", {
|
|
326
|
+
return this.callScopedSdkMethod("session", "delete", {
|
|
327
|
+
legacyParameters: { sessionID: sessionId },
|
|
328
|
+
parameters: { path: { id: sessionId } }
|
|
329
|
+
});
|
|
305
330
|
}
|
|
306
331
|
async forkSession(sessionId, messageId) {
|
|
307
|
-
return this.callScopedSdkMethod("session", "fork", {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
332
|
+
return this.callScopedSdkMethod("session", "fork", {
|
|
333
|
+
legacyParameters: {
|
|
334
|
+
sessionID: sessionId,
|
|
335
|
+
...messageId?.trim() ? { messageID: messageId.trim() } : {}
|
|
336
|
+
},
|
|
337
|
+
parameters: {
|
|
338
|
+
path: { id: sessionId },
|
|
339
|
+
...messageId?.trim() ? { body: { messageID: messageId.trim() } } : {}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
311
342
|
}
|
|
312
343
|
async getPath() {
|
|
313
344
|
return this.callScopedSdkMethod("path", "get", {});
|
|
314
345
|
}
|
|
315
346
|
async listLspStatuses(directory) {
|
|
316
|
-
return this.callScopedSdkMethod("lsp", "status", {
|
|
347
|
+
return this.callScopedSdkMethod("lsp", "status", {
|
|
348
|
+
legacyParameters: directory ? { directory } : void 0,
|
|
349
|
+
parameters: directory ? { query: { directory } } : void 0
|
|
350
|
+
});
|
|
317
351
|
}
|
|
318
352
|
async listMcpStatuses(directory) {
|
|
319
|
-
return this.callScopedSdkMethod("mcp", "status", {
|
|
353
|
+
return this.callScopedSdkMethod("mcp", "status", {
|
|
354
|
+
legacyParameters: directory ? { directory } : void 0,
|
|
355
|
+
parameters: directory ? { query: { directory } } : void 0
|
|
356
|
+
});
|
|
320
357
|
}
|
|
321
358
|
async getSessionStatuses() {
|
|
322
359
|
return this.loadSessionStatuses();
|
|
@@ -331,29 +368,69 @@ var OpenCodeClient = class {
|
|
|
331
368
|
return this.callScopedSdkMethod("project", "current", {});
|
|
332
369
|
}
|
|
333
370
|
async createSessionForDirectory(directory, title) {
|
|
334
|
-
return this.callScopedSdkMethod("session", "create", {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
371
|
+
return this.callScopedSdkMethod("session", "create", {
|
|
372
|
+
legacyParameters: title ? {
|
|
373
|
+
directory,
|
|
374
|
+
title
|
|
375
|
+
} : { directory },
|
|
376
|
+
parameters: {
|
|
377
|
+
query: { directory },
|
|
378
|
+
...title ? { body: { title } } : {}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
338
381
|
}
|
|
339
382
|
async renameSession(sessionId, title) {
|
|
340
|
-
return this.callScopedSdkMethod("session", "update", {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
383
|
+
return this.callScopedSdkMethod("session", "update", {
|
|
384
|
+
legacyParameters: {
|
|
385
|
+
sessionID: sessionId,
|
|
386
|
+
title
|
|
387
|
+
},
|
|
388
|
+
parameters: {
|
|
389
|
+
path: { id: sessionId },
|
|
390
|
+
body: { title }
|
|
391
|
+
}
|
|
392
|
+
});
|
|
344
393
|
}
|
|
345
394
|
async listAgents() {
|
|
346
395
|
return this.callScopedSdkMethod("app", "agents", {});
|
|
347
396
|
}
|
|
348
397
|
async listPendingPermissions(directory) {
|
|
349
|
-
return this.callScopedSdkMethod("permission", "list", {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
398
|
+
return (await this.callScopedSdkMethod("permission", "list", {
|
|
399
|
+
legacyParameters: directory ? { directory } : void 0,
|
|
400
|
+
parameters: directory ? { query: { directory } } : void 0
|
|
401
|
+
})).map(normalizePermissionRequest).filter((permission) => !!permission);
|
|
402
|
+
}
|
|
403
|
+
async replyToPermission(requestId, reply, message, sessionId) {
|
|
404
|
+
const normalizedMessage = message?.trim();
|
|
405
|
+
const rootPermissionHandler = this.client.postSessionIdPermissionsPermissionId;
|
|
406
|
+
if (sessionId && typeof rootPermissionHandler === "function") return unwrapSdkData(await rootPermissionHandler.call(this.client, {
|
|
407
|
+
...SDK_OPTIONS,
|
|
408
|
+
body: {
|
|
409
|
+
response: reply,
|
|
410
|
+
...normalizedMessage ? { message: normalizedMessage } : {}
|
|
411
|
+
},
|
|
412
|
+
path: {
|
|
413
|
+
id: sessionId,
|
|
414
|
+
permissionID: requestId
|
|
415
|
+
}
|
|
416
|
+
}));
|
|
417
|
+
return this.callScopedSdkMethod("permission", "reply", {
|
|
418
|
+
legacyParameters: {
|
|
419
|
+
requestID: requestId,
|
|
420
|
+
reply,
|
|
421
|
+
...normalizedMessage ? { message: normalizedMessage } : {}
|
|
422
|
+
},
|
|
423
|
+
parameters: sessionId ? {
|
|
424
|
+
body: {
|
|
425
|
+
response: reply,
|
|
426
|
+
...normalizedMessage ? { message: normalizedMessage } : {}
|
|
427
|
+
},
|
|
428
|
+
path: {
|
|
429
|
+
id: sessionId,
|
|
430
|
+
permissionID: requestId
|
|
431
|
+
}
|
|
432
|
+
} : void 0
|
|
433
|
+
});
|
|
357
434
|
}
|
|
358
435
|
async listModels() {
|
|
359
436
|
const now = Date.now();
|
|
@@ -489,10 +566,14 @@ var OpenCodeClient = class {
|
|
|
489
566
|
messageId
|
|
490
567
|
}, async (requestSignal) => {
|
|
491
568
|
return normalizePromptResponse(await this.callScopedSdkMethod("session", "message", {
|
|
492
|
-
|
|
569
|
+
legacyParameters: {
|
|
493
570
|
sessionID: sessionId,
|
|
494
571
|
messageID: messageId
|
|
495
572
|
},
|
|
573
|
+
parameters: { path: {
|
|
574
|
+
id: sessionId,
|
|
575
|
+
messageID: messageId
|
|
576
|
+
} },
|
|
496
577
|
signal: requestSignal
|
|
497
578
|
}));
|
|
498
579
|
}, signal);
|
|
@@ -520,10 +601,14 @@ var OpenCodeClient = class {
|
|
|
520
601
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
521
602
|
}, async (requestSignal) => {
|
|
522
603
|
return normalizePromptResponses(await this.callScopedSdkMethod("session", "messages", {
|
|
523
|
-
|
|
604
|
+
legacyParameters: {
|
|
524
605
|
sessionID: sessionId,
|
|
525
606
|
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
526
607
|
},
|
|
608
|
+
parameters: {
|
|
609
|
+
path: { id: sessionId },
|
|
610
|
+
query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
|
|
611
|
+
},
|
|
527
612
|
signal: requestSignal
|
|
528
613
|
}));
|
|
529
614
|
}, signal);
|
|
@@ -584,10 +669,14 @@ var OpenCodeClient = class {
|
|
|
584
669
|
...input.variant ? { variant: input.variant } : {},
|
|
585
670
|
parts
|
|
586
671
|
};
|
|
587
|
-
const
|
|
672
|
+
const legacyRequestParameters = {
|
|
588
673
|
sessionID: input.sessionId,
|
|
589
674
|
...requestBody
|
|
590
675
|
};
|
|
676
|
+
const requestParameters = {
|
|
677
|
+
body: requestBody,
|
|
678
|
+
path: { id: input.sessionId }
|
|
679
|
+
};
|
|
591
680
|
try {
|
|
592
681
|
if (typeof this.client.session?.promptAsync === "function") {
|
|
593
682
|
await this.runPromptRequestWithTimeout({
|
|
@@ -596,6 +685,7 @@ var OpenCodeClient = class {
|
|
|
596
685
|
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
597
686
|
}, async (signal) => {
|
|
598
687
|
await this.callScopedSdkMethod("session", "promptAsync", {
|
|
688
|
+
legacyParameters: legacyRequestParameters,
|
|
599
689
|
parameters: requestParameters,
|
|
600
690
|
signal
|
|
601
691
|
});
|
|
@@ -613,10 +703,16 @@ var OpenCodeClient = class {
|
|
|
613
703
|
throw new Error("OpenCode SDK client does not expose session.promptAsync().");
|
|
614
704
|
}
|
|
615
705
|
async loadSessionStatuses(signal) {
|
|
616
|
-
return this.callScopedSdkMethod("session", "status", {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
706
|
+
return this.callScopedSdkMethod("session", "status", { signal });
|
|
707
|
+
}
|
|
708
|
+
async callRawSdkGet(url, signal) {
|
|
709
|
+
const rawClient = getRawSdkRequestClient(this.client);
|
|
710
|
+
if (typeof rawClient?.get !== "function") throw new Error(`OpenCode SDK client does not expose a compatible raw GET endpoint for ${url}.`);
|
|
711
|
+
return unwrapSdkData(await rawClient.get({
|
|
712
|
+
...SDK_OPTIONS,
|
|
713
|
+
...signal ? { signal } : {},
|
|
714
|
+
url
|
|
715
|
+
}));
|
|
620
716
|
}
|
|
621
717
|
async runPromptRequestWithTimeout(input, operation, signal) {
|
|
622
718
|
const startedAt = Date.now();
|
|
@@ -684,12 +780,16 @@ var OpenCodeClient = class {
|
|
|
684
780
|
logPromptRequest(level, extra, message) {
|
|
685
781
|
const log = this.client.app?.log;
|
|
686
782
|
if (typeof log !== "function") return;
|
|
687
|
-
|
|
783
|
+
const payload = {
|
|
688
784
|
service: PROMPT_LOG_SERVICE,
|
|
689
785
|
level,
|
|
690
786
|
message,
|
|
691
787
|
extra
|
|
692
|
-
}
|
|
788
|
+
};
|
|
789
|
+
(log.length >= 2 ? log.call(this.client.app, payload, SDK_OPTIONS) : log.call(this.client.app, {
|
|
790
|
+
body: payload,
|
|
791
|
+
...SDK_OPTIONS
|
|
792
|
+
})).catch(() => void 0);
|
|
693
793
|
}
|
|
694
794
|
};
|
|
695
795
|
function createOpenCodeClientFromSdkClient(client, fetchFn = fetch, promptTimeoutPolicy = {}) {
|
|
@@ -811,7 +911,7 @@ function isPromptResponseUsable(data, structured) {
|
|
|
811
911
|
}
|
|
812
912
|
function normalizePromptResponse(response) {
|
|
813
913
|
return {
|
|
814
|
-
info: isPlainRecord(response?.info) ? response.info : null,
|
|
914
|
+
info: isPlainRecord$1(response?.info) ? response.info : null,
|
|
815
915
|
parts: normalizePromptParts(response?.parts)
|
|
816
916
|
};
|
|
817
917
|
}
|
|
@@ -835,7 +935,7 @@ function toAssistantMessage(message) {
|
|
|
835
935
|
if ("mode" in message && typeof message.mode === "string" && message.mode.trim().length > 0) normalized.mode = message.mode;
|
|
836
936
|
if ("modelID" in message && typeof message.modelID === "string" && message.modelID.trim().length > 0) normalized.modelID = message.modelID;
|
|
837
937
|
if ("parentID" in message && typeof message.parentID === "string" && message.parentID.trim().length > 0) normalized.parentID = message.parentID;
|
|
838
|
-
if ("path" in message && isPlainRecord(message.path)) normalized.path = {
|
|
938
|
+
if ("path" in message && isPlainRecord$1(message.path)) normalized.path = {
|
|
839
939
|
...typeof message.path.cwd === "string" && message.path.cwd.trim().length > 0 ? { cwd: message.path.cwd } : {},
|
|
840
940
|
...typeof message.path.root === "string" && message.path.root.trim().length > 0 ? { root: message.path.root } : {}
|
|
841
941
|
};
|
|
@@ -845,16 +945,16 @@ function toAssistantMessage(message) {
|
|
|
845
945
|
const structuredPayload = extractStructuredPayload(message);
|
|
846
946
|
if (structuredPayload !== null) normalized.structured = structuredPayload;
|
|
847
947
|
if ("summary" in message && typeof message.summary === "boolean") normalized.summary = message.summary;
|
|
848
|
-
if ("time" in message && isPlainRecord(message.time)) normalized.time = {
|
|
948
|
+
if ("time" in message && isPlainRecord$1(message.time)) normalized.time = {
|
|
849
949
|
...typeof message.time.created === "number" && Number.isFinite(message.time.created) ? { created: message.time.created } : {},
|
|
850
950
|
...typeof message.time.completed === "number" && Number.isFinite(message.time.completed) ? { completed: message.time.completed } : {}
|
|
851
951
|
};
|
|
852
|
-
if ("tokens" in message && isPlainRecord(message.tokens)) normalized.tokens = {
|
|
952
|
+
if ("tokens" in message && isPlainRecord$1(message.tokens)) normalized.tokens = {
|
|
853
953
|
...typeof message.tokens.input === "number" && Number.isFinite(message.tokens.input) ? { input: message.tokens.input } : {},
|
|
854
954
|
...typeof message.tokens.output === "number" && Number.isFinite(message.tokens.output) ? { output: message.tokens.output } : {},
|
|
855
955
|
...typeof message.tokens.reasoning === "number" && Number.isFinite(message.tokens.reasoning) ? { reasoning: message.tokens.reasoning } : {},
|
|
856
956
|
...typeof message.tokens.total === "number" && Number.isFinite(message.tokens.total) ? { total: message.tokens.total } : {},
|
|
857
|
-
...isPlainRecord(message.tokens.cache) ? { cache: {
|
|
957
|
+
...isPlainRecord$1(message.tokens.cache) ? { cache: {
|
|
858
958
|
...typeof message.tokens.cache.read === "number" && Number.isFinite(message.tokens.cache.read) ? { read: message.tokens.cache.read } : {},
|
|
859
959
|
...typeof message.tokens.cache.write === "number" && Number.isFinite(message.tokens.cache.write) ? { write: message.tokens.cache.write } : {}
|
|
860
960
|
} } : {}
|
|
@@ -863,7 +963,7 @@ function toAssistantMessage(message) {
|
|
|
863
963
|
return normalized;
|
|
864
964
|
}
|
|
865
965
|
function extractMessageId(message) {
|
|
866
|
-
if (!isPlainRecord(message)) return null;
|
|
966
|
+
if (!isPlainRecord$1(message)) return null;
|
|
867
967
|
return typeof message.id === "string" && message.id.trim().length > 0 ? message.id : null;
|
|
868
968
|
}
|
|
869
969
|
function delay(ms, signal) {
|
|
@@ -959,6 +1059,34 @@ function unwrapSdkData(response) {
|
|
|
959
1059
|
if (response && typeof response === "object" && "data" in response) return response.data;
|
|
960
1060
|
return response;
|
|
961
1061
|
}
|
|
1062
|
+
function normalizePermissionRequest(permission) {
|
|
1063
|
+
if (!isPlainRecord$1(permission)) return null;
|
|
1064
|
+
const id = typeof permission.id === "string" && permission.id.trim().length > 0 ? permission.id : null;
|
|
1065
|
+
const sessionID = typeof permission.sessionID === "string" && permission.sessionID.trim().length > 0 ? permission.sessionID : null;
|
|
1066
|
+
const permissionName = typeof permission.permission === "string" && permission.permission.trim().length > 0 ? permission.permission : typeof permission.type === "string" && permission.type.trim().length > 0 ? permission.type : null;
|
|
1067
|
+
if (!id || !sessionID || !permissionName) return null;
|
|
1068
|
+
return {
|
|
1069
|
+
always: Array.isArray(permission.always) ? permission.always.filter((value) => typeof value === "string") : [],
|
|
1070
|
+
id,
|
|
1071
|
+
metadata: isPlainRecord$1(permission.metadata) ? permission.metadata : {},
|
|
1072
|
+
patterns: normalizePermissionPatterns$1(permission),
|
|
1073
|
+
permission: permissionName,
|
|
1074
|
+
sessionID
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function normalizePermissionPatterns$1(permission) {
|
|
1078
|
+
if (Array.isArray(permission.patterns)) return permission.patterns.filter((value) => typeof value === "string");
|
|
1079
|
+
if (typeof permission.pattern === "string" && permission.pattern.trim().length > 0) return [permission.pattern];
|
|
1080
|
+
if (Array.isArray(permission.pattern)) return permission.pattern.filter((value) => typeof value === "string");
|
|
1081
|
+
return [];
|
|
1082
|
+
}
|
|
1083
|
+
function getRawSdkRequestClient(client) {
|
|
1084
|
+
const compatibleClient = client;
|
|
1085
|
+
return compatibleClient.client ?? compatibleClient._client ?? null;
|
|
1086
|
+
}
|
|
1087
|
+
function isMissingScopedSdkMethodError(error, scope, method) {
|
|
1088
|
+
return error instanceof Error && error.message === `OpenCode SDK client does not expose a compatible ${scope}.${method} method.`;
|
|
1089
|
+
}
|
|
962
1090
|
function resolvePromptTimeoutPolicy(input) {
|
|
963
1091
|
return {
|
|
964
1092
|
pollRequestTimeoutMs: input.pollRequestTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.pollRequestTimeoutMs,
|
|
@@ -967,11 +1095,11 @@ function resolvePromptTimeoutPolicy(input) {
|
|
|
967
1095
|
};
|
|
968
1096
|
}
|
|
969
1097
|
function normalizeAssistantError(value) {
|
|
970
|
-
if (!isPlainRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
|
|
1098
|
+
if (!isPlainRecord$1(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
|
|
971
1099
|
return {
|
|
972
1100
|
...value,
|
|
973
1101
|
name: value.name,
|
|
974
|
-
...isPlainRecord(value.data) ? { data: value.data } : {}
|
|
1102
|
+
...isPlainRecord$1(value.data) ? { data: value.data } : {}
|
|
975
1103
|
};
|
|
976
1104
|
}
|
|
977
1105
|
function isAssistantMessageCompleted(message) {
|
|
@@ -984,7 +1112,7 @@ function isCompletedEmptyPromptResponse(data, structured) {
|
|
|
984
1112
|
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
|
|
985
1113
|
}
|
|
986
1114
|
function extractStructuredPayload(message) {
|
|
987
|
-
if (!isPlainRecord(message)) return null;
|
|
1115
|
+
if (!isPlainRecord$1(message)) return null;
|
|
988
1116
|
if ("structured" in message && message.structured !== void 0) return message.structured;
|
|
989
1117
|
if ("structured_output" in message && message.structured_output !== void 0) return message.structured_output;
|
|
990
1118
|
return null;
|
|
@@ -1058,7 +1186,7 @@ function throwIfAborted(signal) {
|
|
|
1058
1186
|
if (!signal?.aborted) return;
|
|
1059
1187
|
throw normalizeAbortReason(signal.reason);
|
|
1060
1188
|
}
|
|
1061
|
-
function isPlainRecord(value) {
|
|
1189
|
+
function isPlainRecord$1(value) {
|
|
1062
1190
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1063
1191
|
}
|
|
1064
1192
|
async function resolveProviderAvailability(config, fetchFn) {
|
|
@@ -2274,25 +2402,35 @@ function escapeMarkdownV2(value) {
|
|
|
2274
2402
|
async function handleTelegramBotPluginEvent(runtime, event) {
|
|
2275
2403
|
switch (event.type) {
|
|
2276
2404
|
case "permission.asked":
|
|
2277
|
-
|
|
2405
|
+
case "permission.updated": {
|
|
2406
|
+
const request = normalizePermissionRequestEvent(event.properties);
|
|
2407
|
+
if (request) await handlePermissionAsked(runtime, request);
|
|
2278
2408
|
return;
|
|
2279
|
-
|
|
2280
|
-
|
|
2409
|
+
}
|
|
2410
|
+
case "permission.replied": {
|
|
2411
|
+
const replyEvent = normalizePermissionReplyEvent(event.properties);
|
|
2412
|
+
if (replyEvent) await handlePermissionReplied(runtime, replyEvent);
|
|
2281
2413
|
return;
|
|
2282
|
-
|
|
2283
|
-
|
|
2414
|
+
}
|
|
2415
|
+
case "session.error": {
|
|
2416
|
+
const sessionError = normalizeSessionErrorEvent(event.properties);
|
|
2417
|
+
if (sessionError) await handleSessionError(runtime, sessionError);
|
|
2284
2418
|
return;
|
|
2285
|
-
|
|
2286
|
-
|
|
2419
|
+
}
|
|
2420
|
+
case "session.idle": {
|
|
2421
|
+
const sessionIdle = normalizeSessionIdleEvent(event.properties);
|
|
2422
|
+
if (sessionIdle) await handleSessionIdle(runtime, sessionIdle);
|
|
2287
2423
|
return;
|
|
2288
|
-
|
|
2289
|
-
|
|
2424
|
+
}
|
|
2425
|
+
case "session.status": {
|
|
2426
|
+
const sessionStatus = normalizeSessionStatusEvent(event.properties);
|
|
2427
|
+
if (sessionStatus) await handleSessionStatus(runtime, sessionStatus);
|
|
2290
2428
|
return;
|
|
2429
|
+
}
|
|
2291
2430
|
default: return;
|
|
2292
2431
|
}
|
|
2293
2432
|
}
|
|
2294
|
-
async function handlePermissionAsked(runtime,
|
|
2295
|
-
const request = event.properties;
|
|
2433
|
+
async function handlePermissionAsked(runtime, request) {
|
|
2296
2434
|
const bindings = await runtime.container.sessionRepo.listBySessionId(request.sessionID);
|
|
2297
2435
|
const chatIds = new Set([...bindings.map((binding) => binding.chatId), ...runtime.container.foregroundSessionTracker.listChatIds(request.sessionID)]);
|
|
2298
2436
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(request.id);
|
|
@@ -2322,52 +2460,42 @@ async function handlePermissionAsked(runtime, event) {
|
|
|
2322
2460
|
}
|
|
2323
2461
|
}
|
|
2324
2462
|
async function handlePermissionReplied(runtime, event) {
|
|
2325
|
-
const
|
|
2326
|
-
const reply = event.properties.reply;
|
|
2327
|
-
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(requestId);
|
|
2463
|
+
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(event.requestId);
|
|
2328
2464
|
await Promise.all(approvals.map(async (approval) => {
|
|
2329
2465
|
try {
|
|
2330
|
-
await runtime.bot.api.editMessageText(approval.chatId, approval.messageId, buildPermissionApprovalResolvedMessage(requestId, reply));
|
|
2466
|
+
await runtime.bot.api.editMessageText(approval.chatId, approval.messageId, buildPermissionApprovalResolvedMessage(event.requestId, event.reply));
|
|
2331
2467
|
} catch (error) {
|
|
2332
2468
|
runtime.container.logger.warn({
|
|
2333
2469
|
error,
|
|
2334
2470
|
chatId: approval.chatId,
|
|
2335
|
-
requestId
|
|
2471
|
+
requestId: event.requestId,
|
|
2472
|
+
sessionId: event.sessionId
|
|
2336
2473
|
}, "failed to update Telegram permission message");
|
|
2337
2474
|
}
|
|
2338
|
-
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, reply));
|
|
2475
|
+
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, event.reply));
|
|
2339
2476
|
}));
|
|
2340
2477
|
}
|
|
2341
2478
|
async function handleSessionError(runtime, event) {
|
|
2342
|
-
|
|
2343
|
-
const error = event.properties.error;
|
|
2344
|
-
if (!sessionId) {
|
|
2345
|
-
runtime.container.logger.error({ error }, "session error received without a session id");
|
|
2346
|
-
return;
|
|
2347
|
-
}
|
|
2348
|
-
if (runtime.container.foregroundSessionTracker.fail(sessionId, error ?? /* @__PURE__ */ new Error("Unknown session error."))) {
|
|
2479
|
+
if (runtime.container.foregroundSessionTracker.fail(event.sessionId, event.error instanceof Error ? event.error : /* @__PURE__ */ new Error("Unknown session error."))) {
|
|
2349
2480
|
runtime.container.logger.warn({
|
|
2350
|
-
error,
|
|
2351
|
-
sessionId
|
|
2481
|
+
error: event.error,
|
|
2482
|
+
sessionId: event.sessionId
|
|
2352
2483
|
}, "session error suppressed for foreground Telegram session");
|
|
2353
2484
|
return;
|
|
2354
2485
|
}
|
|
2355
|
-
|
|
2486
|
+
const message = extractSessionErrorMessage(event.error) ?? "Unknown session error.";
|
|
2487
|
+
await notifyBoundChats(runtime, event.sessionId, `Session failed.\n\nSession: ${event.sessionId}\nError: ${message}`);
|
|
2356
2488
|
}
|
|
2357
2489
|
async function handleSessionIdle(runtime, event) {
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
runtime.container.logger.info({ sessionId }, "session idle notification suppressed for foreground Telegram session");
|
|
2490
|
+
if (runtime.container.foregroundSessionTracker.clear(event.sessionId)) {
|
|
2491
|
+
runtime.container.logger.info({ sessionId: event.sessionId }, "session idle notification suppressed for foreground Telegram session");
|
|
2361
2492
|
return;
|
|
2362
2493
|
}
|
|
2363
|
-
await notifyBoundChats(runtime, sessionId, `Session finished.\n\nSession: ${sessionId}`);
|
|
2494
|
+
await notifyBoundChats(runtime, event.sessionId, `Session finished.\n\nSession: ${event.sessionId}`);
|
|
2364
2495
|
}
|
|
2365
2496
|
async function handleSessionStatus(runtime, event) {
|
|
2366
|
-
if (event.
|
|
2367
|
-
await handleSessionIdle(runtime,
|
|
2368
|
-
type: "session.idle",
|
|
2369
|
-
properties: { sessionID: event.properties.sessionID }
|
|
2370
|
-
});
|
|
2497
|
+
if (event.statusType !== "idle") return;
|
|
2498
|
+
await handleSessionIdle(runtime, event);
|
|
2371
2499
|
}
|
|
2372
2500
|
async function notifyBoundChats(runtime, sessionId, text) {
|
|
2373
2501
|
const bindings = await runtime.container.sessionRepo.listBySessionId(sessionId);
|
|
@@ -2391,6 +2519,79 @@ function toResolvedApproval(approval, reply) {
|
|
|
2391
2519
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2392
2520
|
};
|
|
2393
2521
|
}
|
|
2522
|
+
function normalizePermissionRequestEvent(properties) {
|
|
2523
|
+
if (!isPlainRecord(properties)) return null;
|
|
2524
|
+
const id = asNonEmptyString(properties.id);
|
|
2525
|
+
const sessionID = asNonEmptyString(properties.sessionID);
|
|
2526
|
+
const permission = asNonEmptyString(properties.permission) ?? asNonEmptyString(properties.type);
|
|
2527
|
+
if (!id || !sessionID || !permission) return null;
|
|
2528
|
+
return {
|
|
2529
|
+
always: Array.isArray(properties.always) ? properties.always.filter((value) => typeof value === "string") : [],
|
|
2530
|
+
id,
|
|
2531
|
+
metadata: isPlainRecord(properties.metadata) ? properties.metadata : {},
|
|
2532
|
+
patterns: normalizePermissionPatterns(properties),
|
|
2533
|
+
permission,
|
|
2534
|
+
sessionID
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
function normalizePermissionReplyEvent(properties) {
|
|
2538
|
+
if (!isPlainRecord(properties)) return null;
|
|
2539
|
+
const requestId = asNonEmptyString(properties.requestID) ?? asNonEmptyString(properties.permissionID);
|
|
2540
|
+
const reply = normalizePermissionReply(asNonEmptyString(properties.reply) ?? asNonEmptyString(properties.response));
|
|
2541
|
+
const sessionId = asNonEmptyString(properties.sessionID);
|
|
2542
|
+
if (!requestId || !reply || !sessionId) return null;
|
|
2543
|
+
return {
|
|
2544
|
+
reply,
|
|
2545
|
+
requestId,
|
|
2546
|
+
sessionId
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
function normalizeSessionErrorEvent(properties) {
|
|
2550
|
+
if (!isPlainRecord(properties)) return null;
|
|
2551
|
+
const sessionId = asNonEmptyString(properties.sessionID);
|
|
2552
|
+
if (!sessionId) return null;
|
|
2553
|
+
return {
|
|
2554
|
+
error: properties.error,
|
|
2555
|
+
sessionId
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
function normalizeSessionIdleEvent(properties) {
|
|
2559
|
+
if (!isPlainRecord(properties)) return null;
|
|
2560
|
+
const sessionId = asNonEmptyString(properties.sessionID);
|
|
2561
|
+
return sessionId ? { sessionId } : null;
|
|
2562
|
+
}
|
|
2563
|
+
function normalizeSessionStatusEvent(properties) {
|
|
2564
|
+
if (!isPlainRecord(properties) || !isPlainRecord(properties.status)) return null;
|
|
2565
|
+
const sessionId = asNonEmptyString(properties.sessionID);
|
|
2566
|
+
const statusType = asNonEmptyString(properties.status.type);
|
|
2567
|
+
if (!sessionId || !statusType) return null;
|
|
2568
|
+
return {
|
|
2569
|
+
sessionId,
|
|
2570
|
+
statusType
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
function normalizePermissionPatterns(properties) {
|
|
2574
|
+
if (Array.isArray(properties.patterns)) return properties.patterns.filter((value) => typeof value === "string");
|
|
2575
|
+
if (typeof properties.pattern === "string" && properties.pattern.trim().length > 0) return [properties.pattern];
|
|
2576
|
+
if (Array.isArray(properties.pattern)) return properties.pattern.filter((value) => typeof value === "string");
|
|
2577
|
+
return [];
|
|
2578
|
+
}
|
|
2579
|
+
function normalizePermissionReply(value) {
|
|
2580
|
+
if (value === "once" || value === "always" || value === "reject") return value;
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
function extractSessionErrorMessage(error) {
|
|
2584
|
+
if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
|
|
2585
|
+
if (!isPlainRecord(error)) return null;
|
|
2586
|
+
if (isPlainRecord(error.data) && typeof error.data.message === "string" && error.data.message.trim().length > 0) return error.data.message.trim();
|
|
2587
|
+
return asNonEmptyString(error.name);
|
|
2588
|
+
}
|
|
2589
|
+
function asNonEmptyString(value) {
|
|
2590
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
2591
|
+
}
|
|
2592
|
+
function isPlainRecord(value) {
|
|
2593
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2594
|
+
}
|
|
2394
2595
|
var SUPPORTED_BOT_LANGUAGES = [
|
|
2395
2596
|
"en",
|
|
2396
2597
|
"zh-CN",
|
|
@@ -3094,111 +3295,6 @@ async function syncTelegramCommandsForChat(api, chatId, language) {
|
|
|
3094
3295
|
} });
|
|
3095
3296
|
}
|
|
3096
3297
|
//#endregion
|
|
3097
|
-
//#region src/bot/i18n.ts
|
|
3098
|
-
async function getChatLanguage(sessionRepo, chatId) {
|
|
3099
|
-
if (!chatId) return "en";
|
|
3100
|
-
return normalizeBotLanguage((await sessionRepo.getByChatId(chatId))?.language);
|
|
3101
|
-
}
|
|
3102
|
-
async function getChatCopy(sessionRepo, chatId) {
|
|
3103
|
-
return getBotCopy(await getChatLanguage(sessionRepo, chatId));
|
|
3104
|
-
}
|
|
3105
|
-
async function setChatLanguage(sessionRepo, chatId, language) {
|
|
3106
|
-
const binding = await sessionRepo.getByChatId(chatId);
|
|
3107
|
-
await sessionRepo.setCurrent({
|
|
3108
|
-
chatId,
|
|
3109
|
-
sessionId: binding?.sessionId ?? null,
|
|
3110
|
-
projectId: binding?.projectId ?? null,
|
|
3111
|
-
directory: binding?.directory ?? null,
|
|
3112
|
-
agentName: binding?.agentName ?? null,
|
|
3113
|
-
modelProviderId: binding?.modelProviderId ?? null,
|
|
3114
|
-
modelId: binding?.modelId ?? null,
|
|
3115
|
-
modelVariant: binding?.modelVariant ?? null,
|
|
3116
|
-
language,
|
|
3117
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3118
|
-
});
|
|
3119
|
-
}
|
|
3120
|
-
var NUMBERED_BUTTONS_PER_ROW = 5;
|
|
3121
|
-
function buildModelsKeyboard(models, requestedPage, copy = BOT_COPY) {
|
|
3122
|
-
const page = getModelsPage(models, requestedPage);
|
|
3123
|
-
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (_, index) => `model:pick:${page.startIndex + index + 1}`);
|
|
3124
|
-
appendPaginationButtons(keyboard, page.page, page.totalPages, "model:page", copy);
|
|
3125
|
-
return {
|
|
3126
|
-
keyboard,
|
|
3127
|
-
page
|
|
3128
|
-
};
|
|
3129
|
-
}
|
|
3130
|
-
function buildAgentsKeyboard(agents, requestedPage, copy = BOT_COPY) {
|
|
3131
|
-
const page = getAgentsPage(agents, requestedPage);
|
|
3132
|
-
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (_, index) => `agents:select:${page.startIndex + index + 1}`);
|
|
3133
|
-
appendPaginationButtons(keyboard, page.page, page.totalPages, "agents:page", copy);
|
|
3134
|
-
return {
|
|
3135
|
-
keyboard,
|
|
3136
|
-
page
|
|
3137
|
-
};
|
|
3138
|
-
}
|
|
3139
|
-
function buildSessionsKeyboard(sessions, requestedPage, copy = BOT_COPY) {
|
|
3140
|
-
const page = getSessionsPage(sessions, requestedPage);
|
|
3141
|
-
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (session) => `sessions:pick:${page.page}:${session.id}`);
|
|
3142
|
-
appendPaginationButtons(keyboard, page.page, page.totalPages, "sessions:page", copy);
|
|
3143
|
-
return {
|
|
3144
|
-
keyboard,
|
|
3145
|
-
page
|
|
3146
|
-
};
|
|
3147
|
-
}
|
|
3148
|
-
function buildSessionActionKeyboard(sessionId, page, copy = BOT_COPY) {
|
|
3149
|
-
return new InlineKeyboard().text(copy.sessions.switchAction, `sessions:switch:${page}:${sessionId}`).text(copy.sessions.renameAction, `sessions:rename:${page}:${sessionId}`).row().text(copy.sessions.backToList, `sessions:back:${page}`);
|
|
3150
|
-
}
|
|
3151
|
-
function buildModelVariantsKeyboard(variants, modelIndex) {
|
|
3152
|
-
return buildNumberedKeyboard(variants, 0, (_, index) => `model:variant:${modelIndex}:${index + 1}`);
|
|
3153
|
-
}
|
|
3154
|
-
function buildLanguageKeyboard(currentLanguage, copy = BOT_COPY) {
|
|
3155
|
-
const keyboard = new InlineKeyboard();
|
|
3156
|
-
SUPPORTED_BOT_LANGUAGES.forEach((language, index) => {
|
|
3157
|
-
const label = currentLanguage === language ? `[${getLanguageLabel(language, copy)}]` : getLanguageLabel(language, copy);
|
|
3158
|
-
keyboard.text(label, `language:select:${language}`);
|
|
3159
|
-
if (index !== SUPPORTED_BOT_LANGUAGES.length - 1) keyboard.row();
|
|
3160
|
-
});
|
|
3161
|
-
return keyboard;
|
|
3162
|
-
}
|
|
3163
|
-
function getModelsPage(models, requestedPage) {
|
|
3164
|
-
return getPagedItems(models, requestedPage, 10);
|
|
3165
|
-
}
|
|
3166
|
-
function getAgentsPage(agents, requestedPage) {
|
|
3167
|
-
return getPagedItems(agents, requestedPage, 10);
|
|
3168
|
-
}
|
|
3169
|
-
function getSessionsPage(sessions, requestedPage) {
|
|
3170
|
-
return getPagedItems(sessions, requestedPage, 10);
|
|
3171
|
-
}
|
|
3172
|
-
function buildNumberedKeyboard(items, startIndex, buildCallbackData) {
|
|
3173
|
-
const keyboard = new InlineKeyboard();
|
|
3174
|
-
items.forEach((item, index) => {
|
|
3175
|
-
const displayIndex = startIndex + index + 1;
|
|
3176
|
-
keyboard.text(`${displayIndex}`, buildCallbackData(item, index));
|
|
3177
|
-
if (index !== items.length - 1 && (index + 1) % NUMBERED_BUTTONS_PER_ROW === 0) keyboard.row();
|
|
3178
|
-
});
|
|
3179
|
-
return keyboard;
|
|
3180
|
-
}
|
|
3181
|
-
function appendPaginationButtons(keyboard, page, totalPages, prefix, copy) {
|
|
3182
|
-
if (totalPages <= 1) return;
|
|
3183
|
-
if (page > 0) keyboard.text(copy.common.previousPage, `${prefix}:${page - 1}`);
|
|
3184
|
-
if (page < totalPages - 1) keyboard.text(copy.common.nextPage, `${prefix}:${page + 1}`);
|
|
3185
|
-
}
|
|
3186
|
-
function getPagedItems(items, requestedPage, pageSize) {
|
|
3187
|
-
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
|
|
3188
|
-
const page = clampPage(requestedPage, totalPages);
|
|
3189
|
-
const startIndex = page * pageSize;
|
|
3190
|
-
return {
|
|
3191
|
-
items: items.slice(startIndex, startIndex + pageSize),
|
|
3192
|
-
page,
|
|
3193
|
-
startIndex,
|
|
3194
|
-
totalPages
|
|
3195
|
-
};
|
|
3196
|
-
}
|
|
3197
|
-
function clampPage(page, totalPages) {
|
|
3198
|
-
if (!Number.isInteger(page) || page < 0) return 0;
|
|
3199
|
-
return Math.min(page, totalPages - 1);
|
|
3200
|
-
}
|
|
3201
|
-
//#endregion
|
|
3202
3298
|
//#region src/bot/presenters/error.presenter.ts
|
|
3203
3299
|
function presentError(error, copy = BOT_COPY) {
|
|
3204
3300
|
const presented = normalizeError(error, copy);
|
|
@@ -3313,6 +3409,183 @@ function stringifyUnknown(value) {
|
|
|
3313
3409
|
}
|
|
3314
3410
|
}
|
|
3315
3411
|
//#endregion
|
|
3412
|
+
//#region src/bot/error-boundary.ts
|
|
3413
|
+
function extractTelegramUpdateContext(ctx) {
|
|
3414
|
+
const updateId = getNestedNumber(ctx, ["update", "update_id"]);
|
|
3415
|
+
const chatId = getNestedNumber(ctx, ["chat", "id"]);
|
|
3416
|
+
const messageText = getNestedString(ctx, ["message", "text"]);
|
|
3417
|
+
const callbackData = getNestedString(ctx, ["callbackQuery", "data"]);
|
|
3418
|
+
return {
|
|
3419
|
+
...typeof updateId === "number" ? { updateId } : {},
|
|
3420
|
+
...typeof chatId === "number" ? { chatId } : {},
|
|
3421
|
+
...typeof messageText === "string" && messageText.trim().length > 0 ? { messageText } : {},
|
|
3422
|
+
...typeof callbackData === "string" && callbackData.trim().length > 0 ? { callbackData } : {}
|
|
3423
|
+
};
|
|
3424
|
+
}
|
|
3425
|
+
async function replyWithDefaultTelegramError(ctx, logger, error) {
|
|
3426
|
+
const text = presentError(error, BOT_COPY);
|
|
3427
|
+
const editMessageText = getFunction(ctx, "editMessageText");
|
|
3428
|
+
const reply = getFunction(ctx, "reply");
|
|
3429
|
+
const callbackData = getNestedString(ctx, ["callbackQuery", "data"]);
|
|
3430
|
+
try {
|
|
3431
|
+
if (typeof callbackData === "string" && editMessageText) {
|
|
3432
|
+
await editMessageText.call(ctx, text);
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
if (reply) await reply.call(ctx, text);
|
|
3436
|
+
} catch (replyError) {
|
|
3437
|
+
logger.warn?.({
|
|
3438
|
+
...extractTelegramUpdateContext(ctx),
|
|
3439
|
+
error: replyError
|
|
3440
|
+
}, "failed to deliver fallback Telegram error message");
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
function getFunction(value, key) {
|
|
3444
|
+
if (!(key in value)) return null;
|
|
3445
|
+
const candidate = value[key];
|
|
3446
|
+
return typeof candidate === "function" ? candidate : null;
|
|
3447
|
+
}
|
|
3448
|
+
function getNestedNumber(value, path) {
|
|
3449
|
+
const candidate = getNestedValue(value, path);
|
|
3450
|
+
return typeof candidate === "number" ? candidate : null;
|
|
3451
|
+
}
|
|
3452
|
+
function getNestedString(value, path) {
|
|
3453
|
+
const candidate = getNestedValue(value, path);
|
|
3454
|
+
return typeof candidate === "string" ? candidate : null;
|
|
3455
|
+
}
|
|
3456
|
+
function getNestedValue(value, path) {
|
|
3457
|
+
let current = value;
|
|
3458
|
+
for (const segment of path) {
|
|
3459
|
+
if (!current || typeof current !== "object" || !(segment in current)) return null;
|
|
3460
|
+
current = current[segment];
|
|
3461
|
+
}
|
|
3462
|
+
return current;
|
|
3463
|
+
}
|
|
3464
|
+
//#endregion
|
|
3465
|
+
//#region src/bot/i18n.ts
|
|
3466
|
+
async function getChatLanguage(sessionRepo, chatId) {
|
|
3467
|
+
if (!chatId) return "en";
|
|
3468
|
+
return normalizeBotLanguage((await sessionRepo.getByChatId(chatId))?.language);
|
|
3469
|
+
}
|
|
3470
|
+
async function getSafeChatLanguage(sessionRepo, chatId, logger) {
|
|
3471
|
+
try {
|
|
3472
|
+
return await getChatLanguage(sessionRepo, chatId);
|
|
3473
|
+
} catch (error) {
|
|
3474
|
+
logger?.warn?.({
|
|
3475
|
+
error,
|
|
3476
|
+
chatId: chatId ?? void 0
|
|
3477
|
+
}, "failed to resolve Telegram chat language; falling back to the default locale");
|
|
3478
|
+
return "en";
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
async function getSafeChatCopy(sessionRepo, chatId, logger) {
|
|
3482
|
+
try {
|
|
3483
|
+
return getBotCopy(await getSafeChatLanguage(sessionRepo, chatId, logger));
|
|
3484
|
+
} catch (error) {
|
|
3485
|
+
logger?.warn?.({
|
|
3486
|
+
error,
|
|
3487
|
+
chatId: chatId ?? void 0
|
|
3488
|
+
}, "failed to resolve Telegram copy; falling back to the default locale");
|
|
3489
|
+
return BOT_COPY;
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
async function setChatLanguage(sessionRepo, chatId, language) {
|
|
3493
|
+
const binding = await sessionRepo.getByChatId(chatId);
|
|
3494
|
+
await sessionRepo.setCurrent({
|
|
3495
|
+
chatId,
|
|
3496
|
+
sessionId: binding?.sessionId ?? null,
|
|
3497
|
+
projectId: binding?.projectId ?? null,
|
|
3498
|
+
directory: binding?.directory ?? null,
|
|
3499
|
+
agentName: binding?.agentName ?? null,
|
|
3500
|
+
modelProviderId: binding?.modelProviderId ?? null,
|
|
3501
|
+
modelId: binding?.modelId ?? null,
|
|
3502
|
+
modelVariant: binding?.modelVariant ?? null,
|
|
3503
|
+
language,
|
|
3504
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
var NUMBERED_BUTTONS_PER_ROW = 5;
|
|
3508
|
+
function buildModelsKeyboard(models, requestedPage, copy = BOT_COPY) {
|
|
3509
|
+
const page = getModelsPage(models, requestedPage);
|
|
3510
|
+
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (_, index) => `model:pick:${page.startIndex + index + 1}`);
|
|
3511
|
+
appendPaginationButtons(keyboard, page.page, page.totalPages, "model:page", copy);
|
|
3512
|
+
return {
|
|
3513
|
+
keyboard,
|
|
3514
|
+
page
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
function buildAgentsKeyboard(agents, requestedPage, copy = BOT_COPY) {
|
|
3518
|
+
const page = getAgentsPage(agents, requestedPage);
|
|
3519
|
+
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (_, index) => `agents:select:${page.startIndex + index + 1}`);
|
|
3520
|
+
appendPaginationButtons(keyboard, page.page, page.totalPages, "agents:page", copy);
|
|
3521
|
+
return {
|
|
3522
|
+
keyboard,
|
|
3523
|
+
page
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
function buildSessionsKeyboard(sessions, requestedPage, copy = BOT_COPY) {
|
|
3527
|
+
const page = getSessionsPage(sessions, requestedPage);
|
|
3528
|
+
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (session) => `sessions:pick:${page.page}:${session.id}`);
|
|
3529
|
+
appendPaginationButtons(keyboard, page.page, page.totalPages, "sessions:page", copy);
|
|
3530
|
+
return {
|
|
3531
|
+
keyboard,
|
|
3532
|
+
page
|
|
3533
|
+
};
|
|
3534
|
+
}
|
|
3535
|
+
function buildSessionActionKeyboard(sessionId, page, copy = BOT_COPY) {
|
|
3536
|
+
return new InlineKeyboard().text(copy.sessions.switchAction, `sessions:switch:${page}:${sessionId}`).text(copy.sessions.renameAction, `sessions:rename:${page}:${sessionId}`).row().text(copy.sessions.backToList, `sessions:back:${page}`);
|
|
3537
|
+
}
|
|
3538
|
+
function buildModelVariantsKeyboard(variants, modelIndex) {
|
|
3539
|
+
return buildNumberedKeyboard(variants, 0, (_, index) => `model:variant:${modelIndex}:${index + 1}`);
|
|
3540
|
+
}
|
|
3541
|
+
function buildLanguageKeyboard(currentLanguage, copy = BOT_COPY) {
|
|
3542
|
+
const keyboard = new InlineKeyboard();
|
|
3543
|
+
SUPPORTED_BOT_LANGUAGES.forEach((language, index) => {
|
|
3544
|
+
const label = currentLanguage === language ? `[${getLanguageLabel(language, copy)}]` : getLanguageLabel(language, copy);
|
|
3545
|
+
keyboard.text(label, `language:select:${language}`);
|
|
3546
|
+
if (index !== SUPPORTED_BOT_LANGUAGES.length - 1) keyboard.row();
|
|
3547
|
+
});
|
|
3548
|
+
return keyboard;
|
|
3549
|
+
}
|
|
3550
|
+
function getModelsPage(models, requestedPage) {
|
|
3551
|
+
return getPagedItems(models, requestedPage, 10);
|
|
3552
|
+
}
|
|
3553
|
+
function getAgentsPage(agents, requestedPage) {
|
|
3554
|
+
return getPagedItems(agents, requestedPage, 10);
|
|
3555
|
+
}
|
|
3556
|
+
function getSessionsPage(sessions, requestedPage) {
|
|
3557
|
+
return getPagedItems(sessions, requestedPage, 10);
|
|
3558
|
+
}
|
|
3559
|
+
function buildNumberedKeyboard(items, startIndex, buildCallbackData) {
|
|
3560
|
+
const keyboard = new InlineKeyboard();
|
|
3561
|
+
items.forEach((item, index) => {
|
|
3562
|
+
const displayIndex = startIndex + index + 1;
|
|
3563
|
+
keyboard.text(`${displayIndex}`, buildCallbackData(item, index));
|
|
3564
|
+
if (index !== items.length - 1 && (index + 1) % NUMBERED_BUTTONS_PER_ROW === 0) keyboard.row();
|
|
3565
|
+
});
|
|
3566
|
+
return keyboard;
|
|
3567
|
+
}
|
|
3568
|
+
function appendPaginationButtons(keyboard, page, totalPages, prefix, copy) {
|
|
3569
|
+
if (totalPages <= 1) return;
|
|
3570
|
+
if (page > 0) keyboard.text(copy.common.previousPage, `${prefix}:${page - 1}`);
|
|
3571
|
+
if (page < totalPages - 1) keyboard.text(copy.common.nextPage, `${prefix}:${page + 1}`);
|
|
3572
|
+
}
|
|
3573
|
+
function getPagedItems(items, requestedPage, pageSize) {
|
|
3574
|
+
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
|
|
3575
|
+
const page = clampPage(requestedPage, totalPages);
|
|
3576
|
+
const startIndex = page * pageSize;
|
|
3577
|
+
return {
|
|
3578
|
+
items: items.slice(startIndex, startIndex + pageSize),
|
|
3579
|
+
page,
|
|
3580
|
+
startIndex,
|
|
3581
|
+
totalPages
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3584
|
+
function clampPage(page, totalPages) {
|
|
3585
|
+
if (!Number.isInteger(page) || page < 0) return 0;
|
|
3586
|
+
return Math.min(page, totalPages - 1);
|
|
3587
|
+
}
|
|
3588
|
+
//#endregion
|
|
3316
3589
|
//#region src/bot/presenters/message.presenter.ts
|
|
3317
3590
|
var VARIANT_ORDER = [
|
|
3318
3591
|
"minimal",
|
|
@@ -3692,7 +3965,7 @@ function formatSessionLabel(session) {
|
|
|
3692
3965
|
//#endregion
|
|
3693
3966
|
//#region src/bot/commands/agents.ts
|
|
3694
3967
|
async function handleAgentsCommand(ctx, dependencies) {
|
|
3695
|
-
const copy = await
|
|
3968
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3696
3969
|
try {
|
|
3697
3970
|
const result = await dependencies.listAgentsUseCase.execute({ chatId: ctx.chat.id });
|
|
3698
3971
|
if (result.agents.length === 0) {
|
|
@@ -3718,7 +3991,7 @@ function registerAgentsCommand(bot, dependencies) {
|
|
|
3718
3991
|
//#endregion
|
|
3719
3992
|
//#region src/bot/sessions-menu.ts
|
|
3720
3993
|
async function buildSessionsListView(chatId, requestedPage, dependencies) {
|
|
3721
|
-
const copy = await
|
|
3994
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, chatId, dependencies.logger);
|
|
3722
3995
|
const result = await dependencies.listSessionsUseCase.execute({ chatId });
|
|
3723
3996
|
if (result.sessions.length === 0) return {
|
|
3724
3997
|
copy,
|
|
@@ -3777,14 +4050,14 @@ async function getPendingSessionRenameAction(dependencies, chatId) {
|
|
|
3777
4050
|
}
|
|
3778
4051
|
async function replyIfSessionRenamePending(ctx, dependencies) {
|
|
3779
4052
|
if (!await getPendingSessionRenameAction(dependencies, ctx.chat.id)) return false;
|
|
3780
|
-
const copy = await
|
|
4053
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3781
4054
|
await ctx.reply(copy.sessions.renamePendingInput);
|
|
3782
4055
|
return true;
|
|
3783
4056
|
}
|
|
3784
4057
|
async function handlePendingSessionRenameText(ctx, dependencies) {
|
|
3785
4058
|
const pendingAction = await getPendingSessionRenameAction(dependencies, ctx.chat.id);
|
|
3786
4059
|
if (!pendingAction) return false;
|
|
3787
|
-
const copy = await
|
|
4060
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3788
4061
|
const title = ctx.message.text?.trim() ?? "";
|
|
3789
4062
|
if (title.startsWith("/")) {
|
|
3790
4063
|
await ctx.reply(copy.sessions.renamePendingInput);
|
|
@@ -3816,7 +4089,7 @@ async function handlePendingSessionRenameText(ctx, dependencies) {
|
|
|
3816
4089
|
async function cancelPendingSessionRename(ctx, dependencies) {
|
|
3817
4090
|
const pendingAction = await getPendingSessionRenameAction(dependencies, ctx.chat.id);
|
|
3818
4091
|
if (!pendingAction) return false;
|
|
3819
|
-
const copy = await
|
|
4092
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3820
4093
|
await dependencies.pendingActionRepo.clear(ctx.chat.id);
|
|
3821
4094
|
await bestEffortRestoreSessionsList(ctx.api, pendingAction, dependencies);
|
|
3822
4095
|
await ctx.reply(copy.sessions.renameCancelled);
|
|
@@ -3835,7 +4108,7 @@ function isSessionRenamePendingAction(action) {
|
|
|
3835
4108
|
//#endregion
|
|
3836
4109
|
//#region src/bot/commands/cancel.ts
|
|
3837
4110
|
async function handleCancelCommand(ctx, dependencies) {
|
|
3838
|
-
const copy = await
|
|
4111
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3839
4112
|
try {
|
|
3840
4113
|
if (await cancelPendingSessionRename(ctx, dependencies)) return;
|
|
3841
4114
|
const result = await dependencies.abortPromptUseCase.execute({ chatId: ctx.chat.id });
|
|
@@ -3861,8 +4134,8 @@ function registerCancelCommand(bot, dependencies) {
|
|
|
3861
4134
|
//#endregion
|
|
3862
4135
|
//#region src/bot/commands/language.ts
|
|
3863
4136
|
async function handleLanguageCommand(ctx, dependencies) {
|
|
3864
|
-
const language = await
|
|
3865
|
-
const copy = await
|
|
4137
|
+
const language = await getSafeChatLanguage(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4138
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3866
4139
|
try {
|
|
3867
4140
|
await syncTelegramCommandsForChat(ctx.api, ctx.chat.id, language);
|
|
3868
4141
|
await ctx.reply(presentLanguageMessage(language, copy), { reply_markup: buildLanguageKeyboard(language, copy) });
|
|
@@ -3872,7 +4145,7 @@ async function handleLanguageCommand(ctx, dependencies) {
|
|
|
3872
4145
|
}
|
|
3873
4146
|
}
|
|
3874
4147
|
async function switchLanguageForChat(api, chatId, language, dependencies) {
|
|
3875
|
-
const currentCopy = await
|
|
4148
|
+
const currentCopy = await getSafeChatCopy(dependencies.sessionRepo, chatId, dependencies.logger);
|
|
3876
4149
|
if (!isBotLanguage(language)) return {
|
|
3877
4150
|
found: false,
|
|
3878
4151
|
copy: currentCopy
|
|
@@ -3881,7 +4154,7 @@ async function switchLanguageForChat(api, chatId, language, dependencies) {
|
|
|
3881
4154
|
await syncTelegramCommandsForChat(api, chatId, language);
|
|
3882
4155
|
return {
|
|
3883
4156
|
found: true,
|
|
3884
|
-
copy: await
|
|
4157
|
+
copy: await getSafeChatCopy(dependencies.sessionRepo, chatId, dependencies.logger),
|
|
3885
4158
|
language
|
|
3886
4159
|
};
|
|
3887
4160
|
}
|
|
@@ -3891,7 +4164,7 @@ async function presentLanguageSwitchForChat(chatId, api, language, dependencies)
|
|
|
3891
4164
|
found: false,
|
|
3892
4165
|
copy: result.copy,
|
|
3893
4166
|
text: result.copy.language.expired,
|
|
3894
|
-
keyboard: buildLanguageKeyboard(await
|
|
4167
|
+
keyboard: buildLanguageKeyboard(await getSafeChatLanguage(dependencies.sessionRepo, chatId, dependencies.logger), result.copy)
|
|
3895
4168
|
};
|
|
3896
4169
|
return {
|
|
3897
4170
|
found: true,
|
|
@@ -3908,7 +4181,7 @@ function registerLanguageCommand(bot, dependencies) {
|
|
|
3908
4181
|
//#endregion
|
|
3909
4182
|
//#region src/bot/commands/models.ts
|
|
3910
4183
|
async function handleModelsCommand(ctx, dependencies) {
|
|
3911
|
-
const copy = await
|
|
4184
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3912
4185
|
try {
|
|
3913
4186
|
const result = await dependencies.listModelsUseCase.execute({ chatId: ctx.chat.id });
|
|
3914
4187
|
if (result.models.length === 0) {
|
|
@@ -3936,7 +4209,7 @@ function registerModelsCommand(bot, dependencies) {
|
|
|
3936
4209
|
//#endregion
|
|
3937
4210
|
//#region src/bot/commands/new.ts
|
|
3938
4211
|
async function handleNewCommand(ctx, dependencies) {
|
|
3939
|
-
const copy = await
|
|
4212
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3940
4213
|
try {
|
|
3941
4214
|
const title = extractSessionTitle(ctx);
|
|
3942
4215
|
const result = await dependencies.createSessionUseCase.execute({
|
|
@@ -4300,7 +4573,7 @@ function escapeLinkDestination(url) {
|
|
|
4300
4573
|
//#endregion
|
|
4301
4574
|
//#region src/bot/commands/status.ts
|
|
4302
4575
|
async function handleStatusCommand(ctx, dependencies) {
|
|
4303
|
-
const copy = await
|
|
4576
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat?.id, dependencies.logger);
|
|
4304
4577
|
try {
|
|
4305
4578
|
const result = await dependencies.getStatusUseCase.execute({ chatId: ctx.chat?.id ?? 0 });
|
|
4306
4579
|
const renderedMarkdown = renderMarkdownToTelegramMarkdownV2(presentStatusMarkdownMessage(result, copy));
|
|
@@ -4322,7 +4595,7 @@ function registerStatusCommand(bot, dependencies) {
|
|
|
4322
4595
|
//#endregion
|
|
4323
4596
|
//#region src/bot/commands/sessions.ts
|
|
4324
4597
|
async function handleSessionsCommand(ctx, dependencies) {
|
|
4325
|
-
const copy = await
|
|
4598
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4326
4599
|
try {
|
|
4327
4600
|
await dependencies.pendingActionRepo.clear(ctx.chat.id);
|
|
4328
4601
|
const view = await buildSessionsListView(ctx.chat.id, 0, dependencies);
|
|
@@ -4345,7 +4618,7 @@ function presentStartMarkdownMessage(copy = BOT_COPY) {
|
|
|
4345
4618
|
//#endregion
|
|
4346
4619
|
//#region src/bot/commands/start.ts
|
|
4347
4620
|
async function handleStartCommand(ctx, dependencies) {
|
|
4348
|
-
const copy = await
|
|
4621
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat?.id, dependencies.logger);
|
|
4349
4622
|
const reply = buildTelegramStaticReply(presentStartMarkdownMessage(copy));
|
|
4350
4623
|
try {
|
|
4351
4624
|
await ctx.reply(reply.preferred.text, reply.preferred.options);
|
|
@@ -4383,7 +4656,7 @@ async function handleAgentsCallback(ctx, dependencies) {
|
|
|
4383
4656
|
if (!data.startsWith("agents:")) return;
|
|
4384
4657
|
await ctx.answerCallbackQuery();
|
|
4385
4658
|
if (!ctx.chat) return;
|
|
4386
|
-
const copy = await
|
|
4659
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4387
4660
|
try {
|
|
4388
4661
|
if (data.startsWith(AGENTS_PAGE_PREFIX)) {
|
|
4389
4662
|
const requestedPage = Number(data.slice(12));
|
|
@@ -4427,7 +4700,7 @@ async function handleModelsCallback(ctx, dependencies) {
|
|
|
4427
4700
|
if (!data.startsWith("model:")) return;
|
|
4428
4701
|
await ctx.answerCallbackQuery();
|
|
4429
4702
|
if (!ctx.chat) return;
|
|
4430
|
-
const copy = await
|
|
4703
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4431
4704
|
try {
|
|
4432
4705
|
if (data.startsWith(MODEL_PAGE_PREFIX)) {
|
|
4433
4706
|
const requestedPage = Number(data.slice(11));
|
|
@@ -4506,7 +4779,7 @@ async function handleSessionsCallback(ctx, dependencies) {
|
|
|
4506
4779
|
if (!data.startsWith("sessions:")) return;
|
|
4507
4780
|
await ctx.answerCallbackQuery();
|
|
4508
4781
|
if (!ctx.chat) return;
|
|
4509
|
-
const copy = await
|
|
4782
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4510
4783
|
try {
|
|
4511
4784
|
if (data.startsWith(SESSIONS_PAGE_PREFIX)) {
|
|
4512
4785
|
const requestedPage = Number(data.slice(14));
|
|
@@ -4587,10 +4860,10 @@ async function handleLanguageCallback(ctx, dependencies) {
|
|
|
4587
4860
|
if (!data.startsWith("language:")) return;
|
|
4588
4861
|
await ctx.answerCallbackQuery();
|
|
4589
4862
|
if (!ctx.chat || !ctx.api) return;
|
|
4590
|
-
const currentCopy = await
|
|
4863
|
+
const currentCopy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4591
4864
|
try {
|
|
4592
4865
|
if (!data.startsWith(LANGUAGE_SELECT_PREFIX)) {
|
|
4593
|
-
await ctx.editMessageText(presentLanguageMessage(await
|
|
4866
|
+
await ctx.editMessageText(presentLanguageMessage(await getSafeChatLanguage(dependencies.sessionRepo, ctx.chat.id, dependencies.logger), currentCopy), { reply_markup: buildLanguageKeyboard(await getSafeChatLanguage(dependencies.sessionRepo, ctx.chat.id, dependencies.logger), currentCopy) });
|
|
4594
4867
|
return;
|
|
4595
4868
|
}
|
|
4596
4869
|
const selectedLanguage = data.slice(16);
|
|
@@ -4657,7 +4930,7 @@ function parseSessionActionTarget(data, prefix) {
|
|
|
4657
4930
|
//#endregion
|
|
4658
4931
|
//#region src/bot/handlers/prompt.handler.ts
|
|
4659
4932
|
async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
4660
|
-
const copy = await
|
|
4933
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4661
4934
|
const foregroundRequest = dependencies.foregroundSessionTracker.acquire(ctx.chat.id);
|
|
4662
4935
|
if (!foregroundRequest) {
|
|
4663
4936
|
await ctx.reply(copy.status.alreadyProcessing);
|
|
@@ -4794,7 +5067,7 @@ function registerMessageHandler(bot, dependencies) {
|
|
|
4794
5067
|
async function handleVoiceMessage(ctx, dependencies) {
|
|
4795
5068
|
if (!ctx.message.voice) return;
|
|
4796
5069
|
if (await replyIfSessionRenamePending(ctx, dependencies)) return;
|
|
4797
|
-
const copy = await
|
|
5070
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4798
5071
|
await ctx.reply(copy.errors.voiceUnsupported);
|
|
4799
5072
|
}
|
|
4800
5073
|
function registerVoiceHandler(bot, dependencies) {
|
|
@@ -4839,45 +5112,119 @@ function createLoggingMiddleware(logger) {
|
|
|
4839
5112
|
function registerBot(bot, container, options) {
|
|
4840
5113
|
bot.use(createLoggingMiddleware(container.logger));
|
|
4841
5114
|
bot.use(createAuthMiddleware(options.telegramAllowedChatIds));
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
5115
|
+
const safeBot = bot.errorBoundary(async (error) => {
|
|
5116
|
+
container.logger.error({
|
|
5117
|
+
...extractTelegramUpdateContext(error.ctx),
|
|
5118
|
+
error: error.error
|
|
5119
|
+
}, "telegram middleware failed");
|
|
5120
|
+
await replyWithDefaultTelegramError(error.ctx, container.logger, error.error);
|
|
5121
|
+
});
|
|
5122
|
+
registerStartCommand(safeBot, container);
|
|
5123
|
+
registerStatusCommand(safeBot, container);
|
|
5124
|
+
registerNewCommand(safeBot, container);
|
|
5125
|
+
registerAgentsCommand(safeBot, container);
|
|
5126
|
+
registerSessionsCommand(safeBot, container);
|
|
5127
|
+
registerCancelCommand(safeBot, container);
|
|
5128
|
+
registerModelsCommand(safeBot, container);
|
|
5129
|
+
registerLanguageCommand(safeBot, container);
|
|
5130
|
+
registerCallbackHandler(safeBot, container);
|
|
5131
|
+
registerFileHandler(safeBot, container);
|
|
5132
|
+
registerMessageHandler(safeBot, container);
|
|
5133
|
+
registerVoiceHandler(safeBot, container);
|
|
4854
5134
|
}
|
|
4855
5135
|
//#endregion
|
|
4856
5136
|
//#region src/app/runtime.ts
|
|
5137
|
+
var TELEGRAM_RUNNER_OPTIONS = { runner: {
|
|
5138
|
+
fetch: { timeout: 30 },
|
|
5139
|
+
maxRetryTime: 900 * 1e3,
|
|
5140
|
+
retryInterval: "exponential",
|
|
5141
|
+
silent: true
|
|
5142
|
+
} };
|
|
4857
5143
|
async function startTelegramBotRuntime(input) {
|
|
4858
|
-
const
|
|
4859
|
-
|
|
5144
|
+
const runtimeKey = buildTelegramRuntimeKey(input.config);
|
|
5145
|
+
const registry = getTelegramBotRuntimeRegistry();
|
|
5146
|
+
const existingRuntime = registry.activeByKey.get(runtimeKey);
|
|
5147
|
+
if (existingRuntime) {
|
|
5148
|
+
input.container.logger.warn({
|
|
5149
|
+
runtimeKey,
|
|
5150
|
+
telegramApiRoot: input.config.telegramApiRoot
|
|
5151
|
+
}, "telegram runtime already active in this process; reusing the existing runner");
|
|
5152
|
+
await input.container.dispose();
|
|
5153
|
+
return existingRuntime;
|
|
5154
|
+
}
|
|
5155
|
+
const runtimePromise = startTelegramBotRuntimeInternal(input, runtimeKey, () => {
|
|
5156
|
+
if (registry.activeByKey.get(runtimeKey) === runtimePromise) registry.activeByKey.delete(runtimeKey);
|
|
5157
|
+
}).catch((error) => {
|
|
5158
|
+
if (registry.activeByKey.get(runtimeKey) === runtimePromise) registry.activeByKey.delete(runtimeKey);
|
|
5159
|
+
throw error;
|
|
5160
|
+
});
|
|
5161
|
+
registry.activeByKey.set(runtimeKey, runtimePromise);
|
|
5162
|
+
return runtimePromise;
|
|
5163
|
+
}
|
|
5164
|
+
async function startTelegramBotRuntimeInternal(input, runtimeKey, releaseRuntime) {
|
|
5165
|
+
const bot = (input.botFactory ?? ((token, options) => new Bot(token, options)))(input.config.telegramBotToken, { client: { apiRoot: input.config.telegramApiRoot } });
|
|
5166
|
+
wrapTelegramGetUpdates(bot, input.container);
|
|
5167
|
+
(input.registerBotHandlers ?? registerBot)(bot, input.container, { telegramAllowedChatIds: input.config.telegramAllowedChatIds });
|
|
4860
5168
|
bot.catch((error) => {
|
|
5169
|
+
const metadata = extractTelegramUpdateContext(error.ctx);
|
|
5170
|
+
if (error.error instanceof GrammyError) {
|
|
5171
|
+
input.container.logger.error({
|
|
5172
|
+
...metadata,
|
|
5173
|
+
errorCode: error.error.error_code,
|
|
5174
|
+
description: error.error.description,
|
|
5175
|
+
method: error.error.method,
|
|
5176
|
+
parameters: error.error.parameters,
|
|
5177
|
+
payload: error.error.payload
|
|
5178
|
+
}, "telegram bot api request failed");
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
5181
|
+
if (error.error instanceof HttpError) {
|
|
5182
|
+
input.container.logger.error({
|
|
5183
|
+
...metadata,
|
|
5184
|
+
error: error.error.error,
|
|
5185
|
+
message: error.error.message
|
|
5186
|
+
}, "telegram bot network request failed");
|
|
5187
|
+
return;
|
|
5188
|
+
}
|
|
4861
5189
|
input.container.logger.error({
|
|
4862
|
-
|
|
4863
|
-
|
|
5190
|
+
...metadata,
|
|
5191
|
+
error: error.error
|
|
4864
5192
|
}, "telegram bot update failed");
|
|
4865
5193
|
});
|
|
4866
|
-
input.container.logger.info("bot starting
|
|
4867
|
-
|
|
4868
|
-
const runner = run(bot);
|
|
5194
|
+
input.container.logger.info({ runtimeKey }, "telegram bot polling starting");
|
|
5195
|
+
const runner = (input.runBot ?? run)(bot, TELEGRAM_RUNNER_OPTIONS);
|
|
4869
5196
|
let stopped = false;
|
|
4870
5197
|
let disposed = false;
|
|
4871
|
-
|
|
5198
|
+
if (input.syncCommands ?? true) (input.syncCommandsHandler ?? syncTelegramCommands)(bot, input.container.logger).catch((error) => {
|
|
5199
|
+
input.container.logger.warn({
|
|
5200
|
+
error,
|
|
5201
|
+
runtimeKey
|
|
5202
|
+
}, "failed to sync telegram commands; polling continues without command registration updates");
|
|
5203
|
+
});
|
|
5204
|
+
let stopPromise = null;
|
|
5205
|
+
const requestStop = async () => {
|
|
4872
5206
|
if (stopped) return;
|
|
4873
5207
|
stopped = true;
|
|
4874
|
-
runner.stop()
|
|
5208
|
+
stopPromise = runner.stop().catch((error) => {
|
|
5209
|
+
input.container.logger.warn({
|
|
5210
|
+
error,
|
|
5211
|
+
runtimeKey
|
|
5212
|
+
}, "failed to stop telegram runner cleanly");
|
|
5213
|
+
});
|
|
5214
|
+
await stopPromise;
|
|
5215
|
+
};
|
|
5216
|
+
const stop = () => {
|
|
5217
|
+
requestStop();
|
|
4875
5218
|
};
|
|
4876
5219
|
const dispose = async () => {
|
|
4877
5220
|
if (disposed) return;
|
|
4878
5221
|
disposed = true;
|
|
4879
|
-
|
|
4880
|
-
|
|
5222
|
+
try {
|
|
5223
|
+
await requestStop();
|
|
5224
|
+
await input.container.dispose();
|
|
5225
|
+
} finally {
|
|
5226
|
+
releaseRuntime();
|
|
5227
|
+
}
|
|
4881
5228
|
};
|
|
4882
5229
|
return {
|
|
4883
5230
|
bot,
|
|
@@ -4886,39 +5233,69 @@ async function startTelegramBotRuntime(input) {
|
|
|
4886
5233
|
dispose
|
|
4887
5234
|
};
|
|
4888
5235
|
}
|
|
5236
|
+
function wrapTelegramGetUpdates(bot, container) {
|
|
5237
|
+
const originalGetUpdates = bot.api.getUpdates.bind(bot.api);
|
|
5238
|
+
bot.api.getUpdates = async (options, signal) => {
|
|
5239
|
+
const requestOptions = options ?? {
|
|
5240
|
+
limit: 100,
|
|
5241
|
+
offset: 0,
|
|
5242
|
+
timeout: 30
|
|
5243
|
+
};
|
|
5244
|
+
try {
|
|
5245
|
+
return await originalGetUpdates(requestOptions, signal);
|
|
5246
|
+
} catch (error) {
|
|
5247
|
+
container.logger.warn({
|
|
5248
|
+
error,
|
|
5249
|
+
limit: requestOptions.limit,
|
|
5250
|
+
offset: requestOptions.offset,
|
|
5251
|
+
timeout: requestOptions.timeout
|
|
5252
|
+
}, "telegram getUpdates failed");
|
|
5253
|
+
throw error;
|
|
5254
|
+
}
|
|
5255
|
+
};
|
|
5256
|
+
}
|
|
5257
|
+
function buildTelegramRuntimeKey(config) {
|
|
5258
|
+
return `${config.telegramApiRoot}::${config.telegramBotToken}`;
|
|
5259
|
+
}
|
|
5260
|
+
function getTelegramBotRuntimeRegistry() {
|
|
5261
|
+
const globalScope = globalThis;
|
|
5262
|
+
globalScope.__opencodeTbotTelegramRuntimeRegistry__ ??= { activeByKey: /* @__PURE__ */ new Map() };
|
|
5263
|
+
return globalScope.__opencodeTbotTelegramRuntimeRegistry__;
|
|
5264
|
+
}
|
|
4889
5265
|
//#endregion
|
|
4890
5266
|
//#region src/plugin.ts
|
|
4891
|
-
var runtimeState = null;
|
|
4892
5267
|
async function ensureTelegramBotPluginRuntime(options) {
|
|
5268
|
+
const runtimeStateHolder = getTelegramBotPluginRuntimeStateHolder();
|
|
4893
5269
|
const cwd = resolvePluginRuntimeCwd(options.context);
|
|
4894
|
-
if (
|
|
4895
|
-
const activeState =
|
|
4896
|
-
|
|
5270
|
+
if (runtimeStateHolder.state && runtimeStateHolder.state.cwd !== cwd) {
|
|
5271
|
+
const activeState = runtimeStateHolder.state;
|
|
5272
|
+
runtimeStateHolder.state = null;
|
|
4897
5273
|
await disposeTelegramBotPluginRuntimeState(activeState);
|
|
4898
5274
|
}
|
|
4899
|
-
if (!
|
|
5275
|
+
if (!runtimeStateHolder.state) {
|
|
4900
5276
|
const runtimePromise = startPluginRuntime(options, cwd).then((runtime) => {
|
|
4901
|
-
if (
|
|
5277
|
+
if (runtimeStateHolder.state?.runtimePromise === runtimePromise) runtimeStateHolder.state.runtime = runtime;
|
|
4902
5278
|
return runtime;
|
|
4903
5279
|
}).catch((error) => {
|
|
4904
|
-
if (
|
|
5280
|
+
if (runtimeStateHolder.state?.runtimePromise === runtimePromise) runtimeStateHolder.state = null;
|
|
4905
5281
|
throw error;
|
|
4906
5282
|
});
|
|
4907
|
-
|
|
5283
|
+
runtimeStateHolder.state = {
|
|
4908
5284
|
cwd,
|
|
4909
5285
|
runtime: null,
|
|
4910
5286
|
runtimePromise
|
|
4911
5287
|
};
|
|
4912
5288
|
}
|
|
4913
|
-
return
|
|
5289
|
+
return runtimeStateHolder.state.runtimePromise;
|
|
4914
5290
|
}
|
|
4915
5291
|
var TelegramBotPlugin = async (context) => {
|
|
4916
5292
|
return createHooks(await ensureTelegramBotPluginRuntime({ context }));
|
|
4917
5293
|
};
|
|
4918
5294
|
async function resetTelegramBotPluginRuntimeForTests() {
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
5295
|
+
const runtimeStateHolder = getTelegramBotPluginRuntimeStateHolder();
|
|
5296
|
+
if (!runtimeStateHolder.state) return;
|
|
5297
|
+
const activeState = runtimeStateHolder.state;
|
|
5298
|
+
runtimeStateHolder.state = null;
|
|
4922
5299
|
await disposeTelegramBotPluginRuntimeState(activeState);
|
|
4923
5300
|
}
|
|
4924
5301
|
async function startPluginRuntime(options, cwd) {
|
|
@@ -4971,6 +5348,11 @@ function createHooks(runtime) {
|
|
|
4971
5348
|
}
|
|
4972
5349
|
};
|
|
4973
5350
|
}
|
|
5351
|
+
function getTelegramBotPluginRuntimeStateHolder() {
|
|
5352
|
+
const globalScope = globalThis;
|
|
5353
|
+
globalScope.__opencodeTbotPluginRuntimeState__ ??= { state: null };
|
|
5354
|
+
return globalScope.__opencodeTbotPluginRuntimeState__;
|
|
5355
|
+
}
|
|
4974
5356
|
//#endregion
|
|
4975
5357
|
export { TelegramBotPlugin, TelegramBotPlugin as default, ensureTelegramBotPluginRuntime, resetTelegramBotPluginRuntimeForTests };
|
|
4976
5358
|
|