























@@ -110,49 +110,22 @@ const silentReplyDispatchLogger = createSubsystemLogger("telegram/silent-reply-d
110110/** Minimum chars before sending first streaming message (improves push notification UX) */
111111const DRAFT_MIN_INITIAL_CHARS = 30;
112112113-function appendWithOverlap(previous: string, fragment: string): string {
114-const maxOverlap = Math.min(previous.length, fragment.length);
115-for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
116-if (previous.endsWith(fragment.slice(0, overlap))) {
117-return `${previous}${fragment.slice(overlap)}`;
118-}
119-}
120-return `${previous}${fragment}`;
121-}
122-123-function looksLikeDraftDeltaFragment(previous: string, text: string): boolean {
124-if (!previous || !text) {
125-return false;
126-}
127-if (text.startsWith(previous) || previous.startsWith(text)) {
128-return false;
129-}
130-if (/^\s/.test(text)) {
131-return true;
132-}
133-if (previous.length < DRAFT_MIN_INITIAL_CHARS) {
134-return true;
135-}
136-if (/\s$/.test(previous) && text.length <= DRAFT_MIN_INITIAL_CHARS) {
137-return true;
138-}
139-return text.length <= Math.max(16, Math.floor(previous.length / 2));
140-}
113+type DraftPartialTextUpdate = {
114+text: string;
115+delta?: string;
116+replace?: true;
117+};
141118142-function resolveDraftPartialText(previous: string, text: string): string | undefined {
143-if (!previous) {
144-return text;
145-}
146-if (text === previous) {
147-return undefined;
148-}
149-if (text.startsWith(previous)) {
150-return text;
151-}
152-if (previous.startsWith(text) && text.length < previous.length) {
119+function resolveDraftPartialText(
120+previous: string,
121+update: DraftPartialTextUpdate,
122+): string | undefined {
123+const nextText =
124+update.replace || update.delta === undefined ? update.text : `${previous}${update.delta}`;
125+if (nextText === previous) {
153126return undefined;
154127}
155-return looksLikeDraftDeltaFragment(previous, text) ? appendWithOverlap(previous, text) : text;
128+return nextText;
156129}
157130158131async function resolveStickerVisionSupport(cfg: OpenClawConfig, agentId: string) {
@@ -714,23 +687,36 @@ export const dispatchTelegramMessage = async ({
714687});
715688return draftLaneEventQueue;
716689};
717-type SplitLaneSegment = { lane: LaneName; text: string };
690+type SplitLaneSegment = { lane: LaneName; update: DraftPartialTextUpdate };
718691type SplitLaneSegmentsResult = {
719692segments: SplitLaneSegment[];
720693suppressedReasoningOnly: boolean;
721694};
722695const splitTextIntoLaneSegments = (
723-text?: string,
696+update: { text?: string; delta?: string; replace?: true },
724697isReasoning?: boolean,
725698): SplitLaneSegmentsResult => {
726-const split = splitTelegramReasoningText(text, isReasoning);
699+const split = splitTelegramReasoningText(update.text, isReasoning);
700+const splitSegments: Array<{ lane: LaneName; text: string }> = [];
701+const useDelta = !update.replace && update.delta !== undefined;
727702const segments: SplitLaneSegment[] = [];
728703const suppressReasoning = resolvedReasoningLevel === "off";
729704if (split.reasoningText && !suppressReasoning) {
730-segments.push({ lane: "reasoning", text: split.reasoningText });
705+splitSegments.push({ lane: "reasoning", text: split.reasoningText });
731706}
732707if (split.answerText) {
733-segments.push({ lane: "answer", text: split.answerText });
708+splitSegments.push({ lane: "answer", text: split.answerText });
709+}
710+for (const segment of splitSegments) {
711+const canApplyDelta = useDelta && splitSegments.length === 1;
712+segments.push({
713+lane: segment.lane,
714+update: {
715+text: segment.text,
716+ ...(canApplyDelta ? { delta: update.delta } : {}),
717+ ...(update.replace ? { replace: true } : {}),
718+},
719+});
734720}
735721return {
736722 segments,
@@ -761,13 +747,13 @@ export const dispatchTelegramMessage = async ({
761747}
762748await rotateLaneForNewMessage(answerLane);
763749};
764-const updateDraftFromPartial = (lane: DraftLaneState, text: string | undefined) => {
750+const updateDraftFromPartial = (lane: DraftLaneState, update: DraftPartialTextUpdate) => {
765751const laneStream = lane.stream;
766-if (!laneStream || !text) {
752+if (!laneStream || !update.text) {
767753return;
768754}
769755const previousText = lane === answerLane ? lastAnswerPartialText : lane.lastPartialText;
770-const nextText = resolveDraftPartialText(previousText, text);
756+const nextText = resolveDraftPartialText(previousText, update);
771757if (!nextText) {
772758return;
773759}
@@ -786,8 +772,11 @@ export const dispatchTelegramMessage = async ({
786772lane.lastPartialText = nextText;
787773laneStream.update(nextText);
788774};
789-const ingestDraftLaneSegments = async (text: string | undefined, isReasoning?: boolean) => {
790-const split = splitTextIntoLaneSegments(text, isReasoning);
775+const ingestDraftLaneSegments = async (
776+update: { text?: string; delta?: string; replace?: true },
777+isReasoning?: boolean,
778+) => {
779+const split = splitTextIntoLaneSegments(update, isReasoning);
791780for (const segment of split.segments) {
792781if (segment.lane === "answer") {
793782await prepareAnswerLaneForText();
@@ -796,7 +785,7 @@ export const dispatchTelegramMessage = async ({
796785reasoningStepState.noteReasoningHint();
797786reasoningStepState.noteReasoningDelivered();
798787}
799-updateDraftFromPartial(lanes[segment.lane], segment.text);
788+updateDraftFromPartial(lanes[segment.lane], segment.update);
800789}
801790};
802791const flushDraftLane = async (lane: DraftLaneState) => {
@@ -1207,7 +1196,10 @@ export const dispatchTelegramMessage = async ({
12071196| { buttons?: TelegramInlineButtons }
12081197| undefined
12091198)?.buttons;
1210-const split = splitTextIntoLaneSegments(payload.text, payload.isReasoning);
1199+const split = splitTextIntoLaneSegments(
1200+{ text: payload.text },
1201+payload.isReasoning,
1202+);
12111203const segments = split.segments;
12121204const reply = resolveSendableOutboundReplyParts(payload);
12131205const _hasMedia = reply.hasMedia;
@@ -1241,7 +1233,7 @@ export const dispatchTelegramMessage = async ({
12411233) {
12421234reasoningStepState.bufferFinalAnswer({
12431235 payload,
1244-text: segment.text,
1236+text: segment.update.text,
12451237bufferedGeneration: replyFenceGeneration,
12461238});
12471239continue;
@@ -1253,10 +1245,10 @@ export const dispatchTelegramMessage = async ({
12531245streamMode === "progress" &&
12541246segment.lane === "answer" &&
12551247info.kind === "final"
1256- ? await deliverProgressModeFinalAnswer(payload, segment.text)
1248+ ? await deliverProgressModeFinalAnswer(payload, segment.update.text)
12571249 : await deliverLaneText({
12581250laneName: segment.lane,
1259-text: segment.text,
1251+text: segment.update.text,
12601252 payload,
12611253infoKind: info.kind,
12621254buttons: telegramButtons,
@@ -1351,7 +1343,7 @@ export const dispatchTelegramMessage = async ({
13511343answerLane.stream || reasoningLane.stream
13521344 ? (payload) =>
13531345enqueueDraftLaneEvent(async () => {
1354-await ingestDraftLaneSegments(payload.text);
1346+await ingestDraftLaneSegments(payload);
13551347})
13561348 : undefined,
13571349onReasoningStream: reasoningLane.stream
@@ -1362,7 +1354,7 @@ export const dispatchTelegramMessage = async ({
13621354resetDraftLaneState(reasoningLane);
13631355splitReasoningOnNextStream = false;
13641356}
1365-await ingestDraftLaneSegments(payload.text, true);
1357+await ingestDraftLaneSegments(payload, true);
13661358})
13671359 : undefined,
13681360onAssistantMessageStart: answerLane.stream
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。