diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb258e7959..3ccec3cfcf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 (`&`, `"`, `<`, `>`, 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. diff --git a/src/agents/pi-embedded-utils.test.ts b/src/agents/pi-embedded-utils.test.ts index 5e8a9f39b8e..6a5ce710c85 100644 --- a/src/agents/pi-embedded-utils.test.ts +++ b/src/agents/pi-embedded-utils.test.ts @@ -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: "hellook" }], + 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]; diff --git a/src/agents/pi-embedded-utils.ts b/src/agents/pi-embedded-utils.ts index 82ad3efc03d..21a4eb39fd5 100644 --- a/src/agents/pi-embedded-utils.ts +++ b/src/agents/pi-embedded-utils.ts @@ -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;