opencode-tbot 0.1.28 → 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 +607 -246
- 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,7 +303,10 @@ 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 {
|
|
@@ -303,25 +317,43 @@ var OpenCodeClient = class {
|
|
|
303
317
|
}
|
|
304
318
|
}
|
|
305
319
|
async abortSession(sessionId) {
|
|
306
|
-
return this.callScopedSdkMethod("session", "abort", {
|
|
320
|
+
return this.callScopedSdkMethod("session", "abort", {
|
|
321
|
+
legacyParameters: { sessionID: sessionId },
|
|
322
|
+
parameters: { path: { id: sessionId } }
|
|
323
|
+
});
|
|
307
324
|
}
|
|
308
325
|
async deleteSession(sessionId) {
|
|
309
|
-
return this.callScopedSdkMethod("session", "delete", {
|
|
326
|
+
return this.callScopedSdkMethod("session", "delete", {
|
|
327
|
+
legacyParameters: { sessionID: sessionId },
|
|
328
|
+
parameters: { path: { id: sessionId } }
|
|
329
|
+
});
|
|
310
330
|
}
|
|
311
331
|
async forkSession(sessionId, messageId) {
|
|
312
|
-
return this.callScopedSdkMethod("session", "fork", {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
+
});
|
|
316
342
|
}
|
|
317
343
|
async getPath() {
|
|
318
344
|
return this.callScopedSdkMethod("path", "get", {});
|
|
319
345
|
}
|
|
320
346
|
async listLspStatuses(directory) {
|
|
321
|
-
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
|
+
});
|
|
322
351
|
}
|
|
323
352
|
async listMcpStatuses(directory) {
|
|
324
|
-
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
|
+
});
|
|
325
357
|
}
|
|
326
358
|
async getSessionStatuses() {
|
|
327
359
|
return this.loadSessionStatuses();
|
|
@@ -336,29 +368,69 @@ var OpenCodeClient = class {
|
|
|
336
368
|
return this.callScopedSdkMethod("project", "current", {});
|
|
337
369
|
}
|
|
338
370
|
async createSessionForDirectory(directory, title) {
|
|
339
|
-
return this.callScopedSdkMethod("session", "create", {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
+
});
|
|
343
381
|
}
|
|
344
382
|
async renameSession(sessionId, title) {
|
|
345
|
-
return this.callScopedSdkMethod("session", "update", {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
});
|
|
349
393
|
}
|
|
350
394
|
async listAgents() {
|
|
351
395
|
return this.callScopedSdkMethod("app", "agents", {});
|
|
352
396
|
}
|
|
353
397
|
async listPendingPermissions(directory) {
|
|
354
|
-
return this.callScopedSdkMethod("permission", "list", {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
+
});
|
|
362
434
|
}
|
|
363
435
|
async listModels() {
|
|
364
436
|
const now = Date.now();
|
|
@@ -494,10 +566,14 @@ var OpenCodeClient = class {
|
|
|
494
566
|
messageId
|
|
495
567
|
}, async (requestSignal) => {
|
|
496
568
|
return normalizePromptResponse(await this.callScopedSdkMethod("session", "message", {
|
|
497
|
-
|
|
569
|
+
legacyParameters: {
|
|
498
570
|
sessionID: sessionId,
|
|
499
571
|
messageID: messageId
|
|
500
572
|
},
|
|
573
|
+
parameters: { path: {
|
|
574
|
+
id: sessionId,
|
|
575
|
+
messageID: messageId
|
|
576
|
+
} },
|
|
501
577
|
signal: requestSignal
|
|
502
578
|
}));
|
|
503
579
|
}, signal);
|
|
@@ -525,10 +601,14 @@ var OpenCodeClient = class {
|
|
|
525
601
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
526
602
|
}, async (requestSignal) => {
|
|
527
603
|
return normalizePromptResponses(await this.callScopedSdkMethod("session", "messages", {
|
|
528
|
-
|
|
604
|
+
legacyParameters: {
|
|
529
605
|
sessionID: sessionId,
|
|
530
606
|
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
531
607
|
},
|
|
608
|
+
parameters: {
|
|
609
|
+
path: { id: sessionId },
|
|
610
|
+
query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
|
|
611
|
+
},
|
|
532
612
|
signal: requestSignal
|
|
533
613
|
}));
|
|
534
614
|
}, signal);
|
|
@@ -589,10 +669,14 @@ var OpenCodeClient = class {
|
|
|
589
669
|
...input.variant ? { variant: input.variant } : {},
|
|
590
670
|
parts
|
|
591
671
|
};
|
|
592
|
-
const
|
|
672
|
+
const legacyRequestParameters = {
|
|
593
673
|
sessionID: input.sessionId,
|
|
594
674
|
...requestBody
|
|
595
675
|
};
|
|
676
|
+
const requestParameters = {
|
|
677
|
+
body: requestBody,
|
|
678
|
+
path: { id: input.sessionId }
|
|
679
|
+
};
|
|
596
680
|
try {
|
|
597
681
|
if (typeof this.client.session?.promptAsync === "function") {
|
|
598
682
|
await this.runPromptRequestWithTimeout({
|
|
@@ -601,6 +685,7 @@ var OpenCodeClient = class {
|
|
|
601
685
|
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
602
686
|
}, async (signal) => {
|
|
603
687
|
await this.callScopedSdkMethod("session", "promptAsync", {
|
|
688
|
+
legacyParameters: legacyRequestParameters,
|
|
604
689
|
parameters: requestParameters,
|
|
605
690
|
signal
|
|
606
691
|
});
|
|
@@ -618,10 +703,7 @@ var OpenCodeClient = class {
|
|
|
618
703
|
throw new Error("OpenCode SDK client does not expose session.promptAsync().");
|
|
619
704
|
}
|
|
620
705
|
async loadSessionStatuses(signal) {
|
|
621
|
-
return this.callScopedSdkMethod("session", "status", {
|
|
622
|
-
signal,
|
|
623
|
-
parameters: void 0
|
|
624
|
-
});
|
|
706
|
+
return this.callScopedSdkMethod("session", "status", { signal });
|
|
625
707
|
}
|
|
626
708
|
async callRawSdkGet(url, signal) {
|
|
627
709
|
const rawClient = getRawSdkRequestClient(this.client);
|
|
@@ -698,12 +780,16 @@ var OpenCodeClient = class {
|
|
|
698
780
|
logPromptRequest(level, extra, message) {
|
|
699
781
|
const log = this.client.app?.log;
|
|
700
782
|
if (typeof log !== "function") return;
|
|
701
|
-
|
|
783
|
+
const payload = {
|
|
702
784
|
service: PROMPT_LOG_SERVICE,
|
|
703
785
|
level,
|
|
704
786
|
message,
|
|
705
787
|
extra
|
|
706
|
-
}
|
|
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);
|
|
707
793
|
}
|
|
708
794
|
};
|
|
709
795
|
function createOpenCodeClientFromSdkClient(client, fetchFn = fetch, promptTimeoutPolicy = {}) {
|
|
@@ -825,7 +911,7 @@ function isPromptResponseUsable(data, structured) {
|
|
|
825
911
|
}
|
|
826
912
|
function normalizePromptResponse(response) {
|
|
827
913
|
return {
|
|
828
|
-
info: isPlainRecord(response?.info) ? response.info : null,
|
|
914
|
+
info: isPlainRecord$1(response?.info) ? response.info : null,
|
|
829
915
|
parts: normalizePromptParts(response?.parts)
|
|
830
916
|
};
|
|
831
917
|
}
|
|
@@ -849,7 +935,7 @@ function toAssistantMessage(message) {
|
|
|
849
935
|
if ("mode" in message && typeof message.mode === "string" && message.mode.trim().length > 0) normalized.mode = message.mode;
|
|
850
936
|
if ("modelID" in message && typeof message.modelID === "string" && message.modelID.trim().length > 0) normalized.modelID = message.modelID;
|
|
851
937
|
if ("parentID" in message && typeof message.parentID === "string" && message.parentID.trim().length > 0) normalized.parentID = message.parentID;
|
|
852
|
-
if ("path" in message && isPlainRecord(message.path)) normalized.path = {
|
|
938
|
+
if ("path" in message && isPlainRecord$1(message.path)) normalized.path = {
|
|
853
939
|
...typeof message.path.cwd === "string" && message.path.cwd.trim().length > 0 ? { cwd: message.path.cwd } : {},
|
|
854
940
|
...typeof message.path.root === "string" && message.path.root.trim().length > 0 ? { root: message.path.root } : {}
|
|
855
941
|
};
|
|
@@ -859,16 +945,16 @@ function toAssistantMessage(message) {
|
|
|
859
945
|
const structuredPayload = extractStructuredPayload(message);
|
|
860
946
|
if (structuredPayload !== null) normalized.structured = structuredPayload;
|
|
861
947
|
if ("summary" in message && typeof message.summary === "boolean") normalized.summary = message.summary;
|
|
862
|
-
if ("time" in message && isPlainRecord(message.time)) normalized.time = {
|
|
948
|
+
if ("time" in message && isPlainRecord$1(message.time)) normalized.time = {
|
|
863
949
|
...typeof message.time.created === "number" && Number.isFinite(message.time.created) ? { created: message.time.created } : {},
|
|
864
950
|
...typeof message.time.completed === "number" && Number.isFinite(message.time.completed) ? { completed: message.time.completed } : {}
|
|
865
951
|
};
|
|
866
|
-
if ("tokens" in message && isPlainRecord(message.tokens)) normalized.tokens = {
|
|
952
|
+
if ("tokens" in message && isPlainRecord$1(message.tokens)) normalized.tokens = {
|
|
867
953
|
...typeof message.tokens.input === "number" && Number.isFinite(message.tokens.input) ? { input: message.tokens.input } : {},
|
|
868
954
|
...typeof message.tokens.output === "number" && Number.isFinite(message.tokens.output) ? { output: message.tokens.output } : {},
|
|
869
955
|
...typeof message.tokens.reasoning === "number" && Number.isFinite(message.tokens.reasoning) ? { reasoning: message.tokens.reasoning } : {},
|
|
870
956
|
...typeof message.tokens.total === "number" && Number.isFinite(message.tokens.total) ? { total: message.tokens.total } : {},
|
|
871
|
-
...isPlainRecord(message.tokens.cache) ? { cache: {
|
|
957
|
+
...isPlainRecord$1(message.tokens.cache) ? { cache: {
|
|
872
958
|
...typeof message.tokens.cache.read === "number" && Number.isFinite(message.tokens.cache.read) ? { read: message.tokens.cache.read } : {},
|
|
873
959
|
...typeof message.tokens.cache.write === "number" && Number.isFinite(message.tokens.cache.write) ? { write: message.tokens.cache.write } : {}
|
|
874
960
|
} } : {}
|
|
@@ -877,7 +963,7 @@ function toAssistantMessage(message) {
|
|
|
877
963
|
return normalized;
|
|
878
964
|
}
|
|
879
965
|
function extractMessageId(message) {
|
|
880
|
-
if (!isPlainRecord(message)) return null;
|
|
966
|
+
if (!isPlainRecord$1(message)) return null;
|
|
881
967
|
return typeof message.id === "string" && message.id.trim().length > 0 ? message.id : null;
|
|
882
968
|
}
|
|
883
969
|
function delay(ms, signal) {
|
|
@@ -973,6 +1059,27 @@ function unwrapSdkData(response) {
|
|
|
973
1059
|
if (response && typeof response === "object" && "data" in response) return response.data;
|
|
974
1060
|
return response;
|
|
975
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
|
+
}
|
|
976
1083
|
function getRawSdkRequestClient(client) {
|
|
977
1084
|
const compatibleClient = client;
|
|
978
1085
|
return compatibleClient.client ?? compatibleClient._client ?? null;
|
|
@@ -988,11 +1095,11 @@ function resolvePromptTimeoutPolicy(input) {
|
|
|
988
1095
|
};
|
|
989
1096
|
}
|
|
990
1097
|
function normalizeAssistantError(value) {
|
|
991
|
-
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;
|
|
992
1099
|
return {
|
|
993
1100
|
...value,
|
|
994
1101
|
name: value.name,
|
|
995
|
-
...isPlainRecord(value.data) ? { data: value.data } : {}
|
|
1102
|
+
...isPlainRecord$1(value.data) ? { data: value.data } : {}
|
|
996
1103
|
};
|
|
997
1104
|
}
|
|
998
1105
|
function isAssistantMessageCompleted(message) {
|
|
@@ -1005,7 +1112,7 @@ function isCompletedEmptyPromptResponse(data, structured) {
|
|
|
1005
1112
|
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
|
|
1006
1113
|
}
|
|
1007
1114
|
function extractStructuredPayload(message) {
|
|
1008
|
-
if (!isPlainRecord(message)) return null;
|
|
1115
|
+
if (!isPlainRecord$1(message)) return null;
|
|
1009
1116
|
if ("structured" in message && message.structured !== void 0) return message.structured;
|
|
1010
1117
|
if ("structured_output" in message && message.structured_output !== void 0) return message.structured_output;
|
|
1011
1118
|
return null;
|
|
@@ -1079,7 +1186,7 @@ function throwIfAborted(signal) {
|
|
|
1079
1186
|
if (!signal?.aborted) return;
|
|
1080
1187
|
throw normalizeAbortReason(signal.reason);
|
|
1081
1188
|
}
|
|
1082
|
-
function isPlainRecord(value) {
|
|
1189
|
+
function isPlainRecord$1(value) {
|
|
1083
1190
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1084
1191
|
}
|
|
1085
1192
|
async function resolveProviderAvailability(config, fetchFn) {
|
|
@@ -2295,25 +2402,35 @@ function escapeMarkdownV2(value) {
|
|
|
2295
2402
|
async function handleTelegramBotPluginEvent(runtime, event) {
|
|
2296
2403
|
switch (event.type) {
|
|
2297
2404
|
case "permission.asked":
|
|
2298
|
-
|
|
2405
|
+
case "permission.updated": {
|
|
2406
|
+
const request = normalizePermissionRequestEvent(event.properties);
|
|
2407
|
+
if (request) await handlePermissionAsked(runtime, request);
|
|
2299
2408
|
return;
|
|
2300
|
-
|
|
2301
|
-
|
|
2409
|
+
}
|
|
2410
|
+
case "permission.replied": {
|
|
2411
|
+
const replyEvent = normalizePermissionReplyEvent(event.properties);
|
|
2412
|
+
if (replyEvent) await handlePermissionReplied(runtime, replyEvent);
|
|
2302
2413
|
return;
|
|
2303
|
-
|
|
2304
|
-
|
|
2414
|
+
}
|
|
2415
|
+
case "session.error": {
|
|
2416
|
+
const sessionError = normalizeSessionErrorEvent(event.properties);
|
|
2417
|
+
if (sessionError) await handleSessionError(runtime, sessionError);
|
|
2305
2418
|
return;
|
|
2306
|
-
|
|
2307
|
-
|
|
2419
|
+
}
|
|
2420
|
+
case "session.idle": {
|
|
2421
|
+
const sessionIdle = normalizeSessionIdleEvent(event.properties);
|
|
2422
|
+
if (sessionIdle) await handleSessionIdle(runtime, sessionIdle);
|
|
2308
2423
|
return;
|
|
2309
|
-
|
|
2310
|
-
|
|
2424
|
+
}
|
|
2425
|
+
case "session.status": {
|
|
2426
|
+
const sessionStatus = normalizeSessionStatusEvent(event.properties);
|
|
2427
|
+
if (sessionStatus) await handleSessionStatus(runtime, sessionStatus);
|
|
2311
2428
|
return;
|
|
2429
|
+
}
|
|
2312
2430
|
default: return;
|
|
2313
2431
|
}
|
|
2314
2432
|
}
|
|
2315
|
-
async function handlePermissionAsked(runtime,
|
|
2316
|
-
const request = event.properties;
|
|
2433
|
+
async function handlePermissionAsked(runtime, request) {
|
|
2317
2434
|
const bindings = await runtime.container.sessionRepo.listBySessionId(request.sessionID);
|
|
2318
2435
|
const chatIds = new Set([...bindings.map((binding) => binding.chatId), ...runtime.container.foregroundSessionTracker.listChatIds(request.sessionID)]);
|
|
2319
2436
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(request.id);
|
|
@@ -2343,52 +2460,42 @@ async function handlePermissionAsked(runtime, event) {
|
|
|
2343
2460
|
}
|
|
2344
2461
|
}
|
|
2345
2462
|
async function handlePermissionReplied(runtime, event) {
|
|
2346
|
-
const
|
|
2347
|
-
const reply = event.properties.reply;
|
|
2348
|
-
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(requestId);
|
|
2463
|
+
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(event.requestId);
|
|
2349
2464
|
await Promise.all(approvals.map(async (approval) => {
|
|
2350
2465
|
try {
|
|
2351
|
-
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));
|
|
2352
2467
|
} catch (error) {
|
|
2353
2468
|
runtime.container.logger.warn({
|
|
2354
2469
|
error,
|
|
2355
2470
|
chatId: approval.chatId,
|
|
2356
|
-
requestId
|
|
2471
|
+
requestId: event.requestId,
|
|
2472
|
+
sessionId: event.sessionId
|
|
2357
2473
|
}, "failed to update Telegram permission message");
|
|
2358
2474
|
}
|
|
2359
|
-
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, reply));
|
|
2475
|
+
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, event.reply));
|
|
2360
2476
|
}));
|
|
2361
2477
|
}
|
|
2362
2478
|
async function handleSessionError(runtime, event) {
|
|
2363
|
-
|
|
2364
|
-
const error = event.properties.error;
|
|
2365
|
-
if (!sessionId) {
|
|
2366
|
-
runtime.container.logger.error({ error }, "session error received without a session id");
|
|
2367
|
-
return;
|
|
2368
|
-
}
|
|
2369
|
-
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."))) {
|
|
2370
2480
|
runtime.container.logger.warn({
|
|
2371
|
-
error,
|
|
2372
|
-
sessionId
|
|
2481
|
+
error: event.error,
|
|
2482
|
+
sessionId: event.sessionId
|
|
2373
2483
|
}, "session error suppressed for foreground Telegram session");
|
|
2374
2484
|
return;
|
|
2375
2485
|
}
|
|
2376
|
-
|
|
2486
|
+
const message = extractSessionErrorMessage(event.error) ?? "Unknown session error.";
|
|
2487
|
+
await notifyBoundChats(runtime, event.sessionId, `Session failed.\n\nSession: ${event.sessionId}\nError: ${message}`);
|
|
2377
2488
|
}
|
|
2378
2489
|
async function handleSessionIdle(runtime, event) {
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
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");
|
|
2382
2492
|
return;
|
|
2383
2493
|
}
|
|
2384
|
-
await notifyBoundChats(runtime, sessionId, `Session finished.\n\nSession: ${sessionId}`);
|
|
2494
|
+
await notifyBoundChats(runtime, event.sessionId, `Session finished.\n\nSession: ${event.sessionId}`);
|
|
2385
2495
|
}
|
|
2386
2496
|
async function handleSessionStatus(runtime, event) {
|
|
2387
|
-
if (event.
|
|
2388
|
-
await handleSessionIdle(runtime,
|
|
2389
|
-
type: "session.idle",
|
|
2390
|
-
properties: { sessionID: event.properties.sessionID }
|
|
2391
|
-
});
|
|
2497
|
+
if (event.statusType !== "idle") return;
|
|
2498
|
+
await handleSessionIdle(runtime, event);
|
|
2392
2499
|
}
|
|
2393
2500
|
async function notifyBoundChats(runtime, sessionId, text) {
|
|
2394
2501
|
const bindings = await runtime.container.sessionRepo.listBySessionId(sessionId);
|
|
@@ -2412,6 +2519,79 @@ function toResolvedApproval(approval, reply) {
|
|
|
2412
2519
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2413
2520
|
};
|
|
2414
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
|
+
}
|
|
2415
2595
|
var SUPPORTED_BOT_LANGUAGES = [
|
|
2416
2596
|
"en",
|
|
2417
2597
|
"zh-CN",
|
|
@@ -3115,111 +3295,6 @@ async function syncTelegramCommandsForChat(api, chatId, language) {
|
|
|
3115
3295
|
} });
|
|
3116
3296
|
}
|
|
3117
3297
|
//#endregion
|
|
3118
|
-
//#region src/bot/i18n.ts
|
|
3119
|
-
async function getChatLanguage(sessionRepo, chatId) {
|
|
3120
|
-
if (!chatId) return "en";
|
|
3121
|
-
return normalizeBotLanguage((await sessionRepo.getByChatId(chatId))?.language);
|
|
3122
|
-
}
|
|
3123
|
-
async function getChatCopy(sessionRepo, chatId) {
|
|
3124
|
-
return getBotCopy(await getChatLanguage(sessionRepo, chatId));
|
|
3125
|
-
}
|
|
3126
|
-
async function setChatLanguage(sessionRepo, chatId, language) {
|
|
3127
|
-
const binding = await sessionRepo.getByChatId(chatId);
|
|
3128
|
-
await sessionRepo.setCurrent({
|
|
3129
|
-
chatId,
|
|
3130
|
-
sessionId: binding?.sessionId ?? null,
|
|
3131
|
-
projectId: binding?.projectId ?? null,
|
|
3132
|
-
directory: binding?.directory ?? null,
|
|
3133
|
-
agentName: binding?.agentName ?? null,
|
|
3134
|
-
modelProviderId: binding?.modelProviderId ?? null,
|
|
3135
|
-
modelId: binding?.modelId ?? null,
|
|
3136
|
-
modelVariant: binding?.modelVariant ?? null,
|
|
3137
|
-
language,
|
|
3138
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3139
|
-
});
|
|
3140
|
-
}
|
|
3141
|
-
var NUMBERED_BUTTONS_PER_ROW = 5;
|
|
3142
|
-
function buildModelsKeyboard(models, requestedPage, copy = BOT_COPY) {
|
|
3143
|
-
const page = getModelsPage(models, requestedPage);
|
|
3144
|
-
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (_, index) => `model:pick:${page.startIndex + index + 1}`);
|
|
3145
|
-
appendPaginationButtons(keyboard, page.page, page.totalPages, "model:page", copy);
|
|
3146
|
-
return {
|
|
3147
|
-
keyboard,
|
|
3148
|
-
page
|
|
3149
|
-
};
|
|
3150
|
-
}
|
|
3151
|
-
function buildAgentsKeyboard(agents, requestedPage, copy = BOT_COPY) {
|
|
3152
|
-
const page = getAgentsPage(agents, requestedPage);
|
|
3153
|
-
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (_, index) => `agents:select:${page.startIndex + index + 1}`);
|
|
3154
|
-
appendPaginationButtons(keyboard, page.page, page.totalPages, "agents:page", copy);
|
|
3155
|
-
return {
|
|
3156
|
-
keyboard,
|
|
3157
|
-
page
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
function buildSessionsKeyboard(sessions, requestedPage, copy = BOT_COPY) {
|
|
3161
|
-
const page = getSessionsPage(sessions, requestedPage);
|
|
3162
|
-
const keyboard = buildNumberedKeyboard(page.items, page.startIndex, (session) => `sessions:pick:${page.page}:${session.id}`);
|
|
3163
|
-
appendPaginationButtons(keyboard, page.page, page.totalPages, "sessions:page", copy);
|
|
3164
|
-
return {
|
|
3165
|
-
keyboard,
|
|
3166
|
-
page
|
|
3167
|
-
};
|
|
3168
|
-
}
|
|
3169
|
-
function buildSessionActionKeyboard(sessionId, page, copy = BOT_COPY) {
|
|
3170
|
-
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}`);
|
|
3171
|
-
}
|
|
3172
|
-
function buildModelVariantsKeyboard(variants, modelIndex) {
|
|
3173
|
-
return buildNumberedKeyboard(variants, 0, (_, index) => `model:variant:${modelIndex}:${index + 1}`);
|
|
3174
|
-
}
|
|
3175
|
-
function buildLanguageKeyboard(currentLanguage, copy = BOT_COPY) {
|
|
3176
|
-
const keyboard = new InlineKeyboard();
|
|
3177
|
-
SUPPORTED_BOT_LANGUAGES.forEach((language, index) => {
|
|
3178
|
-
const label = currentLanguage === language ? `[${getLanguageLabel(language, copy)}]` : getLanguageLabel(language, copy);
|
|
3179
|
-
keyboard.text(label, `language:select:${language}`);
|
|
3180
|
-
if (index !== SUPPORTED_BOT_LANGUAGES.length - 1) keyboard.row();
|
|
3181
|
-
});
|
|
3182
|
-
return keyboard;
|
|
3183
|
-
}
|
|
3184
|
-
function getModelsPage(models, requestedPage) {
|
|
3185
|
-
return getPagedItems(models, requestedPage, 10);
|
|
3186
|
-
}
|
|
3187
|
-
function getAgentsPage(agents, requestedPage) {
|
|
3188
|
-
return getPagedItems(agents, requestedPage, 10);
|
|
3189
|
-
}
|
|
3190
|
-
function getSessionsPage(sessions, requestedPage) {
|
|
3191
|
-
return getPagedItems(sessions, requestedPage, 10);
|
|
3192
|
-
}
|
|
3193
|
-
function buildNumberedKeyboard(items, startIndex, buildCallbackData) {
|
|
3194
|
-
const keyboard = new InlineKeyboard();
|
|
3195
|
-
items.forEach((item, index) => {
|
|
3196
|
-
const displayIndex = startIndex + index + 1;
|
|
3197
|
-
keyboard.text(`${displayIndex}`, buildCallbackData(item, index));
|
|
3198
|
-
if (index !== items.length - 1 && (index + 1) % NUMBERED_BUTTONS_PER_ROW === 0) keyboard.row();
|
|
3199
|
-
});
|
|
3200
|
-
return keyboard;
|
|
3201
|
-
}
|
|
3202
|
-
function appendPaginationButtons(keyboard, page, totalPages, prefix, copy) {
|
|
3203
|
-
if (totalPages <= 1) return;
|
|
3204
|
-
if (page > 0) keyboard.text(copy.common.previousPage, `${prefix}:${page - 1}`);
|
|
3205
|
-
if (page < totalPages - 1) keyboard.text(copy.common.nextPage, `${prefix}:${page + 1}`);
|
|
3206
|
-
}
|
|
3207
|
-
function getPagedItems(items, requestedPage, pageSize) {
|
|
3208
|
-
const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
|
|
3209
|
-
const page = clampPage(requestedPage, totalPages);
|
|
3210
|
-
const startIndex = page * pageSize;
|
|
3211
|
-
return {
|
|
3212
|
-
items: items.slice(startIndex, startIndex + pageSize),
|
|
3213
|
-
page,
|
|
3214
|
-
startIndex,
|
|
3215
|
-
totalPages
|
|
3216
|
-
};
|
|
3217
|
-
}
|
|
3218
|
-
function clampPage(page, totalPages) {
|
|
3219
|
-
if (!Number.isInteger(page) || page < 0) return 0;
|
|
3220
|
-
return Math.min(page, totalPages - 1);
|
|
3221
|
-
}
|
|
3222
|
-
//#endregion
|
|
3223
3298
|
//#region src/bot/presenters/error.presenter.ts
|
|
3224
3299
|
function presentError(error, copy = BOT_COPY) {
|
|
3225
3300
|
const presented = normalizeError(error, copy);
|
|
@@ -3334,6 +3409,183 @@ function stringifyUnknown(value) {
|
|
|
3334
3409
|
}
|
|
3335
3410
|
}
|
|
3336
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
|
|
3337
3589
|
//#region src/bot/presenters/message.presenter.ts
|
|
3338
3590
|
var VARIANT_ORDER = [
|
|
3339
3591
|
"minimal",
|
|
@@ -3713,7 +3965,7 @@ function formatSessionLabel(session) {
|
|
|
3713
3965
|
//#endregion
|
|
3714
3966
|
//#region src/bot/commands/agents.ts
|
|
3715
3967
|
async function handleAgentsCommand(ctx, dependencies) {
|
|
3716
|
-
const copy = await
|
|
3968
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3717
3969
|
try {
|
|
3718
3970
|
const result = await dependencies.listAgentsUseCase.execute({ chatId: ctx.chat.id });
|
|
3719
3971
|
if (result.agents.length === 0) {
|
|
@@ -3739,7 +3991,7 @@ function registerAgentsCommand(bot, dependencies) {
|
|
|
3739
3991
|
//#endregion
|
|
3740
3992
|
//#region src/bot/sessions-menu.ts
|
|
3741
3993
|
async function buildSessionsListView(chatId, requestedPage, dependencies) {
|
|
3742
|
-
const copy = await
|
|
3994
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, chatId, dependencies.logger);
|
|
3743
3995
|
const result = await dependencies.listSessionsUseCase.execute({ chatId });
|
|
3744
3996
|
if (result.sessions.length === 0) return {
|
|
3745
3997
|
copy,
|
|
@@ -3798,14 +4050,14 @@ async function getPendingSessionRenameAction(dependencies, chatId) {
|
|
|
3798
4050
|
}
|
|
3799
4051
|
async function replyIfSessionRenamePending(ctx, dependencies) {
|
|
3800
4052
|
if (!await getPendingSessionRenameAction(dependencies, ctx.chat.id)) return false;
|
|
3801
|
-
const copy = await
|
|
4053
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3802
4054
|
await ctx.reply(copy.sessions.renamePendingInput);
|
|
3803
4055
|
return true;
|
|
3804
4056
|
}
|
|
3805
4057
|
async function handlePendingSessionRenameText(ctx, dependencies) {
|
|
3806
4058
|
const pendingAction = await getPendingSessionRenameAction(dependencies, ctx.chat.id);
|
|
3807
4059
|
if (!pendingAction) return false;
|
|
3808
|
-
const copy = await
|
|
4060
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3809
4061
|
const title = ctx.message.text?.trim() ?? "";
|
|
3810
4062
|
if (title.startsWith("/")) {
|
|
3811
4063
|
await ctx.reply(copy.sessions.renamePendingInput);
|
|
@@ -3837,7 +4089,7 @@ async function handlePendingSessionRenameText(ctx, dependencies) {
|
|
|
3837
4089
|
async function cancelPendingSessionRename(ctx, dependencies) {
|
|
3838
4090
|
const pendingAction = await getPendingSessionRenameAction(dependencies, ctx.chat.id);
|
|
3839
4091
|
if (!pendingAction) return false;
|
|
3840
|
-
const copy = await
|
|
4092
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3841
4093
|
await dependencies.pendingActionRepo.clear(ctx.chat.id);
|
|
3842
4094
|
await bestEffortRestoreSessionsList(ctx.api, pendingAction, dependencies);
|
|
3843
4095
|
await ctx.reply(copy.sessions.renameCancelled);
|
|
@@ -3856,7 +4108,7 @@ function isSessionRenamePendingAction(action) {
|
|
|
3856
4108
|
//#endregion
|
|
3857
4109
|
//#region src/bot/commands/cancel.ts
|
|
3858
4110
|
async function handleCancelCommand(ctx, dependencies) {
|
|
3859
|
-
const copy = await
|
|
4111
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3860
4112
|
try {
|
|
3861
4113
|
if (await cancelPendingSessionRename(ctx, dependencies)) return;
|
|
3862
4114
|
const result = await dependencies.abortPromptUseCase.execute({ chatId: ctx.chat.id });
|
|
@@ -3882,8 +4134,8 @@ function registerCancelCommand(bot, dependencies) {
|
|
|
3882
4134
|
//#endregion
|
|
3883
4135
|
//#region src/bot/commands/language.ts
|
|
3884
4136
|
async function handleLanguageCommand(ctx, dependencies) {
|
|
3885
|
-
const language = await
|
|
3886
|
-
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);
|
|
3887
4139
|
try {
|
|
3888
4140
|
await syncTelegramCommandsForChat(ctx.api, ctx.chat.id, language);
|
|
3889
4141
|
await ctx.reply(presentLanguageMessage(language, copy), { reply_markup: buildLanguageKeyboard(language, copy) });
|
|
@@ -3893,7 +4145,7 @@ async function handleLanguageCommand(ctx, dependencies) {
|
|
|
3893
4145
|
}
|
|
3894
4146
|
}
|
|
3895
4147
|
async function switchLanguageForChat(api, chatId, language, dependencies) {
|
|
3896
|
-
const currentCopy = await
|
|
4148
|
+
const currentCopy = await getSafeChatCopy(dependencies.sessionRepo, chatId, dependencies.logger);
|
|
3897
4149
|
if (!isBotLanguage(language)) return {
|
|
3898
4150
|
found: false,
|
|
3899
4151
|
copy: currentCopy
|
|
@@ -3902,7 +4154,7 @@ async function switchLanguageForChat(api, chatId, language, dependencies) {
|
|
|
3902
4154
|
await syncTelegramCommandsForChat(api, chatId, language);
|
|
3903
4155
|
return {
|
|
3904
4156
|
found: true,
|
|
3905
|
-
copy: await
|
|
4157
|
+
copy: await getSafeChatCopy(dependencies.sessionRepo, chatId, dependencies.logger),
|
|
3906
4158
|
language
|
|
3907
4159
|
};
|
|
3908
4160
|
}
|
|
@@ -3912,7 +4164,7 @@ async function presentLanguageSwitchForChat(chatId, api, language, dependencies)
|
|
|
3912
4164
|
found: false,
|
|
3913
4165
|
copy: result.copy,
|
|
3914
4166
|
text: result.copy.language.expired,
|
|
3915
|
-
keyboard: buildLanguageKeyboard(await
|
|
4167
|
+
keyboard: buildLanguageKeyboard(await getSafeChatLanguage(dependencies.sessionRepo, chatId, dependencies.logger), result.copy)
|
|
3916
4168
|
};
|
|
3917
4169
|
return {
|
|
3918
4170
|
found: true,
|
|
@@ -3929,7 +4181,7 @@ function registerLanguageCommand(bot, dependencies) {
|
|
|
3929
4181
|
//#endregion
|
|
3930
4182
|
//#region src/bot/commands/models.ts
|
|
3931
4183
|
async function handleModelsCommand(ctx, dependencies) {
|
|
3932
|
-
const copy = await
|
|
4184
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3933
4185
|
try {
|
|
3934
4186
|
const result = await dependencies.listModelsUseCase.execute({ chatId: ctx.chat.id });
|
|
3935
4187
|
if (result.models.length === 0) {
|
|
@@ -3957,7 +4209,7 @@ function registerModelsCommand(bot, dependencies) {
|
|
|
3957
4209
|
//#endregion
|
|
3958
4210
|
//#region src/bot/commands/new.ts
|
|
3959
4211
|
async function handleNewCommand(ctx, dependencies) {
|
|
3960
|
-
const copy = await
|
|
4212
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
3961
4213
|
try {
|
|
3962
4214
|
const title = extractSessionTitle(ctx);
|
|
3963
4215
|
const result = await dependencies.createSessionUseCase.execute({
|
|
@@ -4321,7 +4573,7 @@ function escapeLinkDestination(url) {
|
|
|
4321
4573
|
//#endregion
|
|
4322
4574
|
//#region src/bot/commands/status.ts
|
|
4323
4575
|
async function handleStatusCommand(ctx, dependencies) {
|
|
4324
|
-
const copy = await
|
|
4576
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat?.id, dependencies.logger);
|
|
4325
4577
|
try {
|
|
4326
4578
|
const result = await dependencies.getStatusUseCase.execute({ chatId: ctx.chat?.id ?? 0 });
|
|
4327
4579
|
const renderedMarkdown = renderMarkdownToTelegramMarkdownV2(presentStatusMarkdownMessage(result, copy));
|
|
@@ -4343,7 +4595,7 @@ function registerStatusCommand(bot, dependencies) {
|
|
|
4343
4595
|
//#endregion
|
|
4344
4596
|
//#region src/bot/commands/sessions.ts
|
|
4345
4597
|
async function handleSessionsCommand(ctx, dependencies) {
|
|
4346
|
-
const copy = await
|
|
4598
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4347
4599
|
try {
|
|
4348
4600
|
await dependencies.pendingActionRepo.clear(ctx.chat.id);
|
|
4349
4601
|
const view = await buildSessionsListView(ctx.chat.id, 0, dependencies);
|
|
@@ -4366,7 +4618,7 @@ function presentStartMarkdownMessage(copy = BOT_COPY) {
|
|
|
4366
4618
|
//#endregion
|
|
4367
4619
|
//#region src/bot/commands/start.ts
|
|
4368
4620
|
async function handleStartCommand(ctx, dependencies) {
|
|
4369
|
-
const copy = await
|
|
4621
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat?.id, dependencies.logger);
|
|
4370
4622
|
const reply = buildTelegramStaticReply(presentStartMarkdownMessage(copy));
|
|
4371
4623
|
try {
|
|
4372
4624
|
await ctx.reply(reply.preferred.text, reply.preferred.options);
|
|
@@ -4404,7 +4656,7 @@ async function handleAgentsCallback(ctx, dependencies) {
|
|
|
4404
4656
|
if (!data.startsWith("agents:")) return;
|
|
4405
4657
|
await ctx.answerCallbackQuery();
|
|
4406
4658
|
if (!ctx.chat) return;
|
|
4407
|
-
const copy = await
|
|
4659
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4408
4660
|
try {
|
|
4409
4661
|
if (data.startsWith(AGENTS_PAGE_PREFIX)) {
|
|
4410
4662
|
const requestedPage = Number(data.slice(12));
|
|
@@ -4448,7 +4700,7 @@ async function handleModelsCallback(ctx, dependencies) {
|
|
|
4448
4700
|
if (!data.startsWith("model:")) return;
|
|
4449
4701
|
await ctx.answerCallbackQuery();
|
|
4450
4702
|
if (!ctx.chat) return;
|
|
4451
|
-
const copy = await
|
|
4703
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4452
4704
|
try {
|
|
4453
4705
|
if (data.startsWith(MODEL_PAGE_PREFIX)) {
|
|
4454
4706
|
const requestedPage = Number(data.slice(11));
|
|
@@ -4527,7 +4779,7 @@ async function handleSessionsCallback(ctx, dependencies) {
|
|
|
4527
4779
|
if (!data.startsWith("sessions:")) return;
|
|
4528
4780
|
await ctx.answerCallbackQuery();
|
|
4529
4781
|
if (!ctx.chat) return;
|
|
4530
|
-
const copy = await
|
|
4782
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4531
4783
|
try {
|
|
4532
4784
|
if (data.startsWith(SESSIONS_PAGE_PREFIX)) {
|
|
4533
4785
|
const requestedPage = Number(data.slice(14));
|
|
@@ -4608,10 +4860,10 @@ async function handleLanguageCallback(ctx, dependencies) {
|
|
|
4608
4860
|
if (!data.startsWith("language:")) return;
|
|
4609
4861
|
await ctx.answerCallbackQuery();
|
|
4610
4862
|
if (!ctx.chat || !ctx.api) return;
|
|
4611
|
-
const currentCopy = await
|
|
4863
|
+
const currentCopy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4612
4864
|
try {
|
|
4613
4865
|
if (!data.startsWith(LANGUAGE_SELECT_PREFIX)) {
|
|
4614
|
-
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) });
|
|
4615
4867
|
return;
|
|
4616
4868
|
}
|
|
4617
4869
|
const selectedLanguage = data.slice(16);
|
|
@@ -4678,7 +4930,7 @@ function parseSessionActionTarget(data, prefix) {
|
|
|
4678
4930
|
//#endregion
|
|
4679
4931
|
//#region src/bot/handlers/prompt.handler.ts
|
|
4680
4932
|
async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
4681
|
-
const copy = await
|
|
4933
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4682
4934
|
const foregroundRequest = dependencies.foregroundSessionTracker.acquire(ctx.chat.id);
|
|
4683
4935
|
if (!foregroundRequest) {
|
|
4684
4936
|
await ctx.reply(copy.status.alreadyProcessing);
|
|
@@ -4815,7 +5067,7 @@ function registerMessageHandler(bot, dependencies) {
|
|
|
4815
5067
|
async function handleVoiceMessage(ctx, dependencies) {
|
|
4816
5068
|
if (!ctx.message.voice) return;
|
|
4817
5069
|
if (await replyIfSessionRenamePending(ctx, dependencies)) return;
|
|
4818
|
-
const copy = await
|
|
5070
|
+
const copy = await getSafeChatCopy(dependencies.sessionRepo, ctx.chat.id, dependencies.logger);
|
|
4819
5071
|
await ctx.reply(copy.errors.voiceUnsupported);
|
|
4820
5072
|
}
|
|
4821
5073
|
function registerVoiceHandler(bot, dependencies) {
|
|
@@ -4860,45 +5112,119 @@ function createLoggingMiddleware(logger) {
|
|
|
4860
5112
|
function registerBot(bot, container, options) {
|
|
4861
5113
|
bot.use(createLoggingMiddleware(container.logger));
|
|
4862
5114
|
bot.use(createAuthMiddleware(options.telegramAllowedChatIds));
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
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);
|
|
4875
5134
|
}
|
|
4876
5135
|
//#endregion
|
|
4877
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
|
+
} };
|
|
4878
5143
|
async function startTelegramBotRuntime(input) {
|
|
4879
|
-
const
|
|
4880
|
-
|
|
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 });
|
|
4881
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
|
+
}
|
|
4882
5189
|
input.container.logger.error({
|
|
4883
|
-
|
|
4884
|
-
|
|
5190
|
+
...metadata,
|
|
5191
|
+
error: error.error
|
|
4885
5192
|
}, "telegram bot update failed");
|
|
4886
5193
|
});
|
|
4887
|
-
input.container.logger.info("bot starting
|
|
4888
|
-
|
|
4889
|
-
const runner = run(bot);
|
|
5194
|
+
input.container.logger.info({ runtimeKey }, "telegram bot polling starting");
|
|
5195
|
+
const runner = (input.runBot ?? run)(bot, TELEGRAM_RUNNER_OPTIONS);
|
|
4890
5196
|
let stopped = false;
|
|
4891
5197
|
let disposed = false;
|
|
4892
|
-
|
|
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 () => {
|
|
4893
5206
|
if (stopped) return;
|
|
4894
5207
|
stopped = true;
|
|
4895
|
-
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();
|
|
4896
5218
|
};
|
|
4897
5219
|
const dispose = async () => {
|
|
4898
5220
|
if (disposed) return;
|
|
4899
5221
|
disposed = true;
|
|
4900
|
-
|
|
4901
|
-
|
|
5222
|
+
try {
|
|
5223
|
+
await requestStop();
|
|
5224
|
+
await input.container.dispose();
|
|
5225
|
+
} finally {
|
|
5226
|
+
releaseRuntime();
|
|
5227
|
+
}
|
|
4902
5228
|
};
|
|
4903
5229
|
return {
|
|
4904
5230
|
bot,
|
|
@@ -4907,39 +5233,69 @@ async function startTelegramBotRuntime(input) {
|
|
|
4907
5233
|
dispose
|
|
4908
5234
|
};
|
|
4909
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
|
+
}
|
|
4910
5265
|
//#endregion
|
|
4911
5266
|
//#region src/plugin.ts
|
|
4912
|
-
var runtimeState = null;
|
|
4913
5267
|
async function ensureTelegramBotPluginRuntime(options) {
|
|
5268
|
+
const runtimeStateHolder = getTelegramBotPluginRuntimeStateHolder();
|
|
4914
5269
|
const cwd = resolvePluginRuntimeCwd(options.context);
|
|
4915
|
-
if (
|
|
4916
|
-
const activeState =
|
|
4917
|
-
|
|
5270
|
+
if (runtimeStateHolder.state && runtimeStateHolder.state.cwd !== cwd) {
|
|
5271
|
+
const activeState = runtimeStateHolder.state;
|
|
5272
|
+
runtimeStateHolder.state = null;
|
|
4918
5273
|
await disposeTelegramBotPluginRuntimeState(activeState);
|
|
4919
5274
|
}
|
|
4920
|
-
if (!
|
|
5275
|
+
if (!runtimeStateHolder.state) {
|
|
4921
5276
|
const runtimePromise = startPluginRuntime(options, cwd).then((runtime) => {
|
|
4922
|
-
if (
|
|
5277
|
+
if (runtimeStateHolder.state?.runtimePromise === runtimePromise) runtimeStateHolder.state.runtime = runtime;
|
|
4923
5278
|
return runtime;
|
|
4924
5279
|
}).catch((error) => {
|
|
4925
|
-
if (
|
|
5280
|
+
if (runtimeStateHolder.state?.runtimePromise === runtimePromise) runtimeStateHolder.state = null;
|
|
4926
5281
|
throw error;
|
|
4927
5282
|
});
|
|
4928
|
-
|
|
5283
|
+
runtimeStateHolder.state = {
|
|
4929
5284
|
cwd,
|
|
4930
5285
|
runtime: null,
|
|
4931
5286
|
runtimePromise
|
|
4932
5287
|
};
|
|
4933
5288
|
}
|
|
4934
|
-
return
|
|
5289
|
+
return runtimeStateHolder.state.runtimePromise;
|
|
4935
5290
|
}
|
|
4936
5291
|
var TelegramBotPlugin = async (context) => {
|
|
4937
5292
|
return createHooks(await ensureTelegramBotPluginRuntime({ context }));
|
|
4938
5293
|
};
|
|
4939
5294
|
async function resetTelegramBotPluginRuntimeForTests() {
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
5295
|
+
const runtimeStateHolder = getTelegramBotPluginRuntimeStateHolder();
|
|
5296
|
+
if (!runtimeStateHolder.state) return;
|
|
5297
|
+
const activeState = runtimeStateHolder.state;
|
|
5298
|
+
runtimeStateHolder.state = null;
|
|
4943
5299
|
await disposeTelegramBotPluginRuntimeState(activeState);
|
|
4944
5300
|
}
|
|
4945
5301
|
async function startPluginRuntime(options, cwd) {
|
|
@@ -4992,6 +5348,11 @@ function createHooks(runtime) {
|
|
|
4992
5348
|
}
|
|
4993
5349
|
};
|
|
4994
5350
|
}
|
|
5351
|
+
function getTelegramBotPluginRuntimeStateHolder() {
|
|
5352
|
+
const globalScope = globalThis;
|
|
5353
|
+
globalScope.__opencodeTbotPluginRuntimeState__ ??= { state: null };
|
|
5354
|
+
return globalScope.__opencodeTbotPluginRuntimeState__;
|
|
5355
|
+
}
|
|
4995
5356
|
//#endregion
|
|
4996
5357
|
export { TelegramBotPlugin, TelegramBotPlugin as default, ensureTelegramBotPluginRuntime, resetTelegramBotPluginRuntimeForTests };
|
|
4997
5358
|
|