fix(agents): guard promoteThinkingTagsToBlocks against malformed content entries (#35143)

Merged via squash.

Prepared head SHA: 3971122f5f
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
This commit is contained in:
Sid
2026-03-05 13:37:33 +08:00
committed by GitHub
parent ce0c13191f
commit d9b69a6145
3 changed files with 42 additions and 1 deletions

View File

@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
- Agents/schema cleaning: detect Venice + Grok model IDs as xAI-proxied targets so unsupported JSON Schema keywords are stripped before requests, preventing Venice/Grok `Invalid arguments` failures. (openclaw#35355) thanks @Sid-Qin.
- Skills/native command deduplication: centralize skill command dedupe by canonical `skillName` in `listSkillCommandsForAgents` so duplicate suffixed variants (for example `_2`) are no longer surfaced across interfaces outside Discord. (#27521) thanks @shivama205.
- Agents/xAI tool-call argument decoding: decode HTML-entity encoded xAI/Grok tool-call argument values (`&amp;`, `&quot;`, `&lt;`, `&gt;`, numeric entities) before tool execution so commands with shell operators and quotes no longer fail with parse errors. (#35276) Thanks @Sid-Qin.
- Agents/thinking-tag promotion hardening: guard `promoteThinkingTagsToBlocks` against malformed assistant content entries (`null`/`undefined`) before `block.type` reads so malformed provider payloads no longer crash session processing while preserving pass-through behavior. (#35143) thanks @Sid-Qin.
- Feishu/streaming card delivery synthesis: unify snapshot and delta streaming merge semantics, apply overlap-aware final merge, suppress duplicate final text delivery (including text+media final packets), prefer topic-thread `message.reply` routing when a reply target exists, and tune card print cadence to avoid duplicate incremental rendering. (from #33245, #32896, #33840) Thanks @rexl2018, @kcinzgg, and @aerelune.
- Security/dependency audit: patch transitive Hono vulnerabilities by pinning `hono` to `4.12.5` and `@hono/node-server` to `1.19.10` in production resolution paths. Thanks @shakkernerd.
- Security/dependency audit: bump `tar` to `7.5.10` (from `7.5.9`) to address the high-severity hardlink path traversal advisory (`GHSA-qffp-2rhf-9h96`). Thanks @shakkernerd.

View File

@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
import {
extractAssistantText,
formatReasoningMessage,
promoteThinkingTagsToBlocks,
stripDowngradedToolCallText,
} from "./pi-embedded-utils.js";
@@ -549,6 +550,39 @@ describe("stripDowngradedToolCallText", () => {
});
});
describe("promoteThinkingTagsToBlocks", () => {
it("does not crash on malformed null content entries", () => {
const msg = makeAssistantMessage({
role: "assistant",
content: [null as never, { type: "text", text: "<thinking>hello</thinking>ok" }],
timestamp: Date.now(),
});
expect(() => promoteThinkingTagsToBlocks(msg)).not.toThrow();
const types = msg.content.map((b: { type?: string }) => b?.type);
expect(types).toContain("thinking");
expect(types).toContain("text");
});
it("does not crash on undefined content entries", () => {
const msg = makeAssistantMessage({
role: "assistant",
content: [undefined as never, { type: "text", text: "no tags here" }],
timestamp: Date.now(),
});
expect(() => promoteThinkingTagsToBlocks(msg)).not.toThrow();
});
it("passes through well-formed content unchanged when no thinking tags", () => {
const msg = makeAssistantMessage({
role: "assistant",
content: [{ type: "text", text: "hello world" }],
timestamp: Date.now(),
});
promoteThinkingTagsToBlocks(msg);
expect(msg.content).toEqual([{ type: "text", text: "hello world" }]);
});
});
describe("empty input handling", () => {
it("returns empty string", () => {
const helpers = [formatReasoningMessage, stripDowngradedToolCallText];

View File

@@ -333,7 +333,9 @@ export function promoteThinkingTagsToBlocks(message: AssistantMessage): void {
if (!Array.isArray(message.content)) {
return;
}
const hasThinkingBlock = message.content.some((block) => block.type === "thinking");
const hasThinkingBlock = message.content.some(
(block) => block && typeof block === "object" && block.type === "thinking",
);
if (hasThinkingBlock) {
return;
}
@@ -342,6 +344,10 @@ export function promoteThinkingTagsToBlocks(message: AssistantMessage): void {
let changed = false;
for (const block of message.content) {
if (!block || typeof block !== "object" || !("type" in block)) {
next.push(block);
continue;
}
if (block.type !== "text") {
next.push(block);
continue;