synth-ai 0.2.12__py3-none-any.whl → 0.2.13.dev2__py3-none-any.whl

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.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (229) hide show
  1. examples/multi_step/configs/crafter_rl_outcome.toml +74 -0
  2. examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +186 -0
  3. examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +83 -0
  4. examples/multi_step/configs/crafter_rl_stepwise_simple.toml +78 -0
  5. examples/multi_step/crafter_rl_lora.md +51 -10
  6. examples/multi_step/sse_metrics_streaming_notes.md +357 -0
  7. examples/multi_step/task_app_config_notes.md +7 -1
  8. examples/swe/task_app/grpo_swe_mini.py +55 -26
  9. examples/swe/task_app/hosted/rollout.py +40 -0
  10. examples/swe/task_app/hosted/test_service.py +5 -6
  11. examples/task_apps/TESTING.md +275 -0
  12. examples/task_apps/__init__.py +0 -0
  13. examples/task_apps/crafter/__init__.py +0 -0
  14. examples/task_apps/crafter/task_app/__init__.py +2 -0
  15. examples/{warming_up_to_rl → task_apps/crafter}/task_app/grpo_crafter.py +21 -46
  16. examples/{warming_up_to_rl → task_apps/crafter}/task_app/grpo_crafter_task_app.py +1 -1
  17. examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/policy.py +60 -4
  18. examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/inference/openai_client.py +109 -45
  19. examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/policy_routes.py +67 -49
  20. examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/rollout.py +242 -193
  21. examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/test_service.py +5 -6
  22. examples/task_apps/dev/pokemon_emerald/__init__.py +2 -0
  23. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +811 -0
  24. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +120 -0
  25. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +160 -0
  26. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +155 -0
  27. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +69 -0
  28. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +96 -0
  29. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +1502 -0
  30. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +4 -0
  31. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +68 -0
  32. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +216 -0
  33. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +35 -0
  34. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +631 -0
  35. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +1544 -0
  36. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +1428 -0
  37. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +4848 -0
  38. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +41 -0
  39. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +298 -0
  40. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +95 -0
  41. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +204 -0
  42. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/__init__.py +0 -0
  43. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +2152 -0
  44. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +429 -0
  45. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +155 -0
  46. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +78 -0
  47. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/__init__.py +0 -0
  48. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +122 -0
  49. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +76 -0
  50. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +413 -0
  51. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +204 -0
  52. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +133 -0
  53. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +229 -0
  54. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +300 -0
  55. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +205 -0
  56. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +200 -0
  57. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +284 -0
  58. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +468 -0
  59. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +575 -0
  60. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +311 -0
  61. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +259 -0
  62. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/__init__.py +0 -0
  63. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +372 -0
  64. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +296 -0
  65. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +275 -0
  66. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +22 -0
  67. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +44 -0
  68. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +514 -0
  69. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +415 -0
  70. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +1763 -0
  71. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +33 -0
  72. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +106 -0
  73. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +334 -0
  74. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +1020 -0
  75. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +188 -0
  76. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +1481 -0
  77. examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +862 -0
  78. examples/task_apps/dev/pokemon_emerald/modal_app.py +114 -0
  79. examples/task_apps/dev/pokemon_emerald/task_app/README.md +81 -0
  80. examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +6 -0
  81. examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +685 -0
  82. examples/task_apps/enron/__init__.py +1 -0
  83. examples/task_apps/enron/eval_groq_qwen32.toml +16 -0
  84. examples/task_apps/enron/task_app/README.md +14 -0
  85. examples/task_apps/enron/task_app/__init__.py +1 -0
  86. examples/task_apps/enron/task_app/grpo_enron.py +906 -0
  87. examples/task_apps/enron/task_app/grpo_enron_task_app.py +146 -0
  88. examples/task_apps/enron/tests/__init__.py +2 -0
  89. examples/task_apps/enron/tests/conftest.py +115 -0
  90. examples/task_apps/enron/tests/integration/__init__.py +2 -0
  91. examples/task_apps/enron/tests/integration/test_enron_eval.py +177 -0
  92. examples/task_apps/enron/tests/integration/test_enron_rollout.py +135 -0
  93. examples/task_apps/enron/tests/unit/__init__.py +2 -0
  94. examples/task_apps/enron/tests/unit/test_enron_environment.py +126 -0
  95. examples/task_apps/math/__init__.py +0 -0
  96. examples/{rl/task_app → task_apps/math}/math_single_step.py +19 -10
  97. examples/task_apps/pokemon_battle/__init__.py +2 -0
  98. examples/task_apps/pokemon_battle/modal_app.py +104 -0
  99. examples/task_apps/pokemon_battle/task_app/README.md +68 -0
  100. examples/task_apps/pokemon_battle/task_app/__init__.py +6 -0
  101. examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +932 -0
  102. examples/task_apps/pokemon_red/README.md +357 -0
  103. examples/task_apps/pokemon_red/__init__.py +3 -0
  104. examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +225 -0
  105. examples/task_apps/pokemon_red/pallet_town_rl_config.toml +73 -0
  106. examples/task_apps/pokemon_red/task_app.py +606 -0
  107. examples/task_apps/pokemon_red/test_pallet_town_rewards.py +191 -0
  108. examples/task_apps/sokoban/README.md +307 -0
  109. examples/task_apps/sokoban/__init__.py +3 -0
  110. examples/task_apps/sokoban/eval_groq_qwen32.toml +16 -0
  111. examples/task_apps/sokoban/eval_openai_gpt5.toml +16 -0
  112. examples/task_apps/sokoban/task_app.py +1058 -0
  113. examples/task_apps/sokoban/tests/__init__.py +2 -0
  114. examples/task_apps/sokoban/tests/conftest.py +113 -0
  115. examples/task_apps/sokoban/tests/integration/__init__.py +2 -0
  116. examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +57 -0
  117. examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +198 -0
  118. examples/task_apps/sokoban/tests/unit/__init__.py +2 -0
  119. examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +114 -0
  120. examples/task_apps/verilog/__init__.py +1 -0
  121. examples/task_apps/verilog/eval_groq_qwen32b.toml +20 -0
  122. examples/task_apps/verilog/task_app/README.md +12 -0
  123. examples/task_apps/verilog/task_app/__init__.py +1 -0
  124. examples/task_apps/verilog/task_app/grpo_verilog.py +931 -0
  125. examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +145 -0
  126. examples/task_apps/verilog/tests/__init__.py +2 -0
  127. examples/task_apps/verilog/tests/conftest.py +115 -0
  128. examples/task_apps/verilog/tests/integration/__init__.py +2 -0
  129. examples/task_apps/verilog/tests/integration/test_verilog_eval.py +179 -0
  130. examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +55 -0
  131. examples/task_apps/verilog/tests/unit/__init__.py +2 -0
  132. examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +118 -0
  133. examples/vlm/crafter_openai_vlm_agent.py +4 -4
  134. examples/vlm/run_crafter_vlm_benchmark.py +4 -4
  135. examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +4 -2
  136. examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +4 -2
  137. examples/warming_up_to_rl/run_eval.py +127 -18
  138. examples/workflows/__init__.py +0 -0
  139. examples/workflows/math_rl/__init__.py +0 -0
  140. examples/workflows/math_rl/download_dataset.py +80 -0
  141. synth_ai/__init__.py +41 -1
  142. synth_ai/api/train/builders.py +73 -29
  143. synth_ai/api/train/cli.py +12 -6
  144. synth_ai/api/train/configs/__init__.py +44 -0
  145. synth_ai/api/train/configs/rl.py +134 -0
  146. synth_ai/api/train/configs/sft.py +95 -0
  147. synth_ai/api/train/configs/shared.py +24 -0
  148. synth_ai/api/train/env_resolver.py +5 -2
  149. synth_ai/api/train/supported_algos.py +10 -5
  150. synth_ai/api/train/utils.py +7 -4
  151. synth_ai/cli/__init__.py +7 -51
  152. synth_ai/cli/_storage.py +4 -3
  153. synth_ai/cli/_validate_task_app.py +11 -0
  154. synth_ai/cli/balance.py +4 -3
  155. synth_ai/cli/calc.py +2 -2
  156. synth_ai/cli/demo.py +49 -43
  157. synth_ai/cli/legacy_root_backup.py +1 -1
  158. synth_ai/cli/rl_demo.py +86 -106
  159. synth_ai/cli/root.py +0 -97
  160. synth_ai/cli/task_apps.py +1710 -186
  161. synth_ai/demos/core/cli.py +121 -159
  162. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +28 -16
  163. synth_ai/environments/examples/crafter_classic/environment.py +16 -0
  164. synth_ai/environments/examples/enron/engine.py +7 -2
  165. synth_ai/environments/examples/enron/environment.py +68 -0
  166. synth_ai/environments/examples/red/engine.py +27 -0
  167. synth_ai/environments/examples/red/engine_helpers/memory_map.py +7 -0
  168. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +477 -0
  169. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +32 -0
  170. synth_ai/environments/examples/red/environment.py +60 -0
  171. synth_ai/environments/examples/sokoban/taskset.py +116 -0
  172. synth_ai/environments/examples/verilog/engine.py +30 -4
  173. synth_ai/evals/__init__.py +15 -0
  174. synth_ai/evals/client.py +82 -0
  175. synth_ai/evals/types.py +42 -0
  176. synth_ai/jobs/client.py +16 -4
  177. synth_ai/judge_schemas.py +127 -0
  178. synth_ai/py.typed +0 -0
  179. synth_ai/task/__init__.py +14 -5
  180. synth_ai/task/contracts.py +124 -38
  181. synth_ai/task/proxy.py +48 -56
  182. synth_ai/task/rubrics/__init__.py +53 -0
  183. synth_ai/task/rubrics/loaders.py +133 -0
  184. synth_ai/task/rubrics/models.py +57 -0
  185. synth_ai/task/rubrics/scoring.py +113 -0
  186. synth_ai/task/rubrics/strict.py +149 -0
  187. synth_ai/task/server.py +8 -7
  188. synth_ai/task/validators.py +269 -6
  189. synth_ai/tracing_v3/decorators.py +7 -3
  190. synth_ai/tracing_v3/replica_sync.py +4 -4
  191. synth_ai/tracing_v3/serialization.py +130 -0
  192. synth_ai/tracing_v3/trace_utils.py +317 -0
  193. synth_ai/tracing_v3/turso/native_manager.py +3 -3
  194. {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/METADATA +4 -1
  195. {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/RECORD +228 -89
  196. {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/entry_points.txt +0 -1
  197. synth_ai/task/rubrics.py +0 -219
  198. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/README.md +0 -0
  199. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/README.md +0 -0
  200. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/__init__.py +0 -0
  201. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/branching.py +0 -0
  202. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/environment_routes.py +0 -0
  203. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/__init__.py +0 -0
  204. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/__init__.py +0 -0
  205. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/app.py +0 -0
  206. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/environment.py +0 -0
  207. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/react_agent.py +0 -0
  208. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/shared.py +0 -0
  209. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/envs/crafter/tools.py +0 -0
  210. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/hosted_app.py +0 -0
  211. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/inference/__init__.py +0 -0
  212. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/main.py +0 -0
  213. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/registry.py +0 -0
  214. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/storage/__init__.py +0 -0
  215. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/storage/volume.py +0 -0
  216. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/test_agents.py +0 -0
  217. /examples/{warming_up_to_rl → task_apps/crafter}/task_app/synth_envs_hosted/utils.py +0 -0
  218. /examples/{rl/task_app → task_apps/math}/README.md +0 -0
  219. /examples/{rl/task_app → task_apps/math}/math_task_app.py +0 -0
  220. /examples/{rl → workflows/math_rl}/configs/eval_base_qwen.toml +0 -0
  221. /examples/{rl → workflows/math_rl}/configs/eval_rl_qwen.toml +0 -0
  222. /examples/{rl → workflows/math_rl}/configs/rl_from_base_qwen.toml +0 -0
  223. /examples/{rl → workflows/math_rl}/configs/rl_from_base_qwen17.toml +0 -0
  224. /examples/{rl → workflows/math_rl}/configs/rl_from_ft_qwen.toml +0 -0
  225. /examples/{rl → workflows/math_rl}/run_eval.py +0 -0
  226. /examples/{rl → workflows/math_rl}/run_rl_and_save.py +0 -0
  227. {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/WHEEL +0 -0
  228. {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/licenses/LICENSE +0 -0
  229. {synth_ai-0.2.12.dist-info → synth_ai-0.2.13.dev2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Error handling and recovery utilities for the Pokemon agent.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import time
9
+ import signal
10
+ import traceback
11
+ import subprocess
12
+ from typing import Optional, Callable
13
+
14
+
15
+ class ErrorHandler:
16
+ """Manages error recovery and graceful shutdowns"""
17
+
18
+ def __init__(self, max_consecutive_errors=3, recovery_delay=2.0):
19
+ """
20
+ Initialize error handler.
21
+
22
+ Args:
23
+ max_consecutive_errors: Max errors before triggering recovery
24
+ recovery_delay: Delay in seconds before retrying after errors
25
+ """
26
+ self.consecutive_errors = 0
27
+ self.max_consecutive_errors = max_consecutive_errors
28
+ self.recovery_delay = recovery_delay
29
+ self.total_errors = 0
30
+ self.shutdown_requested = False
31
+
32
+ # Callbacks
33
+ self.on_shutdown = None
34
+ self.on_recovery = None
35
+
36
+ # Install signal handlers
37
+ self._install_signal_handlers()
38
+
39
+ def _install_signal_handlers(self):
40
+ """Install signal handlers for graceful shutdown"""
41
+ signal.signal(signal.SIGINT, self._signal_handler)
42
+ signal.signal(signal.SIGTERM, self._signal_handler)
43
+
44
+ def _signal_handler(self, signum, frame):
45
+ """Handle shutdown signals"""
46
+ print(f"\n🛑 Received signal {signum}, initiating graceful shutdown...")
47
+ self.shutdown_requested = True
48
+
49
+ if self.on_shutdown:
50
+ try:
51
+ self.on_shutdown()
52
+ except:
53
+ pass
54
+
55
+ # Give a moment for cleanup
56
+ time.sleep(0.5)
57
+ sys.exit(0)
58
+
59
+ def handle_error(self, error: Exception, context: str = "") -> bool:
60
+ """
61
+ Handle an error with potential recovery.
62
+
63
+ Args:
64
+ error: The exception that occurred
65
+ context: Additional context about where the error occurred
66
+
67
+ Returns:
68
+ bool: True if recovery should be attempted, False if fatal
69
+ """
70
+ self.consecutive_errors += 1
71
+ self.total_errors += 1
72
+
73
+ # Log the error
74
+ print(f"❌ Error in {context}: {str(error)}")
75
+ if self.consecutive_errors > 1:
76
+ print(f" (Error {self.consecutive_errors}/{self.max_consecutive_errors})")
77
+
78
+ # Check if we should attempt recovery
79
+ if self.consecutive_errors >= self.max_consecutive_errors:
80
+ print(f"⚠️ Too many consecutive errors, initiating recovery...")
81
+ return self.attempt_recovery()
82
+
83
+ # Simple delay before continuing
84
+ time.sleep(self.recovery_delay)
85
+ return True
86
+
87
+ def attempt_recovery(self) -> bool:
88
+ """
89
+ Attempt to recover from errors.
90
+
91
+ Returns:
92
+ bool: True if recovery successful, False otherwise
93
+ """
94
+ try:
95
+ print("🔄 Attempting recovery...")
96
+
97
+ # Call recovery callback if set
98
+ if self.on_recovery:
99
+ success = self.on_recovery()
100
+ if success:
101
+ self.reset_error_counter()
102
+ print("✅ Recovery successful")
103
+ return True
104
+
105
+ # Default recovery: just reset and continue
106
+ self.reset_error_counter()
107
+ time.sleep(self.recovery_delay * 2) # Longer delay for recovery
108
+ return True
109
+
110
+ except Exception as e:
111
+ print(f"❌ Recovery failed: {e}")
112
+ return False
113
+
114
+ def reset_error_counter(self):
115
+ """Reset the consecutive error counter"""
116
+ if self.consecutive_errors > 0:
117
+ print(f"✅ Resetting error counter (was {self.consecutive_errors})")
118
+ self.consecutive_errors = 0
119
+
120
+ def track_success(self):
121
+ """Track a successful operation to reset error counter"""
122
+ if self.consecutive_errors > 0:
123
+ self.consecutive_errors = 0
124
+
125
+ def is_shutdown_requested(self) -> bool:
126
+ """Check if shutdown has been requested"""
127
+ return self.shutdown_requested
128
+
129
+ def get_error_stats(self) -> dict:
130
+ """Get error statistics"""
131
+ return {
132
+ 'consecutive_errors': self.consecutive_errors,
133
+ 'total_errors': self.total_errors,
134
+ 'max_consecutive': self.max_consecutive_errors
135
+ }
136
+
137
+
138
+ class ServerRestartHandler:
139
+ """Handles server process restart on failures"""
140
+
141
+ def __init__(self, restart_threshold=5):
142
+ """
143
+ Initialize server restart handler.
144
+
145
+ Args:
146
+ restart_threshold: Number of failures before restart
147
+ """
148
+ self.restart_threshold = restart_threshold
149
+ self.failure_count = 0
150
+ self.server_process = None
151
+ self.server_cmd = []
152
+ self.server_env = {}
153
+
154
+ def set_server_process(self, process: subprocess.Popen, cmd: list, env: dict = None):
155
+ """
156
+ Set the server process to manage.
157
+
158
+ Args:
159
+ process: The subprocess.Popen instance
160
+ cmd: Command used to start the server
161
+ env: Environment variables for the server
162
+ """
163
+ self.server_process = process
164
+ self.server_cmd = cmd
165
+ self.server_env = env or {}
166
+
167
+ def check_and_restart(self) -> bool:
168
+ """
169
+ Check if restart is needed and perform it.
170
+
171
+ Returns:
172
+ bool: True if restarted successfully
173
+ """
174
+ self.failure_count += 1
175
+
176
+ if self.failure_count >= self.restart_threshold:
177
+ return self.restart_server()
178
+
179
+ return False
180
+
181
+ def restart_server(self) -> bool:
182
+ """
183
+ Restart the server process.
184
+
185
+ Returns:
186
+ bool: True if restarted successfully
187
+ """
188
+ if not self.server_cmd:
189
+ print("❌ No server command configured for restart")
190
+ return False
191
+
192
+ try:
193
+ print("🔄 Restarting server process...")
194
+
195
+ # Kill existing process
196
+ if self.server_process:
197
+ try:
198
+ self.server_process.terminate()
199
+ time.sleep(1)
200
+ if self.server_process.poll() is None:
201
+ self.server_process.kill()
202
+ except:
203
+ pass
204
+
205
+ # Start new process
206
+ self.server_process = subprocess.Popen(
207
+ self.server_cmd,
208
+ env={**os.environ, **self.server_env},
209
+ stdout=subprocess.PIPE,
210
+ stderr=subprocess.STDOUT,
211
+ universal_newlines=True,
212
+ bufsize=1
213
+ )
214
+
215
+ print(f"✅ Server restarted with PID {self.server_process.pid}")
216
+
217
+ # Give it time to initialize
218
+ time.sleep(3)
219
+
220
+ # Reset failure count
221
+ self.failure_count = 0
222
+ return True
223
+
224
+ except Exception as e:
225
+ print(f"❌ Failed to restart server: {e}")
226
+ return False
227
+
228
+ def reset_failure_count(self):
229
+ """Reset the failure counter"""
230
+ self.failure_count = 0
231
+
232
+
233
+ # Global error handler instance
234
+ _error_handler = None
235
+
236
+
237
+ def get_error_handler() -> ErrorHandler:
238
+ """Get or create the global error handler"""
239
+ global _error_handler
240
+ if _error_handler is None:
241
+ _error_handler = ErrorHandler()
242
+ return _error_handler
243
+
244
+
245
+ def handle_agent_error(error: Exception, context: str = "") -> bool:
246
+ """
247
+ Handle an agent error using the global error handler.
248
+
249
+ Args:
250
+ error: The exception that occurred
251
+ context: Context about where the error occurred
252
+
253
+ Returns:
254
+ bool: True if recovery should be attempted
255
+ """
256
+ handler = get_error_handler()
257
+ return handler.handle_error(error, context)
258
+
259
+
260
+ def reset_error_counter():
261
+ """Reset the global error counter"""
262
+ handler = get_error_handler()
263
+ handler.reset_error_counter()
264
+
265
+
266
+ def install_shutdown_handler(callback: Optional[Callable] = None):
267
+ """
268
+ Install a shutdown handler.
269
+
270
+ Args:
271
+ callback: Optional callback to run on shutdown
272
+ """
273
+ handler = get_error_handler()
274
+ if callback:
275
+ handler.on_shutdown = callback
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Get local IP address for network interface display
4
+ """
5
+
6
+ import socket
7
+
8
+ def get_local_ip():
9
+ """Get the local IP address of this machine"""
10
+ try:
11
+ # Connect to a remote address to determine local IP
12
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
13
+ # Use Google DNS as target (doesn't actually connect)
14
+ s.connect(("8.8.8.8", 80))
15
+ local_ip = s.getsockname()[0]
16
+ return local_ip
17
+ except Exception:
18
+ # Fallback to localhost
19
+ return "127.0.0.1"
20
+
21
+ if __name__ == "__main__":
22
+ print(f"Local IP: {get_local_ip()}")
@@ -0,0 +1,44 @@
1
+ import time
2
+ import base64
3
+ from io import BytesIO
4
+ import cv2
5
+ from PIL import Image
6
+
7
+ def frame_to_base64(frame):
8
+ """Convert PIL Image frame to base64 encoded PNG"""
9
+ if hasattr(frame, 'convert'): # It's a PIL Image
10
+ # Resize the PIL Image to 640x480
11
+ resized_frame = frame.resize((640, 480), Image.Resampling.NEAREST)
12
+ buffered = BytesIO()
13
+ resized_frame.save(buffered, format="PNG")
14
+ return base64.b64encode(buffered.getvalue()).decode('utf-8')
15
+ elif hasattr(frame, 'shape'): # It's a numpy array
16
+ # Convert numpy array to PIL Image first
17
+ resized_frame = cv2.resize(frame, (640, 480), interpolation=cv2.INTER_NEAREST)
18
+ img = Image.fromarray(resized_frame)
19
+ buffered = BytesIO()
20
+ img.save(buffered, format="PNG")
21
+ return base64.b64encode(buffered.getvalue()).decode('utf-8')
22
+ else:
23
+ raise ValueError(f"Unsupported frame type: {type(frame)}")
24
+
25
+ def add_text_update(text, category=None, socket_queue=None, text_updates=None):
26
+ """Add text to the text updates list with optional category"""
27
+ timestamp = time.strftime("%H:%M:%S", time.localtime())
28
+
29
+ if category:
30
+ formatted_text = f"[{timestamp}] [{category}]: {text}"
31
+ else:
32
+ formatted_text = f"[{timestamp}] [DEBUG]: {text}"
33
+
34
+ if text_updates is not None:
35
+ text_updates.append(formatted_text)
36
+ # Keep only the last 100 updates
37
+ if len(text_updates) > 100:
38
+ text_updates.pop(0)
39
+
40
+ print(formatted_text)
41
+
42
+ # Emit update via WebSocket
43
+ if socket_queue is not None:
44
+ socket_queue.put(('text_update', {'text': formatted_text}))