11import type { AgentMessage } from "openclaw/plugin-sdk/agent-core" ;
22import { describe , expect , it } from "vitest" ;
33import {
4+ DEFAULT_MISSING_TOOL_RESULT_TEXT ,
45 sanitizeToolCallInputs ,
56 sanitizeToolUseResultPairing ,
67 repairToolUseResultPairing ,
@@ -418,15 +419,12 @@ describe("sanitizeToolUseResultPairing", () => {
418419} ) ;
419420
420421describe ( "repairToolUseResultPairing prefers real result over synthetic error" , ( ) => {
421- const SYNTHETIC_TEXT =
422- "[openclaw] missing tool result in session history; inserted synthetic error result for transcript repair." ;
423-
424422 function makeSyntheticResult ( toolCallId : string ) {
425423 return {
426424 role : "toolResult" as const ,
427425 toolCallId,
428426 toolName : "read" ,
429- content : [ { type : "text" , text : SYNTHETIC_TEXT } ] ,
427+ content : [ { type : "text" , text : DEFAULT_MISSING_TOOL_RESULT_TEXT } ] ,
430428 isError : true ,
431429 } ;
432430 }
@@ -457,12 +455,13 @@ describe("repairToolUseResultPairing prefers real result over synthetic error",
457455
458456 const result = repairToolUseResultPairing ( input ) ;
459457
460- const toolResult = result . messages . find ( ( m ) => m . role === "toolResult" ) as {
458+ const toolResults = result . messages . filter ( ( m ) => m . role === "toolResult" ) as Array < {
461459 isError ?: boolean ;
462460 content ?: Array < { text ?: string } > ;
463- } ;
464- expect ( toolResult ?. isError ) . not . toBe ( true ) ;
465- expect ( toolResult ?. content ?. [ 0 ] ?. text ) . toBe ( "real output" ) ;
461+ } > ;
462+ expect ( toolResults ) . toHaveLength ( 1 ) ;
463+ expect ( toolResults [ 0 ] ?. isError ) . not . toBe ( true ) ;
464+ expect ( toolResults [ 0 ] ?. content ?. [ 0 ] ?. text ) . toBe ( "real output" ) ;
466465 } ) ;
467466
468467 it ( "real first, synthetic second → keeps real" , ( ) => {
@@ -474,12 +473,13 @@ describe("repairToolUseResultPairing prefers real result over synthetic error",
474473
475474 const result = repairToolUseResultPairing ( input ) ;
476475
477- const toolResult = result . messages . find ( ( m ) => m . role === "toolResult" ) as {
476+ const toolResults = result . messages . filter ( ( m ) => m . role === "toolResult" ) as Array < {
478477 isError ?: boolean ;
479478 content ?: Array < { text ?: string } > ;
480- } ;
481- expect ( toolResult ?. isError ) . not . toBe ( true ) ;
482- expect ( toolResult ?. content ?. [ 0 ] ?. text ) . toBe ( "real output" ) ;
479+ } > ;
480+ expect ( toolResults ) . toHaveLength ( 1 ) ;
481+ expect ( toolResults [ 0 ] ?. isError ) . not . toBe ( true ) ;
482+ expect ( toolResults [ 0 ] ?. content ?. [ 0 ] ?. text ) . toBe ( "real output" ) ;
483483 } ) ;
484484
485485 it ( "two real results → keeps first (unchanged behavior)" , ( ) => {
@@ -491,10 +491,11 @@ describe("repairToolUseResultPairing prefers real result over synthetic error",
491491
492492 const result = repairToolUseResultPairing ( input ) ;
493493
494- const toolResult = result . messages . find ( ( m ) => m . role === "toolResult" ) as {
494+ const toolResults = result . messages . filter ( ( m ) => m . role === "toolResult" ) as Array < {
495495 content ?: Array < { text ?: string } > ;
496- } ;
497- expect ( toolResult ?. content ?. [ 0 ] ?. text ) . toBe ( "first real" ) ;
496+ } > ;
497+ expect ( toolResults ) . toHaveLength ( 1 ) ;
498+ expect ( toolResults [ 0 ] ?. content ?. [ 0 ] ?. text ) . toBe ( "first real" ) ;
498499 } ) ;
499500
500501 it ( "two synthetic errors → keeps first (unchanged behavior)" , ( ) => {
@@ -506,12 +507,13 @@ describe("repairToolUseResultPairing prefers real result over synthetic error",
506507
507508 const result = repairToolUseResultPairing ( input ) ;
508509
509- const toolResult = result . messages . find ( ( m ) => m . role === "toolResult" ) as {
510+ const toolResults = result . messages . filter ( ( m ) => m . role === "toolResult" ) as Array < {
510511 isError ?: boolean ;
511512 content ?: Array < { text ?: string } > ;
512- } ;
513- expect ( toolResult ?. isError ) . toBe ( true ) ;
514- expect ( toolResult ?. content ?. [ 0 ] ?. text ) . toBe ( SYNTHETIC_TEXT ) ;
513+ } > ;
514+ expect ( toolResults ) . toHaveLength ( 1 ) ;
515+ expect ( toolResults [ 0 ] ?. isError ) . toBe ( true ) ;
516+ expect ( toolResults [ 0 ] ?. content ?. [ 0 ] ?. text ) . toBe ( DEFAULT_MISSING_TOOL_RESULT_TEXT ) ;
515517 } ) ;
516518
517519 it ( "span-level: synthetic then real in span → picks real" , ( ) => {
@@ -532,6 +534,47 @@ describe("repairToolUseResultPairing prefers real result over synthetic error",
532534 expect ( toolResults [ 0 ] ?. isError ) . not . toBe ( true ) ;
533535 expect ( toolResults [ 0 ] ?. content ?. [ 0 ] ?. text ) . toBe ( "real output" ) ;
534536 } ) ;
537+
538+ it ( "does not treat real error containing marker substring as synthetic" , ( ) => {
539+ const input = castAgentMessages ( [
540+ makeAssistant ( "call_1" ) ,
541+ {
542+ role : "toolResult" as const ,
543+ toolCallId : "call_1" ,
544+ toolName : "read" ,
545+ content : [
546+ {
547+ type : "text" ,
548+ text : DEFAULT_MISSING_TOOL_RESULT_TEXT + " (extra context from real error)" ,
549+ } ,
550+ ] ,
551+ isError : true ,
552+ } ,
553+ makeRealResult ( "call_1" ) ,
554+ ] ) ;
555+
556+ const result = repairToolUseResultPairing ( input ) ;
557+
558+ const toolResults = result . messages . filter ( ( m ) => m . role === "toolResult" ) as Array < {
559+ isError ?: boolean ;
560+ content ?: Array < { text ?: string } > ;
561+ } > ;
562+ expect ( toolResults ) . toHaveLength ( 1 ) ;
563+ expect ( toolResults [ 0 ] ?. content ?. [ 0 ] ?. text ) . toContain ( "extra context from real error" ) ;
564+ } ) ;
565+
566+ it ( "changed flag is true when duplicates are dropped" , ( ) => {
567+ const input = castAgentMessages ( [
568+ makeAssistant ( "call_1" ) ,
569+ makeRealResult ( "call_1" ) ,
570+ makeRealResult ( "call_1" , "duplicate" ) ,
571+ ] ) ;
572+
573+ const result = repairToolUseResultPairing ( input ) ;
574+
575+ expect ( result . messages ) . not . toBe ( input ) ;
576+ expect ( result . droppedDuplicateCount ) . toBeGreaterThan ( 0 ) ;
577+ } ) ;
535578} ) ;
536579
537580describe ( "sanitizeToolCallInputs legacy block filtering" , ( ) => {
0 commit comments