33import fs from "node:fs" ;
44import os from "node:os" ;
55import path from "node:path" ;
6+ import type { Message } from "openclaw/plugin-sdk/llm" ;
67import { SessionManager } from "openclaw/plugin-sdk/agent-sessions" ;
78import { describe , expect , it } from "vitest" ;
89import { makeAgentAssistantMessage } from "../test-helpers/agent-message-fixtures.js" ;
910import {
1011 rotateTranscriptAfterCompaction ,
1112} from "./compaction-successor-transcript.js" ;
1213
14+ function isMessageWithContent ( value : unknown ) : value is Message {
15+ return (
16+ typeof value === "object" &&
17+ value !== null &&
18+ "role" in value &&
19+ ( value as { role : unknown } ) . role !== "bashExecution"
20+ ) ;
21+ }
22+
1323function makeAssistant ( text : string , timestamp : number ) {
1424 return makeAgentAssistantMessage ( {
1525 content : [ { type : "text" , text } ] ,
1626 timestamp,
1727 } ) ;
1828}
1929
20- function getMessageText ( msg : { role : string ; content ?: unknown } ) : string {
30+ function getMessageText ( msg : Message ) : string {
2131 if ( typeof msg . content === "string" ) {
2232 return msg . content ;
2333 }
2434 if ( Array . isArray ( msg . content ) ) {
25- return msg . content . map ( ( b : Record < string , unknown > ) => String ( b . text ?? "" ) ) . join ( " " ) ;
35+ return msg . content
36+ . map ( ( b ) => ( b . type === "text" ? b . text : "" ) )
37+ . join ( " " ) ;
2638 }
2739 return "" ;
2840}
@@ -65,12 +77,15 @@ describe("prove-76729 — real behavior proof", () => {
6577 proof . push ( "Session branch entries:" ) ;
6678 for ( const entry of manager . getBranch ( ) ) {
6779 if ( entry . type === "message" ) {
68- const txt = typeof entry . message . content === "string"
69- ? entry . message . content
70- : entry . message . content ?. [ 0 ] ?. text ?? "" ;
71- proof . push ( ` ${ entry . message . role } [t=${ entry . message . timestamp } ]: ${ txt } ` ) ;
80+ if ( isMessageWithContent ( entry . message ) ) {
81+ const msg = entry . message ;
82+ const txt = getMessageText ( msg ) ;
83+ proof . push ( ` [${ msg . role } ] "${ txt } "` ) ;
84+ } else {
85+ proof . push ( ` [${ entry . message . role } ] (no text content)` ) ;
86+ }
7287 } else if ( entry . type === "compaction" ) {
73- proof . push ( ` compaction(firstKept=${ entry . firstKeptEntryId ? .slice ( 0 , 8 ) } ): ${ entry . summary } ` ) ;
88+ proof . push ( ` compaction(firstKept=${ entry . firstKeptEntryId . slice ( 0 , 8 ) } ): ${ entry . summary } ` ) ;
7489 } else {
7590 proof . push ( ` ${ entry . type } ` ) ;
7691 }
@@ -81,8 +96,10 @@ describe("prove-76729 — real behavior proof", () => {
8196 for ( const msg of successor . buildSessionContext ( ) . messages ) {
8297 if ( msg . role === "compactionSummary" ) {
8398 proof . push ( ` [compactionSummary] summary="${ msg . summary } "` ) ;
84- } else if ( "content" in msg ) {
85- proof . push ( ` [${ msg . role } ] "${ getMessageText ( msg as { role : string ; content : unknown } ) } "` ) ;
99+ } else if ( isMessageWithContent ( msg ) ) {
100+ proof . push ( ` [${ msg . role } ] "${ getMessageText ( msg ) } "` ) ;
101+ } else {
102+ proof . push ( ` [${ msg . role } ] (no text content)` ) ;
86103 }
87104 }
88105
@@ -92,27 +109,27 @@ describe("prove-76729 — real behavior proof", () => {
92109 if ( m . role === "compactionSummary" ) {
93110 return { role : "compactionSummary" , summary : m . summary , tokensBefore : m . tokensBefore } ;
94111 }
95- if ( "content" in m ) {
96- return { role : m . role , content : ( m as { content : unknown } ) . content , timestamp : ( m as { timestamp : number } ) . timestamp } ;
112+ if ( isMessageWithContent ( m ) ) {
113+ return { role : m . role , content : m . content , timestamp : m . timestamp } ;
97114 }
98115 return { role : m . role } ;
99116 } ) ;
100117 proof . push ( JSON . stringify ( json , null , 2 ) ) ;
101118
102119 const ctx = successor . buildSessionContext ( ) . messages ;
103120 const hasAssistantPreserved = ctx . some (
104- ( m ) => m . role === "assistant" && "content" in m &&
105- getMessageText ( m as { role : string ; content : unknown } ) . includes ( "sunny" ) ,
121+ ( m ) => m . role === "assistant" && isMessageWithContent ( m ) &&
122+ getMessageText ( m ) . includes ( "sunny" ) ,
106123 ) ;
107124 const hasUserSurviving = ctx . some (
108- ( m ) => m . role === "user" && "content" in m &&
109- typeof ( m as { content : unknown } ) . content === "string" &&
110- ( ( m as { content : string } ) . content ) . includes ( "tomorrow" ) ,
125+ ( m ) => m . role === "user" && isMessageWithContent ( m ) &&
126+ typeof m . content === "string" &&
127+ m . content . includes ( "tomorrow" ) ,
111128 ) ;
112129 const summarizedRemoved = ! ctx . some (
113- ( m ) => m . role === "user" && "content" in m &&
114- typeof ( m as { content : unknown } ) . content === "string" &&
115- ( ( m as { content : string } ) . content ) . includes ( "weather today" ) ,
130+ ( m ) => m . role === "user" && isMessageWithContent ( m ) &&
131+ typeof m . content === "string" &&
132+ m . content . includes ( "weather today" ) ,
116133 ) ;
117134
118135 proof . push ( "" ) ;
0 commit comments