Skip to content

Commit 5dad685

Browse files
fix(twitch): strip internal tool-trace banners from outbound text
Co-authored-by: Masato Hoshino <[email protected]>
1 parent 129e279 commit 5dad685

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Twitch outbound must strip assistant internal tool-trace scaffolding before
2+
// delivery (#90684). The hook runs in core delivery before chunk planning, so
3+
// the 500-char Twitch chunker only ever sees sanitized text.
4+
import { describe, expect, it } from "vitest";
5+
import { twitchPlugin } from "./plugin.js";
6+
7+
function sanitizeOutboundText(text: string): string {
8+
const sanitizeText = twitchPlugin.outbound?.sanitizeText;
9+
if (!sanitizeText) {
10+
throw new Error("Expected Twitch outbound sanitizeText hook");
11+
}
12+
return sanitizeText({ text, payload: { text } });
13+
}
14+
15+
describe("twitch outbound sanitizeText", () => {
16+
it("strips internal tool-trace banners before outbound delivery", () => {
17+
const text = "Done.\n⚠️ 🛠️ `search repos (agent)` failed";
18+
19+
expect(sanitizeOutboundText(text)).toBe("Done.");
20+
});
21+
22+
it("strips XML tool-call scaffolding leaked into assistant text", () => {
23+
const text = '<tool_call>{"name":"exec"}</tool_call>Stream is live.';
24+
25+
expect(sanitizeOutboundText(text)).toBe("Stream is live.");
26+
});
27+
28+
it("preserves ordinary assistant prose while sanitizing", () => {
29+
const text = "The pipeline has 3 open deals.";
30+
31+
expect(sanitizeOutboundText(text)).toBe(text);
32+
});
33+
});

extensions/twitch/src/outbound.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type MessageReceiptPartKind,
1313
} from "openclaw/plugin-sdk/channel-outbound";
1414
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
15+
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
1516
import { resolveTwitchAccountContext } from "./config.js";
1617
import { sendMessageTwitchInternal } from "./send.js";
1718
import type {
@@ -43,6 +44,9 @@ export const twitchOutbound: ChannelOutboundAdapter = {
4344
/** Twitch chat message limit is 500 characters */
4445
textChunkLimit: 500,
4546

47+
/** Strip internal assistant tool-trace scaffolding before delivery */
48+
sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text),
49+
4650
/** Word-boundary chunker with markdown stripping */
4751
chunker: chunkTextForTwitch,
4852

0 commit comments

Comments
 (0)