Skip to content

Commit 84905d9

Browse files
committed
fix(discord): sanitize tool progress scaffolding
1 parent 1a40edf commit 84905d9

5 files changed

Lines changed: 66 additions & 5 deletions

File tree

extensions/discord/src/monitor/reply-delivery.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,33 @@ describe("deliverDiscordReply", () => {
176176
);
177177
});
178178

179+
it("strips assistant scaffolding from explicit tool progress payloads", async () => {
180+
await deliverDiscordReply({
181+
replies: [
182+
{
183+
text: [
184+
"<think>private reasoning</think>",
185+
'<tool_call>{"name":"x"}</tool_call>',
186+
"🛠️ run git status",
187+
].join("\n"),
188+
},
189+
],
190+
target: "channel:101",
191+
token: "token",
192+
accountId: "default",
193+
runtime,
194+
cfg,
195+
textLimit: 2000,
196+
kind: "tool",
197+
});
198+
199+
expect(sendDurableMessageBatchMock).toHaveBeenCalledWith(
200+
expect.objectContaining({
201+
payloads: [{ text: "🛠️ run git status" }],
202+
}),
203+
);
204+
});
205+
179206
it("strips internal execution trace lines at the final Discord send boundary", async () => {
180207
await deliverDiscordReply({
181208
replies: [

extensions/discord/src/monitor/reply-safety.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
22
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
3-
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
3+
import {
4+
sanitizeAssistantVisibleText,
5+
sanitizeAssistantVisibleTextWithProfile,
6+
} from "openclaw/plugin-sdk/text-chunking";
47
import { stripPlainTextToolCallBlocks } from "openclaw/plugin-sdk/tool-payload";
58

69
const DISCORD_INTERNAL_CHANNEL_LINE_RE =
@@ -71,7 +74,9 @@ export function sanitizeDiscordFrontChannelReplyPayloads(
7174
const safeText =
7275
typeof payload.text === "string"
7376
? preserveVerboseToolProgress
74-
? collapseExcessBlankLines(payload.text).trim()
77+
? collapseExcessBlankLines(
78+
sanitizeAssistantVisibleTextWithProfile(payload.text, "tool-progress"),
79+
).trim()
7580
: sanitizeDiscordFrontChannelText(payload.text)
7681
: payload.text;
7782
const nextPayload =

src/agents/embedded-agent-helpers.sanitizeuserfacingtext.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ describe("sanitizeUserFacingText", () => {
309309
"Visible intro.",
310310
"⚠️ 🛠️ `run openclaw definitely-not-a-real-subcommand (agent)` failed",
311311
"⚠️ 🛠️ gh search issues --repo openclaw/openclaw --state open --no-search-pages.jsonl /tmp/openclaw_open_unlabeled_current.json (agent) failed",
312+
"⚠️ 🛠️ gh search issues --repo openclaw/openclaw --state open (agent) failed: command timed out",
312313
"🛠️ run git status",
313314
"📖 Read: lines 1-40 from secret.md",
314315
"Visible outro.",

src/shared/text/assistant-visible-text.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,7 @@ describe("sanitizeAssistantVisibleText", () => {
835835
"Visible intro.",
836836
"⚠️ 🛠️ `run openclaw definitely-not-a-real-subcommand (agent)` failed",
837837
"⚠️ 🛠️ gh search issues --repo openclaw/openclaw --state open --no-search-pages.jsonl /tmp/openclaw_open_unlabeled_current.json (agent) failed",
838+
"⚠️ 🛠️ gh search issues --repo openclaw/openclaw --state open (agent) failed: command timed out",
838839
"🛠️ run git status",
839840
"Visible outro.",
840841
].join("\n");
@@ -905,4 +906,16 @@ describe("sanitizeAssistantVisibleTextWithProfile", () => {
905906
"[Tool Call: read (ID: toolu_1)]",
906907
);
907908
});
909+
910+
it("uses the tool-progress profile to strip scaffolding while preserving progress lines", () => {
911+
const input = [
912+
"<think>private reasoning</think>",
913+
'<tool_call>{"name":"x"}</tool_call>',
914+
"🛠️ run git status",
915+
].join("\n");
916+
917+
expect(sanitizeAssistantVisibleTextWithProfile(input, "tool-progress")).toBe(
918+
"🛠️ run git status",
919+
);
920+
});
908921
});

src/shared/text/assistant-visible-text.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const INTERNAL_TRACE_LINE_QUICK_RE =
1616
const INTERNAL_TRACE_LINE_RE =
1717
/^(?:>\s*)?(?:\s*)?(?:📊|🛠|📖|📝|🔍|🔎|)\s*(?:Session Status|Exec|Read|Edit|Write|Patch|Search|Open|Click|Find|Screenshot|Update Plan|Tool Call|Tool Result|Function Call|Shell|Command)\s*:/i;
1818
const INTERNAL_COMPACT_FAILURE_TRACE_LINE_RE =
19-
/^(?:>\s*)?\s*🛠\s+\S[\s\S]*\s+\(agent\)`{0,2}\s+failed\s*$/i;
19+
/^(?:>\s*)?\s*🛠\s+\S[\s\S]*\s+\(agent\)`{0,2}\s+failed(?:\s*:.*)?\s*$/i;
2020
const INTERNAL_COMPACT_COMMAND_TRACE_LINE_RE =
2121
/^(?:>\s*)?🛠\s*(?:(?:(?:elevated|pty)\b\s*(?:·|,)\s*)+)?(?:`{1,2}\s*\S|(?:run|check|fetch|pull|push|view|show|list|switch|create|merge|rebase|stage|restore|reset|stash|search|find|print|copy|move|remove|install|start|cd|git|pnpm|npm|yarn|bun|node|python|python3|bash|sh)\b)/i;
2222
const INTERNAL_CHANNEL_TRACE_LINE_RE =
@@ -798,14 +798,19 @@ export function stripAssistantInternalTraceLines(text: string): string {
798798
return result;
799799
}
800800

801-
export type AssistantVisibleTextSanitizerProfile = "delivery" | "history" | "internal-scaffolding";
801+
export type AssistantVisibleTextSanitizerProfile =
802+
| "delivery"
803+
| "history"
804+
| "internal-scaffolding"
805+
| "tool-progress";
802806

803807
type AssistantVisibleTextPipelineOptions = {
804808
finalTrim: ReasoningTagTrim;
805809
preserveDowngradedToolText?: boolean;
806810
preserveMinimaxToolXml?: boolean;
807811
stripFunctionCallsXmlPayloads?: boolean;
808812
stripFunctionResponseAfterPluralToolCalls?: boolean;
813+
stripInternalTraceLines?: boolean;
809814
reasoningMode: ReasoningTagMode;
810815
reasoningTrim: ReasoningTagTrim;
811816
stageOrder: "reasoning-first" | "reasoning-last";
@@ -836,6 +841,14 @@ const ASSISTANT_VISIBLE_TEXT_PIPELINE_OPTIONS: Record<
836841
reasoningTrim: "start",
837842
stageOrder: "reasoning-first",
838843
},
844+
"tool-progress": {
845+
finalTrim: "both",
846+
stripFunctionCallsXmlPayloads: true,
847+
stripInternalTraceLines: false,
848+
reasoningMode: "strict",
849+
reasoningTrim: "both",
850+
stageOrder: "reasoning-last",
851+
},
839852
};
840853

841854
function applyAssistantVisibleTextStagePipeline(
@@ -871,7 +884,9 @@ function applyAssistantVisibleTextStagePipeline(
871884
stripFunctionCallsXmlPayloads: options.stripFunctionCallsXmlPayloads,
872885
stripFunctionResponseAfterPluralToolCalls: options.stripFunctionResponseAfterPluralToolCalls,
873886
});
874-
cleaned = stripAssistantInternalTraceLines(cleaned);
887+
if (options.stripInternalTraceLines !== false) {
888+
cleaned = stripAssistantInternalTraceLines(cleaned);
889+
}
875890
cleaned = stripLegacyBracketToolCallBlocks(cleaned);
876891
cleaned = stripPlainTextToolCallBlocks(cleaned);
877892
if (!options.preserveDowngradedToolText) {

0 commit comments

Comments
 (0)