@@ -108,11 +108,10 @@ struct PatchApplyBegin {
108
108
auto_approved : bool ,
109
109
}
110
110
111
- // Timestamped println helper. The timestamp is styled with self.dimmed.
112
- #[ macro_export]
113
- macro_rules! ts_println {
111
+ /// Timestamped helper. The timestamp is styled with self.dimmed.
112
+ macro_rules! ts_msg {
114
113
( $self: ident, $( $arg: tt) * ) => { {
115
- println !( $( $arg) * ) ;
114
+ eprintln !( $( $arg) * ) ;
116
115
} } ;
117
116
}
118
117
@@ -127,7 +126,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
127
126
session_configured_event : & SessionConfiguredEvent ,
128
127
) {
129
128
const VERSION : & str = env ! ( "CARGO_PKG_VERSION" ) ;
130
- ts_println ! (
129
+ ts_msg ! (
131
130
self ,
132
131
"OpenAI Codex v{} (research preview)\n --------" ,
133
132
VERSION
@@ -140,37 +139,48 @@ impl EventProcessor for EventProcessorWithHumanOutput {
140
139
) ) ;
141
140
142
141
for ( key, value) in entries {
143
- println ! ( "{} {}" , format!( "{key}:" ) . style( self . bold) , value) ;
142
+ eprintln ! ( "{} {}" , format!( "{key}:" ) . style( self . bold) , value) ;
144
143
}
145
144
146
- println ! ( "--------" ) ;
145
+ eprintln ! ( "--------" ) ;
147
146
148
147
// Echo the prompt that will be sent to the agent so it is visible in the
149
148
// transcript/logs before any events come in. Note the prompt may have been
150
149
// read from stdin, so it may not be visible in the terminal otherwise.
151
- ts_println ! ( self , "{}\n {}" , "user" . style( self . cyan) , prompt) ;
150
+ ts_msg ! ( self , "{}\n {}" , "user" . style( self . cyan) , prompt) ;
152
151
}
153
152
154
153
fn process_event ( & mut self , event : Event ) -> CodexStatus {
155
154
let Event { id : _, msg } = event;
156
155
match msg {
157
156
EventMsg :: Error ( ErrorEvent { message } ) => {
158
157
let prefix = "ERROR:" . style ( self . red ) ;
159
- ts_println ! ( self , "{prefix} {message}" ) ;
158
+ ts_msg ! ( self , "{prefix} {message}" ) ;
160
159
}
161
160
EventMsg :: BackgroundEvent ( BackgroundEventEvent { message } ) => {
162
- ts_println ! ( self , "{}" , message. style( self . dimmed) ) ;
161
+ ts_msg ! ( self , "{}" , message. style( self . dimmed) ) ;
163
162
}
164
163
EventMsg :: StreamError ( StreamErrorEvent { message } ) => {
165
- ts_println ! ( self , "{}" , message. style( self . dimmed) ) ;
164
+ ts_msg ! ( self , "{}" , message. style( self . dimmed) ) ;
166
165
}
167
166
EventMsg :: TaskStarted ( _) => {
168
167
// Ignore.
169
168
}
170
169
EventMsg :: TaskComplete ( TaskCompleteEvent { last_agent_message } ) => {
170
+ let last_message = last_agent_message. as_deref ( ) ;
171
171
if let Some ( output_file) = self . last_message_path . as_deref ( ) {
172
- handle_last_message ( last_agent_message . as_deref ( ) , output_file) ;
172
+ handle_last_message ( last_message , output_file) ;
173
173
}
174
+
175
+ #[ allow( clippy:: print_stdout) ]
176
+ if let Some ( message) = last_message {
177
+ if message. ends_with ( '\n' ) {
178
+ print ! ( "{message}" ) ;
179
+ } else {
180
+ println ! ( "{message}" ) ;
181
+ }
182
+ }
183
+
174
184
return CodexStatus :: InitiateShutdown ;
175
185
}
176
186
EventMsg :: TokenCount ( ev) => {
@@ -181,11 +191,11 @@ impl EventProcessor for EventProcessorWithHumanOutput {
181
191
if !self . show_agent_reasoning {
182
192
return CodexStatus :: Running ;
183
193
}
184
- println ! ( ) ;
194
+ eprintln ! ( ) ;
185
195
}
186
196
EventMsg :: AgentReasoningRawContent ( AgentReasoningRawContentEvent { text } ) => {
187
197
if self . show_raw_agent_reasoning {
188
- ts_println ! (
198
+ ts_msg ! (
189
199
self ,
190
200
"{}\n {}" ,
191
201
"thinking" . style( self . italic) . style( self . magenta) ,
@@ -194,15 +204,15 @@ impl EventProcessor for EventProcessorWithHumanOutput {
194
204
}
195
205
}
196
206
EventMsg :: AgentMessage ( AgentMessageEvent { message } ) => {
197
- ts_println ! (
207
+ ts_msg ! (
198
208
self ,
199
209
"{}\n {}" ,
200
210
"codex" . style( self . italic) . style( self . magenta) ,
201
211
message,
202
212
) ;
203
213
}
204
214
EventMsg :: ExecCommandBegin ( ExecCommandBeginEvent { command, cwd, .. } ) => {
205
- print ! (
215
+ eprint ! (
206
216
"{}\n {} in {}" ,
207
217
"exec" . style( self . italic) . style( self . magenta) ,
208
218
escape_command( & command) . style( self . bold) ,
@@ -226,20 +236,20 @@ impl EventProcessor for EventProcessorWithHumanOutput {
226
236
match exit_code {
227
237
0 => {
228
238
let title = format ! ( " succeeded{duration}:" ) ;
229
- ts_println ! ( self , "{}" , title. style( self . green) ) ;
239
+ ts_msg ! ( self , "{}" , title. style( self . green) ) ;
230
240
}
231
241
_ => {
232
242
let title = format ! ( " exited {exit_code}{duration}:" ) ;
233
- ts_println ! ( self , "{}" , title. style( self . red) ) ;
243
+ ts_msg ! ( self , "{}" , title. style( self . red) ) ;
234
244
}
235
245
}
236
- println ! ( "{}" , truncated_output. style( self . dimmed) ) ;
246
+ eprintln ! ( "{}" , truncated_output. style( self . dimmed) ) ;
237
247
}
238
248
EventMsg :: McpToolCallBegin ( McpToolCallBeginEvent {
239
249
call_id : _,
240
250
invocation,
241
251
} ) => {
242
- ts_println ! (
252
+ ts_msg ! (
243
253
self ,
244
254
"{} {}" ,
245
255
"tool" . style( self . magenta) ,
@@ -264,21 +274,21 @@ impl EventProcessor for EventProcessorWithHumanOutput {
264
274
format_mcp_invocation( & invocation)
265
275
) ;
266
276
267
- ts_println ! ( self , "{}" , title. style( title_style) ) ;
277
+ ts_msg ! ( self , "{}" , title. style( title_style) ) ;
268
278
269
279
if let Ok ( res) = result {
270
280
let val: serde_json:: Value = res. into ( ) ;
271
281
let pretty =
272
282
serde_json:: to_string_pretty ( & val) . unwrap_or_else ( |_| val. to_string ( ) ) ;
273
283
274
284
for line in pretty. lines ( ) . take ( MAX_OUTPUT_LINES_FOR_EXEC_TOOL_CALL ) {
275
- println ! ( "{}" , line. style( self . dimmed) ) ;
285
+ eprintln ! ( "{}" , line. style( self . dimmed) ) ;
276
286
}
277
287
}
278
288
}
279
289
EventMsg :: WebSearchBegin ( WebSearchBeginEvent { call_id : _ } ) => { }
280
290
EventMsg :: WebSearchEnd ( WebSearchEndEvent { call_id : _, query } ) => {
281
- ts_println ! ( self , "🌐 Searched: {query}" ) ;
291
+ ts_msg ! ( self , "🌐 Searched: {query}" ) ;
282
292
}
283
293
EventMsg :: PatchApplyBegin ( PatchApplyBeginEvent {
284
294
call_id,
@@ -295,7 +305,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
295
305
} ,
296
306
) ;
297
307
298
- ts_println ! (
308
+ ts_msg ! (
299
309
self ,
300
310
"{}" ,
301
311
"file update" . style( self . magenta) . style( self . italic) ,
@@ -311,9 +321,9 @@ impl EventProcessor for EventProcessorWithHumanOutput {
311
321
format_file_change( change) ,
312
322
path. to_string_lossy( )
313
323
) ;
314
- println ! ( "{}" , header. style( self . magenta) ) ;
324
+ eprintln ! ( "{}" , header. style( self . magenta) ) ;
315
325
for line in content. lines ( ) {
316
- println ! ( "{}" , line. style( self . green) ) ;
326
+ eprintln ! ( "{}" , line. style( self . green) ) ;
317
327
}
318
328
}
319
329
FileChange :: Delete { content } => {
@@ -322,9 +332,9 @@ impl EventProcessor for EventProcessorWithHumanOutput {
322
332
format_file_change( change) ,
323
333
path. to_string_lossy( )
324
334
) ;
325
- println ! ( "{}" , header. style( self . magenta) ) ;
335
+ eprintln ! ( "{}" , header. style( self . magenta) ) ;
326
336
for line in content. lines ( ) {
327
- println ! ( "{}" , line. style( self . red) ) ;
337
+ eprintln ! ( "{}" , line. style( self . red) ) ;
328
338
}
329
339
}
330
340
FileChange :: Update {
@@ -341,20 +351,20 @@ impl EventProcessor for EventProcessorWithHumanOutput {
341
351
} else {
342
352
format ! ( "{} {}" , format_file_change( change) , path. to_string_lossy( ) )
343
353
} ;
344
- println ! ( "{}" , header. style( self . magenta) ) ;
354
+ eprintln ! ( "{}" , header. style( self . magenta) ) ;
345
355
346
356
// Colorize diff lines. We keep file header lines
347
357
// (--- / +++) without extra coloring so they are
348
358
// still readable.
349
359
for diff_line in unified_diff. lines ( ) {
350
360
if diff_line. starts_with ( '+' ) && !diff_line. starts_with ( "+++" ) {
351
- println ! ( "{}" , diff_line. style( self . green) ) ;
361
+ eprintln ! ( "{}" , diff_line. style( self . green) ) ;
352
362
} else if diff_line. starts_with ( '-' )
353
363
&& !diff_line. starts_with ( "---" )
354
364
{
355
- println ! ( "{}" , diff_line. style( self . red) ) ;
365
+ eprintln ! ( "{}" , diff_line. style( self . red) ) ;
356
366
} else {
357
- println ! ( "{diff_line}" ) ;
367
+ eprintln ! ( "{diff_line}" ) ;
358
368
}
359
369
}
360
370
}
@@ -391,18 +401,18 @@ impl EventProcessor for EventProcessorWithHumanOutput {
391
401
} ;
392
402
393
403
let title = format ! ( "{label} exited {exit_code}{duration}:" ) ;
394
- ts_println ! ( self , "{}" , title. style( title_style) ) ;
404
+ ts_msg ! ( self , "{}" , title. style( title_style) ) ;
395
405
for line in output. lines ( ) {
396
- println ! ( "{}" , line. style( self . dimmed) ) ;
406
+ eprintln ! ( "{}" , line. style( self . dimmed) ) ;
397
407
}
398
408
}
399
409
EventMsg :: TurnDiff ( TurnDiffEvent { unified_diff } ) => {
400
- ts_println ! (
410
+ ts_msg ! (
401
411
self ,
402
412
"{}" ,
403
413
"file update:" . style( self . magenta) . style( self . italic)
404
414
) ;
405
- println ! ( "{unified_diff}" ) ;
415
+ eprintln ! ( "{unified_diff}" ) ;
406
416
}
407
417
EventMsg :: ExecApprovalRequest ( _) => {
408
418
// Should we exit?
@@ -412,7 +422,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
412
422
}
413
423
EventMsg :: AgentReasoning ( agent_reasoning_event) => {
414
424
if self . show_agent_reasoning {
415
- ts_println ! (
425
+ ts_msg ! (
416
426
self ,
417
427
"{}\n {}" ,
418
428
"thinking" . style( self . italic) . style( self . magenta) ,
@@ -431,41 +441,41 @@ impl EventProcessor for EventProcessorWithHumanOutput {
431
441
rollout_path : _,
432
442
} = session_configured_event;
433
443
434
- ts_println ! (
444
+ ts_msg ! (
435
445
self ,
436
446
"{} {}" ,
437
447
"codex session" . style( self . magenta) . style( self . bold) ,
438
448
conversation_id. to_string( ) . style( self . dimmed)
439
449
) ;
440
450
441
- ts_println ! ( self , "model: {}" , model) ;
442
- println ! ( ) ;
451
+ ts_msg ! ( self , "model: {}" , model) ;
452
+ eprintln ! ( ) ;
443
453
}
444
454
EventMsg :: PlanUpdate ( plan_update_event) => {
445
455
let UpdatePlanArgs { explanation, plan } = plan_update_event;
446
456
447
457
// Header
448
- ts_println ! ( self , "{}" , "Plan update" . style( self . magenta) ) ;
458
+ ts_msg ! ( self , "{}" , "Plan update" . style( self . magenta) ) ;
449
459
450
460
// Optional explanation
451
461
if let Some ( explanation) = explanation
452
462
&& !explanation. trim ( ) . is_empty ( )
453
463
{
454
- ts_println ! ( self , "{}" , explanation. style( self . italic) ) ;
464
+ ts_msg ! ( self , "{}" , explanation. style( self . italic) ) ;
455
465
}
456
466
457
467
// Pretty-print the plan items with simple status markers.
458
468
for item in plan {
459
469
use codex_core:: plan_tool:: StepStatus ;
460
470
match item. status {
461
471
StepStatus :: Completed => {
462
- ts_println ! ( self , " {} {}" , "✓" . style( self . green) , item. step) ;
472
+ ts_msg ! ( self , " {} {}" , "✓" . style( self . green) , item. step) ;
463
473
}
464
474
StepStatus :: InProgress => {
465
- ts_println ! ( self , " {} {}" , "→" . style( self . cyan) , item. step) ;
475
+ ts_msg ! ( self , " {} {}" , "→" . style( self . cyan) , item. step) ;
466
476
}
467
477
StepStatus :: Pending => {
468
- ts_println ! (
478
+ ts_msg ! (
469
479
self ,
470
480
" {} {}" ,
471
481
"•" . style( self . dimmed) ,
@@ -485,7 +495,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
485
495
// Currently ignored in exec output.
486
496
}
487
497
EventMsg :: ViewImageToolCall ( view) => {
488
- ts_println ! (
498
+ ts_msg ! (
489
499
self ,
490
500
"{} {}" ,
491
501
"viewed image" . style( self . magenta) ,
@@ -494,13 +504,13 @@ impl EventProcessor for EventProcessorWithHumanOutput {
494
504
}
495
505
EventMsg :: TurnAborted ( abort_reason) => match abort_reason. reason {
496
506
TurnAbortReason :: Interrupted => {
497
- ts_println ! ( self , "task interrupted" ) ;
507
+ ts_msg ! ( self , "task interrupted" ) ;
498
508
}
499
509
TurnAbortReason :: Replaced => {
500
- ts_println ! ( self , "task aborted: replaced by a new task" ) ;
510
+ ts_msg ! ( self , "task aborted: replaced by a new task" ) ;
501
511
}
502
512
TurnAbortReason :: ReviewEnded => {
503
- ts_println ! ( self , "task aborted: review ended" ) ;
513
+ ts_msg ! ( self , "task aborted: review ended" ) ;
504
514
}
505
515
} ,
506
516
EventMsg :: ShutdownComplete => return CodexStatus :: Shutdown ,
@@ -517,8 +527,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
517
527
518
528
fn print_final_output ( & mut self ) {
519
529
if let Some ( usage_info) = & self . last_total_token_usage {
520
- ts_println ! (
521
- self ,
530
+ eprintln ! (
522
531
"{}\n {}" ,
523
532
"tokens used" . style( self . magenta) . style( self . italic) ,
524
533
format_with_separators( usage_info. total_token_usage. blended_total( ) )
0 commit comments