@@ -105,33 +105,41 @@ const plugin: Plugin = (async (ctx) => {
105105 // Cache tool parameters for janitor metadata
106106 cacheToolParameters ( body . messages , "global-fetch" )
107107
108+ // Always save wrapped context if debug is enabled (even when no tool messages)
109+ // This captures janitor's AI inference which has messageCount=1 (just prompt)
110+ const shouldLogAllRequests = logger . enabled
111+
108112 // Check for tool messages that might need pruning
109113 const toolMessages = body . messages . filter ( ( m : any ) => m . role === 'tool' )
114+
115+ // Collect all pruned IDs across all sessions (excluding subagents)
116+ // This is safe because tool_call_ids are globally unique
117+ const allSessions = await ctx . client . session . list ( )
118+ const allPrunedIds = new Set < string > ( )
119+
120+ if ( allSessions . data ) {
121+ for ( const session of allSessions . data ) {
122+ // Skip subagent sessions (don't log - it's normal and would spam logs)
123+ if ( session . parentID ) {
124+ continue
125+ }
126+
127+ const prunedIds = await stateManager . get ( session . id )
128+ prunedIds . forEach ( id => allPrunedIds . add ( id ) )
129+ }
130+ }
131+
132+ // Only process tool message replacement if there are tool messages
110133 if ( toolMessages . length > 0 ) {
111134 logger . debug ( "global-fetch" , "Found tool messages in request" , {
112135 toolMessageCount : toolMessages . length ,
113136 toolCallIds : toolMessages . map ( ( m : any ) => m . tool_call_id ) . slice ( 0 , 5 )
114137 } )
115-
116- // Collect all pruned IDs across all sessions (excluding subagents)
117- // This is safe because tool_call_ids are globally unique
118- const allSessions = await ctx . client . session . list ( )
119- const allPrunedIds = new Set < string > ( )
120-
121- if ( allSessions . data ) {
122- for ( const session of allSessions . data ) {
123- // Skip subagent sessions (don't log - it's normal and would spam logs)
124- if ( session . parentID ) {
125- continue
126- }
127-
128- const prunedIds = await stateManager . get ( session . id )
129- prunedIds . forEach ( id => allPrunedIds . add ( id ) )
130- }
131- }
132-
138+
133139 if ( allPrunedIds . size > 0 ) {
134140 let replacedCount = 0
141+ const originalMessages = JSON . parse ( JSON . stringify ( body . messages ) ) // Deep copy for logging
142+
135143 body . messages = body . messages . map ( ( m : any ) => {
136144 // Normalize ID to lowercase for case-insensitive matching
137145 if ( m . role === 'tool' && allPrunedIds . has ( m . tool_call_id ?. toLowerCase ( ) ) ) {
@@ -151,10 +159,75 @@ const plugin: Plugin = (async (ctx) => {
151159 totalMessages : body . messages . length
152160 } )
153161
162+ // Save wrapped context to file if debug is enabled
163+ await logger . saveWrappedContext (
164+ "global" , // Use "global" as session ID since we don't know which session this is
165+ body . messages ,
166+ {
167+ url : typeof input === 'string' ? input : 'URL object' ,
168+ totalPrunedIds : allPrunedIds . size ,
169+ replacedCount,
170+ totalMessages : body . messages . length ,
171+ originalMessageCount : originalMessages . length
172+ }
173+ )
174+
154175 // Update the request body with modified messages
155176 init . body = JSON . stringify ( body )
177+ } else if ( shouldLogAllRequests ) {
178+ // Log even when no replacements occurred (tool messages exist but none were pruned)
179+ await logger . saveWrappedContext (
180+ "global" ,
181+ body . messages ,
182+ {
183+ url : typeof input === 'string' ? input : 'URL object' ,
184+ totalPrunedIds : allPrunedIds . size ,
185+ replacedCount : 0 ,
186+ totalMessages : body . messages . length ,
187+ toolMessageCount : toolMessages . length ,
188+ note : "Tool messages exist but none were replaced"
189+ }
190+ )
156191 }
192+ } else if ( shouldLogAllRequests ) {
193+ // Log when tool messages exist but no pruned IDs exist yet
194+ await logger . saveWrappedContext (
195+ "global" ,
196+ body . messages ,
197+ {
198+ url : typeof input === 'string' ? input : 'URL object' ,
199+ totalPrunedIds : 0 ,
200+ replacedCount : 0 ,
201+ totalMessages : body . messages . length ,
202+ toolMessageCount : toolMessages . length ,
203+ note : "No pruned IDs exist yet"
204+ }
205+ )
157206 }
207+ } else if ( shouldLogAllRequests ) {
208+ // Log requests with NO tool messages (e.g., janitor's shadow inference)
209+ // Detect if this is a janitor request by checking the prompt content
210+ const isJanitorRequest = body . messages . length === 1 &&
211+ body . messages [ 0 ] ?. role === 'user' &&
212+ typeof body . messages [ 0 ] ?. content === 'string' &&
213+ body . messages [ 0 ] . content . includes ( 'conversation analyzer that identifies obsolete tool outputs' )
214+
215+ const sessionId = isJanitorRequest ? "janitor-shadow" : "global"
216+
217+ await logger . saveWrappedContext (
218+ sessionId ,
219+ body . messages ,
220+ {
221+ url : typeof input === 'string' ? input : 'URL object' ,
222+ totalPrunedIds : allPrunedIds . size ,
223+ replacedCount : 0 ,
224+ totalMessages : body . messages . length ,
225+ toolMessageCount : 0 ,
226+ note : isJanitorRequest
227+ ? "Janitor shadow inference with embedded session history in prompt"
228+ : "No tool messages in request (likely title generation or other inference)"
229+ }
230+ )
158231 }
159232 }
160233 } catch ( e ) {
@@ -363,6 +436,19 @@ const plugin: Plugin = (async (ctx) => {
363436 toolCallIds : remainingToolMessages . map ( ( m : any ) => m . tool_call_id )
364437 } )
365438
439+ // Save wrapped context to file if debug is enabled
440+ await logger . saveWrappedContext (
441+ sessionId ,
442+ body . messages ,
443+ {
444+ url : typeof fetchInput === 'string' ? fetchInput : 'URL object' ,
445+ totalMessages : originalMessageCount ,
446+ replacedCount : prunedThisRequest ,
447+ prunedIds,
448+ wrapper : 'session-specific'
449+ }
450+ )
451+
366452 // Update the request body with modified messages
367453 init . body = JSON . stringify ( body )
368454 parsedBody = body
0 commit comments