fix: tighten reply payload typing and safe text coercion

This commit is contained in:
joshavant 2026-03-31 22:52:36 -05:00
parent 05c311e67d
commit ed83d79a05
No known key found for this signature in database
GPG Key ID: 4463B60B0DD49BC4
4 changed files with 74 additions and 6 deletions

View File

@ -545,7 +545,28 @@ export function classifyFailoverReasonFromHttpStatus(
}
function coerceText(value: unknown): string {
return typeof value === "string" ? value : value == null ? "" : String(value);
if (typeof value === "string") {
return value;
}
if (value == null) {
return "";
}
if (
typeof value === "number" ||
typeof value === "boolean" ||
typeof value === "bigint" ||
typeof value === "symbol"
) {
return String(value);
}
if (typeof value === "object") {
try {
return JSON.stringify(value) ?? "";
} catch {
return "";
}
}
return "";
}
function stripFinalTagsFromText(text: unknown): string {

View File

@ -38,8 +38,30 @@ const stripTrailingDirective = (text: string): string => {
return text.slice(0, openIndex);
};
const coerceText = (value: unknown): string =>
typeof value === "string" ? value : value == null ? "" : String(value);
const coerceText = (value: unknown): string => {
if (typeof value === "string") {
return value;
}
if (value == null) {
return "";
}
if (
typeof value === "number" ||
typeof value === "boolean" ||
typeof value === "bigint" ||
typeof value === "symbol"
) {
return String(value);
}
if (typeof value === "object") {
try {
return JSON.stringify(value) ?? "";
} catch {
return "";
}
}
return "";
};
function isTranscriptOnlyOpenClawAssistantMessage(message: AgentMessage | undefined): boolean {
if (!message || message.role !== "assistant") {

View File

@ -145,7 +145,10 @@ describe("plugin-sdk/approval-renderers", () => {
},
},
])("$name", ({ payload, textExpected, interactiveExpected, channelDataExpected }) => {
textExpected(payload.text);
expect(payload.text).toBeDefined();
if (payload.text !== undefined) {
textExpected(payload.text);
}
if (interactiveExpected) {
expect(payload.interactive).toEqual(interactiveExpected);
}

View File

@ -8,8 +8,30 @@ export function extractTextFromChatContent(
): string | null {
const normalizeText = opts?.normalizeText ?? ((text: string) => text.replace(/\s+/g, " ").trim());
const joinWith = opts?.joinWith ?? " ";
const coerceText = (value: unknown): string =>
typeof value === "string" ? value : value == null ? "" : String(value);
const coerceText = (value: unknown): string => {
if (typeof value === "string") {
return value;
}
if (value == null) {
return "";
}
if (
typeof value === "number" ||
typeof value === "boolean" ||
typeof value === "bigint" ||
typeof value === "symbol"
) {
return String(value);
}
if (typeof value === "object") {
try {
return JSON.stringify(value) ?? "";
} catch {
return "";
}
}
return "";
};
const sanitize = (text: unknown): string => {
const raw = coerceText(text);
const sanitized = opts?.sanitizeText ? opts.sanitizeText(raw) : raw;