--- name: comfyui_stream_progress kind: function lang: py domain: ml version: "1.1.0" purity: impure signature: "def comfyui_stream_progress(prompt_id: str, *, server: str = \"127.0.0.1:8188\", client_id: str | None = None, timeout: float = 300.0) -> dict" description: "Sigue en vivo el progreso de un prompt ComfyUI por WebSocket ws:///ws?clientId= (eventos progress paso/total, executing por nodo, execution_success/execution_error) en vez de hacer polling. Alternativa en-vivo a comfyui_wait_result. Si websocket-client NO esta en el interprete que ejecuta (el venv del registry no lo trae; el de ComfyUI si), cae limpiamente a polling de /history reutilizando comfyui_wait_result y marca method='polling'. Devuelve {ok, completed, steps_seen, last_node, method, error}. Impura: WebSocket o HTTP." tags: [comfyui, ml, progress, websocket, stream, http] uses_functions: [comfyui_wait_result_py_ml] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] params: - name: prompt_id desc: "id devuelto por comfyui_submit_workflow, el prompt cuyo progreso seguir." - name: server desc: "host:port del servidor ComfyUI sin esquema (default '127.0.0.1:8188')." - name: client_id desc: "clientId para registrar el socket; si None se genera un uuid4 hex." - name: timeout desc: "maximo de segundos a esperar a que el prompt complete (default 300)." output: "dict con ok (bool), completed (bool, True si el prompt termino), steps_seen (int, mensajes 'progress' vistos por WS; 0 en fallback de polling), last_node (str, ultimo nodo en ejecucion visto), method (str, 'websocket' o 'polling'), error (str, vacio si OK)." tested: false tests: [] test_file_path: "" file_path: "python/functions/ml/comfyui_stream_progress.py" --- ## Ejemplo ```python import sys, os, uuid sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions")) from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow from ml.comfyui_submit_workflow import comfyui_submit_workflow from ml.comfyui_stream_progress import comfyui_stream_progress cid = uuid.uuid4().hex # MISMO clientId en submit y en stream para ver progress en vivo wf = comfyui_build_txt2img_workflow( ckpt_name="dreamshaper_8.safetensors", positive="an ornate brass clockwork dragon", steps=25, seed=424242) pid = comfyui_submit_workflow(wf, client_id=cid)["prompt_id"] res = comfyui_stream_progress(pid, client_id=cid, timeout=300) # {'ok': True, 'completed': True, 'steps_seen': 13, 'last_node': '9', # 'method': 'websocket', 'error': ''} ``` Si NO compartes el `client_id`, el seguimiento sigue funcionando (detecta el fin por `/history`) pero `steps_seen` sale 0: ComfyUI envia los eventos `progress` al socket del `clientId` que encolo el prompt, no a otro. Para WebSocket real hay que ejecutarlo con un interprete que tenga `websocket-client` (el venv de ComfyUI lo trae). Con el venv del registry (`./fn run`) cae a polling automaticamente y devuelve `method='polling'`. ## Cuando usarla Cuando quieres feedback en vivo de una generacion larga (hires-fix, vídeo, 3D multi-vista) en lugar de esperar a ciegas con `comfyui_wait_result`: ver por que nodo va el grafo (`last_node`) y cuantos pasos de sampler han pasado (`steps_seen`). Util para barras de progreso o para detectar un cuelgue (si `steps_seen` no avanza). Para el caso simple "solo dime cuando esta listo", `comfyui_wait_result` (polling) basta y es mas portable. ## Gotchas - `websocket-client` NO esta en el venv del registry, asi que `./fn run comfyui_stream_progress ` cae a polling (`method='polling'`, `steps_seen=0`). Para el WebSocket real, ejecutalo con el python de ComfyUI (que si lo trae). El fallback es transparente: mismo dict de retorno. - `steps_seen` cuenta mensajes `progress` del WS, no el "step N/total" exacto del sampler; sirve como senal de avance, no como porcentaje preciso. En trabajos con nodos cacheados (que completan en <1s) puede salir 0 con `completed=True`: no hubo pasos que emitir, el fin se detecto por el chequeo de `/history`. - **Carrera submit/WS (v1.1.0):** un prompt rapido o cacheado puede terminar antes de que el WS reciba su `execution_success`. La funcion se defiende: comprueba `/history` al entrar (si ya termino, retorna ya) y en cada ventana de recv sin eventos (detecta el fin sin esperar al timeout). Por eso NO se cuelga 300s en trabajos veloces. - **`client_id` debe coincidir con el del submit para ver `progress`.** ComfyUI enruta los eventos `progress`/`executing` al socket cuyo `clientId` encolo el prompt (`send_sync(..., sid=client_id)`). Si llamas a esta funcion con un `client_id` distinto (o None, que genera uno nuevo) NO recibiras esos eventos de ese prompt y `steps_seen` saldra 0 — aunque `completed` se detecta igual por el chequeo de `/history`. Para barra de progreso real: genera un `cid`, pasalo a `comfyui_submit_workflow(..., client_id=cid)` Y a esta funcion. - En fallo de conexion del WS degrada al fallback de polling en vez de lanzar; si tambien falla el polling, devuelve `ok=False` con el motivo en `error`. - Los frames binarios del WS (previews de imagen en vivo) se ignoran; esta funcion solo sigue el progreso, no descarga la imagen (para eso, `comfyui_fetch_output_image`). ## Capability growth log - v1.1.0 (24/06/2026) — robustez ante la carrera submit/WS: pre-check de `/history` al entrar + re-check de `/history` en cada ventana de recv sin eventos y al agotar el timeout. Evita el cuelgue hasta timeout cuando un trabajo cacheado/rapido completa antes de que el WS reciba `execution_success`. Smoke previo lo destapo (prompt cacheado completaba en ~0.7s y la v1.0.0 esperaba 180s en vano).