@@ -27,6 +27,8 @@ import { BashTool } from "../../tool/bash"
2727import { TodoWriteTool } from "../../tool/todo"
2828import { Locale } from "../../util"
2929import { AppRuntime } from "@/effect/app-runtime"
30+ import { SessionID } from "@/session/schema"
31+ import { RunEvents } from "./run-events"
3032
3133type ToolProps < T > = {
3234 input : Tool . InferParameters < T >
@@ -380,14 +382,14 @@ export const RunCommand = cmd({
380382
381383 if ( baseID && args . fork ) {
382384 const forked = await sdk . session . fork ( { sessionID : baseID } )
383- return forked . data ?. id
385+ return forked . data ?. id ? SessionID . make ( forked . data . id ) : undefined
384386 }
385387
386- if ( baseID ) return baseID
388+ if ( baseID ) return SessionID . make ( baseID )
387389
388390 const name = title ( )
389391 const result = await sdk . session . create ( { title : name , permission : rules } )
390- return result . data ?. id
392+ return result . data ?. id ? SessionID . make ( result . data . id ) : undefined
391393 }
392394
393395 async function share ( sdk : OpencodeClient , sessionID : string ) {
@@ -406,6 +408,8 @@ export const RunCommand = cmd({
406408 }
407409
408410 async function execute ( sdk : OpencodeClient ) {
411+ const jsonMode = args . format === "json"
412+
409413 function tool ( part : ToolPart ) {
410414 try {
411415 if ( part . tool === "bash" ) return bash ( props < typeof BashTool > ( part ) )
@@ -427,7 +431,7 @@ export const RunCommand = cmd({
427431 }
428432
429433 function emit ( type : string , data : Record < string , unknown > ) {
430- if ( args . format === "json" ) {
434+ if ( jsonMode ) {
431435 process . stdout . write ( JSON . stringify ( { type, timestamp : Date . now ( ) , sessionID, ...data } ) + EOL )
432436 return true
433437 }
@@ -440,124 +444,139 @@ export const RunCommand = cmd({
440444 async function loop ( ) {
441445 const toggles = new Map < string , boolean > ( )
442446
443- for await ( const event of events . stream ) {
444- if (
445- event . type === "message.updated" &&
446- event . properties . info . role === "assistant" &&
447- args . format !== "json" &&
448- toggles . get ( "start" ) !== true
449- ) {
450- UI . empty ( )
451- UI . println ( `> ${ event . properties . info . agent } · ${ event . properties . info . modelID } ` )
452- UI . empty ( )
453- toggles . set ( "start" , true )
454- }
455-
456- if ( event . type === "message.part.updated" ) {
457- const part = event . properties . part
458- if ( part . sessionID !== sessionID ) continue
459-
460- if ( part . type === "tool" && ( part . state . status === "completed" || part . state . status === "error" ) ) {
461- if ( emit ( "tool_use" , { part } ) ) continue
462- if ( part . state . status === "completed" ) {
463- tool ( part )
464- continue
465- }
466- inline ( {
467- icon : "✗" ,
468- title : `${ part . tool } failed` ,
469- } )
470- UI . error ( part . state . error )
471- }
472-
447+ try {
448+ for await ( const event of events . stream ) {
473449 if (
474- part . type === "tool " &&
475- part . tool === "task " &&
476- part . state . status === "running " &&
477- args . format !== "json"
450+ event . type === "message.updated " &&
451+ event . properties . info . role === "assistant " &&
452+ args . format !== "json " &&
453+ toggles . get ( "start" ) !== true
478454 ) {
479- if ( toggles . get ( part . id ) === true ) continue
480- task ( props < typeof TaskTool > ( part ) )
481- toggles . set ( part . id , true )
455+ UI . empty ( )
456+ UI . println ( `> ${ event . properties . info . agent } · ${ event . properties . info . modelID } ` )
457+ UI . empty ( )
458+ toggles . set ( "start" , true )
482459 }
483460
484- if ( part . type === "step-start" ) {
485- if ( emit ( "step_start" , { part } ) ) continue
486- }
461+ if ( event . type === "message.part.updated" ) {
462+ const part = event . properties . part
463+ if ( part . sessionID !== sessionID ) continue
464+
465+ if ( part . type === "tool" && ( part . state . status === "completed" || part . state . status === "error" ) ) {
466+ if ( emit ( "tool_use" , { part } ) ) continue
467+ if ( part . state . status === "completed" ) {
468+ tool ( part )
469+ continue
470+ }
471+ inline ( {
472+ icon : "✗" ,
473+ title : `${ part . tool } failed` ,
474+ } )
475+ UI . error ( part . state . error )
476+ }
487477
488- if ( part . type === "step-finish" ) {
489- if ( emit ( "step_finish" , { part } ) ) continue
490- }
478+ if (
479+ part . type === "tool" &&
480+ part . tool === "task" &&
481+ part . state . status === "running" &&
482+ args . format !== "json"
483+ ) {
484+ if ( toggles . get ( part . id ) === true ) continue
485+ task ( props < typeof TaskTool > ( part ) )
486+ toggles . set ( part . id , true )
487+ }
491488
492- if ( part . type === "text" && part . time ?. end ) {
493- if ( emit ( "text" , { part } ) ) continue
494- const text = part . text . trim ( )
495- if ( ! text ) continue
496- if ( ! process . stdout . isTTY ) {
497- process . stdout . write ( text + EOL )
498- continue
489+ if ( part . type === "step-start" ) {
490+ if ( emit ( "step_start" , { part } ) ) continue
499491 }
500- UI . empty ( )
501- UI . println ( text )
502- UI . empty ( )
503- }
504492
505- if ( part . type === "reasoning" && part . time ?. end && args . thinking ) {
506- if ( emit ( "reasoning" , { part } ) ) continue
507- const text = part . text . trim ( )
508- if ( ! text ) continue
509- const line = `Thinking: ${ text } `
510- if ( process . stdout . isTTY ) {
493+ if ( part . type === "step-finish" ) {
494+ if ( emit ( "step_finish" , { part } ) ) continue
495+ }
496+
497+ if ( part . type === "text" && part . time ?. end ) {
498+ if ( emit ( "text" , { part } ) ) continue
499+ const text = part . text . trim ( )
500+ if ( ! text ) continue
501+ if ( ! process . stdout . isTTY ) {
502+ process . stdout . write ( text + EOL )
503+ continue
504+ }
511505 UI . empty ( )
512- UI . println ( ` ${ UI . Style . TEXT_DIM } \u001b[3m ${ line } \u001b[0m ${ UI . Style . TEXT_NORMAL } ` )
506+ UI . println ( text )
513507 UI . empty ( )
514- continue
515508 }
516- process . stdout . write ( line + EOL )
509+
510+ if ( part . type === "reasoning" && part . time ?. end && args . thinking ) {
511+ if ( emit ( "reasoning" , { part } ) ) continue
512+ const text = part . text . trim ( )
513+ if ( ! text ) continue
514+ const line = `Thinking: ${ text } `
515+ if ( process . stdout . isTTY ) {
516+ UI . empty ( )
517+ UI . println ( `${ UI . Style . TEXT_DIM } \u001b[3m${ line } \u001b[0m${ UI . Style . TEXT_NORMAL } ` )
518+ UI . empty ( )
519+ continue
520+ }
521+ process . stdout . write ( line + EOL )
522+ }
517523 }
518- }
519524
520- if ( event . type === "session.error" ) {
521- const props = event . properties
522- if ( props . sessionID !== sessionID || ! props . error ) continue
523- let err = String ( props . error . name )
524- if ( "data" in props . error && props . error . data && "message" in props . error . data ) {
525- err = String ( props . error . data . message )
525+ if ( event . type === "session.error" ) {
526+ const props = event . properties
527+ if ( props . sessionID !== sessionID || ! props . error ) continue
528+ let err = String ( props . error . name )
529+ if ( "data" in props . error && props . error . data && "message" in props . error . data ) {
530+ err = String ( props . error . data . message )
531+ }
532+ error = error ? error + EOL + err : err
533+ if ( emit ( "error" , { error : props . error } ) ) continue
534+ UI . error ( err )
526535 }
527- error = error ? error + EOL + err : err
528- if ( emit ( "error" , { error : props . error } ) ) continue
529- UI . error ( err )
530- }
531536
532- if (
533- event . type === "session.status" &&
534- event . properties . sessionID === sessionID &&
535- event . properties . status . type === "idle"
536- ) {
537- break
538- }
537+ if (
538+ event . type === "session.status" &&
539+ event . properties . sessionID === sessionID &&
540+ event . properties . status . type === "idle"
541+ ) {
542+ break
543+ }
544+
545+ if ( event . type === "permission.asked" ) {
546+ const permission = event . properties
547+ if ( permission . sessionID !== sessionID ) continue
548+
549+ if ( runEventsHandle ) {
550+ if ( ! args [ "dangerously-skip-permissions" ] && ! jsonMode ) {
551+ UI . println (
552+ UI . Style . TEXT_WARNING_BOLD + "!" ,
553+ UI . Style . TEXT_NORMAL +
554+ `permission requested: ${ permission . permission } (${ permission . patterns . join ( ", " ) } ); auto-rejecting` ,
555+ )
556+ }
557+ continue
558+ }
539559
540- if ( event . type === "permission.asked" ) {
541- const permission = event . properties
542- if ( permission . sessionID !== sessionID ) continue
543-
544- if ( args [ "dangerously-skip-permissions" ] ) {
545- await sdk . permission . reply ( {
546- requestID : permission . id ,
547- reply : "once" ,
548- } )
549- } else {
550- UI . println (
551- UI . Style . TEXT_WARNING_BOLD + "!" ,
552- UI . Style . TEXT_NORMAL +
553- `permission requested: ${ permission . permission } (${ permission . patterns . join ( ", " ) } ); auto-rejecting` ,
554- )
555- await sdk . permission . reply ( {
556- requestID : permission . id ,
557- reply : "reject" ,
558- } )
560+ if ( args [ "dangerously-skip-permissions" ] ) {
561+ await sdk . permission . reply ( {
562+ requestID : permission . id ,
563+ reply : "once" ,
564+ } )
565+ } else {
566+ UI . println (
567+ UI . Style . TEXT_WARNING_BOLD + "!" ,
568+ UI . Style . TEXT_NORMAL +
569+ `permission requested: ${ permission . permission } (${ permission . patterns . join ( ", " ) } ); auto-rejecting` ,
570+ )
571+ await sdk . permission . reply ( {
572+ requestID : permission . id ,
573+ reply : "reject" ,
574+ } )
575+ }
559576 }
560577 }
578+ } finally {
579+ runEventsHandle ?. unsubscribe ( )
561580 }
562581 }
563582
@@ -629,6 +648,17 @@ export const RunCommand = cmd({
629648 UI . error ( "Session not found" )
630649 process . exit ( 1 )
631650 }
651+
652+ const runEventsHandle = args . attach
653+ ? null
654+ : await AppRuntime . runPromise (
655+ RunEvents . make ( {
656+ rootSessionID : sessionID ,
657+ skipPermissions : args [ "dangerously-skip-permissions" ] === true ,
658+ jsonMode,
659+ } ) ,
660+ )
661+
632662 await share ( sdk , sessionID )
633663
634664 loop ( ) . catch ( ( e ) => {
0 commit comments