@@ -2,6 +2,7 @@ use super::*;
22use std:: process:: Stdio ;
33use std:: sync:: atomic:: { AtomicBool , Ordering } ;
44use std:: sync:: Arc ;
5+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
56use tokio:: io:: { AsyncBufReadExt , AsyncRead , BufReader } ;
67use tokio:: process:: Command ;
78use tokio:: sync:: Mutex as AsyncMutex ;
@@ -13,32 +14,41 @@ static AGENT_HARNESS_PROCESSES: LazyLock<AsyncMutex<HashMap<String, u32>>> =
1314#[ serde( rename_all = "camelCase" , tag = "kind" ) ]
1415pub ( crate ) enum AgentHarnessEvent {
1516 Started {
17+ #[ serde( rename = "sessionId" ) ]
1618 session_id : String ,
1719 provider : String ,
1820 command : String ,
1921 } ,
2022 Json {
23+ #[ serde( rename = "sessionId" ) ]
2124 session_id : String ,
2225 stream : String ,
26+ #[ serde( rename = "eventType" ) ]
2327 event_type : Option < String > ,
28+ #[ serde( rename = "itemId" ) ]
29+ item_id : Option < String > ,
2430 role : Option < String > ,
2531 text : Option < String > ,
2632 raw : Value ,
2733 } ,
2834 Stdout {
35+ #[ serde( rename = "sessionId" ) ]
2936 session_id : String ,
3037 text : String ,
3138 } ,
3239 Stderr {
40+ #[ serde( rename = "sessionId" ) ]
3341 session_id : String ,
3442 text : String ,
3543 } ,
3644 Exit {
45+ #[ serde( rename = "sessionId" ) ]
3746 session_id : String ,
3847 code : Option < i32 > ,
3948 success : bool ,
4049 } ,
4150 Error {
51+ #[ serde( rename = "sessionId" ) ]
4252 session_id : String ,
4353 message : String ,
4454 } ,
@@ -47,6 +57,7 @@ pub(crate) enum AgentHarnessEvent {
4757struct HarnessCommand {
4858 program : String ,
4959 args : Vec < String > ,
60+ source : & ' static str ,
5061}
5162
5263impl HarnessCommand {
@@ -79,7 +90,11 @@ fn build_harness_command(
7990 args. push ( resume_id. to_string ( ) ) ;
8091 }
8192 args. push ( prompt. to_string ( ) ) ;
82- return Ok ( HarnessCommand { program, args } ) ;
93+ return Ok ( HarnessCommand {
94+ program,
95+ args,
96+ source : "custom-harness" ,
97+ } ) ;
8398 }
8499
85100 let command_name = match provider {
@@ -99,10 +114,11 @@ fn build_harness_command(
99114 let args = match provider {
100115 "claude" => {
101116 let mut args = vec ! [
102- "--print" . to_string( ) ,
103- "--verbose" . to_string( ) ,
104117 "--output-format" . to_string( ) ,
105118 "stream-json" . to_string( ) ,
119+ "--verbose" . to_string( ) ,
120+ "-p" . to_string( ) ,
121+ prompt. to_string( ) ,
106122 "--include-partial-messages" . to_string( ) ,
107123 ] ;
108124 if let Some ( resume_id) = resume_session_id. filter ( |id| !id. trim ( ) . is_empty ( ) ) {
@@ -112,7 +128,6 @@ fn build_harness_command(
112128 args. push ( "--session-id" . to_string ( ) ) ;
113129 args. push ( session_id. to_string ( ) ) ;
114130 }
115- args. push ( prompt. to_string ( ) ) ;
116131 args
117132 }
118133 "codex" => {
@@ -133,11 +148,24 @@ fn build_harness_command(
133148 _ => unreachable ! ( ) ,
134149 } ;
135150
136- Ok ( HarnessCommand { program, args } )
151+ Ok ( HarnessCommand {
152+ program,
153+ args,
154+ source : "direct-cli" ,
155+ } )
137156}
138157
139158fn emit_harness_event ( app : & tauri:: AppHandle , event : AgentHarnessEvent ) {
140- let _ = app. emit ( "agent-harness-event" , event) ;
159+ if let Err ( error) = app. emit ( "agent-harness-event" , event) {
160+ eprintln ! ( "[DEBUG][HarnessBackend] emit failed: {}" , error) ;
161+ }
162+ }
163+
164+ fn harness_debug_now_ms ( ) -> u128 {
165+ SystemTime :: now ( )
166+ . duration_since ( UNIX_EPOCH )
167+ . map ( |duration| duration. as_millis ( ) )
168+ . unwrap_or_default ( )
141169}
142170
143171fn json_string ( value : & Value , keys : & [ & str ] ) -> Option < String > {
@@ -160,7 +188,14 @@ fn collect_content_text(value: &Value, out: &mut Vec<String>) {
160188 Value :: Object ( map) => {
161189 if matches ! (
162190 map. get( "type" ) . and_then( Value :: as_str) ,
163- Some ( "text" | "output_text" | "input_text" )
191+ Some (
192+ "text"
193+ | "text_delta"
194+ | "output_text"
195+ | "output_text_delta"
196+ | "input_text"
197+ | "agent_message"
198+ )
164199 ) {
165200 if let Some ( text) = map. get ( "text" ) . and_then ( Value :: as_str) {
166201 if !text. trim ( ) . is_empty ( ) {
@@ -187,15 +222,29 @@ fn collect_content_text(value: &Value, out: &mut Vec<String>) {
187222}
188223
189224fn extract_harness_text ( raw : & Value ) -> Option < String > {
225+ if let Some ( text) = raw
226+ . get ( "item" )
227+ . filter ( |item| item. get ( "type" ) . and_then ( Value :: as_str) == Some ( "agent_message" ) )
228+ . and_then ( |item| item. get ( "text" ) . and_then ( Value :: as_str) )
229+ . map ( str:: to_string)
230+ {
231+ if !text. trim ( ) . is_empty ( ) {
232+ return Some ( text) ;
233+ }
234+ }
235+
190236 if let Some ( text) = json_string ( raw, & [ "text" , "result" , "last_message" ] ) {
191237 if !text. trim ( ) . is_empty ( ) {
192238 return Some ( text) ;
193239 }
194240 }
195241
196242 let mut parts = Vec :: new ( ) ;
197- for key in [ "message" , "content" , "delta" , "item" , "payload" ] {
243+ for key in [ "message" , "content" , "delta" , "event" , " item", "payload" ] {
198244 if let Some ( value) = raw. get ( key) {
245+ if key == "event" && !value. is_object ( ) && !value. is_array ( ) {
246+ continue ;
247+ }
199248 collect_content_text ( value, & mut parts) ;
200249 }
201250 }
@@ -207,10 +256,41 @@ fn extract_harness_text(raw: &Value) -> Option<String> {
207256 }
208257}
209258
259+ fn json_event_type ( raw : & Value ) -> Option < String > {
260+ let event_type = json_string ( raw, & [ "type" , "event" , "kind" ] ) ;
261+ if event_type. as_deref ( ) == Some ( "stream_event" ) {
262+ return raw
263+ . get ( "event" )
264+ . and_then ( |event| json_string ( event, & [ "type" , "event" , "kind" ] ) )
265+ . or ( event_type) ;
266+ }
267+ event_type
268+ }
269+
270+ fn json_item_id ( raw : & Value ) -> Option < String > {
271+ json_string ( raw, & [ "id" , "message_id" , "item_id" ] )
272+ . or_else ( || raw. get ( "message" ) . and_then ( |v| json_string ( v, & [ "id" ] ) ) )
273+ . or_else ( || raw. get ( "event" ) ?. get ( "message" ) . and_then ( |v| json_string ( v, & [ "id" ] ) ) )
274+ . or_else ( || raw. get ( "item" ) . and_then ( |v| json_string ( v, & [ "id" ] ) ) )
275+ . or_else ( || raw. get ( "payload" ) . and_then ( |v| json_string ( v, & [ "id" ] ) ) )
276+ }
277+
210278fn json_role ( raw : & Value ) -> Option < String > {
279+ if let Some ( item_type) = raw. get ( "item" ) . and_then ( |item| item. get ( "type" ) ) . and_then ( Value :: as_str) {
280+ return match item_type {
281+ "agent_message" => Some ( "assistant" . to_string ( ) ) ,
282+ "command_execution" | "file_change" | "mcp_tool_call" | "web_search" | "todo_list" => {
283+ Some ( "tool" . to_string ( ) )
284+ }
285+ "error" => Some ( "error" . to_string ( ) ) ,
286+ _ => None ,
287+ } ;
288+ }
289+
211290 raw. get ( "role" )
212291 . and_then ( Value :: as_str)
213292 . or_else ( || raw. get ( "message" ) ?. get ( "role" ) ?. as_str ( ) )
293+ . or_else ( || raw. get ( "event" ) ?. get ( "message" ) ?. get ( "role" ) ?. as_str ( ) )
214294 . map ( str:: to_string)
215295}
216296
@@ -220,27 +300,58 @@ fn emit_harness_line(app: &tauri::AppHandle, session_id: &str, stream: &str, lin
220300 }
221301
222302 if let Ok ( raw) = serde_json:: from_str :: < Value > ( & line) {
223- let event_type = json_string ( & raw , & [ "type" , "event" , "kind" ] ) ;
303+ let event_type = json_event_type ( & raw ) ;
304+ let item_id = json_item_id ( & raw ) ;
305+ let role = json_role ( & raw ) ;
306+ let text = extract_harness_text ( & raw ) ;
307+ let event_type_for_log = event_type. as_deref ( ) . unwrap_or ( "json" ) ;
308+ if matches ! (
309+ event_type_for_log,
310+ "content_block_delta" | "assistant" | "result" | "message_stop" | "content_block_stop"
311+ ) {
312+ eprintln ! (
313+ "[DEBUG][HarnessBackend] emit-json t={} session={} stream={} event_type={} text_len={} line_len={}" ,
314+ harness_debug_now_ms( ) ,
315+ session_id,
316+ stream,
317+ event_type_for_log,
318+ text. as_ref( ) . map( |value| value. len( ) ) . unwrap_or( 0 ) ,
319+ line. len( )
320+ ) ;
321+ }
224322 emit_harness_event (
225323 app,
226324 AgentHarnessEvent :: Json {
227325 session_id : session_id. to_string ( ) ,
228326 stream : stream. to_string ( ) ,
229327 event_type,
230- role : json_role ( & raw ) ,
231- text : extract_harness_text ( & raw ) ,
328+ item_id,
329+ role,
330+ text,
232331 raw,
233332 } ,
234333 ) ;
235334 return ;
236335 }
237336
238337 let event = if stream == "stderr" {
338+ eprintln ! (
339+ "[DEBUG][HarnessBackend] emit-stderr t={} session={} text_len={}" ,
340+ harness_debug_now_ms( ) ,
341+ session_id,
342+ line. len( )
343+ ) ;
239344 AgentHarnessEvent :: Stderr {
240345 session_id : session_id. to_string ( ) ,
241346 text : line,
242347 }
243348 } else {
349+ eprintln ! (
350+ "[DEBUG][HarnessBackend] emit-stdout t={} session={} text_len={}" ,
351+ harness_debug_now_ms( ) ,
352+ session_id,
353+ line. len( )
354+ ) ;
244355 AgentHarnessEvent :: Stdout {
245356 session_id : session_id. to_string ( ) ,
246357 text : line,
@@ -280,6 +391,16 @@ async fn read_harness_lines<R>(
280391 }
281392}
282393
394+ #[ tauri:: command]
395+ pub ( crate ) async fn list_agent_harness_sessions ( ) -> Vec < String > {
396+ AGENT_HARNESS_PROCESSES
397+ . lock ( )
398+ . await
399+ . keys ( )
400+ . cloned ( )
401+ . collect ( )
402+ }
403+
283404#[ tauri:: command]
284405pub ( crate ) async fn start_agent_harness_session (
285406 app : tauri:: AppHandle ,
@@ -312,6 +433,20 @@ pub(crate) async fn start_agent_harness_session(
312433 resume_session_id. as_deref ( ) ,
313434 ) ?;
314435 let display_command = harness. display ( ) ;
436+ eprintln ! (
437+ "[DEBUG][HarnessBackend] start t={} session={} provider={} source={} prompt_len={} has_resume={} program={} arg_count={}" ,
438+ harness_debug_now_ms( ) ,
439+ session_id,
440+ provider,
441+ harness. source,
442+ prompt. len( ) ,
443+ resume_session_id
444+ . as_deref( )
445+ . map( |id| !id. trim( ) . is_empty( ) )
446+ . unwrap_or( false ) ,
447+ harness. program,
448+ harness. args. len( )
449+ ) ;
315450
316451 let mut child = Command :: new ( & harness. program )
317452 . args ( & harness. args )
@@ -367,21 +502,38 @@ pub(crate) async fn start_agent_harness_session(
367502 cancelled. store ( true , Ordering :: Relaxed ) ;
368503 AGENT_HARNESS_PROCESSES . lock ( ) . await . remove ( & session_id) ;
369504 match status {
370- Ok ( status) => emit_harness_event (
371- & app,
372- AgentHarnessEvent :: Exit {
505+ Ok ( status) => {
506+ eprintln ! (
507+ "[DEBUG][HarnessBackend] exit t={} session={} code={:?} success={}" ,
508+ harness_debug_now_ms( ) ,
373509 session_id,
374- code : status. code ( ) ,
375- success : status. success ( ) ,
376- } ,
377- ) ,
378- Err ( error) => emit_harness_event (
379- & app,
380- AgentHarnessEvent :: Error {
510+ status. code( ) ,
511+ status. success( )
512+ ) ;
513+ emit_harness_event (
514+ & app,
515+ AgentHarnessEvent :: Exit {
516+ session_id,
517+ code : status. code ( ) ,
518+ success : status. success ( ) ,
519+ } ,
520+ ) ;
521+ }
522+ Err ( error) => {
523+ eprintln ! (
524+ "[DEBUG][HarnessBackend] wait-error t={} session={} message={}" ,
525+ harness_debug_now_ms( ) ,
381526 session_id,
382- message : error. to_string ( ) ,
383- } ,
384- ) ,
527+ error
528+ ) ;
529+ emit_harness_event (
530+ & app,
531+ AgentHarnessEvent :: Error {
532+ session_id,
533+ message : error. to_string ( ) ,
534+ } ,
535+ ) ;
536+ }
385537 }
386538 } ) ;
387539
0 commit comments