
























@@ -1,4 +1,9 @@
1+import path from "node:path";
12import type { Bot } from "grammy";
3+import {
4+appendSessionTranscriptMessage,
5+emitSessionTranscriptUpdate,
6+} from "openclaw/plugin-sdk/agent-harness-runtime";
27import {
38DEFAULT_TIMING,
49logAckFailure,
@@ -60,6 +65,7 @@ import {
6065resolveAutoTopicLabelConfig,
6166resolveChunkMode,
6267resolveMarkdownTableMode,
68+resolveAndPersistSessionFile,
6369resolveSessionStoreEntry,
6470} from "./bot-message-dispatch.runtime.js";
6571import type { TelegramBotOptions } from "./bot.types.js";
@@ -133,6 +139,8 @@ type DispatchTelegramMessageParams = {
133139134140type TelegramReasoningLevel = "off" | "on" | "stream";
135141142+type TelegramTranscriptMirrorPayload = { text?: string; mediaUrls?: string[] };
143+136144type TelegramReplyFenceState = {
137145generation: number;
138146activeDispatches: number;
@@ -235,6 +243,94 @@ function resolveTelegramReasoningLevel(params: {
235243return configDefault;
236244}
237245246+function resolveTelegramMirroredTranscriptText(
247+payload: TelegramTranscriptMirrorPayload,
248+): string | null {
249+const mediaUrls = payload.mediaUrls?.filter((url) => url.trim()) ?? [];
250+if (mediaUrls.length > 0) {
251+return mediaUrls
252+.map((url) => {
253+const pathname = url.split("#")[0]?.split("?")[0] ?? url;
254+const base = path.basename(pathname);
255+return base && base !== "." && base !== "/" ? base : "media";
256+})
257+.join(", ");
258+}
259+260+const text = payload.text?.trim();
261+return text ? text : null;
262+}
263+264+async function mirrorTelegramAssistantReplyToTranscript(params: {
265+cfg: OpenClawConfig;
266+route: TelegramMessageContext["route"];
267+sessionKey: string;
268+telegramDeps: TelegramBotDeps;
269+payload: TelegramTranscriptMirrorPayload;
270+}) {
271+const text = resolveTelegramMirroredTranscriptText(params.payload);
272+if (!text) {
273+return;
274+}
275+const storePath = params.telegramDeps.resolveStorePath(params.cfg.session?.store, {
276+agentId: params.route.agentId,
277+});
278+const store = (params.telegramDeps.loadSessionStore ?? loadSessionStore)(storePath, {
279+skipCache: true,
280+});
281+const sessionEntry = resolveSessionStoreEntry({
282+ store,
283+sessionKey: params.sessionKey,
284+}).existing;
285+if (!sessionEntry?.sessionId) {
286+return;
287+}
288+const { sessionFile } = await resolveAndPersistSessionFile({
289+sessionId: sessionEntry.sessionId,
290+sessionKey: params.sessionKey,
291+sessionStore: store,
292+ storePath,
293+ sessionEntry,
294+agentId: params.route.agentId,
295+sessionsDir: path.dirname(storePath),
296+});
297+const message = {
298+role: "assistant" as const,
299+content: [{ type: "text" as const, text }],
300+api: "openai-responses",
301+provider: "openclaw",
302+model: "delivery-mirror",
303+usage: {
304+input: 0,
305+output: 0,
306+total: 0,
307+prompt_tokens: 0,
308+completion_tokens: 0,
309+total_tokens: 0,
310+cache: {
311+read: 0,
312+write: 0,
313+cacheRead: 0,
314+cacheWrite: 0,
315+total: 0,
316+},
317+},
318+stopReason: "stop" as const,
319+timestamp: Date.now(),
320+};
321+const { messageId } = await appendSessionTranscriptMessage({
322+transcriptPath: sessionFile,
323+ message,
324+config: params.cfg,
325+});
326+emitSessionTranscriptUpdate({
327+ sessionFile,
328+sessionKey: params.sessionKey,
329+ message,
330+ messageId,
331+});
332+}
333+238334const MAX_PROGRESS_MARKDOWN_TEXT_CHARS = 300;
239335240336function clipProgressMarkdownText(text: string): string {
@@ -710,6 +806,7 @@ export const dispatchTelegramMessage = async ({
710806});
711807}
712808};
809+const sessionKey = ctxPayload.SessionKey;
713810const deliveryBaseOptions = {
714811chatId: String(chatId),
715812accountId: route.accountId,
@@ -731,6 +828,17 @@ export const dispatchTelegramMessage = async ({
731828 replyQuotePosition,
732829 replyQuoteEntities,
733830 replyQuoteByMessageId,
831+transcriptMirror: sessionKey
832+ ? async (payload: TelegramTranscriptMirrorPayload) => {
833+await mirrorTelegramAssistantReplyToTranscript({
834+ cfg,
835+ route,
836+ sessionKey,
837+ telegramDeps,
838+ payload,
839+});
840+}
841+ : undefined,
734842};
735843const silentErrorReplies = telegramCfg.silentErrorReplies === true;
736844const isDmTopic = !isGroup && threadSpec.scope === "dm" && threadSpec.id != null;
@@ -902,6 +1010,15 @@ export const dispatchTelegramMessage = async ({
9021010isGroup: deliveryBaseOptions.mirrorIsGroup,
9031011groupId: deliveryBaseOptions.mirrorGroupId,
9041012});
1013+if (deliveryBaseOptions.transcriptMirror && result.delivery.content) {
1014+void deliveryBaseOptions
1015+.transcriptMirror({ text: result.delivery.content })
1016+.catch((err: unknown) => {
1017+logVerbose(
1018+`telegram preview-finalized transcriptMirror failed: ${formatErrorMessage(err)}`,
1019+);
1020+});
1021+}
9051022};
9061023const deliverLaneText = createLaneTextDeliverer({
9071024 lanes,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。