
























@@ -1,6 +1,7 @@
11import { EmbeddedBlockChunker } from "openclaw/plugin-sdk/agent-runtime";
22import {
33createChannelProgressDraftGate,
4+type ChannelProgressDraftLine,
45formatChannelProgressDraftText,
56isChannelProgressDraftWorkToolName,
67resolveChannelProgressDraftMaxLines,
@@ -81,7 +82,7 @@ export function createDiscordDraftPreviewController(params: {
8182 previewToolProgressEnabled,
8283});
8384let previewToolProgressSuppressed = false;
84-let previewToolProgressLines: string[] = [];
85+let previewToolProgressLines: Array<string | ChannelProgressDraftLine> = [];
8586let reasoningProgressRawText = "";
8687let lastReasoningProgressLine: string | undefined;
8788const progressSeed = `${params.accountId}:${params.deliverChannelId}`;
@@ -94,6 +95,8 @@ export function createDiscordDraftPreviewController(params: {
9495entry: params.discordConfig,
9596lines: previewToolProgressLines,
9697seed: progressSeed,
98+labelPlacement: "line",
99+formatStructuredLine: formatDiscordProgressDraftLine,
97100});
98101if (!previewText || previewText === lastPartialText) {
99102return;
@@ -156,7 +159,10 @@ export function createDiscordDraftPreviewController(params: {
156159}
157160await progressDraftGate.startNow();
158161},
159-async pushToolProgress(line?: string, options?: { toolName?: string }) {
162+async pushToolProgress(
163+line?: string | ChannelProgressDraftLine,
164+options?: { toolName?: string },
165+) {
160166if (!draftStream) {
161167return;
162168}
@@ -166,25 +172,32 @@ export function createDiscordDraftPreviewController(params: {
166172) {
167173return;
168174}
169-const normalized = line?.replace(/\s+/g, " ").trim();
175+if (isEmptyDiscordProgressLine(line)) {
176+return;
177+}
178+const normalized = normalizeProgressLineIdentity(line);
170179if (!normalized) {
171180return;
172181}
182+const progressLine: string | ChannelProgressDraftLine =
183+typeof line === "object" && line !== undefined ? line : normalized;
173184if (discordStreamMode !== "progress") {
174185if (!previewToolProgressEnabled || previewToolProgressSuppressed) {
175186return;
176187}
177-const previous = previewToolProgressLines.at(-1);
188+const previous = normalizeProgressLineIdentity(previewToolProgressLines.at(-1));
178189if (previous === normalized) {
179190return;
180191}
181-previewToolProgressLines = [...previewToolProgressLines, normalized].slice(
192+previewToolProgressLines = [...previewToolProgressLines, progressLine].slice(
182193-resolveChannelProgressDraftMaxLines(params.discordConfig),
183194);
184195const previewText = formatChannelProgressDraftText({
185196entry: params.discordConfig,
186197lines: previewToolProgressLines,
187198seed: progressSeed,
199+labelPlacement: "line",
200+formatStructuredLine: formatDiscordProgressDraftLine,
188201});
189202lastPartialText = previewText;
190203draftText = previewText;
@@ -194,15 +207,19 @@ export function createDiscordDraftPreviewController(params: {
194207return;
195208}
196209if (previewToolProgressEnabled && !previewToolProgressSuppressed && normalized) {
197-const previous = previewToolProgressLines.at(-1);
210+const previous = normalizeProgressLineIdentity(previewToolProgressLines.at(-1));
198211if (previous !== normalized) {
199-previewToolProgressLines = [...previewToolProgressLines, normalized].slice(
212+previewToolProgressLines = [...previewToolProgressLines, progressLine].slice(
200213-resolveChannelProgressDraftMaxLines(params.discordConfig),
201214);
202215}
203216}
204217const alreadyStarted = progressDraftGate.hasStarted;
205-await progressDraftGate.noteWork();
218+if (shouldStartDiscordProgressDraftNow(line)) {
219+await progressDraftGate.startNow();
220+} else {
221+await progressDraftGate.noteWork();
222+}
206223if (alreadyStarted && progressDraftGate.hasStarted) {
207224await renderProgressDraft();
208225}
@@ -392,3 +409,42 @@ function mergeReasoningProgressText(current: string, incoming: string): string {
392409function isReasoningSnapshotText(text: string): boolean {
393410return /^\s*(?:>\s*)?Reasoning:\s*/i.test(text);
394411}
412+413+function normalizeProgressLineIdentity(
414+line: string | ChannelProgressDraftLine | undefined,
415+): string {
416+const text = typeof line === "string" ? line : line?.text;
417+return text?.replace(/\s+/g, " ").trim() ?? "";
418+}
419+420+function isEmptyDiscordProgressLine(line: string | ChannelProgressDraftLine | undefined): boolean {
421+if (!line || typeof line === "string") {
422+return false;
423+}
424+return line.toolName === "apply_patch" && !line.detail && !line.status;
425+}
426+427+function shouldStartDiscordProgressDraftNow(
428+line: string | ChannelProgressDraftLine | undefined,
429+): boolean {
430+return typeof line === "object" && line?.kind === "patch" && Boolean(line.detail);
431+}
432+433+function formatDiscordProgressDraftLine(line: ChannelProgressDraftLine): string {
434+const icon = line.icon?.trim();
435+const prefix = icon ? `${icon} ` : "";
436+const detail = line.detail?.trim();
437+if (detail) {
438+return `${prefix}${detail}`;
439+}
440+const status = line.status?.trim();
441+if (status) {
442+return `${prefix}${status}`;
443+}
444+const text = line.text.trim();
445+const label = line.label.trim();
446+if (!icon && text && text !== label) {
447+return text;
448+}
449+return `${prefix}${label}`.trim();
450+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。