mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(telegram): render progress drafts with clean HTML transport
Render Telegram progress draft rows with clean plain previews while preserving Telegram HTML formatting through parse_mode. The progress HTML path now stays transport-owned, including richMessages=false progress messages, while debug/plain/sanitized text remains readable without raw markup. Thanks @snowzlmbot!
This commit is contained in:
@@ -69,6 +69,17 @@
|
||||
"fileName"
|
||||
]
|
||||
},
|
||||
"api": {
|
||||
"emoji": "🌐",
|
||||
"title": "API",
|
||||
"detailKeys": [
|
||||
"url",
|
||||
"endpoint",
|
||||
"path",
|
||||
"method",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"browser": {
|
||||
"emoji": "🌐",
|
||||
"title": "Browser",
|
||||
|
||||
@@ -381,6 +381,13 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
return expectRecordFields(mockCallArg(createTelegramDraftStream), expected);
|
||||
}
|
||||
|
||||
function telegramProgressPreview(text: string, html: string) {
|
||||
return {
|
||||
text,
|
||||
richMessage: { html: html.replaceAll("\n", "<br>"), skip_entity_detection: true },
|
||||
};
|
||||
}
|
||||
|
||||
function expectDeliverRepliesParams(expected: Record<string, unknown>, callIndex = 0) {
|
||||
return expectRecordFields(mockCallArg(deliverReplies, callIndex), expected);
|
||||
}
|
||||
@@ -398,10 +405,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
return expectRecordFields(mockCallArg(dispatchReplyWithBufferedBlockDispatcher), expected);
|
||||
}
|
||||
|
||||
function telegramHtmlPreview(html: string) {
|
||||
return { text: html, parseMode: "HTML" as const };
|
||||
}
|
||||
|
||||
function createContext(overrides?: Partial<TelegramMessageContext>): TelegramMessageContext {
|
||||
const base = {
|
||||
ctxPayload: {},
|
||||
@@ -637,7 +640,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
chatId: 123,
|
||||
thread: { id: 777, scope: "dm" },
|
||||
minInitialChars: 30,
|
||||
minInitialDelayMs: 5000,
|
||||
});
|
||||
expect(draftStream.update).toHaveBeenCalledWith("Hello");
|
||||
const delivery = expectDeliverRepliesParams({ thread: { id: 777, scope: "dm" } });
|
||||
@@ -2475,7 +2477,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, "Site A shows X.");
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(2, "Site A shows X.");
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: expect.stringMatching(/<b>🛠️ Exec<\/b>$/) }),
|
||||
expect.objectContaining({ text: expect.stringMatching(/🛠️ Exec$/) }),
|
||||
);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(3, "Final answer");
|
||||
expect(answerDraftStream.clear).toHaveBeenCalledTimes(1);
|
||||
@@ -2502,7 +2504,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, "Site A shows X.");
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: expect.stringMatching(/<b>🛠️ Exec<\/b>$/) }),
|
||||
expect.objectContaining({ text: expect.stringMatching(/🛠️ Exec$/) }),
|
||||
);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(2, "Site B shows Y.");
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(3, "Final answer");
|
||||
@@ -2544,7 +2546,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
await dispatchWithContext({ context: createContext() });
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: expect.stringMatching(/<b>🛠️ Exec<\/b>$/) }),
|
||||
expect.objectContaining({ text: expect.stringMatching(/🛠️ Exec$/) }),
|
||||
);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, "Branch is up to date");
|
||||
expect(answerDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
@@ -2570,7 +2572,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
await dispatchWithContext({ context: createContext() });
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: expect.stringMatching(/<b>🛠️ Exec<\/b>$/) }),
|
||||
expect.objectContaining({ text: expect.stringMatching(/🛠️ Exec$/) }),
|
||||
);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, "Branch is up to date");
|
||||
expect(answerDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
@@ -2625,8 +2627,9 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview(
|
||||
"<b>Cracking</b><br><b>🛠️ Exec</b><br><b>🛠️ Exec</b> <code>git rev-parse --abbrev-ref HEAD</code>",
|
||||
telegramProgressPreview(
|
||||
"Cracking\n\n🛠️ Exec\n🛠️ git rev-parse --abbrev-ref HEAD",
|
||||
"<b>Cracking</b>\n<b>🛠️ Exec</b>\n<b>🛠️ Exec</b> <code>git rev-parse --abbrev-ref HEAD</code>",
|
||||
),
|
||||
);
|
||||
expect(answerDraftStream.update).not.toHaveBeenCalledWith("Branch is up to date");
|
||||
@@ -2636,27 +2639,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows a stable progress placeholder for progress-mode answer activity", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
|
||||
await replyOptions?.onPartialReply?.({ text: "Short" });
|
||||
await replyOptions?.onPartialReply?.({ text: "Short answer" });
|
||||
return { queuedFinal: false };
|
||||
});
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
|
||||
});
|
||||
|
||||
expect(answerDraftStream.update).not.toHaveBeenCalledWith("Short");
|
||||
expect(answerDraftStream.update).not.toHaveBeenCalledWith("Short answer");
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b>"),
|
||||
);
|
||||
});
|
||||
|
||||
it("replaces Telegram command progress items with matching command output", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
|
||||
@@ -2687,7 +2669,10 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(lastUpdate?.text).toContain("install dependencies");
|
||||
expect(lastUpdate?.text).not.toContain("completed");
|
||||
expect(lastUpdate).toEqual(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b> <code>install dependencies</code>"),
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\n🛠️ install dependencies",
|
||||
"<b>Shelling</b>\n<b>🛠️ Exec</b> <code>install dependencies</code>",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2709,7 +2694,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Cracking</b><br><b>🛠️ Exec</b>"),
|
||||
telegramProgressPreview("Cracking\n\n🛠️ Exec", "<b>Cracking</b>\n<b>🛠️ Exec</b>"),
|
||||
);
|
||||
expect(answerDraftStream.update).toHaveBeenCalledTimes(1);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, trailingFinalStatusText);
|
||||
@@ -2720,67 +2705,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expectDeliveredReply(0, { text: "Branch is up to date" });
|
||||
});
|
||||
|
||||
it("clears progress drafts before durable verbose tool output", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
|
||||
replyOptions?.onVerboseProgressVisibility?.(() => true);
|
||||
await dispatcherOptions.deliver(
|
||||
{ text: "Tool output visible to Telegram" },
|
||||
{ kind: "tool" },
|
||||
);
|
||||
await dispatcherOptions.deliver({ text: "Final answer" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
|
||||
});
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b>"),
|
||||
);
|
||||
expectDeliveredReply(0, { text: "Tool output visible to Telegram" });
|
||||
expectDeliveredReply(0, { text: "Final answer" }, 1);
|
||||
expect(answerDraftStream.clear.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
deliverReplies.mock.invocationCallOrder[0],
|
||||
);
|
||||
});
|
||||
|
||||
it("clears progress drafts before visible tool artifacts", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
|
||||
await dispatcherOptions.deliver(
|
||||
{ mediaUrl: "https://example.com/validation.txt" },
|
||||
{ kind: "tool" },
|
||||
);
|
||||
await dispatcherOptions.deliver({ text: "Final answer" }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
|
||||
});
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b>"),
|
||||
);
|
||||
expectDeliveredReply(0, { mediaUrl: "https://example.com/validation.txt" });
|
||||
expectDeliveredReply(0, { text: "Final answer" }, 1);
|
||||
expect(answerDraftStream.clear.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
deliverReplies.mock.invocationCallOrder[0],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not stream text-only tool results into progress drafts", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
@@ -2806,12 +2730,39 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
);
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
text: "<b>Shelling</b><br><b>🛠️ Exec</b><br><b>🔎 Web Search</b> <code>docs lookup</code>",
|
||||
text: "Shelling\n\n🛠️ Exec\n🔎 Web Search: docs lookup",
|
||||
}),
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders api progress item edge cases as HTML transport previews", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
|
||||
await replyOptions?.onItemEvent?.({ kind: "api", progressText: "GET /v1/users" });
|
||||
await replyOptions?.onItemEvent?.({
|
||||
kind: "api",
|
||||
name: "api",
|
||||
progressText: "POST /v1/jobs",
|
||||
});
|
||||
return { queuedFinal: false };
|
||||
});
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
|
||||
});
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenLastCalledWith(
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\n🌐 API: GET /v1/users\n🌐 API: POST /v1/jobs",
|
||||
"<b>Shelling</b>\n<b>🌐 API</b> <code>GET /v1/users</code>\n<b>🌐 API</b> <code>POST /v1/jobs</code>",
|
||||
),
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not restart progress drafts after final answer delivery", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
@@ -2831,7 +2782,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledTimes(1);
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b>"),
|
||||
telegramProgressPreview("Shelling\n\n🛠️ Exec", "<b>Shelling</b>\n<b>🛠️ Exec</b>"),
|
||||
);
|
||||
expectDeliveredReply(0, { text: "Branch is up to date" });
|
||||
});
|
||||
@@ -2861,7 +2812,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledTimes(1);
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b>"),
|
||||
telegramProgressPreview("Shelling\n\n🛠️ Exec", "<b>Shelling</b>\n<b>🛠️ Exec</b>"),
|
||||
);
|
||||
expectDeliveredReply(0, { text: "Branch is up to date" });
|
||||
});
|
||||
@@ -2895,7 +2846,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledTimes(1);
|
||||
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b>"),
|
||||
telegramProgressPreview("Shelling\n\n🛠️ Exec", "<b>Shelling</b>\n<b>🛠️ Exec</b>"),
|
||||
);
|
||||
expectDeliveredReply(0, { text: "Branch is up to date" });
|
||||
});
|
||||
@@ -3045,7 +2996,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b>"),
|
||||
telegramProgressPreview("Shelling\n\n🛠️ Exec", "<b>Shelling</b>\n<b>🛠️ Exec</b>"),
|
||||
);
|
||||
expect(draftStream.flush).toHaveBeenCalled();
|
||||
});
|
||||
@@ -3085,8 +3036,9 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenLastCalledWith(
|
||||
telegramHtmlPreview(
|
||||
"<b>Shelling</b><br><b>🛠️ Exec</b> <code>command false</code> <i>exit 2</i>",
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\n🛠️ exit 2; command false",
|
||||
"<b>Shelling</b>\n<b>🛠️ Exec</b> <code>command false</code> <i>exit 2</i>",
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -3126,7 +3078,10 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenLastCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b> <code>exit 2</code>"),
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\n🛠️ exit 2",
|
||||
"<b>Shelling</b>\n<b>🛠️ Exec</b> <code>exit 2</code>",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3149,7 +3104,10 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
|
||||
expect(createTelegramDraftStream).toHaveBeenCalledTimes(1);
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><b>🛠️ Exec</b><br><i>Checking files</i>"),
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\n🛠️ Exec\n• Checking files",
|
||||
"<b>Shelling</b>\n<b>🛠️ Exec</b>\n<i>Checking files</i>",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3178,7 +3136,10 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Shelling</b><br><i>Checking recent context</i>"),
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\nChecking recent context",
|
||||
"<b>Shelling</b>\n<i>Checking recent context</i>",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3234,10 +3195,41 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(telegramHtmlPreview("<b>Shelling</b>"));
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramProgressPreview("Shelling", "<b>Shelling</b>"),
|
||||
);
|
||||
expect(draftStream.flush).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([{ label: false }, { label: "Shelling", maxLines: 1 }] as const)(
|
||||
"does not duplicate Telegram progress HTML rows without a visible label",
|
||||
async (progress) => {
|
||||
const draftStream = createSequencedDraftStream(2001);
|
||||
createTelegramDraftStream.mockReturnValue(draftStream);
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
|
||||
await replyOptions?.onReplyStart?.();
|
||||
await replyOptions?.onAssistantMessageStart?.();
|
||||
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
|
||||
return { queuedFinal: false };
|
||||
});
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: {
|
||||
streaming: {
|
||||
mode: "progress",
|
||||
progress,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramProgressPreview("🛠️ Exec", "<b>🛠️ Exec</b>"),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps progress draft labels static while the draft is active", async () => {
|
||||
const draftStream = createSequencedDraftStream(2001);
|
||||
createTelegramDraftStream.mockReturnValue(draftStream);
|
||||
@@ -3264,17 +3256,13 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(telegramHtmlPreview("<b>Working</b>")),
|
||||
);
|
||||
expect(draftStream.updatePreview).not.toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Working.</b>"),
|
||||
);
|
||||
expect(draftStream.updatePreview).not.toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Working..</b>"),
|
||||
);
|
||||
expect(draftStream.updatePreview).not.toHaveBeenCalledWith(
|
||||
telegramHtmlPreview("<b>Working...</b>"),
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramProgressPreview("Working", "<b>Working</b>"),
|
||||
),
|
||||
);
|
||||
expect(draftStream.updatePreview).not.toHaveBeenCalledWith({ text: "Working." });
|
||||
expect(draftStream.updatePreview).not.toHaveBeenCalledWith({ text: "Working.." });
|
||||
expect(draftStream.updatePreview).not.toHaveBeenCalledWith({ text: "Working..." });
|
||||
finishRun?.();
|
||||
await run;
|
||||
});
|
||||
@@ -3297,7 +3285,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
const updateBeforeStatusReaction = draftStream.updatePreview.mock.calls.at(-1)?.[0]?.text;
|
||||
releaseSetTool?.();
|
||||
await pendingToolStart;
|
||||
expect(updateBeforeStatusReaction).toBe("<b>Shelling</b><br><b>🛠️ Exec</b>");
|
||||
expect(updateBeforeStatusReaction).toBe("Shelling\n\n🛠️ Exec");
|
||||
return { queuedFinal: false };
|
||||
});
|
||||
|
||||
@@ -3334,8 +3322,9 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
});
|
||||
|
||||
expect(draftStream.updatePreview).toHaveBeenCalledWith(
|
||||
telegramHtmlPreview(
|
||||
"<b>Shelling</b><br><b>🔎 Web Search</b> <code>docs lookup</code><br><b>Update</b> <code>tests passed</code>",
|
||||
telegramProgressPreview(
|
||||
"Shelling\n\n🔎 Web Search: docs lookup\n• tests passed",
|
||||
"<b>Shelling</b>\n<b>🔎 Web Search</b> <code>docs lookup</code>\n<b>Update</b> <code>tests passed</code>",
|
||||
),
|
||||
);
|
||||
expect(draftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
type ChannelProgressDraftLine,
|
||||
type ChannelProgressDraftCompositorLine,
|
||||
createChannelProgressDraftCompositor,
|
||||
resolveChannelProgressDraftLabel,
|
||||
resolveChannelStreamingBlockEnabled,
|
||||
resolveTranscriptBackedChannelFinalText,
|
||||
} from "openclaw/plugin-sdk/channel-outbound";
|
||||
@@ -152,7 +153,6 @@ const silentReplyDispatchLogger = createSubsystemLogger("telegram/silent-reply-d
|
||||
|
||||
/** Minimum chars before sending first streaming message (improves push notification UX) */
|
||||
const DRAFT_MIN_INITIAL_CHARS = 30;
|
||||
const DRAFT_MIN_INITIAL_DELAY_MS = 5_000;
|
||||
|
||||
type DraftPartialTextUpdate = {
|
||||
text: string;
|
||||
@@ -434,18 +434,6 @@ function sanitizeProgressMarkdownText(text: string): string {
|
||||
return text.replaceAll("`", "'");
|
||||
}
|
||||
|
||||
function formatProgressAsMarkdownCode(text: string): string {
|
||||
const clipped = clipProgressMarkdownText(text);
|
||||
return `\`${sanitizeProgressMarkdownText(clipped)}\``;
|
||||
}
|
||||
|
||||
function formatTelegramProgressLine(text: string): string {
|
||||
const trimmed = text.trim();
|
||||
return trimmed.startsWith("_") && trimmed.endsWith("_")
|
||||
? trimmed
|
||||
: formatProgressAsMarkdownCode(text);
|
||||
}
|
||||
|
||||
function escapeTelegramProgressHtml(text: string): string {
|
||||
return text
|
||||
.replaceAll("&", "&")
|
||||
@@ -454,21 +442,39 @@ function escapeTelegramProgressHtml(text: string): string {
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
|
||||
function renderTelegramProgressStringLine(text: string): string {
|
||||
const clipped = clipProgressMarkdownText(text.trim());
|
||||
function normalizeTelegramProgressText(text: string, options?: { trim?: boolean }): string {
|
||||
const source = options?.trim === false ? text : text.trim();
|
||||
const clipped = clipProgressMarkdownText(source);
|
||||
const italic = clipped.match(/^_(.*)_$/u);
|
||||
if (italic) {
|
||||
return `<i>${escapeTelegramProgressHtml(italic[1] ?? "")}</i>`;
|
||||
return italic[1] ?? "";
|
||||
}
|
||||
return `<code>${escapeTelegramProgressHtml(clipped)}</code>`;
|
||||
return clipped;
|
||||
}
|
||||
|
||||
function renderTelegramProgressLine(line: ChannelProgressDraftCompositorLine): string {
|
||||
function formatTelegramProgressLine(text: string): string {
|
||||
return sanitizeProgressMarkdownText(normalizeTelegramProgressText(text, { trim: false }));
|
||||
}
|
||||
|
||||
function renderTelegramProgressHtmlStringLine(text: string): string {
|
||||
const normalized = normalizeTelegramProgressText(text);
|
||||
const italic = text.trim().match(/^_(.*)_$/u);
|
||||
if (italic) {
|
||||
return `<i>${escapeTelegramProgressHtml(normalized)}</i>`;
|
||||
}
|
||||
return `<code>${escapeTelegramProgressHtml(normalized)}</code>`;
|
||||
}
|
||||
|
||||
function renderTelegramProgressHtmlLine(line: ChannelProgressDraftCompositorLine): string {
|
||||
if (typeof line === "string") {
|
||||
return line.split(/\r?\n/u).map(renderTelegramProgressStringLine).filter(Boolean).join("<br>");
|
||||
return line
|
||||
.split(/\r?\n/u)
|
||||
.map(renderTelegramProgressHtmlStringLine)
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
if (!line.icon && line.label === "Commentary") {
|
||||
return renderTelegramProgressStringLine(line.text);
|
||||
return renderTelegramProgressHtmlStringLine(line.text);
|
||||
}
|
||||
const label = [line.icon, line.label].filter(Boolean).join(" ");
|
||||
const parts = [`<b>${escapeTelegramProgressHtml(label)}</b>`];
|
||||
@@ -478,7 +484,7 @@ function renderTelegramProgressLine(line: ChannelProgressDraftCompositorLine): s
|
||||
} else {
|
||||
const text = line.text.trim();
|
||||
if (text && text !== label) {
|
||||
parts.push(renderTelegramProgressStringLine(text));
|
||||
parts.push(renderTelegramProgressHtmlStringLine(text));
|
||||
}
|
||||
}
|
||||
if (line.status && line.status !== "completed" && line.status !== line.detail) {
|
||||
@@ -490,18 +496,19 @@ function renderTelegramProgressLine(line: ChannelProgressDraftCompositorLine): s
|
||||
function renderTelegramProgressDraftPreview(
|
||||
text: string,
|
||||
lines: readonly ChannelProgressDraftCompositorLine[],
|
||||
richMessages: boolean,
|
||||
label: string | undefined,
|
||||
): TelegramDraftPreview {
|
||||
const trimmed = text.trimEnd();
|
||||
const [heading] = trimmed.split(/\r?\n/u, 1);
|
||||
const renderedLines = lines.map(renderTelegramProgressLine).filter(Boolean);
|
||||
const htmlParts = heading?.trim()
|
||||
? [`<b>${escapeTelegramProgressHtml(heading.trim())}</b>`, ...renderedLines]
|
||||
: renderedLines;
|
||||
const html = htmlParts.join("<br>");
|
||||
if (!richMessages) {
|
||||
return { text: html, parseMode: "HTML" };
|
||||
}
|
||||
const textLines = trimmed.split(/\r?\n/u);
|
||||
const labelVisible =
|
||||
label !== undefined && (trimmed === label || (textLines[0] === label && textLines[1] === ""));
|
||||
const bodyLines = labelVisible ? textLines.slice(textLines[1] === "" ? 2 : 1) : textLines;
|
||||
const renderedLines = lines.map(renderTelegramProgressHtmlLine).filter(Boolean);
|
||||
const visibleLines = renderedLines.slice(-bodyLines.filter(Boolean).length);
|
||||
const htmlParts = labelVisible
|
||||
? [`<b>${escapeTelegramProgressHtml(label)}</b>`, ...visibleLines]
|
||||
: visibleLines;
|
||||
const html = htmlParts.join("\n");
|
||||
return {
|
||||
text: trimmed,
|
||||
richMessage: buildTelegramRichHtml(html, { skipEntityDetection: true }),
|
||||
@@ -1029,7 +1036,6 @@ export const dispatchTelegramMessage = async ({
|
||||
replyToMessageId: draftReplyToMessageId,
|
||||
richMessages: telegramCfg.richMessages,
|
||||
minInitialChars: draftMinInitialChars,
|
||||
minInitialDelayMs: draftMinInitialChars > 0 ? DRAFT_MIN_INITIAL_DELAY_MS : undefined,
|
||||
renderText: renderStreamText,
|
||||
onSupersededPreview: (superseded) => {
|
||||
if (superseded.retain) {
|
||||
@@ -1107,7 +1113,7 @@ export const dispatchTelegramMessage = async ({
|
||||
renderTelegramProgressDraftPreview(
|
||||
streamText,
|
||||
options?.lines ?? [],
|
||||
telegramCfg.richMessages === true,
|
||||
resolveChannelProgressDraftLabel({ entry: telegramCfg, seed: progressSeed }),
|
||||
),
|
||||
);
|
||||
if (options?.flush) {
|
||||
@@ -1389,7 +1395,7 @@ export const dispatchTelegramMessage = async ({
|
||||
recomputeQueuedAnswerBlockRotations();
|
||||
}
|
||||
};
|
||||
const updateDraftFromPartial = async (lane: DraftLaneState, update: DraftPartialTextUpdate) => {
|
||||
const updateDraftFromPartial = (lane: DraftLaneState, update: DraftPartialTextUpdate) => {
|
||||
const laneStream = lane.stream;
|
||||
if (!laneStream || !update.text) {
|
||||
return;
|
||||
@@ -1401,7 +1407,6 @@ export const dispatchTelegramMessage = async ({
|
||||
}
|
||||
if (lane === answerLane) {
|
||||
if (streamMode === "progress") {
|
||||
await progressDraft.noteActivity();
|
||||
return;
|
||||
}
|
||||
resetAnswerToolProgressDraft();
|
||||
@@ -1428,7 +1433,7 @@ export const dispatchTelegramMessage = async ({
|
||||
reasoningStepState.noteReasoningHint();
|
||||
reasoningStepState.noteReasoningDelivered();
|
||||
}
|
||||
await updateDraftFromPartial(lanes[segment.lane], segment.update);
|
||||
updateDraftFromPartial(lanes[segment.lane], segment.update);
|
||||
}
|
||||
};
|
||||
const flushDraftLane = async (lane: DraftLaneState) => {
|
||||
@@ -2145,9 +2150,6 @@ export const dispatchTelegramMessage = async ({
|
||||
}
|
||||
if (segment.lane === "answer" && info.kind === "tool") {
|
||||
if (verboseProgressActive()) {
|
||||
if (streamMode === "progress") {
|
||||
await rotateAnswerLaneAfterToolProgress();
|
||||
}
|
||||
if (
|
||||
await sendPayload(
|
||||
applyTextToPayload(effectivePayload, segment.update.text),
|
||||
@@ -2299,9 +2301,6 @@ export const dispatchTelegramMessage = async ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (streamMode === "progress" && info.kind === "tool") {
|
||||
await rotateAnswerLaneAfterToolProgress();
|
||||
}
|
||||
const delivered = await sendPayload(effectivePayload, {
|
||||
durable: info.kind === "final",
|
||||
});
|
||||
|
||||
@@ -573,13 +573,13 @@ describe("createTelegramDraftStream", () => {
|
||||
stream.updatePreview({
|
||||
text: "Shelling\n\n`🛠️ Exec`",
|
||||
richMessage: {
|
||||
html: "<b>Shelling</b><br><b>🛠️ Exec</b>",
|
||||
html: "<b>Shelling</b>\n<b>🛠️ Exec</b>",
|
||||
skip_entity_detection: true,
|
||||
},
|
||||
});
|
||||
await stream.flush();
|
||||
|
||||
expect(api.sendMessage).toHaveBeenCalledWith(123, "<b>Shelling</b><br><b>🛠️ Exec</b>", {
|
||||
expect(api.sendMessage).toHaveBeenCalledWith(123, "<b>Shelling</b>\n<b>🛠️ Exec</b>", {
|
||||
parse_mode: "HTML",
|
||||
});
|
||||
expect(api.raw.sendRichMessage).not.toHaveBeenCalled();
|
||||
@@ -587,7 +587,7 @@ describe("createTelegramDraftStream", () => {
|
||||
stream.updatePreview({
|
||||
text: "Shelling\n\n`🛠️ Exec`\n• _Checking files_",
|
||||
richMessage: {
|
||||
html: "<b>Shelling</b><br><b>🛠️ Exec</b><br><i>Checking files</i>",
|
||||
html: "<b>Shelling</b>\n<b>🛠️ Exec</b>\n<i>Checking files</i>",
|
||||
skip_entity_detection: true,
|
||||
},
|
||||
});
|
||||
@@ -596,12 +596,70 @@ describe("createTelegramDraftStream", () => {
|
||||
expect(api.editMessageText).toHaveBeenCalledWith(
|
||||
123,
|
||||
17,
|
||||
"<b>Shelling</b><br><b>🛠️ Exec</b><br><i>Checking files</i>",
|
||||
"<b>Shelling</b>\n<b>🛠️ Exec</b>\n<i>Checking files</i>",
|
||||
{ parse_mode: "HTML" },
|
||||
);
|
||||
expect(api.raw.editMessageText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends marked progress rich previews through HTML text transport", async () => {
|
||||
const api = createMockDraftApi();
|
||||
const stream = createDraftStream(api);
|
||||
|
||||
stream.updatePreview({
|
||||
text: "Shelling\n\n🛠️ Exec",
|
||||
richMessage: {
|
||||
html: "<b>Shelling</b><br><b>🛠️ Exec</b>",
|
||||
skip_entity_detection: true,
|
||||
},
|
||||
});
|
||||
await stream.flush();
|
||||
|
||||
expect(api.sendMessage).toHaveBeenCalledWith(123, "<b>Shelling</b>\n<b>🛠️ Exec</b>", {
|
||||
parse_mode: "HTML",
|
||||
});
|
||||
expect(api.raw.sendRichMessage).not.toHaveBeenCalled();
|
||||
|
||||
stream.updatePreview({
|
||||
text: "Shelling\n\n🛠️ Exec\n• Checking files",
|
||||
richMessage: {
|
||||
html: "<b>Shelling</b><br><b>🛠️ Exec</b><br><b>Update</b> <code>Checking files</code>",
|
||||
skip_entity_detection: true,
|
||||
},
|
||||
});
|
||||
await stream.flush();
|
||||
|
||||
expect(api.editMessageText).toHaveBeenCalledWith(
|
||||
123,
|
||||
17,
|
||||
"<b>Shelling</b>\n<b>🛠️ Exec</b>\n<b>Update</b> <code>Checking files</code>",
|
||||
{ parse_mode: "HTML" },
|
||||
);
|
||||
expect(api.raw.editMessageText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to plain preview text when rich preview HTML parsing fails", async () => {
|
||||
const api = createMockDraftApi();
|
||||
api.sendMessage
|
||||
.mockRejectedValueOnce(new Error("can't parse entities: unsupported tag"))
|
||||
.mockResolvedValueOnce({ message_id: 17 });
|
||||
const stream = createDraftStream(api);
|
||||
|
||||
stream.updatePreview({
|
||||
text: "Shelling\n\n🛠️ Exec",
|
||||
richMessage: {
|
||||
html: "<b>Shelling</b>\n<b>🛠️ Exec</b>",
|
||||
skip_entity_detection: true,
|
||||
},
|
||||
});
|
||||
await stream.flush();
|
||||
|
||||
expect(api.sendMessage).toHaveBeenNthCalledWith(1, 123, "<b>Shelling</b>\n<b>🛠️ Exec</b>", {
|
||||
parse_mode: "HTML",
|
||||
});
|
||||
expect(api.sendMessage).toHaveBeenNthCalledWith(2, 123, "Shelling\n\n🛠️ Exec", {});
|
||||
});
|
||||
|
||||
it("uses rich send and edit for previews when explicitly enabled", async () => {
|
||||
const api = createMockDraftApi();
|
||||
const stream = createDraftStream(api, { richMessages: true });
|
||||
@@ -843,16 +901,11 @@ describe("createTelegramDraftStream", () => {
|
||||
describe("draft stream initial message debounce", () => {
|
||||
const createMockApi = () => createMockDraftApi(async () => ({ message_id: 42 }));
|
||||
|
||||
function createDebouncedStream(
|
||||
api: ReturnType<typeof createMockApi>,
|
||||
minInitialChars = 30,
|
||||
minInitialDelayMs?: number,
|
||||
) {
|
||||
function createDebouncedStream(api: ReturnType<typeof createMockApi>, minInitialChars = 30) {
|
||||
return createTelegramDraftStream({
|
||||
api: api as unknown as Bot["api"],
|
||||
chatId: 123,
|
||||
minInitialChars,
|
||||
minInitialDelayMs,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -921,36 +974,6 @@ describe("draft stream initial message debounce", () => {
|
||||
expect(api.sendMessage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("materializes a short first message after the initial delay", async () => {
|
||||
const api = createMockApi();
|
||||
const stream = createDebouncedStream(api, 30, 5000);
|
||||
|
||||
stream.update("Processing");
|
||||
await stream.flush();
|
||||
expect(api.sendMessage).not.toHaveBeenCalled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
|
||||
expectPreviewSend(api, "Processing");
|
||||
});
|
||||
|
||||
it("cancels a delayed first message when clear() removes the draft", async () => {
|
||||
const api = createMockApi();
|
||||
const stream = createDebouncedStream(api, 30, 5000);
|
||||
|
||||
stream.update("Processing");
|
||||
await stream.flush();
|
||||
expect(api.sendMessage).not.toHaveBeenCalled();
|
||||
expect(vi.getTimerCount()).toBe(1);
|
||||
|
||||
await stream.clear();
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
expect(api.sendMessage).not.toHaveBeenCalled();
|
||||
expect(api.editMessageText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("works with longer text above threshold", async () => {
|
||||
const api = createMockApi();
|
||||
const stream = createDebouncedStream(api);
|
||||
|
||||
@@ -88,12 +88,16 @@ function isTelegramHtmlParseError(err: unknown): boolean {
|
||||
return TELEGRAM_PARSE_ERR_RE.test(formatErrorMessage(err));
|
||||
}
|
||||
|
||||
function telegramRichHtmlToParseModeHtml(html: string): string {
|
||||
return html.replace(/<br\s*\/?>/giu, "\n");
|
||||
}
|
||||
|
||||
function normalizeTelegramDraftTransportPreview(
|
||||
preview: TelegramDraftPreview,
|
||||
): TelegramDraftTransportPreview {
|
||||
if (preview.richMessage?.html) {
|
||||
return {
|
||||
text: preview.richMessage.html,
|
||||
text: telegramRichHtmlToParseModeHtml(preview.richMessage.html),
|
||||
parseMode: "HTML",
|
||||
plainText: preview.text,
|
||||
};
|
||||
@@ -178,8 +182,6 @@ export function createTelegramDraftStream(params: {
|
||||
throttleMs?: number;
|
||||
/** Minimum chars before sending first message (debounce for push notifications) */
|
||||
minInitialChars?: number;
|
||||
/** Maximum time to hold a short first preview before materializing it anyway. */
|
||||
minInitialDelayMs?: number;
|
||||
/** Optional preview renderer (e.g. markdown -> HTML + parse mode). */
|
||||
renderText?: (text: string) => TelegramDraftPreview;
|
||||
/** Called when a late send resolves after forceNewMessage() switched generations. */
|
||||
@@ -192,7 +194,6 @@ export function createTelegramDraftStream(params: {
|
||||
const maxChars = Math.min(params.maxChars ?? transportLimit, transportLimit);
|
||||
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
|
||||
const minInitialChars = params.minInitialChars;
|
||||
const minInitialDelayMs = params.minInitialDelayMs;
|
||||
const chatId = params.chatId;
|
||||
const threadParams = buildTelegramThreadParams(params.thread);
|
||||
const replyToMessageId = normalizeTelegramReplyToMessageId(params.replyToMessageId);
|
||||
@@ -227,8 +228,6 @@ export function createTelegramDraftStream(params: {
|
||||
let lastDeliveredText = "";
|
||||
let lastRequestedText = "";
|
||||
let lastRequestedPreview: TelegramDraftPreview | undefined;
|
||||
let firstShortPreviewSeenMs: number | undefined;
|
||||
let initialPreviewTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let previewRevision = 0;
|
||||
let generation = 0;
|
||||
let deliveredTextOffset = 0;
|
||||
@@ -324,26 +323,6 @@ export function createTelegramDraftStream(params: {
|
||||
streamVisibleSinceMs = visibleSinceMs;
|
||||
return true;
|
||||
};
|
||||
const clearInitialPreviewTimer = () => {
|
||||
if (initialPreviewTimer) {
|
||||
clearTimeout(initialPreviewTimer);
|
||||
initialPreviewTimer = undefined;
|
||||
}
|
||||
};
|
||||
const scheduleInitialPreviewFlush = (delayMs: number) => {
|
||||
if (initialPreviewTimer) {
|
||||
return;
|
||||
}
|
||||
initialPreviewTimer = setTimeout(
|
||||
() => {
|
||||
initialPreviewTimer = undefined;
|
||||
void flushInitialPreview().catch((err: unknown) => {
|
||||
params.warn?.(`telegram stream preview delayed send failed: ${formatErrorMessage(err)}`);
|
||||
});
|
||||
},
|
||||
Math.max(0, delayMs),
|
||||
);
|
||||
};
|
||||
const stopOversizedPreview = (payloadLength: number): false => {
|
||||
streamState.stopped = true;
|
||||
params.warn?.(`telegram stream preview stopped (text length ${payloadLength} > ${maxChars})`);
|
||||
@@ -376,8 +355,7 @@ export function createTelegramDraftStream(params: {
|
||||
const renderedPayloadLength = richMessages
|
||||
? telegramDraftRichPayloadLength(rendered)
|
||||
: renderedText.length;
|
||||
const renderedPreview = { ...rendered, text: renderedText };
|
||||
const renderedPreviewKey = telegramDraftPreviewKey(renderedPreview);
|
||||
const renderedPreviewKey = telegramDraftPreviewKey({ ...rendered, text: renderedText });
|
||||
if (!renderedText) {
|
||||
return false;
|
||||
}
|
||||
@@ -430,31 +408,15 @@ export function createTelegramDraftStream(params: {
|
||||
|
||||
if (typeof streamMessageId !== "number" && minInitialChars != null && !streamState.final) {
|
||||
if (renderedText.length < minInitialChars) {
|
||||
if (minInitialDelayMs == null) {
|
||||
return false;
|
||||
}
|
||||
const now = Date.now();
|
||||
firstShortPreviewSeenMs ??= now;
|
||||
const remainingDelayMs = minInitialDelayMs - (now - firstShortPreviewSeenMs);
|
||||
if (remainingDelayMs > 0) {
|
||||
scheduleInitialPreviewFlush(remainingDelayMs);
|
||||
return false;
|
||||
}
|
||||
clearInitialPreviewTimer();
|
||||
} else {
|
||||
firstShortPreviewSeenMs = undefined;
|
||||
clearInitialPreviewTimer();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
firstShortPreviewSeenMs = undefined;
|
||||
clearInitialPreviewTimer();
|
||||
}
|
||||
|
||||
const previousSentPreviewKey = lastSentPreviewKey;
|
||||
lastSentPreviewKey = renderedPreviewKey;
|
||||
try {
|
||||
const sent = await sendMessageTransportPreview({
|
||||
preview: renderedPreview,
|
||||
preview: rendered,
|
||||
sendGeneration,
|
||||
});
|
||||
if (sent) {
|
||||
@@ -508,7 +470,6 @@ export function createTelegramDraftStream(params: {
|
||||
state: streamState,
|
||||
sendOrEditStreamMessage,
|
||||
});
|
||||
const flushInitialPreview = loop.flush;
|
||||
|
||||
const requestDraftUpdate = (text: string, preview?: TelegramDraftPreview) => {
|
||||
if (streamState.stopped || streamState.final) {
|
||||
@@ -555,8 +516,6 @@ export function createTelegramDraftStream(params: {
|
||||
messageSendAttempted = false;
|
||||
streamMessageId = undefined;
|
||||
streamVisibleSinceMs = undefined;
|
||||
firstShortPreviewSeenMs = undefined;
|
||||
clearInitialPreviewTimer();
|
||||
lastSentPreviewKey = "";
|
||||
if (options?.resetOffset !== false) {
|
||||
deliveredTextOffset = 0;
|
||||
@@ -570,7 +529,6 @@ export function createTelegramDraftStream(params: {
|
||||
};
|
||||
|
||||
const clear = async () => {
|
||||
clearInitialPreviewTimer();
|
||||
const messageId = await takeMessageIdAfterStop({
|
||||
stopForClear,
|
||||
readMessageId: () => streamMessageId,
|
||||
@@ -589,7 +547,6 @@ export function createTelegramDraftStream(params: {
|
||||
};
|
||||
|
||||
const discard = async () => {
|
||||
clearInitialPreviewTimer();
|
||||
await stopForClear();
|
||||
};
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ export const TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
|
||||
title: "Attach",
|
||||
detailKeys: ["path", "url", "fileName"],
|
||||
},
|
||||
api: {
|
||||
emoji: "🌐",
|
||||
title: "API",
|
||||
detailKeys: ["url", "endpoint", "path", "method", "name"],
|
||||
},
|
||||
browser: {
|
||||
emoji: "🌐",
|
||||
title: "Browser",
|
||||
|
||||
@@ -372,6 +372,8 @@ function itemKindToToolName(kind: string | undefined): string | undefined {
|
||||
return "apply_patch";
|
||||
case "search":
|
||||
return "web_search";
|
||||
case "api":
|
||||
return "api";
|
||||
case "tool":
|
||||
return "tool_call";
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user