hte-cli 0.2.31__tar.gz → 0.2.32__tar.gz

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.
Files changed (32) hide show
  1. {hte_cli-0.2.31 → hte_cli-0.2.32}/PKG-INFO +1 -1
  2. {hte_cli-0.2.31 → hte_cli-0.2.32}/pyproject.toml +1 -1
  3. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/cli.py +112 -5
  4. {hte_cli-0.2.31 → hte_cli-0.2.32}/.gitignore +0 -0
  5. {hte_cli-0.2.31 → hte_cli-0.2.32}/README.md +0 -0
  6. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/__init__.py +0 -0
  7. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/__main__.py +0 -0
  8. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/api_client.py +0 -0
  9. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/config.py +0 -0
  10. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/errors.py +0 -0
  11. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/events.py +0 -0
  12. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/image_utils.py +0 -0
  13. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/runner.py +0 -0
  14. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/scorers.py +0 -0
  15. {hte_cli-0.2.31 → hte_cli-0.2.32}/src/hte_cli/version_check.py +0 -0
  16. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/__init__.py +0 -0
  17. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/__init__.py +0 -0
  18. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/automated_runner.py +0 -0
  19. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/conftest.py +0 -0
  20. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/e2e_test.py +0 -0
  21. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/test_benchmark_flows.py +0 -0
  22. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/test_eval_logs.py +0 -0
  23. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/test_infrastructure.py +0 -0
  24. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/test_runtime_imports.py +0 -0
  25. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/test_session_lifecycle.py +0 -0
  26. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/e2e/verify_docker_deps.py +0 -0
  27. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/unit/__init__.py +0 -0
  28. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/unit/conftest.py +0 -0
  29. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/unit/test_image_utils.py +0 -0
  30. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/unit/test_runner.py +0 -0
  31. {hte_cli-0.2.31 → hte_cli-0.2.32}/tests/unit/test_scorers.py +0 -0
  32. {hte_cli-0.2.31 → hte_cli-0.2.32}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hte-cli
3
- Version: 0.2.31
3
+ Version: 0.2.32
4
4
  Summary: Human Time-to-Completion Evaluation CLI
5
5
  Project-URL: Homepage, https://github.com/sean-peters-au/lyptus-mono
6
6
  Author: Lyptus Research
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hte-cli"
3
- version = "0.2.31"
3
+ version = "0.2.32"
4
4
  description = "Human Time-to-Completion Evaluation CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -4,7 +4,9 @@ Uses Click for command parsing and Rich for pretty output.
4
4
  """
5
5
 
6
6
  import os
7
+ import signal
7
8
  import sys
9
+ import threading
8
10
  import webbrowser
9
11
 
10
12
  import click
@@ -22,6 +24,83 @@ console = Console()
22
24
  # Support email per spec
23
25
  SUPPORT_EMAIL = "jacktpayne51@gmail.com"
24
26
 
27
+ # Warning before cap (15 minutes)
28
+ CAP_WARNING_SECONDS = 15 * 60
29
+
30
+
31
+ class CapEnforcer:
32
+ """Background timer that enforces time cap on capped_completion tasks.
33
+
34
+ Shows warning 15 minutes before cap and terminates the task when cap is reached.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ time_cap_seconds: int,
40
+ start_time: float,
41
+ console: Console,
42
+ main_thread_id: int,
43
+ ):
44
+ self.time_cap_seconds = time_cap_seconds
45
+ self.start_time = start_time
46
+ self.console = console
47
+ self.main_thread_id = main_thread_id
48
+ self._stop_event = threading.Event()
49
+ self._warning_shown = False
50
+ self._thread: threading.Thread | None = None
51
+ self.cap_reached = False
52
+
53
+ def start(self):
54
+ """Start the background timer thread."""
55
+ self._thread = threading.Thread(target=self._run, daemon=True)
56
+ self._thread.start()
57
+
58
+ def stop(self):
59
+ """Stop the background timer."""
60
+ self._stop_event.set()
61
+ if self._thread:
62
+ self._thread.join(timeout=1.0)
63
+
64
+ def _run(self):
65
+ """Timer loop that checks elapsed time."""
66
+ import time
67
+
68
+ warning_threshold = self.time_cap_seconds - CAP_WARNING_SECONDS
69
+
70
+ while not self._stop_event.is_set():
71
+ elapsed = time.monotonic() - self.start_time
72
+
73
+ # Show warning at 15 minutes before cap
74
+ if elapsed >= warning_threshold and not self._warning_shown:
75
+ self._warning_shown = True
76
+ remaining = self.time_cap_seconds - elapsed
77
+ minutes = int(remaining // 60)
78
+ self.console.print()
79
+ self.console.print(
80
+ f"[yellow bold]Warning: Time cap approaching - {minutes} minutes remaining[/yellow bold]"
81
+ )
82
+ self.console.print(
83
+ "[yellow]When cap is reached, session will end and you'll need to record progress in the web UI.[/yellow]"
84
+ )
85
+ self.console.print()
86
+
87
+ # Cap reached - terminate the main thread
88
+ if elapsed >= self.time_cap_seconds:
89
+ self.cap_reached = True
90
+ self.console.print()
91
+ self.console.print(
92
+ f"[red bold]Time cap reached ({self.time_cap_seconds // 60} minutes). Session ending.[/red bold]"
93
+ )
94
+ self.console.print(
95
+ "[yellow]Return to the web UI to record your progress and estimate completion time.[/yellow]"
96
+ )
97
+ # Send SIGINT to main thread to trigger KeyboardInterrupt
98
+ os.kill(os.getpid(), signal.SIGINT)
99
+ break
100
+
101
+ # Check every second
102
+ self._stop_event.wait(1.0)
103
+
25
104
 
26
105
  def _find_eval_log_bytes(runner) -> bytes | None:
27
106
  """Find and read eval log bytes from runner's work directory.
@@ -45,7 +124,9 @@ def _find_eval_log_bytes(runner) -> bytes | None:
45
124
  return None
46
125
 
47
126
 
48
- def _upload_partial_log(api: APIClient, session_id: str, eval_log_bytes: bytes, console: Console) -> None:
127
+ def _upload_partial_log(
128
+ api: APIClient, session_id: str, eval_log_bytes: bytes, console: Console
129
+ ) -> None:
49
130
  """Upload partial eval log for interrupted session.
50
131
 
51
132
  Best-effort: silently handles failures to not block exit.
@@ -470,7 +551,7 @@ def session_join(ctx, session_id: str, force_setup: bool):
470
551
  parts = line.split(": ", 1)
471
552
  if len(parts) == 2:
472
553
  layer_id = parts[0][-8:]
473
- layer_status = parts[1][:70] # Increased to include size info
554
+ layer_status = parts[1][:85] # Include full progress bar + size
474
555
  display = f"{layer_id}: {layer_status}"
475
556
  if display != last_status[0]:
476
557
  last_status[0] = display
@@ -606,6 +687,23 @@ def session_join(ctx, session_id: str, force_setup: bool):
606
687
 
607
688
  events.docker_started()
608
689
 
690
+ # Start cap enforcer if this is a capped_completion task
691
+ time_cap_seconds = assignment.get("time_cap_seconds")
692
+ cap_enforcer: CapEnforcer | None = None
693
+ if time_cap_seconds and session_info.get("mode") == "capped_completion":
694
+ cap_enforcer = CapEnforcer(
695
+ time_cap_seconds=time_cap_seconds,
696
+ start_time=time.monotonic(),
697
+ console=console,
698
+ main_thread_id=threading.get_ident(),
699
+ )
700
+ cap_enforcer.start()
701
+ console.print(
702
+ f"[dim]Time cap: {time_cap_seconds // 60} minutes "
703
+ f"(warning at {(time_cap_seconds - CAP_WARNING_SECONDS) // 60} min)[/dim]"
704
+ )
705
+ console.print()
706
+
609
707
  runner = TaskRunner()
610
708
  eval_log_bytes = None
611
709
  try:
@@ -624,9 +722,15 @@ def session_join(ctx, session_id: str, force_setup: bool):
624
722
  if eval_log_bytes:
625
723
  _upload_partial_log(api, session_id, eval_log_bytes, console)
626
724
  console.print()
627
- console.print(
628
- "[yellow]Interrupted. Session remains active - you can reconnect later.[/yellow]"
629
- )
725
+ # Different message if cap was reached vs user interrupt
726
+ if cap_enforcer and cap_enforcer.cap_reached:
727
+ console.print(
728
+ "[yellow]Time cap reached. Return to web UI to record progress and estimate completion time.[/yellow]"
729
+ )
730
+ else:
731
+ console.print(
732
+ "[yellow]Interrupted. Session remains active - you can reconnect later.[/yellow]"
733
+ )
630
734
  sys.exit(0)
631
735
  except Exception as e:
632
736
  events.docker_stopped(exit_code=1)
@@ -637,6 +741,9 @@ def session_join(ctx, session_id: str, force_setup: bool):
637
741
  console.print(f"[red]Task execution failed: {e}[/red]")
638
742
  sys.exit(1)
639
743
  finally:
744
+ # Stop cap enforcer
745
+ if cap_enforcer:
746
+ cap_enforcer.stop()
640
747
  runner.cleanup()
641
748
 
642
749
  events.docker_stopped(exit_code=0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes