fix: resolve small triage issues

This commit is contained in:
Peter Steinberger
2026-05-04 07:38:34 +01:00
parent deffd11a43
commit fa689295c6
40 changed files with 739 additions and 61 deletions

View File

@@ -65,6 +65,10 @@ export const discordChannelConfigUiHints = {
label: "Discord Draft Tool Progress",
help: "Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active.",
},
"streaming.preview.commandText": {
label: "Discord Draft Command Text",
help: 'Command/exec detail in preview tool-progress lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"streaming.progress.label": {
label: "Discord Progress Label",
help: 'Initial progress draft title. Use "auto" for built-in single-word labels, a custom string, or false to hide the title.',
@@ -81,6 +85,10 @@ export const discordChannelConfigUiHints = {
label: "Discord Progress Tool Lines",
help: "Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery.",
},
"streaming.progress.commandText": {
label: "Discord Progress Command Text",
help: 'Command/exec detail in progress draft lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"retry.attempts": {
label: "Discord Retry Attempts",
help: "Max retry attempts for outbound Discord API calls (default: 3).",

View File

@@ -1567,6 +1567,37 @@ describe("processDiscordMessage draft streaming", () => {
);
});
it("can hide raw command progress text in Discord progress drafts by config", async () => {
const draftStream = createMockDraftStreamForTest();
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
await params?.replyOptions?.onToolStart?.({
name: "exec",
phase: "start",
args: { command: "pnpm test -- --watch=false" },
detailMode: "raw",
});
await params?.replyOptions?.onItemEvent?.({ progressText: "done" });
return createNoQueuedDispatchResult();
});
const ctx = await createAutomaticSourceDeliveryContext({
discordConfig: {
streaming: {
mode: "progress",
progress: {
label: "Shelling",
commandText: "status",
},
},
},
});
await runProcessDiscordMessage(ctx);
expect(draftStream.update).toHaveBeenCalledWith("Shelling\n🛠 Exec\n• done");
});
it("keeps Discord progress lines across assistant boundaries", async () => {
const draftStream = createMockDraftStreamForTest();

View File

@@ -13,6 +13,7 @@ import {
} from "openclaw/plugin-sdk/channel-reply-pipeline";
import {
formatChannelProgressDraftLine,
formatChannelProgressDraftLineForEntry,
resolveChannelStreamingBlockEnabled,
} from "openclaw/plugin-sdk/channel-streaming";
import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
@@ -669,7 +670,8 @@ export async function processDiscordMessage(
await maybeBindStatusReactionsToToolReaction(payload);
await statusReactions.setTool(payload.name);
await draftPreview.pushToolProgress(
formatChannelProgressDraftLine(
formatChannelProgressDraftLineForEntry(
discordConfig,
{
event: "tool",
name: payload.name,
@@ -683,7 +685,7 @@ export async function processDiscordMessage(
},
onItemEvent: async (payload) => {
await draftPreview.pushToolProgress(
formatChannelProgressDraftLine({
formatChannelProgressDraftLineForEntry(discordConfig, {
event: "item",
itemKind: payload.kind,
title: payload.title,

View File

@@ -1,7 +1,7 @@
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
import {
formatChannelProgressDraftLine,
formatChannelProgressDraftLineForEntry,
isChannelProgressDraftWorkToolName,
} from "openclaw/plugin-sdk/channel-streaming";
import {
@@ -708,7 +708,8 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
if (!isChannelProgressDraftWorkToolName(payload.name)) {
return;
}
const statusLine = formatChannelProgressDraftLine(
const statusLine = formatChannelProgressDraftLineForEntry(
account.config,
{
event: "tool",
name: payload.name,

View File

@@ -17,4 +17,8 @@ export const matrixChannelConfigUiHints = {
label: "Matrix Progress Tool Lines",
help: "Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery.",
},
"streaming.progress.commandText": {
label: "Matrix Progress Command Text",
help: 'Command/exec detail in progress draft lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
} satisfies Record<string, ChannelConfigUiHint>;

View File

@@ -1,6 +1,7 @@
import {
createChannelProgressDraftGate,
formatChannelProgressDraftLine,
formatChannelProgressDraftLineForEntry,
formatChannelProgressDraftText,
isChannelProgressDraftWorkToolName,
resolveChannelProgressDraftMaxLines,
@@ -1580,7 +1581,8 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
onToolStart: async (payload) => {
const toolName = payload.name?.trim();
await pushPreviewToolProgress(
formatChannelProgressDraftLine(
formatChannelProgressDraftLineForEntry(
progressConfigEntry,
{
event: "tool",
name: toolName,
@@ -1594,7 +1596,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
},
onItemEvent: async (payload) => {
await pushPreviewToolProgress(
formatChannelProgressDraftLine({
formatChannelProgressDraftLineForEntry(progressConfigEntry, {
event: "item",
itemKind: payload.kind,
title: payload.title,

View File

@@ -33,10 +33,18 @@ export const mattermostChannelConfigUiHints = {
label: "Mattermost Progress Tool Lines",
help: "Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery.",
},
"streaming.progress.commandText": {
label: "Mattermost Progress Command Text",
help: 'Command/exec detail in progress draft lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"streaming.preview.toolProgress": {
label: "Mattermost Draft Tool Progress",
help: "Show tool/progress activity in the live draft preview post (default: true). Set false to hide interim tool updates while the draft preview stays active.",
},
"streaming.preview.commandText": {
label: "Mattermost Draft Command Text",
help: 'Command/exec detail in preview tool-progress lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"streaming.block.enabled": {
label: "Mattermost Block Streaming Enabled",
help: 'Enable chunked block-style Mattermost preview delivery when channels.mattermost.streaming.mode="block".',

View File

@@ -256,4 +256,15 @@ describe("buildMattermostToolStatusText", () => {
}),
).toBe("🛠️ Exec: run tests, `pnpm test -- --watch=false`");
});
it("can hide raw exec detail from status text", () => {
expect(
buildMattermostToolStatusText({
name: "exec",
args: { command: "pnpm test -- --watch=false" },
detailMode: "raw",
config: { streaming: { preview: { commandText: "status" } } },
}),
).toBe("🛠️ Exec");
});
});

View File

@@ -1,5 +1,5 @@
import { createFinalizableDraftLifecycle } from "openclaw/plugin-sdk/channel-lifecycle";
import { formatChannelProgressDraftLine } from "openclaw/plugin-sdk/channel-streaming";
import { formatChannelProgressDraftLineForEntry } from "openclaw/plugin-sdk/channel-streaming";
import {
createMattermostPost,
deleteMattermostPost,
@@ -37,9 +37,11 @@ export function buildMattermostToolStatusText(params: {
phase?: string;
args?: Record<string, unknown>;
detailMode?: "explain" | "raw";
config?: Parameters<typeof formatChannelProgressDraftLineForEntry>[0];
}): string {
return (
formatChannelProgressDraftLine(
formatChannelProgressDraftLineForEntry(
params.config,
{
event: "tool",
name: params.name,

View File

@@ -1876,7 +1876,12 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
if (!draftToolProgressEnabled) {
return;
}
draftStream.update(buildMattermostToolStatusText(payload));
draftStream.update(
buildMattermostToolStatusText({
...payload,
config: account.config,
}),
);
},
},
}),

View File

@@ -29,4 +29,8 @@ export const msTeamsChannelConfigUiHints = {
label: "MS Teams Progress Tool Lines",
help: "Show compact tool/progress lines in progress mode (default: true). Set false to keep only the title until final delivery.",
},
"streaming.progress.commandText": {
label: "MS Teams Progress Command Text",
help: 'Command/exec detail in progress lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
} satisfies Record<string, ChannelConfigUiHint>;

View File

@@ -1,5 +1,6 @@
import {
formatChannelProgressDraftLine,
formatChannelProgressDraftLineForEntry,
resolveChannelPreviewStreamMode,
resolveChannelStreamingBlockEnabled,
} from "openclaw/plugin-sdk/channel-streaming";
@@ -384,7 +385,8 @@ export function createMSTeamsReplyDispatcher(params: {
detailMode?: "explain" | "raw";
}) => {
await streamController.pushProgressLine(
formatChannelProgressDraftLine(
formatChannelProgressDraftLineForEntry(
msteamsCfg,
{
event: "tool",
name: payload.name,
@@ -407,7 +409,7 @@ export function createMSTeamsReplyDispatcher(params: {
status?: string;
}) => {
await streamController.pushProgressLine(
formatChannelProgressDraftLine({
formatChannelProgressDraftLineForEntry(msteamsCfg, {
event: "item",
itemKind: payload.kind,
title: payload.title,

View File

@@ -117,6 +117,10 @@ export const slackChannelConfigUiHints = {
label: "Slack Draft Tool Progress",
help: "Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active.",
},
"streaming.preview.commandText": {
label: "Slack Draft Command Text",
help: 'Command/exec detail in preview tool-progress lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"streaming.progress.label": {
label: "Slack Progress Label",
help: 'Initial progress draft title. Use "auto" for built-in single-word labels, a custom string, or false to hide the title.',
@@ -137,6 +141,10 @@ export const slackChannelConfigUiHints = {
label: "Slack Progress Tool Lines",
help: "Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery.",
},
"streaming.progress.commandText": {
label: "Slack Progress Command Text",
help: 'Command/exec detail in progress draft lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"thread.historyScope": {
label: "Slack Thread History Scope",
help: 'Scope for Slack thread history context ("thread" isolates per thread; "channel" reuses channel history).',

View File

@@ -37,7 +37,16 @@ let capturedReplyOptions:
| {
disableBlockStreaming?: boolean;
suppressDefaultToolProgressMessages?: boolean;
onItemEvent?: (payload: { progressText: string }) => Promise<void> | void;
onItemEvent?: (payload: {
kind?: string;
progressText?: string;
summary?: string;
title?: string;
name?: string;
phase?: string;
status?: string;
meta?: string;
}) => Promise<void> | void;
onPartialReply?: (payload: { text: string }) => Promise<void> | void;
}
| undefined;
@@ -73,7 +82,18 @@ let mockedDispatchSequence: Array<{
}> = [];
let mockedProgressEvents: string[] = [];
let mockedReplyOptionEvents: Array<
{ kind: "item"; progressText: string } | { kind: "partial"; text: string }
| {
kind: "item";
itemKind?: string;
progressText?: string;
summary?: string;
title?: string;
name?: string;
phase?: string;
status?: string;
meta?: string;
}
| { kind: "partial"; text: string }
> = [];
const noop = () => {};
@@ -246,6 +266,41 @@ vi.mock("openclaw/plugin-sdk/channel-streaming", () => ({
}
: undefined;
},
buildChannelProgressDraftLineForEntry: (
entry: {
streaming?: {
progress?: { commandText?: "raw" | "status" };
preview?: { commandText?: "raw" | "status" };
};
},
params: {
itemKind?: string;
progressText?: string;
summary?: string;
title?: string;
name?: string;
},
) => {
if (
(entry.streaming?.progress?.commandText ?? entry.streaming?.preview?.commandText) ===
"status" &&
(params.itemKind === "command" || params.name === "exec")
) {
return {
kind: "item",
text: "🛠️ Exec",
label: "Exec",
};
}
const text = params.progressText ?? params.summary ?? params.title ?? params.name;
return text
? {
kind: "item",
text,
label: params.title ?? params.name ?? "Update",
}
: undefined;
},
createChannelProgressDraftGate: (params: { onStart: () => void | Promise<void> }) => {
let started = false;
let workEvents = 0;
@@ -290,6 +345,15 @@ vi.mock("openclaw/plugin-sdk/channel-streaming", () => ({
title?: string;
name?: string;
}) => params.progressText ?? params.summary ?? params.title ?? params.name,
formatChannelProgressDraftLineForEntry: (
_entry: unknown,
params: {
progressText?: string;
summary?: string;
title?: string;
name?: string;
},
) => params.progressText ?? params.summary ?? params.title ?? params.name,
resolveChannelProgressDraftMaxLines: (entry?: {
streaming?: { progress?: { maxLines?: number } };
}) => entry?.streaming?.progress?.maxLines ?? 8,
@@ -472,7 +536,16 @@ vi.mock("../reply.runtime.js", () => ({
replyOptions?: {
disableBlockStreaming?: boolean;
suppressDefaultToolProgressMessages?: boolean;
onItemEvent?: (payload: { progressText: string }) => Promise<void> | void;
onItemEvent?: (payload: {
kind?: string;
progressText?: string;
summary?: string;
title?: string;
name?: string;
phase?: string;
status?: string;
meta?: string;
}) => Promise<void> | void;
onPartialReply?: (payload: { text: string }) => Promise<void> | void;
};
dispatcher: {
@@ -492,7 +565,16 @@ vi.mock("../reply.runtime.js", () => ({
if (mockedReplyOptionEvents.length > 0) {
for (const entry of mockedReplyOptionEvents) {
if (entry.kind === "item") {
await params.replyOptions?.onItemEvent?.({ progressText: entry.progressText });
await params.replyOptions?.onItemEvent?.({
kind: entry.itemKind,
progressText: entry.progressText,
summary: entry.summary,
title: entry.title,
name: entry.name,
phase: entry.phase,
status: entry.status,
meta: entry.meta,
});
} else {
await params.replyOptions?.onPartialReply?.({ text: entry.text });
}
@@ -749,6 +831,34 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
);
});
it("can hide raw Slack command progress text by config", async () => {
const draftStream = createDraftStreamStub();
createSlackDraftStreamMock.mockReturnValueOnce(draftStream);
mockedSlackStreamingMode = "progress";
mockedSlackDraftMode = "status_final";
mockedDispatchSequence = [];
mockedReplyOptionEvents = [
{
kind: "item",
itemKind: "command",
name: "exec",
progressText: "exec pnpm test -- --watch=false",
},
{ kind: "item", progressText: "done" },
];
await dispatchPreparedSlackMessage(
createPreparedSlackMessage({
accountConfig: {
streaming: { mode: "progress", progress: { label: "Shelling", commandText: "status" } },
},
}),
);
expect(draftStream.update).toHaveBeenCalledWith("Shelling\n• 🛠️ Exec\n• done");
expect(draftStream.update.mock.calls.flat().join("\n")).not.toContain("pnpm test");
});
it("suppresses standalone Slack tool progress when progress lines are disabled", async () => {
mockedSlackStreamingMode = "progress";
mockedSlackDraftMode = "status_final";

View File

@@ -14,6 +14,7 @@ import {
} from "openclaw/plugin-sdk/channel-reply-pipeline";
import {
buildChannelProgressDraftLine,
buildChannelProgressDraftLineForEntry,
createChannelProgressDraftGate,
formatChannelProgressDraftText,
isChannelProgressDraftWorkToolName,
@@ -1108,7 +1109,8 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
await statusReactions.setTool(payload.name);
}
await pushPreviewToolProgress(
buildChannelProgressDraftLine(
buildChannelProgressDraftLineForEntry(
account.config,
{
event: "tool",
name: payload.name,
@@ -1122,7 +1124,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
},
onItemEvent: async (payload) => {
await pushPreviewToolProgress(
buildChannelProgressDraftLine({
buildChannelProgressDraftLineForEntry(account.config, {
event: "item",
itemKind: payload.kind,
title: payload.title,

View File

@@ -780,14 +780,14 @@ describe("dispatchTelegramMessage draft streaming", () => {
);
});
it("keeps the Telegram progress draft across post-tool assistant boundaries", async () => {
it("keeps non-command Telegram progress draft lines across post-tool assistant boundaries", async () => {
const draftStream = createSequencedDraftStream(2001);
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
async ({ dispatcherOptions, replyOptions }) => {
await replyOptions?.onReplyStart?.();
await replyOptions?.onAssistantMessageStart?.();
await replyOptions?.onItemEvent?.({ progressText: "exec ls ~/Desktop" });
await replyOptions?.onItemEvent?.({ kind: "search", progressText: "docs lookup" });
await replyOptions?.onItemEvent?.({ progressText: "tests passed" });
await replyOptions?.onAssistantMessageStart?.();
await dispatcherOptions.deliver({ text: "Final after tool" }, { kind: "final" });
@@ -802,7 +802,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
});
expect(draftStream.update).toHaveBeenCalledWith(
expect.stringMatching(/^Shelling\n• `exec ls ~\/Desktop`\n• `tests passed`$/),
expect.stringMatching(/^Shelling\n`🔎 Web Search: docs lookup`\n• `tests passed`$/),
);
expect(draftStream.forceNewMessage).not.toHaveBeenCalled();
expect(draftStream.materialize).not.toHaveBeenCalled();
@@ -815,19 +815,23 @@ describe("dispatchTelegramMessage draft streaming", () => {
expect(draftStream.clear).not.toHaveBeenCalled();
});
it("streams Telegram tool progress by default when preview streaming is active", async () => {
it("streams Telegram command progress text by default when preview streaming is active", async () => {
const draftStream = createDraftStream();
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
await replyOptions?.onItemEvent?.({ progressText: "exec ls ~/Desktop" });
await replyOptions?.onItemEvent?.({
kind: "command",
name: "exec",
progressText: "exec ls ~/Desktop",
});
return { queuedFinal: false };
});
await dispatchWithContext({ context: createContext(), streamMode: "partial" });
expect(draftStream.update).toHaveBeenCalledWith(
expect.stringMatching(/\n`🛠️ Exec`\n• `exec ls ~\/Desktop`$/),
expect.stringMatching(/\n`🛠️ Exec`\n`🛠️ Exec: exec ls ~\/Desktop`$/),
);
expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledWith(
expect.objectContaining({
@@ -838,6 +842,36 @@ describe("dispatchTelegramMessage draft streaming", () => {
);
});
it("can hide Telegram command progress text by config", async () => {
const draftStream = createDraftStream();
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
await replyOptions?.onItemEvent?.({
kind: "command",
name: "exec",
progressText: "exec ls ~/Desktop",
});
return { queuedFinal: false };
});
await dispatchWithContext({
context: createContext(),
streamMode: "partial",
telegramCfg: { streaming: { mode: "partial", preview: { commandText: "status" } } },
});
expect(draftStream.update).toHaveBeenCalledWith(expect.stringMatching(/\n`🛠️ Exec`$/));
expect(draftStream.update.mock.calls.at(-1)?.[0]).not.toContain("exec ls");
expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledWith(
expect.objectContaining({
replyOptions: expect.objectContaining({
suppressDefaultToolProgressMessages: true,
}),
}),
);
});
it("suppresses Telegram tool progress when explicitly disabled", async () => {
const draftStream = createDraftStream();
createTelegramDraftStream.mockReturnValue(draftStream);
@@ -882,12 +916,15 @@ describe("dispatchTelegramMessage draft streaming", () => {
);
});
it("keeps Telegram tool progress links inside code formatting", async () => {
it("keeps non-command Telegram tool progress links inside code formatting", async () => {
const draftStream = createDraftStream();
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
await replyOptions?.onItemEvent?.({ progressText: "read [label](tg://user?id=123)" });
await replyOptions?.onItemEvent?.({
kind: "search",
progressText: "read [label](tg://user?id=123)",
});
return { queuedFinal: false };
});
@@ -897,7 +934,9 @@ describe("dispatchTelegramMessage draft streaming", () => {
});
const lastPreviewText = draftStream.update.mock.calls.at(-1)?.[0];
expect(lastPreviewText).toMatch(/\n`🛠️ Exec`\n• `read \[label\]\(tg:\/\/user\?id=123\)`$/);
expect(lastPreviewText).toMatch(
/\n`🛠️ Exec`\n`🔎 Web Search: read \[label\]\(tg:\/\/user\?id=123\)`$/,
);
expect(renderTelegramHtmlText(lastPreviewText ?? "")).not.toContain("<a ");
expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledWith(
expect.objectContaining({
@@ -927,7 +966,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
const progressLine = lastPreviewText.split("\n").at(1) ?? "";
expect(lastPreviewText.length).toBeLessThan(340);
expect(progressLine).toMatch(/^• `'{10}/);
expect(progressLine).toMatch(/^• `.*…`$/);
expect(progressLine).toContain("…");
expect(renderTelegramHtmlText(lastPreviewText)).not.toContain("<a ");
});

View File

@@ -9,6 +9,7 @@ import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pi
import {
createChannelProgressDraftGate,
formatChannelProgressDraftLine,
formatChannelProgressDraftLineForEntry,
formatChannelProgressDraftText,
isChannelProgressDraftWorkToolName,
resolveChannelProgressDraftMaxLines,
@@ -1173,7 +1174,8 @@ export const dispatchTelegramMessage = async ({
await statusReactionController.setTool(toolName);
}
await pushPreviewToolProgress(
formatChannelProgressDraftLine(
formatChannelProgressDraftLineForEntry(
telegramCfg,
{
event: "tool",
name: toolName,
@@ -1187,7 +1189,7 @@ export const dispatchTelegramMessage = async ({
},
onItemEvent: async (payload) => {
await pushPreviewToolProgress(
formatChannelProgressDraftLine({
formatChannelProgressDraftLineForEntry(telegramCfg, {
event: "item",
itemKind: payload.kind,
title: payload.title,

View File

@@ -73,6 +73,10 @@ export const telegramChannelConfigUiHints = {
label: "Telegram Draft Tool Progress",
help: "Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview.",
},
"streaming.preview.commandText": {
label: "Telegram Draft Command Text",
help: 'Command/exec detail in preview tool-progress lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"streaming.progress.label": {
label: "Telegram Progress Label",
help: 'Initial progress draft title. Use "auto" for built-in single-word labels, a custom string, or false to hide the title.',
@@ -89,6 +93,10 @@ export const telegramChannelConfigUiHints = {
label: "Telegram Progress Tool Lines",
help: "Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery.",
},
"streaming.progress.commandText": {
label: "Telegram Progress Command Text",
help: 'Command/exec detail in progress draft lines: "raw" preserves released behavior; "status" shows only the tool label.',
},
"retry.attempts": {
label: "Telegram Retry Attempts",
help: "Max retry attempts for outbound Telegram API calls (default: 3).",