























@@ -152,6 +152,141 @@ describe("pruneContextMessages", () => {
152152).not.toThrow();
153153});
154154155+it("does not crash on toolResult with malformed text block (missing text string)", () => {
156+// Regression: a plugin returning undefined produces {type: "text"} with no text property,
157+// which crashed estimateTextAndImageChars / collectTextSegments / collectPrunableToolResultSegments.
158+// See https://github.com/openclaw/openclaw/issues/34979
159+const malformedToolResult = {
160+role: "toolResult",
161+toolName: "sentinel_control",
162+content: [{ type: "text" }],
163+isError: false,
164+timestamp: Date.now(),
165+} as unknown as AgentMessage;
166+167+const messages: AgentMessage[] = [
168+makeUser("remove sentinel"),
169+makeAssistant([
170+{ type: "toolCall", toolCallId: "call_1", toolName: "sentinel_control", arguments: {} },
171+] as unknown as AssistantContentBlock[]),
172+malformedToolResult,
173+makeUser("follow up"),
174+makeAssistant([{ type: "text", text: "done" }]),
175+];
176+177+expect(() =>
178+pruneContextMessages({
179+ messages,
180+settings: DEFAULT_CONTEXT_PRUNING_SETTINGS,
181+ctx: CONTEXT_WINDOW_1M,
182+}),
183+).not.toThrow();
184+});
185+186+it("does not crash on toolResult with malformed text block during soft-trim (image path)", () => {
187+// The collectPrunableToolResultSegments path is exercised when the tool result
188+// contains image blocks alongside a malformed text block.
189+const malformedToolResult = {
190+role: "toolResult",
191+toolName: "read",
192+content: [{ type: "text" }, { type: "image", data: "img", mimeType: "image/png" }],
193+timestamp: Date.now(),
194+} as unknown as AgentMessage;
195+196+const messages: AgentMessage[] = [
197+makeUser("show image"),
198+malformedToolResult,
199+makeAssistant([{ type: "text", text: "here it is" }]),
200+];
201+202+expect(() =>
203+pruneContextMessages({
204+ messages,
205+settings: {
206+ ...DEFAULT_CONTEXT_PRUNING_SETTINGS,
207+keepLastAssistants: 1,
208+softTrimRatio: 0,
209+hardClear: {
210+ ...DEFAULT_CONTEXT_PRUNING_SETTINGS.hardClear,
211+enabled: false,
212+},
213+softTrim: {
214+maxChars: 5_000,
215+headChars: 2_000,
216+tailChars: 2_000,
217+},
218+},
219+ctx: CONTEXT_WINDOW_1M,
220+isToolPrunable: () => true,
221+contextWindowTokensOverride: 1,
222+}),
223+).not.toThrow();
224+});
225+226+it("counts malformed non-string text blocks when deciding to trim tool results", () => {
227+const malformedToolResult = {
228+role: "toolResult",
229+toolName: "read",
230+content: [{ type: "text", text: { payload: "X".repeat(5_000) } }],
231+timestamp: Date.now(),
232+} as unknown as AgentMessage;
233+234+const result = pruneContextMessages({
235+messages: [
236+makeUser("show data"),
237+malformedToolResult,
238+makeAssistant([{ type: "text", text: "done" }]),
239+],
240+settings: {
241+ ...DEFAULT_CONTEXT_PRUNING_SETTINGS,
242+keepLastAssistants: 1,
243+softTrimRatio: 0,
244+hardClear: {
245+ ...DEFAULT_CONTEXT_PRUNING_SETTINGS.hardClear,
246+enabled: false,
247+},
248+softTrim: {
249+maxChars: 200,
250+headChars: 80,
251+tailChars: 40,
252+},
253+},
254+ctx: CONTEXT_WINDOW_1M,
255+isToolPrunable: () => true,
256+contextWindowTokensOverride: 1,
257+});
258+259+const toolResult = result.find((message) => message.role === "toolResult") as Extract<
260+AgentMessage,
261+{ role: "toolResult" }
262+>;
263+const textBlock = toolResult.content[0] as { type: "text"; text: string };
264+expect(textBlock.text).toContain("[Tool result trimmed:");
265+});
266+267+it("does not crash on toolResult with null content entries", () => {
268+const malformedToolResult = {
269+role: "toolResult",
270+toolName: "read",
271+content: [null, { type: "text", text: "ok" }],
272+timestamp: Date.now(),
273+} as unknown as AgentMessage;
274+275+const messages: AgentMessage[] = [
276+makeUser("hello"),
277+malformedToolResult,
278+makeAssistant([{ type: "text", text: "done" }]),
279+];
280+281+expect(() =>
282+pruneContextMessages({
283+ messages,
284+settings: DEFAULT_CONTEXT_PRUNING_SETTINGS,
285+ctx: CONTEXT_WINDOW_1M,
286+}),
287+).not.toThrow();
288+});
289+155290it("handles well-formed thinking blocks correctly", () => {
156291const messages: AgentMessage[] = [
157292makeUser("hello"),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。