
























@@ -5,6 +5,7 @@ import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion";
55import { asOptionalRecord as readRecord } from "@openclaw/normalization-core/record-coerce";
66import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
77import { OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE } from "../agents/internal-runtime-context.js";
8+import { STREAM_ERROR_FALLBACK_TEXT } from "../agents/stream-message-shared.js";
89import { isHeartbeatOkResponse, isHeartbeatUserMessage } from "../auto-reply/heartbeat-filter.js";
910import { HEARTBEAT_PROMPT } from "../auto-reply/heartbeat.js";
1011import { extractCanvasFromText } from "../chat/canvas-render.js";
@@ -1595,16 +1596,119 @@ function projectSessionsSendInterSessionMessages(
15951596return changed ? projected : messages;
15961597}
159715981599+const GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT = "The agent run failed before producing a reply.";
1600+1601+function sanitizeAssistantErrorDisplayMessage(
1602+message: Record<string, unknown>,
1603+): Record<string, unknown> {
1604+const { content, ...envelope } = message;
1605+const next = sanitizeChatHistoryMessage(envelope, Number.MAX_SAFE_INTEGER).message as Record<
1606+string,
1607+unknown
1608+>;
1609+next.content = Array.isArray(content)
1610+ ? content
1611+.map(
1612+(block) =>
1613+sanitizeChatHistoryContentBlock(block, { maxChars: Number.MAX_SAFE_INTEGER }).block,
1614+)
1615+.filter((block) => {
1616+if (!block || typeof block !== "object" || Array.isArray(block)) {
1617+return true;
1618+}
1619+const type = (block as { type?: unknown }).type;
1620+return type !== "thinking" && type !== "reasoning" && type !== "redacted_thinking";
1621+})
1622+ : content;
1623+delete next.diagnostics;
1624+delete next.errorBody;
1625+delete next.errorCode;
1626+delete next.errorMessage;
1627+delete next.errorType;
1628+return next;
1629+}
1630+1631+function projectEmptyAssistantErrorMessages(
1632+messages: Array<Record<string, unknown>>,
1633+): Array<Record<string, unknown>> {
1634+let changed = false;
1635+const projected = messages.map((message) => {
1636+if (message.role !== "assistant" || message.stopReason !== "error") {
1637+return message;
1638+}
1639+const hasDisplayableStructuredContent =
1640+Array.isArray(message.content) &&
1641+message.content.some((block) => {
1642+if (!block || typeof block !== "object" || Array.isArray(block)) {
1643+return false;
1644+}
1645+const type = (block as { type?: unknown }).type;
1646+return (
1647+type !== "text" &&
1648+type !== "thinking" &&
1649+type !== "reasoning" &&
1650+type !== "redacted_thinking"
1651+);
1652+});
1653+if (hasDisplayableStructuredContent) {
1654+changed = true;
1655+return sanitizeAssistantErrorDisplayMessage(message);
1656+}
1657+const sanitized = sanitizeChatHistoryMessage(message, Number.MAX_SAFE_INTEGER)
1658+.message as Record<string, unknown>;
1659+const visibleTexts: string[] = [];
1660+if (typeof sanitized.content === "string") {
1661+visibleTexts.push(sanitized.content);
1662+} else if (Array.isArray(sanitized.content)) {
1663+for (const block of sanitized.content) {
1664+if (!block || typeof block !== "object" || Array.isArray(block)) {
1665+continue;
1666+}
1667+const entry = block as { type?: unknown; text?: unknown };
1668+if (entry.type === "text" && typeof entry.text === "string") {
1669+visibleTexts.push(entry.text);
1670+}
1671+}
1672+}
1673+if (typeof sanitized.text === "string") {
1674+visibleTexts.push(sanitized.text);
1675+}
1676+const nonEmptyVisibleTexts = visibleTexts.map((text) => text.trim()).filter(Boolean);
1677+const hasVisibleReplyText = nonEmptyVisibleTexts.some(
1678+(text) => text !== STREAM_ERROR_FALLBACK_TEXT && !isSuppressedControlReplyText(text),
1679+);
1680+if (!shouldDropAssistantHistoryMessage(sanitized) && hasVisibleReplyText) {
1681+changed = true;
1682+return sanitizeAssistantErrorDisplayMessage(message);
1683+}
1684+changed = true;
1685+const next: Record<string, unknown> = {
1686+ ...sanitized,
1687+content: [{ type: "text", text: GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT }],
1688+};
1689+delete next.diagnostics;
1690+delete next.errorBody;
1691+delete next.errorCode;
1692+delete next.errorMessage;
1693+delete next.errorType;
1694+delete next.phase;
1695+delete next.text;
1696+return next;
1697+});
1698+return changed ? projected : messages;
1699+}
1700+15981701export function projectChatDisplayMessages(
15991702messages: unknown[],
16001703options?: { maxChars?: number; stripEnvelope?: boolean },
16011704): Array<Record<string, unknown>> {
16021705const source = options?.stripEnvelope === false ? messages : stripEnvelopeFromMessages(messages);
16031706const mirrored = mirrorMessageToolVisibleReplies(source);
1707+const projectedErrors = projectEmptyAssistantErrorMessages(toProjectedMessages(mirrored));
16041708const projectedForwarded = mergeTtsSupplementMessages(
16051709filterVisibleProjectedHistoryMessages(
16061710projectSessionsSendInterSessionMessages(
1607-toProjectedMessages(sanitizeChatHistoryMessages(mirrored, Number.MAX_SAFE_INTEGER)),
1711+toProjectedMessages(sanitizeChatHistoryMessages(projectedErrors, Number.MAX_SAFE_INTEGER)),
16081712),
16091713),
16101714);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。