@@ -5,7 +5,7 @@ import { loadWorkflow, detectOutputNodes } from "cli/utils/fs";
55import { coerceValue , isValidNodePath } from "cli/utils/value-parser" ;
66import { extractMediaFromOutputs , buildResultOverrides , isFilePath } from "cli/runner" ;
77import { createRenderer , resolveMode } from "cli/renderer/index" ;
8- import type { TuiRenderer } from "cli/renderer/tui" ;
8+ import type { InkTuiRenderer } from "cli/renderer/ink/ink- tui-renderer " ;
99import type { RunResult } from "cli/renderer/json" ;
1010import type { RunConfig } from "cli/runner" ;
1111
@@ -23,24 +23,29 @@ const DEBOUNCE_MS = 500;
2323
2424interface WatchState {
2525 runNumber : number ;
26- abortController : AbortController | null ;
2726 debounceTimer : Timer | null ;
2827 running : boolean ;
2928 client : ComfyApi | null ;
3029}
3130
32- function isTui ( renderer : any ) : renderer is TuiRenderer {
31+ function isTui ( renderer : any ) : renderer is InkTuiRenderer {
3332 return renderer && typeof renderer . showWatchStatus === "function" ;
3433}
3534
35+ export function buildWatchClientOptions ( credentials : any , listenTerminal : boolean ) {
36+ return {
37+ ...( credentials ? { credentials } : { } ) ,
38+ listenTerminal
39+ } ;
40+ }
41+
3642export async function watchMode ( config : RunConfig , noTui = false ) : Promise < void > {
3743 const mode = resolveMode ( config . json , config . quiet ) ;
3844 const useTui = mode === "terminal" && ! noTui ;
3945 const renderer = createRenderer ( mode , config . host , config . file , useTui , noTui , useTui ) ;
4046
4147 const state : WatchState = {
4248 runNumber : 0 ,
43- abortController : null ,
4449 debounceTimer : null ,
4550 running : false ,
4651 client : null
@@ -60,7 +65,9 @@ export async function watchMode(config: RunConfig, noTui = false): Promise<void>
6065 credentials = { type : "basic" , username : config . user , password : config . pass } ;
6166 }
6267
63- state . client = new ComfyApi ( config . host , undefined , credentials ? { credentials } : undefined ) ;
68+ state . client = new ComfyApi ( config . host , undefined , buildWatchClientOptions ( credentials , useTui ) ) ;
69+
70+ const detachTerminalStream = isTui ( renderer ) ? attachTerminalLogStream ( state . client , renderer ) : ( ) => { } ;
6471
6572 await state . client . init ( 5 , 2000 ) . waitForReady ( ) ;
6673 if ( isTui ( renderer ) ) {
@@ -71,13 +78,17 @@ export async function watchMode(config: RunConfig, noTui = false): Promise<void>
7178 }
7279
7380 const runOnce = async ( ) => {
81+ state . running = true ;
7482 state . runNumber ++ ;
7583 const runNum = state . runNumber ;
7684 const startTime = performance . now ( ) ;
7785 let interrupted = false ;
7886
7987 if ( isTui ( renderer ) ) {
8088 renderer . resetRun ( ) ;
89+ renderer . startRun ( runNum ) ;
90+ } else if ( ! config . json ) {
91+ console . log ( c . dim ( `[Run #${ runNum } ]` ) + ` ${ c . cyan ( "starting" ) } ${ config . file } ` ) ;
8192 }
8293
8394 try {
@@ -131,11 +142,13 @@ export async function watchMode(config: RunConfig, noTui = false): Promise<void>
131142 } ;
132143 renderer . render ( runResult ) ;
133144 }
145+ } finally {
146+ state . running = false ;
134147 }
135148
136149 if ( ! interrupted && ! isTui ( renderer ) && ! config . json ) {
137150 console . log ( ) ;
138- console . log ( c . dim ( `${ c . blue ( "watching" ) } ${ config . file } ...` ) ) ;
151+ console . log ( c . dim ( `${ c . blue ( "watching" ) } ${ config . file } for changes ...` ) ) ;
139152 }
140153 } ;
141154
@@ -186,16 +199,15 @@ export async function watchMode(config: RunConfig, noTui = false): Promise<void>
186199 }
187200 }
188201
189- state . running = true ;
190202 await runOnce ( ) ;
191- state . running = false ;
192203 } , DEBOUNCE_MS ) ;
193204 } ) ;
194205
195206 return new Promise < void > ( ( ) => {
196207 process . on ( "SIGINT" , ( ) => {
197208 if ( state . debounceTimer ) clearTimeout ( state . debounceTimer ) ;
198209 watcher . close ( ) ;
210+ detachTerminalStream ( ) ;
199211 state . client ! . destroy ( ) ;
200212 if ( isTui ( renderer ) ) {
201213 renderer . stop ( ) ;
@@ -239,11 +251,7 @@ async function executeWorkflow(client: ComfyApi, config: RunConfig, renderer: an
239251 . onProgress ( ( info : any ) => renderer . onProgress ( info ) )
240252 . onOutput ( ( key : any , data : any ) => renderer . onOutput ( String ( key ) , data ) )
241253 . onFinished ( ( _data : any ) => { } )
242- . onFailed ( ( err : Error ) => {
243- if ( ! err . message . includes ( "interrupted" ) && ! err . message . includes ( "Interrupted" ) ) {
244- renderer . onFailed ( err ) ;
245- }
246- } ) ;
254+ . onFailed ( ( _err : Error ) => { } ) ;
247255
248256 const result = await Promise . race ( [
249257 callWrapper . run ( ) ,
@@ -258,3 +266,30 @@ async function executeWorkflow(client: ComfyApi, config: RunConfig, renderer: an
258266
259267 return result ;
260268}
269+
270+ export function attachTerminalLogStream (
271+ client : Pick < ComfyApi , "addEventListener" | "removeEventListener" > ,
272+ renderer : Pick < InkTuiRenderer , "addServerLog" | "showWatchStatus" >
273+ ) : ( ) => void {
274+ let warned = false ;
275+
276+ const onTerminal = ( event : Event ) => {
277+ const detail = ( event as CustomEvent < { m : string ; t : string } | null > ) . detail ;
278+ if ( ! detail ?. m ) return ;
279+ renderer . addServerLog ( detail . m ) ;
280+ } ;
281+
282+ const onSubscriptionError = ( ) => {
283+ if ( warned ) return ;
284+ warned = true ;
285+ renderer . showWatchStatus ( "Live terminal logs unavailable; continuing with progress updates." ) ;
286+ } ;
287+
288+ client . addEventListener ( "terminal" , onTerminal as EventListener ) ;
289+ client . addEventListener ( "terminal_subscription_error" , onSubscriptionError as EventListener ) ;
290+
291+ return ( ) => {
292+ client . removeEventListener ( "terminal" , onTerminal as EventListener ) ;
293+ client . removeEventListener ( "terminal_subscription_error" , onSubscriptionError as EventListener ) ;
294+ } ;
295+ }
0 commit comments