@@ -62,6 +62,7 @@ const mockState = vi.hoisted(() => ({
6262 lastDispatchCtx : undefined as MsgContext | undefined ,
6363 lastDispatchImages : undefined as Array < { mimeType : string ; data : string } > | undefined ,
6464 lastDispatchImageOrder : undefined as string [ ] | undefined ,
65+ lastDispatchUserTurnInput : undefined as unknown ,
6566 modelCatalog : null as ModelCatalogEntry [ ] | null ,
6667 emittedTranscriptUpdates : [ ] as Array < {
6768 sessionFile : string ;
@@ -194,10 +195,12 @@ vi.mock("../../auto-reply/dispatch.js", () => ({
194195 images ?: Array < { mimeType : string ; data : string } > ;
195196 imageOrder ?: string [ ] ;
196197 } ;
198+ userTurnInput ?: unknown ;
197199 } ) => {
198200 mockState . lastDispatchCtx = params . ctx ;
199201 mockState . lastDispatchImages = params . replyOptions ?. images ;
200202 mockState . lastDispatchImageOrder = params . replyOptions ?. imageOrder ;
203+ mockState . lastDispatchUserTurnInput = params . userTurnInput ;
201204 if ( mockState . dispatchError ) {
202205 throw mockState . dispatchError ;
203206 }
@@ -703,6 +706,7 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
703706 mockState . lastDispatchCtx = undefined ;
704707 mockState . lastDispatchImages = undefined ;
705708 mockState . lastDispatchImageOrder = undefined ;
709+ mockState . lastDispatchUserTurnInput = undefined ;
706710 mockState . modelCatalog = null ;
707711 mockState . emittedTranscriptUpdates = [ ] ;
708712 mockState . savedMediaResults = [ ] ;
@@ -2986,7 +2990,7 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
29862990 expect ( mockState . lastDispatchCtx ?. CommandBody ) . toBe ( "bench update" ) ;
29872991 } ) ;
29882992
2989- it ( "emits a user transcript update when chat.send starts an agent run " , async ( ) => {
2993+ it ( "leaves text-only agent-run user persistence to Pi " , async ( ) => {
29902994 createTranscriptFixture ( "openclaw-chat-send-user-transcript-agent-run-" ) ;
29912995 mockState . finalText = "ok" ;
29922996 mockState . triggerAgentRunStart = true ;
@@ -3001,13 +3005,8 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
30013005 expectBroadcast : false ,
30023006 } ) ;
30033007
3004- const userUpdate = findUserUpdate ( ) ;
3005- const message = userUpdateMessage ( userUpdate ) ;
3006- expect ( userUpdate ?. sessionFile . endsWith ( "sess.jsonl" ) ) . toBe ( true ) ;
3007- expect ( userUpdate ?. sessionKey ) . toBe ( "main" ) ;
3008- expect ( message ?. role ) . toBe ( "user" ) ;
3009- expect ( message ?. content ) . toBe ( "hello from dashboard" ) ;
3010- expect ( typeof message ?. timestamp ) . toBe ( "number" ) ;
3008+ expect ( findUserUpdate ( ) ) . toBeUndefined ( ) ;
3009+ expect ( mockState . lastDispatchUserTurnInput ) . toBeUndefined ( ) ;
30113010 const finalBroadcast = (
30123011 context . broadcast as unknown as ReturnType < typeof vi . fn >
30133012 ) . mock . calls . find ( ( call ) => call [ 0 ] === "chat" && call [ 1 ] ?. state === "final" ) ?. [ 1 ] ;
@@ -3100,7 +3099,7 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
31003099 expect ( userUpdates ) . toHaveLength ( 0 ) ;
31013100 } ) ;
31023101
3103- it ( "adds persisted media paths to the user transcript update " , async ( ) => {
3102+ it ( "prepares persisted media paths for Pi user-turn persistence " , async ( ) => {
31043103 createTranscriptFixture ( "openclaw-chat-send-user-transcript-images-" ) ;
31053104 mockState . finalText = "ok" ;
31063105 mockState . triggerAgentRunStart = true ;
@@ -3135,9 +3134,6 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
31353134 } ) ;
31363135
31373136 await waitForAssertion ( ( ) => {
3138- const userUpdate = findUserUpdate ( ) ;
3139- expect ( userUpdate ?. sessionFile . endsWith ( "sess.jsonl" ) ) . toBe ( true ) ;
3140- expect ( userUpdate ?. sessionKey ) . toBe ( "main" ) ;
31413137 expect ( mockState . savedMediaCalls ) . toEqual ( [
31423138 {
31433139 contentType : "image/png" ,
@@ -3152,33 +3148,28 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
31523148 ] ) ;
31533149 expect ( typeof mockState . savedMediaCalls [ 0 ] ?. size ) . toBe ( "number" ) ;
31543150 expect ( typeof mockState . savedMediaCalls [ 1 ] ?. size ) . toBe ( "number" ) ;
3155- const message = userUpdateMessage ( userUpdate ) as
3151+ const userTurnInput = mockState . lastDispatchUserTurnInput as
31563152 | {
3157- content ?: unknown ;
3158- MediaPath ?: string ;
3159- MediaPaths ?: string [ ] ;
3160- MediaType ?: string ;
3161- MediaTypes ?: string [ ] ;
3153+ text ?: unknown ;
3154+ media ?: Array < { path ?: string ; contentType ?: string } > ;
31623155 }
31633156 | undefined ;
3164- if ( ! message ) {
3165- throw new Error ( "expected user transcript update with media metadata" ) ;
3157+ if ( ! userTurnInput ) {
3158+ throw new Error ( "expected user turn input with media metadata" ) ;
31663159 }
3167- expect ( message . content ) . toBe ( "edit these" ) ;
3168- expect ( message . MediaPath ) . toBe ( "/tmp/chat-send-image-a.png " ) ;
3169- expect ( message . MediaPaths ) . toEqual ( [
3170- "/tmp/chat-send-image-a.png" ,
3171- "/tmp/chat-send-image-b.jpg" ,
3160+ expect ( findUserUpdate ( ) ) . toBeUndefined ( ) ;
3161+ expect ( userTurnInput . text ) . toBe ( "edit these " ) ;
3162+ expect ( userTurnInput . media ) . toEqual ( [
3163+ { path : "/tmp/chat-send-image-a.png" , contentType : "image/png" } ,
3164+ { path : "/tmp/chat-send-image-b.jpg" , contentType : "image/jpeg" } ,
31723165 ] ) ;
3173- expect ( message . MediaType ) . toBe ( "image/png" ) ;
3174- expect ( message . MediaTypes ) . toEqual ( [ "image/png" , "image/jpeg" ] ) ;
31753166 expect ( mockState . lastDispatchCtx ?. MediaPath ) . toBeUndefined ( ) ;
31763167 expect ( mockState . lastDispatchCtx ?. MediaPaths ) . toBeUndefined ( ) ;
31773168 expect ( mockState . lastDispatchImages ) . toHaveLength ( 2 ) ;
31783169 } ) ;
31793170 } ) ;
31803171
3181- it ( "persists non-image chat.send attachments as media refs without dispatch images" , async ( ) => {
3172+ it ( "prepares non-image chat.send attachments as media refs without dispatch images" , async ( ) => {
31823173 createTranscriptFixture ( "openclaw-chat-send-user-transcript-file-" ) ;
31833174 mockState . finalText = "ok" ;
31843175 mockState . triggerAgentRunStart = true ;
@@ -3208,14 +3199,10 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
32083199 } ) ;
32093200
32103201 await waitForAssertion ( ( ) => {
3211- const userUpdate = findUserUpdate ( ) ;
3212- const message = userUpdateMessage ( userUpdate ) as
3202+ const userTurnInput = mockState . lastDispatchUserTurnInput as
32133203 | {
3214- content ?: unknown ;
3215- MediaPath ?: string ;
3216- MediaPaths ?: string [ ] ;
3217- MediaType ?: string ;
3218- MediaTypes ?: string [ ] ;
3204+ text ?: unknown ;
3205+ media ?: Array < { path ?: string ; contentType ?: string } > ;
32193206 }
32203207 | undefined ;
32213208 expect ( mockState . lastDispatchImages ) . toBeUndefined ( ) ;
@@ -3224,11 +3211,11 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
32243211 expect ( mockState . savedMediaCalls [ 0 ] ?. contentType ) . toBe ( "application/pdf" ) ;
32253212 expect ( mockState . savedMediaCalls [ 0 ] ?. subdir ) . toBe ( "inbound" ) ;
32263213 expect ( typeof mockState . savedMediaCalls [ 0 ] ?. size ) . toBe ( "number" ) ;
3227- expect ( message ?. content ) . toBe ( "summarize this" ) ;
3228- expect ( message ?. MediaPath ) . toBe ( "/tmp/chat-send-brief.pdf " ) ;
3229- expect ( message ?. MediaPaths ) . toEqual ( [ "/tmp/chat-send-brief.pdf" ] ) ;
3230- expect ( message ?. MediaType ) . toBe ( " application/pdf") ;
3231- expect ( message ?. MediaTypes ) . toEqual ( [ "application/pdf" ] ) ;
3214+ expect ( findUserUpdate ( ) ) . toBeUndefined ( ) ;
3215+ expect ( userTurnInput ?. text ) . toBe ( "summarize this " ) ;
3216+ expect ( userTurnInput ?. media ) . toEqual ( [
3217+ { path : "/tmp/chat-send-brief.pdf" , contentType : " application/pdf" } ,
3218+ ] ) ;
32323219 } ) ;
32333220 } ) ;
32343221
@@ -3280,28 +3267,20 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
32803267 } ) ;
32813268
32823269 await waitForAssertion ( ( ) => {
3283- const userUpdate = mockState . emittedTranscriptUpdates . find (
3284- ( update ) =>
3285- typeof update . message === "object" &&
3286- update . message !== null &&
3287- ( update . message as { role ?: unknown } ) . role === "user" ,
3288- ) ;
3289- const message = userUpdate ?. message as
3270+ const userTurnInput = mockState . lastDispatchUserTurnInput as
32903271 | {
3291- MediaPath ?: string ;
3292- MediaPaths ?: string [ ] ;
3293- MediaType ?: string ;
3294- MediaTypes ?: string [ ] ;
3272+ media ?: Array < { path ?: string ; contentType ?: string } > ;
32953273 }
32963274 | undefined ;
3297- expect ( message ?. MediaPath ) . toBe ( "/tmp/chat-send-inline.png" ) ;
3298- expect ( message ?. MediaPaths ) . toEqual ( [ "/tmp/chat-send-inline.png" , "/tmp/offloaded-big.png" ] ) ;
3299- expect ( message ?. MediaType ) . toBe ( "image/png" ) ;
3300- expect ( message ?. MediaTypes ) . toEqual ( [ "image/png" , "image/png" ] ) ;
3275+ expect ( findUserUpdate ( ) ) . toBeUndefined ( ) ;
3276+ expect ( userTurnInput ?. media ) . toEqual ( [
3277+ { path : "/tmp/chat-send-inline.png" , contentType : "image/png" } ,
3278+ { path : "/tmp/offloaded-big.png" , contentType : "image/png" } ,
3279+ ] ) ;
33013280 } ) ;
33023281 } ) ;
33033282
3304- it ( "skips transcript media notes for ACP bridge clients " , async ( ) => {
3283+ it ( "leaves ACP bridge user persistence to the agent runtime " , async ( ) => {
33053284 createTranscriptFixture ( "openclaw-chat-send-user-transcript-acp-images-" ) ;
33063285 mockState . finalText = "ok" ;
33073286 mockState . triggerAgentRunStart = true ;
@@ -3339,11 +3318,9 @@ describe("chat directive tag stripping for non-streaming final payloads", () =>
33393318 } ) ;
33403319
33413320 await waitForAssertion ( ( ) => {
3342- const userUpdate = findUserUpdate ( ) ;
3343- const message = userUpdateMessage ( userUpdate ) ;
33443321 expect ( mockState . savedMediaCalls ) . toStrictEqual ( [ ] ) ;
3345- expect ( message ?. role ) . toBe ( "user" ) ;
3346- expect ( message ?. content ) . toBe ( "bridge image" ) ;
3322+ expect ( findUserUpdate ( ) ) . toBeUndefined ( ) ;
3323+ expect ( mockState . lastDispatchUserTurnInput ) . toBeUndefined ( ) ;
33473324 } ) ;
33483325 } ) ;
33493326
0 commit comments