@llblab/pi-telegram 0.9.3 → 0.9.5
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 +1 -0
- package/BACKLOG.md +6 -1
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/docs/README.md +1 -0
- package/docs/architecture.md +24 -27
- package/docs/callback-namespaces.md +13 -1
- package/docs/command-templates.md +123 -22
- package/docs/extension-sections.md +293 -0
- package/docs/external-handlers.md +33 -75
- package/docs/inbound-handlers.md +6 -6
- package/docs/outbound-handlers.md +12 -12
- package/index.ts +1 -0
- package/lib/api.ts +8 -3
- package/lib/command-templates.ts +152 -6
- package/lib/menu-queue.ts +37 -7
- package/lib/menu-settings.ts +0 -1
- package/lib/preview.ts +37 -8
- package/lib/queue.ts +18 -10
- package/package.json +1 -1
package/lib/command-templates.ts
CHANGED
|
@@ -10,17 +10,23 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
10
10
|
|
|
11
11
|
export const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
|
|
12
12
|
|
|
13
|
+
export type CommandTemplateMode = "sequence" | "parallel";
|
|
14
|
+
|
|
13
15
|
export interface CommandTemplateObjectConfig {
|
|
16
|
+
label?: string;
|
|
17
|
+
mode?: CommandTemplateMode;
|
|
14
18
|
template?: CommandTemplateValue;
|
|
15
19
|
args?: string[];
|
|
16
20
|
defaults?: Record<string, unknown>;
|
|
17
21
|
timeout?: number;
|
|
22
|
+
delay?: number;
|
|
18
23
|
output?: string;
|
|
19
24
|
retry?: number;
|
|
20
25
|
critical?: boolean;
|
|
26
|
+
repeat?: number;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
export type CommandTemplateValue = string | CommandTemplateConfig[];
|
|
29
|
+
export type CommandTemplateValue = string | CommandTemplateConfig[] | CommandTemplateObjectConfig;
|
|
24
30
|
|
|
25
31
|
export type CommandTemplateConfig = string | CommandTemplateObjectConfig;
|
|
26
32
|
|
|
@@ -78,6 +84,61 @@ function normalizeCommandTemplateDefaults(
|
|
|
78
84
|
return normalized;
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
function normalizeRepeat(value: number | undefined): number | undefined {
|
|
88
|
+
if (value === undefined) return undefined;
|
|
89
|
+
if (!Number.isInteger(value) || value < 1)
|
|
90
|
+
throw new Error("Command template repeat must be a positive integer.");
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pad(value: number, width: number): string {
|
|
95
|
+
return String(value).padStart(width, "0");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function isCommandTemplateRepeatPlaceholder(name: string): boolean {
|
|
99
|
+
return /^_{0,6}(?:index|prev|next|repeat)$/.test(name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getCommandTemplateRepeatDefaults(
|
|
103
|
+
index: number,
|
|
104
|
+
repeat: number,
|
|
105
|
+
): Record<string, string> {
|
|
106
|
+
const prev = (index - 1 + repeat) % repeat;
|
|
107
|
+
const next = (index + 1) % repeat;
|
|
108
|
+
const values: Record<string, string> = {
|
|
109
|
+
index: String(index),
|
|
110
|
+
next: String(next),
|
|
111
|
+
prev: String(prev),
|
|
112
|
+
repeat: String(repeat),
|
|
113
|
+
};
|
|
114
|
+
for (const name of ["index", "prev", "next", "repeat"]) {
|
|
115
|
+
const numeric = Number(values[name]);
|
|
116
|
+
for (let underscores = 1; underscores <= 6; underscores += 1) {
|
|
117
|
+
values[`${"_".repeat(underscores)}${name}`] = pad(numeric, underscores + 1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return values;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function expandRepeatConfig(
|
|
124
|
+
config: CommandTemplateObjectConfig,
|
|
125
|
+
context: Pick<CommandTemplateObjectConfig, "args" | "defaults">,
|
|
126
|
+
): CommandTemplateObjectConfig[] | undefined {
|
|
127
|
+
const repeat = normalizeRepeat(config.repeat);
|
|
128
|
+
if (repeat === undefined) return undefined;
|
|
129
|
+
return Array.from({ length: repeat }, (_unused, index0) => {
|
|
130
|
+
const { repeat: _repeat, ...rest } = config;
|
|
131
|
+
return {
|
|
132
|
+
...rest,
|
|
133
|
+
defaults: {
|
|
134
|
+
...(context.defaults ?? {}),
|
|
135
|
+
...(rest.defaults ?? {}),
|
|
136
|
+
...getCommandTemplateRepeatDefaults(index0, repeat),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
81
142
|
export function expandCommandTemplateConfigs(
|
|
82
143
|
config: CommandTemplateConfig,
|
|
83
144
|
inherited: Pick<CommandTemplateObjectConfig, "args" | "defaults"> = {},
|
|
@@ -99,6 +160,10 @@ export function expandCommandTemplateConfigs(
|
|
|
99
160
|
? { defaults: { ...(inheritedDefaults ?? {}), ...ownDefaults } }
|
|
100
161
|
: {}),
|
|
101
162
|
};
|
|
163
|
+
const repeated = expandRepeatConfig(normalizedConfig, context);
|
|
164
|
+
if (repeated) {
|
|
165
|
+
return repeated.flatMap((step) => expandCommandTemplateConfigs(step, context));
|
|
166
|
+
}
|
|
102
167
|
if (Array.isArray(normalizedConfig.template)) {
|
|
103
168
|
return normalizedConfig.template.flatMap((step) =>
|
|
104
169
|
expandCommandTemplateConfigs(step, context),
|
|
@@ -189,17 +254,92 @@ export function expandCommandTemplateExecutable(
|
|
|
189
254
|
return command;
|
|
190
255
|
}
|
|
191
256
|
|
|
257
|
+
function evaluateCommandTemplateExpression(
|
|
258
|
+
expression: string,
|
|
259
|
+
values: Record<string, string>,
|
|
260
|
+
): number {
|
|
261
|
+
let index = 0;
|
|
262
|
+
const source = expression.replace(/\s+/g, "");
|
|
263
|
+
function peek(): string | undefined {
|
|
264
|
+
return source[index];
|
|
265
|
+
}
|
|
266
|
+
function consume(char: string): boolean {
|
|
267
|
+
if (peek() !== char) return false;
|
|
268
|
+
index += 1;
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
function parsePrimary(): number {
|
|
272
|
+
if (consume("(")) {
|
|
273
|
+
const value = parseExpression();
|
|
274
|
+
if (!consume(")")) throw new Error(`Invalid command template expression: ${expression}`);
|
|
275
|
+
return value;
|
|
276
|
+
}
|
|
277
|
+
const numberMatch = source.slice(index).match(/^\d+/);
|
|
278
|
+
if (numberMatch) {
|
|
279
|
+
index += numberMatch[0].length;
|
|
280
|
+
return Number(numberMatch[0]);
|
|
281
|
+
}
|
|
282
|
+
const nameMatch = source.slice(index).match(/^[A-Za-z_][A-Za-z0-9_-]*/);
|
|
283
|
+
if (nameMatch) {
|
|
284
|
+
index += nameMatch[0].length;
|
|
285
|
+
const value = values[nameMatch[0]];
|
|
286
|
+
if (value === undefined || !/^-?\d+$/.test(value))
|
|
287
|
+
throw new Error(`Invalid command template expression variable: ${nameMatch[0]}`);
|
|
288
|
+
return Number(value);
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`Invalid command template expression: ${expression}`);
|
|
291
|
+
}
|
|
292
|
+
function parseTerm(): number {
|
|
293
|
+
let value = parsePrimary();
|
|
294
|
+
while (true) {
|
|
295
|
+
if (consume("*")) value *= parsePrimary();
|
|
296
|
+
else if (consume("/")) value = Math.trunc(value / parsePrimary());
|
|
297
|
+
else if (consume("%")) value %= parsePrimary();
|
|
298
|
+
else return value;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function parseExpression(): number {
|
|
302
|
+
let value = parseTerm();
|
|
303
|
+
while (true) {
|
|
304
|
+
if (consume("+")) value += parseTerm();
|
|
305
|
+
else if (consume("-")) value -= parseTerm();
|
|
306
|
+
else return value;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const value = parseExpression();
|
|
310
|
+
if (index !== source.length) throw new Error(`Invalid command template expression: ${expression}`);
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function substituteCommandTemplateExpression(
|
|
315
|
+
content: string,
|
|
316
|
+
values: Record<string, string>,
|
|
317
|
+
): string | undefined {
|
|
318
|
+
const padded = content.match(/^(_{1,6})\((.+)\)$/);
|
|
319
|
+
if (padded) {
|
|
320
|
+
return pad(evaluateCommandTemplateExpression(padded[2], values), padded[1].length + 1);
|
|
321
|
+
}
|
|
322
|
+
if (!/[()+\-*\/%]/.test(content)) return undefined;
|
|
323
|
+
return String(evaluateCommandTemplateExpression(content, values));
|
|
324
|
+
}
|
|
325
|
+
|
|
192
326
|
export function substituteCommandTemplateToken(
|
|
193
327
|
token: string,
|
|
194
328
|
values: Record<string, string>,
|
|
195
329
|
missingLabel = "command template",
|
|
196
330
|
): string {
|
|
197
331
|
return token.replace(
|
|
198
|
-
/\{([
|
|
199
|
-
(_match,
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
|
|
332
|
+
/\{([^{}]+)\}/g,
|
|
333
|
+
(_match, content: string) => {
|
|
334
|
+
const simple = content.match(/^([A-Za-z_][A-Za-z0-9_-]*)(?:=([^}]*))?$/);
|
|
335
|
+
if (simple) {
|
|
336
|
+
const [, name, inlineDefault] = simple;
|
|
337
|
+
if (Object.hasOwn(values, name)) return values[name] ?? "";
|
|
338
|
+
if (inlineDefault !== undefined) return inlineDefault;
|
|
339
|
+
}
|
|
340
|
+
const expression = substituteCommandTemplateExpression(content, values);
|
|
341
|
+
if (expression !== undefined) return expression;
|
|
342
|
+
throw new Error(`Missing ${missingLabel} value: ${content}`);
|
|
203
343
|
},
|
|
204
344
|
);
|
|
205
345
|
}
|
|
@@ -300,6 +440,12 @@ export function buildCommandTemplateInvocation(
|
|
|
300
440
|
}
|
|
301
441
|
if (!normalizedConfig.template)
|
|
302
442
|
throw new Error(options.emptyMessage ?? "Command template is required");
|
|
443
|
+
if (typeof normalizedConfig.template !== "string") {
|
|
444
|
+
throw new Error(
|
|
445
|
+
options.emptyMessage ??
|
|
446
|
+
"Command template object cannot be executed as one command",
|
|
447
|
+
);
|
|
448
|
+
}
|
|
303
449
|
const parts = splitCommandTemplate(normalizedConfig.template);
|
|
304
450
|
const commandPart = parts[0];
|
|
305
451
|
if (!commandPart)
|
package/lib/menu-queue.ts
CHANGED
|
@@ -13,6 +13,12 @@ import * as Queue from "./queue.ts";
|
|
|
13
13
|
|
|
14
14
|
const QUEUE_ITEM_PROMPT_HTML_LIMIT = 3600;
|
|
15
15
|
const QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX = "\n… [truncated]";
|
|
16
|
+
const EMPTY_QUEUE_REFRESH_TITLES = [
|
|
17
|
+
"<b>⌛ Queue is still empty.</b>",
|
|
18
|
+
"<b>🫙 Still nothing in queue.</b>",
|
|
19
|
+
"<b>🍃 Queue remains empty.</b>",
|
|
20
|
+
"<b>🕳 Nothing queued yet.</b>",
|
|
21
|
+
] as const;
|
|
16
22
|
type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
17
23
|
interface TelegramQueueMenuItem {
|
|
18
24
|
chatId: number;
|
|
@@ -55,9 +61,16 @@ function toTelegramQueueMenuItems<Context>(
|
|
|
55
61
|
}
|
|
56
62
|
function buildTelegramQueueMenuReplyMarkup(
|
|
57
63
|
items: readonly TelegramQueueMenuItem[],
|
|
64
|
+
emptyRefreshIndex = 0,
|
|
58
65
|
): TelegramQueueMenuReplyMarkup {
|
|
59
66
|
const backRow = [{ text: "⬆️ Main menu", callback_data: "menu:back" }];
|
|
60
|
-
const
|
|
67
|
+
const nextEmptyRefreshIndex =
|
|
68
|
+
(emptyRefreshIndex + 1) % EMPTY_QUEUE_REFRESH_TITLES.length;
|
|
69
|
+
const refreshData =
|
|
70
|
+
items.length === 0
|
|
71
|
+
? `queue:refresh:${nextEmptyRefreshIndex}`
|
|
72
|
+
: "queue:refresh";
|
|
73
|
+
const refreshRow = [{ text: "🌀 Refresh", callback_data: refreshData }];
|
|
61
74
|
if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
|
|
62
75
|
const rows = items.map(function buildTelegramQueueMenuRow(item, index) {
|
|
63
76
|
const prefix = item.isPriority
|
|
@@ -73,7 +86,7 @@ function buildTelegramQueueMenuReplyMarkup(
|
|
|
73
86
|
},
|
|
74
87
|
];
|
|
75
88
|
});
|
|
76
|
-
return { inline_keyboard: [backRow, ...rows
|
|
89
|
+
return { inline_keyboard: [backRow, refreshRow, ...rows] };
|
|
77
90
|
}
|
|
78
91
|
function findTelegramQueueItem<Context>(
|
|
79
92
|
items: readonly Queue.TelegramQueueItem<Context>[],
|
|
@@ -215,7 +228,7 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
215
228
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
216
229
|
return true;
|
|
217
230
|
}
|
|
218
|
-
if (data === "queue:list"
|
|
231
|
+
if (data === "queue:list") {
|
|
219
232
|
await updateTelegramQueueMenuList(
|
|
220
233
|
callbackQueryId,
|
|
221
234
|
replyChatId,
|
|
@@ -224,6 +237,18 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
224
237
|
);
|
|
225
238
|
return true;
|
|
226
239
|
}
|
|
240
|
+
const refreshMatch = data.match(/^queue:refresh(?::(\d+))?$/);
|
|
241
|
+
if (refreshMatch) {
|
|
242
|
+
await updateTelegramQueueMenuList(
|
|
243
|
+
callbackQueryId,
|
|
244
|
+
replyChatId,
|
|
245
|
+
replyMessageId,
|
|
246
|
+
deps,
|
|
247
|
+
undefined,
|
|
248
|
+
refreshMatch[1] === undefined ? 0 : Number(refreshMatch[1]),
|
|
249
|
+
);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
227
252
|
const pickMatch = data.match(/^queue:pick:(\d+):(\d+)$/);
|
|
228
253
|
if (pickMatch) {
|
|
229
254
|
await handleTelegramQueueMenuPick(
|
|
@@ -306,9 +331,13 @@ async function handleTelegramQueueMenuCallback<Context>(
|
|
|
306
331
|
}
|
|
307
332
|
function getTelegramQueueMenuListText(
|
|
308
333
|
items: readonly TelegramQueueMenuItem[],
|
|
334
|
+
emptyRefreshIndex?: number,
|
|
309
335
|
): string {
|
|
310
|
-
if (items.length
|
|
311
|
-
return "<b
|
|
336
|
+
if (items.length > 0) return "<b>⏳ Queue:</b>";
|
|
337
|
+
if (emptyRefreshIndex === undefined) return "<b>⌛ Queue is empty.</b>";
|
|
338
|
+
return EMPTY_QUEUE_REFRESH_TITLES[
|
|
339
|
+
emptyRefreshIndex % EMPTY_QUEUE_REFRESH_TITLES.length
|
|
340
|
+
];
|
|
312
341
|
}
|
|
313
342
|
async function updateTelegramQueueMenuList<Context>(
|
|
314
343
|
callbackQueryId: string,
|
|
@@ -316,13 +345,14 @@ async function updateTelegramQueueMenuList<Context>(
|
|
|
316
345
|
replyMessageId: number,
|
|
317
346
|
deps: TelegramQueueMenuCallbackDeps<Context>,
|
|
318
347
|
notice?: string,
|
|
348
|
+
emptyRefreshIndex?: number,
|
|
319
349
|
): Promise<void> {
|
|
320
350
|
const items = deps.getQueuedItems();
|
|
321
351
|
await deps.updateQueueMessage(
|
|
322
352
|
replyChatId,
|
|
323
353
|
replyMessageId,
|
|
324
|
-
getTelegramQueueMenuListText(items),
|
|
325
|
-
buildTelegramQueueMenuReplyMarkup(items),
|
|
354
|
+
getTelegramQueueMenuListText(items, emptyRefreshIndex),
|
|
355
|
+
buildTelegramQueueMenuReplyMarkup(items, emptyRefreshIndex),
|
|
326
356
|
);
|
|
327
357
|
await deps.answerCallbackQuery(callbackQueryId, notice);
|
|
328
358
|
}
|
package/lib/menu-settings.ts
CHANGED
|
@@ -111,7 +111,6 @@ export function buildProactivePushSettingsText(): string {
|
|
|
111
111
|
PROACTIVE_PUSH_SETTINGS_TITLE,
|
|
112
112
|
"",
|
|
113
113
|
"Send successful local π task results to Telegram when the bridge is connected.",
|
|
114
|
-
"Default: off. Persists until disabled or removed from config.",
|
|
115
114
|
].join("\n");
|
|
116
115
|
}
|
|
117
116
|
|
package/lib/preview.ts
CHANGED
|
@@ -90,6 +90,11 @@ export interface TelegramPreviewRuntimeDeps<
|
|
|
90
90
|
options?: { replyMarkup?: TReplyMarkup },
|
|
91
91
|
) => Promise<number | undefined>;
|
|
92
92
|
canSend?: () => boolean;
|
|
93
|
+
recordRuntimeEvent?: (
|
|
94
|
+
category: string,
|
|
95
|
+
error: unknown,
|
|
96
|
+
details?: Record<string, unknown>,
|
|
97
|
+
) => void;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
export interface TelegramPreviewActiveTurn {
|
|
@@ -191,6 +196,11 @@ export interface TelegramPreviewControllerDeps<
|
|
|
191
196
|
ms: number,
|
|
192
197
|
) => ReturnType<typeof setTimeout>;
|
|
193
198
|
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
199
|
+
recordRuntimeEvent?: (
|
|
200
|
+
category: string,
|
|
201
|
+
error: unknown,
|
|
202
|
+
details?: Record<string, unknown>,
|
|
203
|
+
) => void;
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
export interface TelegramPreviewController<
|
|
@@ -421,6 +431,7 @@ export function createTelegramPreviewController<
|
|
|
421
431
|
),
|
|
422
432
|
editRenderedMessage: deps.editRenderedMessage,
|
|
423
433
|
canSend: deps.canSend,
|
|
434
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
424
435
|
});
|
|
425
436
|
return {
|
|
426
437
|
getState: () => state,
|
|
@@ -645,7 +656,16 @@ export async function flushTelegramPreview<
|
|
|
645
656
|
state.flushPromise = (async () => {
|
|
646
657
|
do {
|
|
647
658
|
state.flushRequested = false;
|
|
648
|
-
|
|
659
|
+
try {
|
|
660
|
+
await performTelegramPreviewFlush(chatId, state, deps);
|
|
661
|
+
} catch (error) {
|
|
662
|
+
deps.recordRuntimeEvent?.("preview", error, {
|
|
663
|
+
phase: "flush",
|
|
664
|
+
chatId,
|
|
665
|
+
messageId: state.messageId,
|
|
666
|
+
});
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
649
669
|
} while (deps.getState() === state && state.flushRequested);
|
|
650
670
|
})();
|
|
651
671
|
try {
|
|
@@ -704,13 +724,22 @@ export async function finalizeTelegramMarkdownPreview<
|
|
|
704
724
|
await clearTelegramPreview(chatId, deps);
|
|
705
725
|
return false;
|
|
706
726
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
727
|
+
try {
|
|
728
|
+
if (state.mode === "draft") {
|
|
729
|
+
await deps.sendRenderedChunks(chatId, chunks, options);
|
|
730
|
+
await clearTelegramPreview(chatId, deps);
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
if (state.messageId === undefined) return false;
|
|
734
|
+
await deps.editRenderedMessage(chatId, state.messageId, chunks, options);
|
|
735
|
+
deps.setState(undefined);
|
|
710
736
|
return true;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
deps.recordRuntimeEvent?.("preview", error, {
|
|
739
|
+
phase: "finalize-markdown",
|
|
740
|
+
chatId,
|
|
741
|
+
messageId: state.messageId,
|
|
742
|
+
});
|
|
743
|
+
return false;
|
|
711
744
|
}
|
|
712
|
-
if (state.messageId === undefined) return false;
|
|
713
|
-
await deps.editRenderedMessage(chatId, state.messageId, chunks, options);
|
|
714
|
-
deps.setState(undefined);
|
|
715
|
-
return true;
|
|
716
745
|
}
|
package/lib/queue.ts
CHANGED
|
@@ -1023,20 +1023,28 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1023
1023
|
if (finalText) deps.setPreviewPendingText(finalText);
|
|
1024
1024
|
if (!finalText && hasOutboundArtifacts) await deps.clearPreview(turn.chatId);
|
|
1025
1025
|
if (endPlan.kind === "text" && finalText) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
finalText,
|
|
1029
|
-
turn.replyToMessageId,
|
|
1030
|
-
{ replyMarkup },
|
|
1031
|
-
);
|
|
1032
|
-
if (!finalized) {
|
|
1033
|
-
await deps.clearPreview(turn.chatId);
|
|
1034
|
-
await deps.sendMarkdownReply(
|
|
1026
|
+
try {
|
|
1027
|
+
const finalized = await deps.finalizeMarkdownPreview(
|
|
1035
1028
|
turn.chatId,
|
|
1036
|
-
turn.replyToMessageId,
|
|
1037
1029
|
finalText,
|
|
1030
|
+
turn.replyToMessageId,
|
|
1038
1031
|
{ replyMarkup },
|
|
1039
1032
|
);
|
|
1033
|
+
if (!finalized) {
|
|
1034
|
+
await deps.clearPreview(turn.chatId);
|
|
1035
|
+
await deps.sendMarkdownReply(
|
|
1036
|
+
turn.chatId,
|
|
1037
|
+
turn.replyToMessageId,
|
|
1038
|
+
finalText,
|
|
1039
|
+
{ replyMarkup },
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
deps.recordRuntimeEvent?.("delivery", error, {
|
|
1044
|
+
phase: "final-text",
|
|
1045
|
+
chatId: turn.chatId,
|
|
1046
|
+
replyToMessageId: turn.replyToMessageId,
|
|
1047
|
+
});
|
|
1040
1048
|
}
|
|
1041
1049
|
}
|
|
1042
1050
|
if (outboundReply && deps.sendOutboundReplyArtifacts) {
|