codex-autorunner 1.1.0__py3-none-any.whl → 1.2.0__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.
- codex_autorunner/agents/opencode/client.py +113 -4
- codex_autorunner/agents/opencode/supervisor.py +4 -0
- codex_autorunner/agents/registry.py +17 -7
- codex_autorunner/bootstrap.py +219 -1
- codex_autorunner/core/__init__.py +17 -1
- codex_autorunner/core/about_car.py +114 -1
- codex_autorunner/core/app_server_threads.py +6 -0
- codex_autorunner/core/config.py +236 -1
- codex_autorunner/core/context_awareness.py +38 -0
- codex_autorunner/core/docs.py +0 -122
- codex_autorunner/core/filebox.py +265 -0
- codex_autorunner/core/flows/controller.py +71 -1
- codex_autorunner/core/flows/reconciler.py +4 -1
- codex_autorunner/core/flows/runtime.py +22 -0
- codex_autorunner/core/flows/store.py +61 -9
- codex_autorunner/core/flows/transition.py +23 -16
- codex_autorunner/core/flows/ux_helpers.py +18 -3
- codex_autorunner/core/flows/worker_process.py +32 -6
- codex_autorunner/core/hub.py +198 -41
- codex_autorunner/core/lifecycle_events.py +253 -0
- codex_autorunner/core/path_utils.py +2 -1
- codex_autorunner/core/pma_audit.py +224 -0
- codex_autorunner/core/pma_context.py +496 -0
- codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
- codex_autorunner/core/pma_lifecycle.py +527 -0
- codex_autorunner/core/pma_queue.py +367 -0
- codex_autorunner/core/pma_safety.py +221 -0
- codex_autorunner/core/pma_state.py +115 -0
- codex_autorunner/core/ports/agent_backend.py +2 -5
- codex_autorunner/core/ports/run_event.py +1 -4
- codex_autorunner/core/prompt.py +0 -80
- codex_autorunner/core/prompts.py +56 -172
- codex_autorunner/core/redaction.py +0 -4
- codex_autorunner/core/review_context.py +11 -9
- codex_autorunner/core/runner_controller.py +35 -33
- codex_autorunner/core/runner_state.py +147 -0
- codex_autorunner/core/runtime.py +829 -0
- codex_autorunner/core/sqlite_utils.py +13 -4
- codex_autorunner/core/state.py +7 -10
- codex_autorunner/core/state_roots.py +5 -0
- codex_autorunner/core/templates/__init__.py +39 -0
- codex_autorunner/core/templates/git_mirror.py +234 -0
- codex_autorunner/core/templates/provenance.py +56 -0
- codex_autorunner/core/templates/scan_cache.py +120 -0
- codex_autorunner/core/ticket_linter_cli.py +17 -0
- codex_autorunner/core/ticket_manager_cli.py +154 -92
- codex_autorunner/core/time_utils.py +11 -0
- codex_autorunner/core/types.py +18 -0
- codex_autorunner/core/utils.py +34 -6
- codex_autorunner/flows/review/service.py +23 -25
- codex_autorunner/flows/ticket_flow/definition.py +43 -1
- codex_autorunner/integrations/agents/__init__.py +2 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
- codex_autorunner/integrations/agents/codex_backend.py +19 -8
- codex_autorunner/integrations/agents/runner.py +3 -8
- codex_autorunner/integrations/agents/wiring.py +8 -0
- codex_autorunner/integrations/telegram/doctor.py +228 -6
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
- codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
- codex_autorunner/integrations/telegram/handlers/messages.py +26 -1
- codex_autorunner/integrations/telegram/helpers.py +1 -3
- codex_autorunner/integrations/telegram/runtime.py +9 -4
- codex_autorunner/integrations/telegram/service.py +30 -0
- codex_autorunner/integrations/telegram/state.py +38 -0
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
- codex_autorunner/integrations/telegram/transport.py +10 -3
- codex_autorunner/integrations/templates/__init__.py +27 -0
- codex_autorunner/integrations/templates/scan_agent.py +312 -0
- codex_autorunner/server.py +2 -2
- codex_autorunner/static/agentControls.js +21 -5
- codex_autorunner/static/app.js +115 -11
- codex_autorunner/static/chatUploads.js +137 -0
- codex_autorunner/static/docChatCore.js +185 -13
- codex_autorunner/static/fileChat.js +68 -40
- codex_autorunner/static/fileboxUi.js +159 -0
- codex_autorunner/static/hub.js +46 -81
- codex_autorunner/static/index.html +303 -24
- codex_autorunner/static/messages.js +82 -4
- codex_autorunner/static/notifications.js +255 -0
- codex_autorunner/static/pma.js +1167 -0
- codex_autorunner/static/settings.js +3 -0
- codex_autorunner/static/streamUtils.js +57 -0
- codex_autorunner/static/styles.css +9125 -6742
- codex_autorunner/static/templateReposSettings.js +225 -0
- codex_autorunner/static/ticketChatActions.js +165 -3
- codex_autorunner/static/ticketChatStream.js +17 -119
- codex_autorunner/static/ticketEditor.js +41 -13
- codex_autorunner/static/ticketTemplates.js +798 -0
- codex_autorunner/static/tickets.js +69 -19
- codex_autorunner/static/turnEvents.js +27 -0
- codex_autorunner/static/turnResume.js +33 -0
- codex_autorunner/static/utils.js +28 -0
- codex_autorunner/static/workspace.js +258 -44
- codex_autorunner/static/workspaceFileBrowser.js +6 -4
- codex_autorunner/surfaces/cli/cli.py +1465 -155
- codex_autorunner/surfaces/cli/pma_cli.py +817 -0
- codex_autorunner/surfaces/web/app.py +253 -49
- codex_autorunner/surfaces/web/routes/__init__.py +4 -0
- codex_autorunner/surfaces/web/routes/analytics.py +29 -22
- codex_autorunner/surfaces/web/routes/file_chat.py +317 -36
- codex_autorunner/surfaces/web/routes/filebox.py +227 -0
- codex_autorunner/surfaces/web/routes/flows.py +219 -29
- codex_autorunner/surfaces/web/routes/messages.py +70 -39
- codex_autorunner/surfaces/web/routes/pma.py +1652 -0
- codex_autorunner/surfaces/web/routes/repos.py +1 -1
- codex_autorunner/surfaces/web/routes/shared.py +0 -3
- codex_autorunner/surfaces/web/routes/templates.py +634 -0
- codex_autorunner/surfaces/web/runner_manager.py +2 -2
- codex_autorunner/surfaces/web/schemas.py +70 -18
- codex_autorunner/tickets/agent_pool.py +27 -0
- codex_autorunner/tickets/files.py +33 -16
- codex_autorunner/tickets/lint.py +50 -0
- codex_autorunner/tickets/models.py +3 -0
- codex_autorunner/tickets/outbox.py +41 -5
- codex_autorunner/tickets/runner.py +350 -69
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/METADATA +15 -19
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/RECORD +125 -94
- codex_autorunner/core/adapter_utils.py +0 -21
- codex_autorunner/core/engine.py +0 -3302
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Runner state and lock management utilities.
|
|
2
|
+
|
|
3
|
+
This module provides runner state operations extracted from Engine.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from .locks import (
|
|
12
|
+
DEFAULT_RUNNER_CMD_HINTS,
|
|
13
|
+
FileLock,
|
|
14
|
+
FileLockBusy,
|
|
15
|
+
assess_lock,
|
|
16
|
+
process_alive,
|
|
17
|
+
read_lock_info,
|
|
18
|
+
write_lock_info,
|
|
19
|
+
)
|
|
20
|
+
from .state import load_state, now_iso
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LockError(Exception):
|
|
24
|
+
"""Raised when a runner lock cannot be acquired."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _timestamp() -> str:
|
|
28
|
+
return now_iso()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RunnerStateManager:
|
|
32
|
+
"""Manages runner state and locks for ticket flows."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
repo_root: Path,
|
|
37
|
+
lock_path: Optional[Path] = None,
|
|
38
|
+
state_path: Optional[Path] = None,
|
|
39
|
+
):
|
|
40
|
+
self.repo_root = repo_root
|
|
41
|
+
self.lock_path = lock_path or (repo_root / ".codex-autorunner" / "lock")
|
|
42
|
+
self.state_path = state_path or (
|
|
43
|
+
repo_root / ".codex-autorunner" / "state.sqlite3"
|
|
44
|
+
)
|
|
45
|
+
self.stop_path = repo_root / ".codex-autorunner" / "stop"
|
|
46
|
+
self._lock_handle: Optional[FileLock] = None
|
|
47
|
+
|
|
48
|
+
def acquire_lock(self, force: bool = False) -> None:
|
|
49
|
+
"""Acquire the runner lock."""
|
|
50
|
+
self._lock_handle = FileLock(self.lock_path)
|
|
51
|
+
try:
|
|
52
|
+
self._lock_handle.acquire(blocking=False)
|
|
53
|
+
except FileLockBusy as exc:
|
|
54
|
+
info = read_lock_info(self.lock_path)
|
|
55
|
+
pid = info.pid
|
|
56
|
+
if pid and process_alive(pid):
|
|
57
|
+
raise LockError(
|
|
58
|
+
f"Another autorunner is active (pid={pid}); stop it before continuing"
|
|
59
|
+
) from exc
|
|
60
|
+
raise LockError(
|
|
61
|
+
"Another autorunner is active; stop it before continuing"
|
|
62
|
+
) from exc
|
|
63
|
+
|
|
64
|
+
info = read_lock_info(self.lock_path)
|
|
65
|
+
pid = info.pid
|
|
66
|
+
if pid and process_alive(pid) and not force:
|
|
67
|
+
self._lock_handle.release()
|
|
68
|
+
self._lock_handle = None
|
|
69
|
+
raise LockError(
|
|
70
|
+
f"Another autorunner is active (pid={pid}); use --force to override"
|
|
71
|
+
)
|
|
72
|
+
write_lock_info(
|
|
73
|
+
self.lock_path,
|
|
74
|
+
os.getpid(),
|
|
75
|
+
started_at=_timestamp(),
|
|
76
|
+
lock_file=self._lock_handle.file,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def release_lock(self) -> None:
|
|
80
|
+
"""Release the runner lock."""
|
|
81
|
+
if self._lock_handle is not None:
|
|
82
|
+
self._lock_handle.release()
|
|
83
|
+
self._lock_handle = None
|
|
84
|
+
if self.lock_path.exists():
|
|
85
|
+
self.lock_path.unlink()
|
|
86
|
+
|
|
87
|
+
def repo_busy_reason(self) -> Optional[str]:
|
|
88
|
+
"""Return a reason why the repo is busy, or None if not busy."""
|
|
89
|
+
if self.lock_path.exists():
|
|
90
|
+
assessment = assess_lock(
|
|
91
|
+
self.lock_path,
|
|
92
|
+
expected_cmd_substrings=DEFAULT_RUNNER_CMD_HINTS,
|
|
93
|
+
)
|
|
94
|
+
if assessment.freeable:
|
|
95
|
+
return "Autorunner lock is stale; clear it before continuing."
|
|
96
|
+
pid = assessment.pid
|
|
97
|
+
if pid and process_alive(pid):
|
|
98
|
+
host = f" on {assessment.host}" if assessment.host else ""
|
|
99
|
+
return f"Autorunner is running (pid={pid}{host}); try again later."
|
|
100
|
+
return "Autorunner lock present; clear or resume before continuing."
|
|
101
|
+
|
|
102
|
+
state = load_state(self.state_path)
|
|
103
|
+
if state.status == "running":
|
|
104
|
+
if state.runner_pid and process_alive(state.runner_pid):
|
|
105
|
+
return f"Autorunner is currently running (pid={state.runner_pid}); try again later."
|
|
106
|
+
return "Autorunner state is stale; use 'car resume' to continue."
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
def request_stop(self) -> None:
|
|
110
|
+
"""Request a stop by writing to the stop path."""
|
|
111
|
+
self.stop_path.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
self.stop_path.write_text(f"{_timestamp()}\n")
|
|
113
|
+
|
|
114
|
+
def clear_stop_request(self) -> None:
|
|
115
|
+
"""Clear a stop request."""
|
|
116
|
+
self.stop_path.unlink(missing_ok=True)
|
|
117
|
+
|
|
118
|
+
def stop_requested(self) -> bool:
|
|
119
|
+
"""Check if a stop has been requested."""
|
|
120
|
+
return self.stop_path.exists()
|
|
121
|
+
|
|
122
|
+
def kill_running_process(self) -> Optional[int]:
|
|
123
|
+
"""Force-kill the process holding the lock, if any. Returns pid if killed."""
|
|
124
|
+
if not self.lock_path.exists():
|
|
125
|
+
return None
|
|
126
|
+
info = read_lock_info(self.lock_path)
|
|
127
|
+
pid = info.pid
|
|
128
|
+
if pid and process_alive(pid):
|
|
129
|
+
try:
|
|
130
|
+
os.kill(pid, signal.SIGTERM)
|
|
131
|
+
return pid
|
|
132
|
+
except OSError:
|
|
133
|
+
return None
|
|
134
|
+
# stale lock
|
|
135
|
+
self.lock_path.unlink(missing_ok=True)
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def runner_pid(self) -> Optional[int]:
|
|
139
|
+
"""Get the PID of the running runner."""
|
|
140
|
+
state = load_state(self.state_path)
|
|
141
|
+
pid = state.runner_pid
|
|
142
|
+
if pid and process_alive(pid):
|
|
143
|
+
return pid
|
|
144
|
+
info = read_lock_info(self.lock_path)
|
|
145
|
+
if info.pid and process_alive(info.pid):
|
|
146
|
+
return info.pid
|
|
147
|
+
return None
|