@danhachuel/thunderbolt 0.3.83 → 0.3.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/main.py CHANGED
@@ -3505,7 +3505,7 @@ def _render_pipeline_worker_banner(worker_status: dict[str, Any], active_count:
3505
3505
  st.warning("Worker de vídeo sem heartbeat recente. O launcher deve estar aberto para processar as tarefas.")
3506
3506
  heartbeat = worker_status.get("last_heartbeat_at") or worker_status.get("updated_at")
3507
3507
  if heartbeat:
3508
- st.caption(f"{_pipeline_time_age(heartbeat)} · timeout de execução: {STALE_TASK_SECONDS // 60} minutos")
3508
+ st.caption(f"{_pipeline_time_age(heartbeat)} · vídeo: até 90 min com watchdog de inactividade de 10 min")
3509
3509
  if worker_status.get("last_error"):
3510
3510
  st.error(f"Último erro do worker: {worker_status['last_error']}")
3511
3511
 
@@ -28,7 +28,8 @@ from hermes_ui.thumbnail_generation import ThumbnailGenerationError, generate_th
28
28
  PIPELINE_LOCK_FILENAME = "pipeline_worker.lock"
29
29
  PIPELINE_LOG_FILENAME = "pipeline_worker.json"
30
30
  VIDEO_TIMEOUT_SECONDS = 20 * 60
31
- LONG_STOCK_VIDEO_TIMEOUT_SECONDS = 45 * 60
31
+ LONG_STOCK_VIDEO_TIMEOUT_SECONDS = 90 * 60
32
+ VIDEO_IDLE_TIMEOUT_SECONDS = 10 * 60
32
33
  STALE_TASK_SECONDS = VIDEO_TIMEOUT_SECONDS + 5 * 60
33
34
  WORKER_HEARTBEAT_TIMEOUT_SECONDS = 15
34
35
  CASCADE_STAGE_ORDER = ("topic", "script", "title", "keywords", "video", "thumbnail_prompt", "thumbnail", "upload")
@@ -636,6 +637,17 @@ def _task_stale_timeout_seconds(task: dict[str, Any]) -> int:
636
637
  return STALE_TASK_SECONDS
637
638
 
638
639
 
640
+ def _latest_helper_log_activity(log_path: Path | None, previous_mtime: float) -> tuple[float, bool]:
641
+ """Return whether the helper's file log advanced without reading its content."""
642
+ if log_path is None:
643
+ return previous_mtime, False
644
+ try:
645
+ current_mtime = log_path.stat().st_mtime
646
+ except OSError:
647
+ return previous_mtime, False
648
+ return (current_mtime, current_mtime > previous_mtime)
649
+
650
+
639
651
  def _mpt_aspect_ratio(value: Any, format_value: Any = "") -> str:
640
652
  raw = str(value or format_value or "").strip().casefold()
641
653
  if raw in {"landscape 16:9", "wide", "16:9", "landscape"}:
@@ -915,6 +927,9 @@ def _run_video_helper(task: dict[str, Any]) -> Path:
915
927
  output_finished = False
916
928
  last_heartbeat = 0.0
917
929
  last_output_line = ""
930
+ last_activity_at = started_at
931
+ helper_log_path: Path | None = None
932
+ helper_log_mtime = 0.0
918
933
  try:
919
934
  while True:
920
935
  try:
@@ -924,9 +939,19 @@ def _run_video_helper(task: dict[str, Any]) -> Path:
924
939
  elif line:
925
940
  last_output_line = _redact_helper_output(line).strip()
926
941
  output_lines.append(line)
942
+ last_activity_at = time.monotonic()
943
+ match = re.search(r"full generation log:\s*(.+)$", last_output_line, flags=re.IGNORECASE)
944
+ if match:
945
+ candidate = Path(match.group(1).strip()).expanduser()
946
+ helper_log_path = candidate if candidate.is_file() else None
927
947
  except queue.Empty:
928
948
  pass
929
949
  elapsed = time.monotonic() - started_at
950
+ helper_log_mtime, helper_log_advanced = _latest_helper_log_activity(helper_log_path, helper_log_mtime)
951
+ if helper_log_advanced:
952
+ last_activity_at = time.monotonic()
953
+ if not last_output_line:
954
+ last_output_line = "[MoneyPrinterTurbo] actividade de geração confirmada"
930
955
  if elapsed - last_heartbeat >= 5:
931
956
  # O helper expõe o resultado final, mas não uma percentagem estável.
932
957
  # Mantemos uma faixa reservada para a etapa de vídeo e avançamos-a
@@ -958,6 +983,14 @@ def _run_video_helper(task: dict[str, Any]) -> Path:
958
983
  message = f"A etapa Vídeo excedeu o limite de {timeout_seconds // 60} minutos e foi encerrada."
959
984
  metadata = _failure_attribution(task, settings, "video", error=message)
960
985
  raise PipelineError(_failure_message(message, metadata), failure_metadata=metadata)
986
+ if time.monotonic() - last_activity_at >= VIDEO_IDLE_TIMEOUT_SECONDS:
987
+ _stop_process(process)
988
+ message = (
989
+ "A etapa Vídeo não apresentou actividade comprovada do motor durante "
990
+ f"{VIDEO_IDLE_TIMEOUT_SECONDS // 60} minutos e foi encerrada."
991
+ )
992
+ metadata = _failure_attribution(task, settings, "video", error=message)
993
+ raise PipelineError(_failure_message(message, metadata), failure_metadata=metadata)
961
994
  finally:
962
995
  reader.join(timeout=2)
963
996
  _persist_video_diagnostics(task, "\n".join(output_lines))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.83",
3
+ "version": "0.3.84",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",