@llblab/pi-telegram 0.52.2 → 0.53.0
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/AGENTS.md +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
|
@@ -25,8 +25,8 @@ import {
|
|
|
25
25
|
TELEGRAM_MESSAGE_PROMPT_SNIPPET,
|
|
26
26
|
} from "./prompts.ts";
|
|
27
27
|
import {
|
|
28
|
-
withTelegramReplyParameters,
|
|
29
28
|
normalizeTelegramNativeMarkdown,
|
|
29
|
+
withTelegramReplyParameters,
|
|
30
30
|
} from "./replies.ts";
|
|
31
31
|
import {
|
|
32
32
|
getTelegramTargetThreadParams,
|
|
@@ -37,6 +37,7 @@ const MAX_ATTACHMENTS_PER_TURN = 10;
|
|
|
37
37
|
|
|
38
38
|
export const TELEGRAM_OUTBOUND_ATTACHMENT_DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
|
|
39
39
|
|
|
40
|
+
// Deliberately local: outbound attachment delivery stays decoupled from Bot API helpers.
|
|
40
41
|
export function getTelegramOutboundAttachmentByteLimitFromEnv(
|
|
41
42
|
env: NodeJS.ProcessEnv,
|
|
42
43
|
names: string[],
|
|
@@ -86,12 +87,10 @@ export interface TelegramOutboundMessagePlan {
|
|
|
86
87
|
replyMarkup?: unknown;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
export interface
|
|
90
|
-
getDefaultChatId
|
|
90
|
+
export interface TelegramOutboundMessageDeliveryPorts {
|
|
91
|
+
getDefaultChatId?: () => number | undefined;
|
|
91
92
|
getDefaultTarget?: () => TelegramTarget | undefined;
|
|
92
|
-
getActiveTurn?: () =>
|
|
93
|
-
| { chatId: number; target?: TelegramTarget }
|
|
94
|
-
| undefined;
|
|
93
|
+
getActiveTurn?: () => { chatId: number; target?: TelegramTarget } | undefined;
|
|
95
94
|
resolveAgentTarget?: (
|
|
96
95
|
selector: TelegramBusAgentTargetSelector,
|
|
97
96
|
) => Promise<TelegramTarget & { threadId: number }>;
|
|
@@ -116,6 +115,13 @@ export interface TelegramOutboundMessageToolRegistrationDeps extends TelegramOut
|
|
|
116
115
|
) => Promise<number | undefined>;
|
|
117
116
|
}
|
|
118
117
|
|
|
118
|
+
export interface TelegramOutboundMessageToolRegistrationDeps
|
|
119
|
+
extends
|
|
120
|
+
TelegramOutboundMessageDeliveryPorts,
|
|
121
|
+
TelegramOutboundAttachmentRuntimeEventRecorderPort {
|
|
122
|
+
getDefaultChatId: () => number | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
119
125
|
export interface TelegramQueuedOutboundAttachmentView {
|
|
120
126
|
path: string;
|
|
121
127
|
fileName: string;
|
|
@@ -136,7 +142,9 @@ class TelegramRichAttachmentCommitUnknownError extends Error {
|
|
|
136
142
|
readonly kind = "commit-unknown" as const;
|
|
137
143
|
|
|
138
144
|
constructor(cause: unknown) {
|
|
139
|
-
super(
|
|
145
|
+
super(
|
|
146
|
+
"Telegram Rich media upload may have committed without a message id.",
|
|
147
|
+
);
|
|
140
148
|
this.name = "TelegramRichAttachmentCommitUnknownError";
|
|
141
149
|
this.cause = cause;
|
|
142
150
|
}
|
|
@@ -180,15 +188,16 @@ export function planTelegramRichOutboundAttachment(options: {
|
|
|
180
188
|
if (options.turn.queuedAttachments.length !== 1) return undefined;
|
|
181
189
|
const attachment = options.turn.queuedAttachments[0]!;
|
|
182
190
|
const normalizedPath = attachment.path.toLowerCase();
|
|
183
|
-
const mediaType =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
const mediaType =
|
|
192
|
+
normalizedPath.endsWith(".jpg") ||
|
|
193
|
+
normalizedPath.endsWith(".jpeg") ||
|
|
194
|
+
normalizedPath.endsWith(".png")
|
|
195
|
+
? "photo"
|
|
196
|
+
: normalizedPath.endsWith(".mp4")
|
|
197
|
+
? "video"
|
|
198
|
+
: normalizedPath.endsWith(".mp3")
|
|
199
|
+
? "audio"
|
|
200
|
+
: undefined;
|
|
192
201
|
if (!mediaType) return undefined;
|
|
193
202
|
const mediaId = "artifact";
|
|
194
203
|
const richMessage = {
|
|
@@ -237,18 +246,27 @@ export function createTelegramRichOutboundAttachmentSender(
|
|
|
237
246
|
if (!plan) return false;
|
|
238
247
|
try {
|
|
239
248
|
const result = await withTelegramReplyParameters(
|
|
240
|
-
turn.chatId,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
turn.chatId,
|
|
250
|
+
turn.replyToMessageId,
|
|
251
|
+
turn.target,
|
|
252
|
+
(replyParameters) =>
|
|
253
|
+
deps.sendMultipart(
|
|
254
|
+
plan.method,
|
|
255
|
+
{
|
|
256
|
+
...plan.fields,
|
|
257
|
+
...(replyParameters
|
|
258
|
+
? { reply_parameters: JSON.stringify(replyParameters) }
|
|
259
|
+
: {}),
|
|
260
|
+
},
|
|
261
|
+
plan.fileField,
|
|
262
|
+
plan.filePath,
|
|
263
|
+
plan.fileName,
|
|
264
|
+
),
|
|
248
265
|
);
|
|
249
266
|
const messageId =
|
|
250
|
-
result &&
|
|
251
|
-
|
|
267
|
+
result &&
|
|
268
|
+
typeof result === "object" &&
|
|
269
|
+
Number.isInteger((result as { message_id?: unknown }).message_id)
|
|
252
270
|
? (result as { message_id: number }).message_id
|
|
253
271
|
: undefined;
|
|
254
272
|
if (messageId === undefined) {
|
|
@@ -361,8 +379,22 @@ function formatTelegramOutboundAttachmentToolResultText(
|
|
|
361
379
|
return ["", `${verb} ${count} Telegram attachment(s).`].join("\n");
|
|
362
380
|
}
|
|
363
381
|
|
|
364
|
-
function
|
|
365
|
-
|
|
382
|
+
function buildTelegramOutboundMessageToolResult(
|
|
383
|
+
chatId: number | string,
|
|
384
|
+
messageId: number | undefined,
|
|
385
|
+
): {
|
|
386
|
+
content: Array<{ type: "text"; text: string }>;
|
|
387
|
+
details: { chatId: number | string; messageId?: number };
|
|
388
|
+
} {
|
|
389
|
+
return {
|
|
390
|
+
content: [
|
|
391
|
+
{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: ["", `Sent Telegram message to ${chatId}.`].join("\n"),
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
details: { chatId, messageId },
|
|
397
|
+
};
|
|
366
398
|
}
|
|
367
399
|
|
|
368
400
|
function getTelegramMultipartTargetFields(
|
|
@@ -450,7 +482,9 @@ async function buildTelegramOutboundAttachmentViews(options: {
|
|
|
450
482
|
|
|
451
483
|
function formatTelegramOutboundToolError(error: unknown): Error {
|
|
452
484
|
const message = error instanceof Error ? error.message : String(error);
|
|
453
|
-
return new Error(
|
|
485
|
+
return new Error(
|
|
486
|
+
`\n${message.replace(/^\n+/u, "") || "Telegram outbound operation failed."}`,
|
|
487
|
+
);
|
|
454
488
|
}
|
|
455
489
|
|
|
456
490
|
export function registerTelegramOutboundAttachmentTool(
|
|
@@ -540,19 +574,25 @@ export function registerTelegramOutboundMessageTool(
|
|
|
540
574
|
}),
|
|
541
575
|
),
|
|
542
576
|
chat_id: Type.Optional(
|
|
543
|
-
Type.Union(
|
|
544
|
-
Type.Number(),
|
|
545
|
-
|
|
546
|
-
|
|
577
|
+
Type.Union(
|
|
578
|
+
[Type.Number(), Type.String({ pattern: "^@[A-Za-z0-9_]{5,32}$" })],
|
|
579
|
+
{
|
|
580
|
+
description:
|
|
581
|
+
"Optional exact Telegram chat id or public channel @username",
|
|
582
|
+
},
|
|
583
|
+
),
|
|
547
584
|
),
|
|
548
585
|
thread_id: Type.Optional(
|
|
549
586
|
Type.Number({
|
|
550
587
|
description: "Optional Telegram topic thread id with chat_id",
|
|
551
588
|
}),
|
|
552
589
|
),
|
|
553
|
-
channel: Type.Optional(
|
|
554
|
-
|
|
555
|
-
|
|
590
|
+
channel: Type.Optional(
|
|
591
|
+
Type.Boolean({
|
|
592
|
+
description:
|
|
593
|
+
"Set true only when a numeric chat_id is explicitly intended as a channel",
|
|
594
|
+
}),
|
|
595
|
+
),
|
|
556
596
|
thread: Type.Optional(
|
|
557
597
|
Type.Union([Type.String(), Type.Number()], {
|
|
558
598
|
description:
|
|
@@ -582,12 +622,17 @@ export function registerTelegramOutboundMessageTool(
|
|
|
582
622
|
sendChannelMediaMessage: deps.sendChannelMediaMessage,
|
|
583
623
|
});
|
|
584
624
|
} catch (error) {
|
|
585
|
-
const isChannelTarget =
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
625
|
+
const isChannelTarget =
|
|
626
|
+
typeof params.chat_id === "string" || params.channel === true;
|
|
627
|
+
const reportableError =
|
|
628
|
+
isChannelTarget && !isTelegramChannelPostValidationError(error)
|
|
629
|
+
? new Error(
|
|
630
|
+
"Telegram channel publication failed; inspect the retained local record before retrying.",
|
|
631
|
+
)
|
|
632
|
+
: error;
|
|
633
|
+
deps.recordRuntimeEvent?.("message", reportableError, {
|
|
634
|
+
phase: "direct",
|
|
635
|
+
});
|
|
591
636
|
throw formatTelegramOutboundToolError(reportableError);
|
|
592
637
|
}
|
|
593
638
|
},
|
|
@@ -702,7 +747,9 @@ export async function deliverTelegramGuestCachedAttachment(options: {
|
|
|
702
747
|
deleteMessage: (chatId: number, messageId: number) => Promise<void>;
|
|
703
748
|
recordRuntimeEvent?: TelegramOutboundAttachmentRuntimeEventRecorderPort["recordRuntimeEvent"];
|
|
704
749
|
}): Promise<void> {
|
|
705
|
-
const transport = getTelegramGuestAttachmentTransport(
|
|
750
|
+
const transport = getTelegramGuestAttachmentTransport(
|
|
751
|
+
options.attachment.path,
|
|
752
|
+
);
|
|
706
753
|
let stagingMessageId: number | undefined;
|
|
707
754
|
let answerAttempted = false;
|
|
708
755
|
try {
|
|
@@ -725,7 +772,8 @@ export async function deliverTelegramGuestCachedAttachment(options: {
|
|
|
725
772
|
const photo = [...(message.photo ?? [])]
|
|
726
773
|
.sort((left, right) => (left.file_size ?? 0) - (right.file_size ?? 0))
|
|
727
774
|
.at(-1);
|
|
728
|
-
if (!photo?.file_id)
|
|
775
|
+
if (!photo?.file_id)
|
|
776
|
+
throw new Error("Guest staging upload returned no photo file_id");
|
|
729
777
|
result = {
|
|
730
778
|
type: "photo",
|
|
731
779
|
id: "attachment-1",
|
|
@@ -765,11 +813,7 @@ export async function deliverTelegramGuestCachedAttachment(options: {
|
|
|
765
813
|
answerAttempted = true;
|
|
766
814
|
await options.answerGuestQuery(options.guestQueryId, result);
|
|
767
815
|
} catch (error) {
|
|
768
|
-
if (
|
|
769
|
-
!answerAttempted &&
|
|
770
|
-
options.answerGuestText &&
|
|
771
|
-
options.fallbackText
|
|
772
|
-
) {
|
|
816
|
+
if (!answerAttempted && options.answerGuestText && options.fallbackText) {
|
|
773
817
|
answerAttempted = true;
|
|
774
818
|
await options.answerGuestText(options.guestQueryId, options.fallbackText);
|
|
775
819
|
} else {
|
|
@@ -790,54 +834,39 @@ export async function deliverTelegramGuestCachedAttachment(options: {
|
|
|
790
834
|
}
|
|
791
835
|
}
|
|
792
836
|
|
|
793
|
-
export async function sendTelegramOutboundMessage(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
| { chatId: number; target?: TelegramTarget }
|
|
806
|
-
| undefined;
|
|
807
|
-
resolveAgentTarget?: (
|
|
808
|
-
selector: TelegramBusAgentTargetSelector,
|
|
809
|
-
) => Promise<TelegramTarget & { threadId: number }>;
|
|
810
|
-
routeAgentMessage?: (message: TelegramBusAgentMessage) => Promise<void>;
|
|
811
|
-
canSendDirect: () => boolean;
|
|
812
|
-
planMessage: (markdown: string) => TelegramOutboundMessagePlan;
|
|
813
|
-
sendMarkdownMessage: (
|
|
814
|
-
chatId: number,
|
|
815
|
-
markdown: string,
|
|
816
|
-
options?: { replyMarkup?: unknown; target?: TelegramTarget },
|
|
817
|
-
) => Promise<number | undefined>;
|
|
818
|
-
sendChannelMarkdownMessage?: (
|
|
819
|
-
channel: number | string,
|
|
820
|
-
markdown: string,
|
|
821
|
-
options: { operationId: string; replyMarkup?: unknown },
|
|
822
|
-
) => Promise<number | undefined>;
|
|
823
|
-
sendChannelMediaMessage?: (
|
|
824
|
-
channel: number | string,
|
|
825
|
-
mediaPath: string,
|
|
826
|
-
markdown: string,
|
|
827
|
-
options: { operationId: string; replyMarkup?: unknown },
|
|
828
|
-
) => Promise<number | undefined>;
|
|
829
|
-
}): Promise<{
|
|
837
|
+
export async function sendTelegramOutboundMessage(
|
|
838
|
+
options: TelegramOutboundMessageDeliveryPorts & {
|
|
839
|
+
text: string;
|
|
840
|
+
media?: string;
|
|
841
|
+
operationId?: string;
|
|
842
|
+
channel?: boolean;
|
|
843
|
+
chatId?: number | string;
|
|
844
|
+
threadId?: number;
|
|
845
|
+
agentThread?: string | number;
|
|
846
|
+
target?: TelegramTarget;
|
|
847
|
+
},
|
|
848
|
+
): Promise<{
|
|
830
849
|
content: Array<{ type: "text"; text: string }>;
|
|
831
850
|
details: { chatId: number | string; messageId?: number };
|
|
832
851
|
}> {
|
|
833
852
|
assertTelegramDirectDeliveryAllowed(options.canSendDirect);
|
|
834
853
|
const activeTurn = options.getActiveTurn?.();
|
|
835
854
|
if (typeof options.chatId === "string" || options.channel === true) {
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
options.
|
|
839
|
-
|
|
840
|
-
|
|
855
|
+
if (
|
|
856
|
+
!(
|
|
857
|
+
(typeof options.chatId === "string" &&
|
|
858
|
+
/^@[A-Za-z0-9_]{5,32}$/u.test(options.chatId)) ||
|
|
859
|
+
(typeof options.chatId === "number" &&
|
|
860
|
+
Number.isSafeInteger(options.chatId) &&
|
|
861
|
+
options.chatId < 0)
|
|
862
|
+
) ||
|
|
863
|
+
options.threadId !== undefined ||
|
|
864
|
+
options.target !== undefined ||
|
|
865
|
+
options.agentThread !== undefined
|
|
866
|
+
) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
"Telegram channel delivery requires one exact @username or negative numeric channel ID without a thread target.",
|
|
869
|
+
);
|
|
841
870
|
}
|
|
842
871
|
const plan = options.planMessage(options.text);
|
|
843
872
|
if (options.media !== undefined) {
|
|
@@ -847,21 +876,24 @@ export async function sendTelegramOutboundMessage(options: {
|
|
|
847
876
|
);
|
|
848
877
|
}
|
|
849
878
|
const messageId = await options.sendChannelMediaMessage(
|
|
850
|
-
options.chatId,
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
879
|
+
options.chatId,
|
|
880
|
+
options.media,
|
|
881
|
+
plan.markdown,
|
|
882
|
+
{ operationId: options.operationId, replyMarkup: plan.replyMarkup },
|
|
883
|
+
);
|
|
884
|
+
return buildTelegramOutboundMessageToolResult(options.chatId, messageId);
|
|
855
885
|
}
|
|
856
886
|
if (!options.sendChannelMarkdownMessage || !options.operationId) {
|
|
857
|
-
throw new Error(
|
|
887
|
+
throw new Error(
|
|
888
|
+
"Telegram channel delivery requires direct leader transport ownership and operation identity.",
|
|
889
|
+
);
|
|
858
890
|
}
|
|
859
891
|
const messageId = await options.sendChannelMarkdownMessage(
|
|
860
|
-
options.chatId,
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
892
|
+
options.chatId,
|
|
893
|
+
plan.markdown,
|
|
894
|
+
{ operationId: options.operationId, replyMarkup: plan.replyMarkup },
|
|
895
|
+
);
|
|
896
|
+
return buildTelegramOutboundMessageToolResult(options.chatId, messageId);
|
|
865
897
|
}
|
|
866
898
|
if (options.media !== undefined) {
|
|
867
899
|
throw new TelegramChannelPostValidationError(
|
|
@@ -876,7 +908,7 @@ export async function sendTelegramOutboundMessage(options: {
|
|
|
876
908
|
: activeTurn &&
|
|
877
909
|
options.resolveAgentTarget &&
|
|
878
910
|
options.routeAgentMessage &&
|
|
879
|
-
(
|
|
911
|
+
(options.target?.threadId !== undefined ||
|
|
880
912
|
(options.chatId !== undefined && options.threadId !== undefined))
|
|
881
913
|
? {
|
|
882
914
|
chatId: options.target?.chatId ?? options.chatId,
|
|
@@ -930,15 +962,7 @@ export async function sendTelegramOutboundMessage(options: {
|
|
|
930
962
|
text: options.text,
|
|
931
963
|
});
|
|
932
964
|
}
|
|
933
|
-
return
|
|
934
|
-
content: [
|
|
935
|
-
{
|
|
936
|
-
type: "text",
|
|
937
|
-
text: formatTelegramOutboundMessageToolResultText(chatId),
|
|
938
|
-
},
|
|
939
|
-
],
|
|
940
|
-
details: { chatId, messageId },
|
|
941
|
-
};
|
|
965
|
+
return buildTelegramOutboundMessageToolResult(chatId, messageId);
|
|
942
966
|
}
|
|
943
967
|
|
|
944
968
|
export async function sendTelegramOutboundFiles(options: {
|
|
@@ -1017,7 +1041,9 @@ export function createTelegramQueuedOutboundAttachmentSender(
|
|
|
1017
1041
|
): Promise<void> => {
|
|
1018
1042
|
await sendQueuedTelegramOutboundAttachments(turn, {
|
|
1019
1043
|
...deps,
|
|
1020
|
-
isDeliveryActive: () =>
|
|
1044
|
+
isDeliveryActive: () =>
|
|
1045
|
+
deps.isDeliveryActive?.() !== false &&
|
|
1046
|
+
options?.isDeliveryActive?.() !== false,
|
|
1021
1047
|
maxAttachmentSizeBytes:
|
|
1022
1048
|
deps.maxAttachmentSizeBytes ?? TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES,
|
|
1023
1049
|
});
|
|
@@ -1047,18 +1073,23 @@ export async function sendQueuedTelegramOutboundAttachments(
|
|
|
1047
1073
|
const method = isPhoto ? "sendPhoto" : "sendDocument";
|
|
1048
1074
|
const fieldName = isPhoto ? "photo" : "document";
|
|
1049
1075
|
await withTelegramReplyParameters(
|
|
1050
|
-
turn.chatId,
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1076
|
+
turn.chatId,
|
|
1077
|
+
turn.replyToMessageId,
|
|
1078
|
+
turn.target,
|
|
1079
|
+
(replyParameters) =>
|
|
1080
|
+
deps.sendMultipart(
|
|
1081
|
+
method,
|
|
1082
|
+
{
|
|
1083
|
+
chat_id: String(turn.chatId),
|
|
1084
|
+
...(replyParameters
|
|
1085
|
+
? { reply_parameters: JSON.stringify(replyParameters) }
|
|
1086
|
+
: {}),
|
|
1087
|
+
...getTelegramMultipartTargetFields(turn.target),
|
|
1088
|
+
},
|
|
1089
|
+
fieldName,
|
|
1090
|
+
attachment.path,
|
|
1091
|
+
attachment.fileName,
|
|
1092
|
+
),
|
|
1062
1093
|
);
|
|
1063
1094
|
} catch (error) {
|
|
1064
1095
|
if (deps.isDeliveryActive?.() === false) return;
|
package/lib/outbound-buttons.ts
CHANGED
|
@@ -169,24 +169,26 @@ export function createTelegramButtonActionStore(
|
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
const DEFAULT_TELEGRAM_BUTTON_REPLY_MARKDOWN =
|
|
173
|
-
"☑️ **Choose an option:**";
|
|
172
|
+
const DEFAULT_TELEGRAM_BUTTON_REPLY_MARKDOWN = "☑️ **Choose an option:**";
|
|
174
173
|
|
|
175
174
|
function escapeTelegramRichButtonText(text: string): string {
|
|
176
|
-
return text.replace(
|
|
177
|
-
|
|
175
|
+
return text.replace(
|
|
176
|
+
/[&<>"'`\\*_\[\]{}$~\r\n]/g,
|
|
177
|
+
(character) => `&#${character.charCodeAt(0)};`,
|
|
178
178
|
);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
function renderTelegramRichButtonRow(
|
|
182
182
|
row: TelegramOutboundButtonMarkup["inline_keyboard"][number],
|
|
183
183
|
): string {
|
|
184
|
-
return `<tg-button-row>${row
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
return `<tg-button-row>${row
|
|
185
|
+
.map((button) => {
|
|
186
|
+
const attributes = button.disabled
|
|
187
|
+
? 'type="disabled"'
|
|
188
|
+
: `type="callback_data" data="${escapeTelegramRichButtonText(button.callback_data)}"`;
|
|
189
|
+
return `<tg-button ${attributes}>${escapeTelegramRichButtonText(button.text)}</tg-button>`;
|
|
190
|
+
})
|
|
191
|
+
.join("")}</tg-button-row>`;
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
export function planTelegramButtonReply(
|
|
@@ -202,40 +204,60 @@ export function planTelegramButtonReply(
|
|
|
202
204
|
payloadRows: Record<string, unknown>[][],
|
|
203
205
|
rich: boolean,
|
|
204
206
|
): TelegramOutboundButtonMarkup["inline_keyboard"] | undefined => {
|
|
205
|
-
const actions = payloadRows.map((row) =>
|
|
207
|
+
const actions = payloadRows.map((row) =>
|
|
208
|
+
row.map(parseTelegramButtonAction),
|
|
209
|
+
);
|
|
206
210
|
if (actions.some((row) => row.some((action) => !action))) return undefined;
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
211
|
+
if (
|
|
212
|
+
rich &&
|
|
213
|
+
actions.some((row) => {
|
|
214
|
+
const projected = row.map((action) =>
|
|
215
|
+
action!.disabled
|
|
216
|
+
? { text: action!.text || "\u00a0", disabled: {} }
|
|
217
|
+
: { text: action!.text, callback_data: "x".repeat(64) },
|
|
218
|
+
);
|
|
219
|
+
return (
|
|
220
|
+
row.length > 8 ||
|
|
221
|
+
renderTelegramRichButtonRow(projected).length > 32768
|
|
222
|
+
);
|
|
223
|
+
})
|
|
224
|
+
)
|
|
225
|
+
return undefined;
|
|
226
|
+
return actions.map((row) =>
|
|
227
|
+
row.map((action) =>
|
|
228
|
+
action!.disabled
|
|
229
|
+
? { text: action!.text || "\u00a0", disabled: {} }
|
|
230
|
+
: {
|
|
231
|
+
text: action!.text,
|
|
232
|
+
callback_data: deps.registerAction({
|
|
233
|
+
...action!,
|
|
234
|
+
...(deps.binding ? { binding: deps.binding } : {}),
|
|
235
|
+
}),
|
|
236
|
+
},
|
|
237
|
+
),
|
|
238
|
+
);
|
|
222
239
|
};
|
|
223
|
-
const withRichButtons = replaceTelegramButtonFences(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
const withRichButtons = replaceTelegramButtonFences(
|
|
241
|
+
markdown,
|
|
242
|
+
(payload, closed) => {
|
|
243
|
+
if (!closed) return "";
|
|
244
|
+
const payloadRows = parseTelegramButtonPayloadRows(payload);
|
|
245
|
+
if (!payloadRows) return "";
|
|
246
|
+
const rich = deps.rendering !== "html";
|
|
247
|
+
const rows = buildRows(payloadRows, rich);
|
|
248
|
+
if (!rows) return "";
|
|
249
|
+
if (!rich) {
|
|
250
|
+
keyboard.push(...rows);
|
|
251
|
+
return "";
|
|
252
|
+
}
|
|
253
|
+
return `\n${rows.map(renderTelegramRichButtonRow).join("\n\n")}\n`;
|
|
254
|
+
},
|
|
255
|
+
);
|
|
236
256
|
const stripped = replaceTopLevelHtmlComments(withRichButtons, (comment) => {
|
|
237
257
|
const command = "telegram_button";
|
|
238
|
-
const normalizedContent = comment.content
|
|
258
|
+
const normalizedContent = comment.content
|
|
259
|
+
.replace(/^\s+/, "")
|
|
260
|
+
.replace(/^!/, "");
|
|
239
261
|
if (!normalizedContent.startsWith(command)) return comment.raw;
|
|
240
262
|
const payloadRows = parseTelegramActionPayloadRows(comment, command);
|
|
241
263
|
if (!payloadRows) return "";
|
|
@@ -297,7 +319,8 @@ export function markTelegramButtonSelected(
|
|
|
297
319
|
let matched = false;
|
|
298
320
|
const inlineKeyboard = replyMarkup.inline_keyboard.map((row) =>
|
|
299
321
|
row.map((button) => {
|
|
300
|
-
if (button.disabled || button.callback_data !== callbackData)
|
|
322
|
+
if (button.disabled || button.callback_data !== callbackData)
|
|
323
|
+
return { ...button };
|
|
301
324
|
matched = true;
|
|
302
325
|
return { ...button, style: selectedStyle };
|
|
303
326
|
}),
|
|
@@ -314,21 +337,21 @@ export async function handleTelegramButtonCallbackQuery<TContext = unknown>(
|
|
|
314
337
|
|
|
315
338
|
if (!action) {
|
|
316
339
|
if (query.data?.startsWith(`${TELEGRAM_BUTTON_CALLBACK_PREFIX}:`)) {
|
|
317
|
-
await deps.answerCallbackQuery(query.id, "Button action expired
|
|
340
|
+
await deps.answerCallbackQuery(query.id, "Button action expired");
|
|
318
341
|
return true;
|
|
319
342
|
}
|
|
320
343
|
return false;
|
|
321
344
|
}
|
|
322
345
|
|
|
323
346
|
if (action.disabled) {
|
|
324
|
-
await deps.answerCallbackQuery(query.id, "Button action unavailable
|
|
347
|
+
await deps.answerCallbackQuery(query.id, "Button action unavailable");
|
|
325
348
|
return true;
|
|
326
349
|
}
|
|
327
350
|
|
|
328
351
|
const chatId = query.message?.chat?.id;
|
|
329
352
|
const messageId = query.message?.message_id;
|
|
330
353
|
if (typeof chatId !== "number" || typeof messageId !== "number") {
|
|
331
|
-
await deps.answerCallbackQuery(query.id, "Button action expired
|
|
354
|
+
await deps.answerCallbackQuery(query.id, "Button action expired");
|
|
332
355
|
return true;
|
|
333
356
|
}
|
|
334
357
|
|
|
@@ -344,24 +367,28 @@ export async function handleTelegramButtonCallbackQuery<TContext = unknown>(
|
|
|
344
367
|
);
|
|
345
368
|
if (selectedMarkup && deps.editMessageReplyMarkup) {
|
|
346
369
|
try {
|
|
347
|
-
await deps.editMessageReplyMarkup(
|
|
370
|
+
await deps.editMessageReplyMarkup(
|
|
371
|
+
chatId,
|
|
372
|
+
messageId,
|
|
373
|
+
selectedMarkup,
|
|
374
|
+
);
|
|
348
375
|
} catch {
|
|
349
376
|
// The action already succeeded; old-surface styling is best-effort.
|
|
350
377
|
}
|
|
351
378
|
}
|
|
352
379
|
}
|
|
353
|
-
await deps.answerCallbackQuery(query.id, "Done
|
|
380
|
+
await deps.answerCallbackQuery(query.id, "Done");
|
|
354
381
|
return true;
|
|
355
382
|
}
|
|
356
383
|
} catch (error) {
|
|
357
|
-
await deps.answerCallbackQuery(query.id, "Generative App action failed
|
|
384
|
+
await deps.answerCallbackQuery(query.id, "Generative App action failed");
|
|
358
385
|
throw error;
|
|
359
386
|
}
|
|
360
387
|
}
|
|
361
388
|
|
|
362
389
|
const enqueued = deps.enqueueButtonPrompt(query, action, ctx);
|
|
363
390
|
if (enqueued === false) {
|
|
364
|
-
await deps.answerCallbackQuery(query.id, "Already queued
|
|
391
|
+
await deps.answerCallbackQuery(query.id, "Already queued");
|
|
365
392
|
return true;
|
|
366
393
|
}
|
|
367
394
|
const selectedMarkup =
|
|
@@ -375,6 +402,6 @@ export async function handleTelegramButtonCallbackQuery<TContext = unknown>(
|
|
|
375
402
|
if (selectedMarkup && deps.editMessageReplyMarkup) {
|
|
376
403
|
await deps.editMessageReplyMarkup(chatId, messageId, selectedMarkup);
|
|
377
404
|
}
|
|
378
|
-
await deps.answerCallbackQuery(query.id, "Queued
|
|
405
|
+
await deps.answerCallbackQuery(query.id, "Queued");
|
|
379
406
|
return true;
|
|
380
407
|
}
|