@llblab/pi-telegram 0.24.10 → 0.25.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 +5 -3
- package/BACKLOG.md +22 -1
- package/CHANGELOG.md +9 -1
- package/README.md +7 -6
- package/docs/activity.md +9 -7
- package/docs/architecture.md +4 -2
- package/docs/multi-instance-bus.md +11 -8
- package/docs/outbound.md +40 -25
- package/docs/public-api.md +4 -2
- package/docs/sections.md +4 -3
- package/docs/ui-style.md +2 -0
- package/index.ts +39 -20
- package/lib/activity-verbosity.ts +434 -0
- package/lib/bindings.ts +13 -2
- package/lib/bus-api.ts +18 -0
- package/lib/bus.ts +14 -0
- package/lib/config.ts +43 -1
- package/lib/menu-settings.ts +111 -45
- package/lib/outbound-buttons.ts +30 -61
- package/lib/outbound-markup.ts +65 -162
- package/lib/polling.ts +7 -17
- package/lib/prompts.ts +4 -4
- package/lib/queue.ts +4 -0
- package/lib/telegram-api.ts +44 -1
- package/package.json +1 -1
package/lib/outbound-markup.ts
CHANGED
|
@@ -16,16 +16,6 @@ interface TelegramTopLevelFenceState {
|
|
|
16
16
|
length: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function isTelegramActionCommentContent(content: string): boolean {
|
|
20
|
-
const normalizedContent = content.replace(/^\s+/, "");
|
|
21
|
-
const [head = ""] = normalizedContent.split(/\r?\n/, 1);
|
|
22
|
-
return ["telegram_voice", "telegram_button"].some((command) => {
|
|
23
|
-
if (!head.startsWith(command)) return false;
|
|
24
|
-
const nextChar = head[command.length];
|
|
25
|
-
return nextChar === undefined || /\s|:/.test(nextChar);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
19
|
function getMarkdownLineEnd(markdown: string, offset: number): number {
|
|
30
20
|
const newlineIndex = markdown.indexOf("\n", offset);
|
|
31
21
|
return newlineIndex === -1 ? markdown.length : newlineIndex + 1;
|
|
@@ -64,58 +54,6 @@ function isTopLevelClosingFence(
|
|
|
64
54
|
);
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
function collectPairedTelegramVoiceActionBody(
|
|
68
|
-
markdown: string,
|
|
69
|
-
bodyStart: number,
|
|
70
|
-
commentContent: string,
|
|
71
|
-
): { content: string; end: number } | undefined {
|
|
72
|
-
const normalizedContent = commentContent.trim();
|
|
73
|
-
if (
|
|
74
|
-
!normalizedContent.startsWith("telegram_voice") ||
|
|
75
|
-
!isTelegramActionCommentContent(commentContent)
|
|
76
|
-
) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
let offset = bodyStart;
|
|
80
|
-
while (offset < markdown.length) {
|
|
81
|
-
const lineEnd = getMarkdownLineEnd(markdown, offset);
|
|
82
|
-
const line = getMarkdownLineText(markdown, offset, lineEnd);
|
|
83
|
-
if (line === "<!-- /telegram_voice -->") {
|
|
84
|
-
const body = markdown.slice(bodyStart, offset).trim();
|
|
85
|
-
if (!body) return undefined;
|
|
86
|
-
return {
|
|
87
|
-
content: `${commentContent.trimEnd()}\n${body}`,
|
|
88
|
-
end: lineEnd,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
if (line.startsWith("<!--")) return undefined;
|
|
92
|
-
offset = lineEnd;
|
|
93
|
-
}
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function collectInlineClosedTelegramActionBody(
|
|
98
|
-
markdown: string,
|
|
99
|
-
bodyStart: number,
|
|
100
|
-
commentContent: string,
|
|
101
|
-
): { content: string; end: number } | undefined {
|
|
102
|
-
const bodyLineEnd = getMarkdownLineEnd(markdown, bodyStart);
|
|
103
|
-
const bodyLine = getMarkdownLineText(markdown, bodyStart, bodyLineEnd);
|
|
104
|
-
const closeLineEnd = getMarkdownLineEnd(markdown, bodyLineEnd);
|
|
105
|
-
const closeLine = getMarkdownLineText(markdown, bodyLineEnd, closeLineEnd);
|
|
106
|
-
const hasRecoverableBody =
|
|
107
|
-
isTelegramActionCommentContent(commentContent) &&
|
|
108
|
-
bodyLine.trim() !== "" &&
|
|
109
|
-
!bodyLine.startsWith("<!--") &&
|
|
110
|
-
!bodyLine.startsWith("-->") &&
|
|
111
|
-
closeLine === "-->";
|
|
112
|
-
if (!hasRecoverableBody) return undefined;
|
|
113
|
-
return {
|
|
114
|
-
content: `${commentContent.trimEnd()}\n${bodyLine}`,
|
|
115
|
-
end: bodyLineEnd + 3,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
57
|
export function collectTopLevelHtmlComments(markdown: string): {
|
|
120
58
|
comments: TelegramTopLevelHtmlComment[];
|
|
121
59
|
openCommentStart?: number;
|
|
@@ -140,27 +78,9 @@ export function collectTopLevelHtmlComments(markdown: string): {
|
|
|
140
78
|
if (line.startsWith("<!--")) {
|
|
141
79
|
const closeIndex = markdown.indexOf("-->", offset + 4);
|
|
142
80
|
if (closeIndex === -1) return { comments, openCommentStart: offset };
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const closeColumn = closeIndex - offset;
|
|
147
|
-
const closesOnOpeningLine = closeIndex < lineEnd;
|
|
148
|
-
const hasOnlyWhitespaceAfterClose =
|
|
149
|
-
line.slice(closeColumn + 3).trim() === "";
|
|
150
|
-
const pairedVoiceBody =
|
|
151
|
-
closesOnOpeningLine && hasOnlyWhitespaceAfterClose
|
|
152
|
-
? collectPairedTelegramVoiceActionBody(markdown, lineEnd, content)
|
|
153
|
-
: undefined;
|
|
154
|
-
const inlineBody =
|
|
155
|
-
!pairedVoiceBody && closesOnOpeningLine && hasOnlyWhitespaceAfterClose
|
|
156
|
-
? collectInlineClosedTelegramActionBody(markdown, lineEnd, content)
|
|
157
|
-
: undefined;
|
|
158
|
-
const recoveredBody = pairedVoiceBody ?? inlineBody;
|
|
159
|
-
if (recoveredBody) {
|
|
160
|
-
end = recoveredBody.end;
|
|
161
|
-
raw = markdown.slice(offset, end);
|
|
162
|
-
content = recoveredBody.content;
|
|
163
|
-
}
|
|
81
|
+
const end = closeIndex + 3;
|
|
82
|
+
const raw = markdown.slice(offset, end);
|
|
83
|
+
const content = raw.slice(4, -3);
|
|
164
84
|
comments.push({ raw, content, start: offset, end });
|
|
165
85
|
offset = getMarkdownLineEnd(markdown, end);
|
|
166
86
|
continue;
|
|
@@ -233,18 +153,47 @@ export function parseTopLevelTelegramComment(
|
|
|
233
153
|
};
|
|
234
154
|
}
|
|
235
155
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
): Record<string, string> {
|
|
156
|
+
function parseCanonicalTelegramActionAttributes(
|
|
157
|
+
source: string,
|
|
158
|
+
): Record<string, string> | undefined {
|
|
239
159
|
const attributes: Record<string, string> = {};
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
160
|
+
const pattern = /\s*([A-Za-z_][A-Za-z0-9_-]*)="([^"]*)"/y;
|
|
161
|
+
let offset = 0;
|
|
162
|
+
while (offset < source.length) {
|
|
163
|
+
pattern.lastIndex = offset;
|
|
164
|
+
const match = pattern.exec(source);
|
|
165
|
+
if (!match) return undefined;
|
|
166
|
+
const value = match[2].trim();
|
|
167
|
+
if (value) attributes[match[1]] = value;
|
|
168
|
+
offset = pattern.lastIndex;
|
|
169
|
+
}
|
|
170
|
+
return Object.keys(attributes).length > 0 ? attributes : undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function parseTelegramActionPayload(
|
|
174
|
+
comment: TelegramTopLevelHtmlComment,
|
|
175
|
+
command: string,
|
|
176
|
+
): Record<string, unknown> | undefined {
|
|
177
|
+
const parsed = parseTopLevelTelegramComment(comment, command);
|
|
178
|
+
if (!parsed) return undefined;
|
|
179
|
+
const source = [parsed.head, parsed.body]
|
|
180
|
+
.filter((part): part is string => part !== undefined)
|
|
181
|
+
.join("\n")
|
|
182
|
+
.trim()
|
|
183
|
+
.replace(/^:\s*/, "");
|
|
184
|
+
if (!source) return undefined;
|
|
185
|
+
if (source.startsWith("{")) {
|
|
186
|
+
try {
|
|
187
|
+
const value: unknown = JSON.parse(source);
|
|
188
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
189
|
+
? (value as Record<string, unknown>)
|
|
190
|
+
: undefined;
|
|
191
|
+
} catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
246
194
|
}
|
|
247
|
-
return
|
|
195
|
+
if (parsed.body !== undefined) return undefined;
|
|
196
|
+
return parseCanonicalTelegramActionAttributes(source);
|
|
248
197
|
}
|
|
249
198
|
|
|
250
199
|
export function normalizeMarkdownAfterVoiceExtraction(
|
|
@@ -295,58 +244,14 @@ export interface TelegramVoiceReplyPlan {
|
|
|
295
244
|
rate?: string;
|
|
296
245
|
}
|
|
297
246
|
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
...(attributes.rate ? { rate: attributes.rate } : {}),
|
|
307
|
-
...(attributes.text ? { text: attributes.text } : {}),
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function parseVoiceCommentBody(
|
|
312
|
-
head: string,
|
|
313
|
-
body: string | undefined,
|
|
314
|
-
): {
|
|
315
|
-
attrs: string;
|
|
316
|
-
text: string;
|
|
317
|
-
} {
|
|
318
|
-
const trimmedHead = head.trim();
|
|
319
|
-
if (body !== undefined) {
|
|
320
|
-
return { attrs: trimmedHead.replace(/^:/, "").trim(), text: body.trim() };
|
|
321
|
-
}
|
|
322
|
-
let colonIndex = -1;
|
|
323
|
-
let inQuote = false;
|
|
324
|
-
let quoteChar = "";
|
|
325
|
-
for (let i = 0; i < trimmedHead.length; i++) {
|
|
326
|
-
const char = trimmedHead[i];
|
|
327
|
-
if (inQuote) {
|
|
328
|
-
if (char === quoteChar) inQuote = false;
|
|
329
|
-
} else {
|
|
330
|
-
if (char === '"' || char === "'") {
|
|
331
|
-
inQuote = true;
|
|
332
|
-
quoteChar = char;
|
|
333
|
-
} else if (char === ":") {
|
|
334
|
-
colonIndex = i;
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
if (colonIndex > 0) {
|
|
340
|
-
const attrsPart = trimmedHead.slice(0, colonIndex).trim();
|
|
341
|
-
const textPart = trimmedHead.slice(colonIndex + 1).trim();
|
|
342
|
-
const attrs = parseVoiceReplyAttributes(attrsPart);
|
|
343
|
-
return { attrs: attrsPart, text: textPart || attrs.text || "", ...attrs };
|
|
344
|
-
}
|
|
345
|
-
if (trimmedHead.startsWith(":")) {
|
|
346
|
-
return { attrs: "", text: trimmedHead.slice(1).trim() };
|
|
347
|
-
}
|
|
348
|
-
const attrs = parseVoiceReplyAttributes(trimmedHead);
|
|
349
|
-
return { attrs: trimmedHead, text: attrs.text ?? "" };
|
|
247
|
+
function getTelegramActionString(
|
|
248
|
+
payload: Record<string, unknown>,
|
|
249
|
+
key: string,
|
|
250
|
+
): string | undefined {
|
|
251
|
+
const value = payload[key];
|
|
252
|
+
if (typeof value !== "string") return undefined;
|
|
253
|
+
const trimmed = value.trim();
|
|
254
|
+
return trimmed || undefined;
|
|
350
255
|
}
|
|
351
256
|
|
|
352
257
|
export function planTelegramVoiceReply(
|
|
@@ -356,26 +261,24 @@ export function planTelegramVoiceReply(
|
|
|
356
261
|
let lang: string | undefined;
|
|
357
262
|
let rate: string | undefined;
|
|
358
263
|
const stripped = replaceTopLevelHtmlComments(markdown, (comment) => {
|
|
359
|
-
|
|
360
|
-
if (!command)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const attrs = parseVoiceReplyAttributes(parsed.attrs);
|
|
370
|
-
if (parsed.text) {
|
|
264
|
+
const command = parseTopLevelTelegramComment(comment, "telegram_voice");
|
|
265
|
+
if (!command) return comment.raw;
|
|
266
|
+
const payload = parseTelegramActionPayload(comment, "telegram_voice");
|
|
267
|
+
if (!payload) return "";
|
|
268
|
+
const text =
|
|
269
|
+
getTelegramActionString(payload, "text") ??
|
|
270
|
+
getTelegramActionString(payload, "value");
|
|
271
|
+
const itemLang = getTelegramActionString(payload, "lang");
|
|
272
|
+
const itemRate = getTelegramActionString(payload, "rate");
|
|
273
|
+
if (text) {
|
|
371
274
|
voiceReplies.push({
|
|
372
|
-
text
|
|
373
|
-
...(
|
|
374
|
-
...(
|
|
275
|
+
text,
|
|
276
|
+
...(itemLang ? { lang: itemLang } : {}),
|
|
277
|
+
...(itemRate ? { rate: itemRate } : {}),
|
|
375
278
|
});
|
|
376
279
|
}
|
|
377
|
-
if (
|
|
378
|
-
if (
|
|
280
|
+
if (itemLang) lang = itemLang;
|
|
281
|
+
if (itemRate) rate = itemRate;
|
|
379
282
|
return "";
|
|
380
283
|
});
|
|
381
284
|
const voiceText = voiceReplies
|
package/lib/polling.ts
CHANGED
|
@@ -319,7 +319,6 @@ export interface TelegramThreadCapabilityRuntimeDeps<
|
|
|
319
319
|
TContext,
|
|
320
320
|
> extends TelegramThreadCapabilityReaderDeps {
|
|
321
321
|
topicTargetStore: TelegramThreadCapabilityStore;
|
|
322
|
-
isBusConfigured: () => boolean;
|
|
323
322
|
ownsLock: (ctx: TContext) => boolean;
|
|
324
323
|
getPollingStartedWithTelegramBus: () => boolean;
|
|
325
324
|
setPollingStartedWithTelegramBus: (started: boolean) => void;
|
|
@@ -394,7 +393,6 @@ export interface TelegramThreadAwarePollingDeps<
|
|
|
394
393
|
TContext,
|
|
395
394
|
TOwner,
|
|
396
395
|
> extends TelegramStartupThreadCapabilityProbeDeps {
|
|
397
|
-
isBusConfigured: () => boolean;
|
|
398
396
|
isBusRuntimeEnabled: () => boolean;
|
|
399
397
|
isTopicModeUnavailableError: (error: unknown) => boolean;
|
|
400
398
|
getPollingStartedWithTelegramBus: () => boolean;
|
|
@@ -419,7 +417,6 @@ export interface TelegramThreadCapabilityOrchestrationDeps<
|
|
|
419
417
|
> extends TelegramThreadCapabilityReaderDeps {
|
|
420
418
|
state: TelegramThreadCapabilityStateRuntime;
|
|
421
419
|
topicTargetStore: TelegramThreadCapabilityStore;
|
|
422
|
-
isBusConfigured: () => boolean;
|
|
423
420
|
isBusRuntimeEnabled: () => boolean;
|
|
424
421
|
ownsLock: (ctx: TContext) => boolean;
|
|
425
422
|
startClassicPolling: (ctx: TContext) => MaybePromise<void>;
|
|
@@ -475,7 +472,6 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
|
|
|
475
472
|
getAllowedUserId: deps.getAllowedUserId,
|
|
476
473
|
callApi: deps.callApi,
|
|
477
474
|
topicTargetStore: deps.topicTargetStore,
|
|
478
|
-
isBusConfigured: deps.isBusConfigured,
|
|
479
475
|
ownsLock: deps.ownsLock,
|
|
480
476
|
getPollingStartedWithTelegramBus: deps.state.isBusPollingStarted,
|
|
481
477
|
setPollingStartedWithTelegramBus: deps.state.setBusPollingStarted,
|
|
@@ -498,7 +494,6 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
|
|
|
498
494
|
getAllowedUserId: deps.getAllowedUserId,
|
|
499
495
|
callApi: deps.callApi,
|
|
500
496
|
topicTargetStore: deps.topicTargetStore,
|
|
501
|
-
isBusConfigured: deps.isBusConfigured,
|
|
502
497
|
isBusRuntimeEnabled: deps.isBusRuntimeEnabled,
|
|
503
498
|
isTopicModeUnavailableError: deps.isTopicModeUnavailableError,
|
|
504
499
|
getPollingStartedWithTelegramBus: deps.state.isBusPollingStarted,
|
|
@@ -590,7 +585,6 @@ export async function applyTelegramThreadCapability<TContext>(
|
|
|
590
585
|
deps: TelegramThreadCapabilityRuntimeDeps<TContext>,
|
|
591
586
|
): Promise<void> {
|
|
592
587
|
await deps.topicTargetStore.load();
|
|
593
|
-
if (!deps.isBusConfigured()) return;
|
|
594
588
|
const nowMs = (deps.getNowMs ?? Date.now)();
|
|
595
589
|
const previousBotState = deps.topicTargetStore.getBotState();
|
|
596
590
|
if (!threadModeEnabled) {
|
|
@@ -691,17 +685,14 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
|
|
|
691
685
|
ctx: TContext,
|
|
692
686
|
options?: { forceFreshLeaderThread?: boolean },
|
|
693
687
|
): Promise<void> => {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
} catch (error) {
|
|
701
|
-
deps.recordEvent("bus", error, { phase: "startup-thread-mode-probe" });
|
|
702
|
-
}
|
|
703
|
-
deps.setTopicModeUnavailable(startupThreadCapability !== true);
|
|
688
|
+
await deps.topicTargetStore.load();
|
|
689
|
+
let startupThreadCapability: boolean | undefined;
|
|
690
|
+
try {
|
|
691
|
+
startupThreadCapability = await probeTelegramStartupThreadCapability(deps);
|
|
692
|
+
} catch (error) {
|
|
693
|
+
deps.recordEvent("bus", error, { phase: "startup-thread-mode-probe" });
|
|
704
694
|
}
|
|
695
|
+
deps.setTopicModeUnavailable(startupThreadCapability !== true);
|
|
705
696
|
if (deps.isBusRuntimeEnabled()) {
|
|
706
697
|
deps.setTopicModeUnavailable(false);
|
|
707
698
|
try {
|
|
@@ -886,7 +877,6 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
|
|
|
886
877
|
return {
|
|
887
878
|
start(ctx) {
|
|
888
879
|
stop();
|
|
889
|
-
if (!deps.isBusConfigured()) return;
|
|
890
880
|
interval = setInterval(() => {
|
|
891
881
|
check(ctx);
|
|
892
882
|
}, intervalMs);
|
package/lib/prompts.ts
CHANGED
|
@@ -138,13 +138,13 @@ How to answer Telegram turns:
|
|
|
138
138
|
Assistant-authored Telegram actions:
|
|
139
139
|
- \`telegram_voice\` and \`telegram_button\` are hidden top-level HTML comments, not Pi tools.
|
|
140
140
|
- Put action comments at column zero, outside code, quotes, lists, and indented examples.
|
|
141
|
-
-
|
|
142
|
-
- Keep the complete action
|
|
141
|
+
- Action payloads use either JSON or double-quoted attributes; the colon after the action name is optional and does not affect parsing. Voice accepts equivalent \`text\` or \`value\`: \`<!-- telegram_voice: {"value":"Short summary","lang":"en"} -->\` or \`<!-- telegram_voice text="Short summary" lang="en" -->\`.
|
|
142
|
+
- Keep the complete action in one top-level comment and include non-empty \`text\` or \`value\`; encode line breaks inside JSON strings as \`\\n\`.
|
|
143
143
|
- Keep voice text TTS-friendly; avoid raw Markdown, code, and tables in voice text.
|
|
144
144
|
- Voice delivery generates and attaches OGG automatically; do not also call \`telegram_attach\` for the same audio.
|
|
145
145
|
- Voice reply modes are compact: \`hidden\` emits no automatic context, \`mirror\` emits it for voice/audio input, and \`always\` emits it for every Telegram turn. Explicit \`telegram_voice\` remains available for an intentionally distinct spoken payload.
|
|
146
|
-
- Button
|
|
147
|
-
- Optional \`selected_style\` controls the button after queue admission: \`primary\` (default, blue), \`success\` (green), or \`danger\` (red). It never suppresses the prompt.
|
|
146
|
+
- Button payloads use \`label\` plus \`prompt\`, or compact \`value\` when both are identical: \`<!-- telegram_button: {"label":"Continue","prompt":"Continue with the current plan."} -->\` or \`<!-- telegram_button value="Continue" -->\`.
|
|
147
|
+
- Optional \`selected_style\` in either payload form controls the button after queue admission: \`primary\` (default, blue), \`success\` (green), or \`danger\` (red). It never suppresses the prompt.
|
|
148
148
|
- If hidden button comments form the whole reply, the bridge supplies the standard \`☑️ **Choose an option:**\` heading automatically.
|
|
149
149
|
|
|
150
150
|
Local/TUI direct delivery:
|
package/lib/queue.ts
CHANGED
|
@@ -968,6 +968,7 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
968
968
|
isSessionActive?: () => boolean;
|
|
969
969
|
isTurnTransportActive?: (turn: TTurn) => boolean;
|
|
970
970
|
waitForTypingIdle?: () => Promise<void>;
|
|
971
|
+
waitForActivityIdle?: () => Promise<void>;
|
|
971
972
|
updateStatus: () => void;
|
|
972
973
|
dispatchNextQueuedTelegramTurn: () => void;
|
|
973
974
|
scheduleActiveTurnDelivery?: (task: () => Promise<void>) => void;
|
|
@@ -1047,6 +1048,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
1047
1048
|
isSessionActive?: (ctx: TContext) => boolean;
|
|
1048
1049
|
isTurnTransportActive?: (turn: TTurn) => boolean;
|
|
1049
1050
|
waitForTypingIdle?: () => Promise<void>;
|
|
1051
|
+
waitForActivityIdle?: () => Promise<void>;
|
|
1050
1052
|
updateStatus: (ctx: TContext) => void;
|
|
1051
1053
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
1052
1054
|
requestDeferredDispatchNextQueuedTelegramTurn: (
|
|
@@ -1188,6 +1190,7 @@ export function createTelegramAgentEndHook<
|
|
|
1188
1190
|
isSessionActive: () => deps.isSessionActive?.(ctx) ?? true,
|
|
1189
1191
|
isTurnTransportActive: deps.isTurnTransportActive,
|
|
1190
1192
|
waitForTypingIdle: deps.waitForTypingIdle,
|
|
1193
|
+
waitForActivityIdle: deps.waitForActivityIdle,
|
|
1191
1194
|
updateStatus: () => deps.updateStatus(ctx),
|
|
1192
1195
|
dispatchNextQueuedTelegramTurn: () => {
|
|
1193
1196
|
deps.requestDeferredDispatchNextQueuedTelegramTurn(
|
|
@@ -1358,6 +1361,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1358
1361
|
return;
|
|
1359
1362
|
}
|
|
1360
1363
|
const deliverActiveTurn = async () => {
|
|
1364
|
+
await deps.waitForActivityIdle?.();
|
|
1361
1365
|
if (!isDeliveryActive()) return;
|
|
1362
1366
|
if (finalText) deps.setPreviewPendingText(finalText);
|
|
1363
1367
|
if (!finalText && hasOutboundArtifacts)
|
package/lib/telegram-api.ts
CHANGED
|
@@ -274,6 +274,24 @@ type TelegramInputRichMessageCommon = {
|
|
|
274
274
|
skip_entity_detection?: boolean;
|
|
275
275
|
};
|
|
276
276
|
|
|
277
|
+
export type TelegramRichText =
|
|
278
|
+
| string
|
|
279
|
+
| TelegramRichText[]
|
|
280
|
+
| { type: "bold" | "code"; text: TelegramRichText };
|
|
281
|
+
|
|
282
|
+
export type TelegramInputRichBlock =
|
|
283
|
+
| { type: "pre"; text: TelegramRichText; language?: string }
|
|
284
|
+
| {
|
|
285
|
+
type: "details";
|
|
286
|
+
summary: TelegramRichText;
|
|
287
|
+
blocks: TelegramInputRichBlock[];
|
|
288
|
+
is_open?: true;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export type TelegramInputRichDraftBlock =
|
|
292
|
+
| TelegramInputRichBlock
|
|
293
|
+
| { type: "thinking"; text: TelegramRichText };
|
|
294
|
+
|
|
277
295
|
export type TelegramInputRichMessage = TelegramInputRichMessageCommon &
|
|
278
296
|
(
|
|
279
297
|
| {
|
|
@@ -288,6 +306,12 @@ export type TelegramInputRichMessage = TelegramInputRichMessageCommon &
|
|
|
288
306
|
blocks?: never;
|
|
289
307
|
media?: TelegramInputRichMessageMedia[];
|
|
290
308
|
}
|
|
309
|
+
| {
|
|
310
|
+
blocks: TelegramInputRichBlock[];
|
|
311
|
+
markdown?: never;
|
|
312
|
+
html?: never;
|
|
313
|
+
media?: never;
|
|
314
|
+
}
|
|
291
315
|
);
|
|
292
316
|
|
|
293
317
|
export type TelegramSendRichMessageBody = Record<string, unknown> & {
|
|
@@ -318,7 +342,14 @@ export type TelegramSendMessageDraftBody = Record<string, unknown> & {
|
|
|
318
342
|
export type TelegramSendRichMessageDraftBody = Record<string, unknown> & {
|
|
319
343
|
chat_id: number;
|
|
320
344
|
draft_id: number;
|
|
321
|
-
rich_message:
|
|
345
|
+
rich_message:
|
|
346
|
+
| TelegramInputRichMessage
|
|
347
|
+
| (TelegramInputRichMessageCommon & {
|
|
348
|
+
blocks: TelegramInputRichDraftBlock[];
|
|
349
|
+
markdown?: never;
|
|
350
|
+
html?: never;
|
|
351
|
+
media?: never;
|
|
352
|
+
});
|
|
322
353
|
message_thread_id?: number;
|
|
323
354
|
};
|
|
324
355
|
|
|
@@ -497,6 +528,11 @@ export interface TelegramBridgeApiRuntime {
|
|
|
497
528
|
editMessageText: (
|
|
498
529
|
body: TelegramEditMessageTextBody,
|
|
499
530
|
) => Promise<"edited" | "unchanged">;
|
|
531
|
+
editMessageReplyMarkup: (
|
|
532
|
+
chatId: number,
|
|
533
|
+
messageId: number,
|
|
534
|
+
replyMarkup: unknown,
|
|
535
|
+
) => Promise<void>;
|
|
500
536
|
answerCallbackQuery: (
|
|
501
537
|
callbackQueryId: string,
|
|
502
538
|
text?: string,
|
|
@@ -1569,6 +1605,13 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1569
1605
|
throw error;
|
|
1570
1606
|
}
|
|
1571
1607
|
},
|
|
1608
|
+
editMessageReplyMarkup: async (chatId, messageId, replyMarkup) => {
|
|
1609
|
+
await callRecorded("editMessageReplyMarkup", {
|
|
1610
|
+
chat_id: chatId,
|
|
1611
|
+
message_id: messageId,
|
|
1612
|
+
reply_markup: replyMarkup,
|
|
1613
|
+
});
|
|
1614
|
+
},
|
|
1572
1615
|
answerCallbackQuery: async (callbackQueryId, text) => {
|
|
1573
1616
|
try {
|
|
1574
1617
|
await deps.client.answerCallbackQuery(callbackQueryId, text);
|