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.
Files changed (127) hide show
  1. codex_autorunner/agents/opencode/client.py +113 -4
  2. codex_autorunner/agents/opencode/supervisor.py +4 -0
  3. codex_autorunner/agents/registry.py +17 -7
  4. codex_autorunner/bootstrap.py +219 -1
  5. codex_autorunner/core/__init__.py +17 -1
  6. codex_autorunner/core/about_car.py +114 -1
  7. codex_autorunner/core/app_server_threads.py +6 -0
  8. codex_autorunner/core/config.py +236 -1
  9. codex_autorunner/core/context_awareness.py +38 -0
  10. codex_autorunner/core/docs.py +0 -122
  11. codex_autorunner/core/filebox.py +265 -0
  12. codex_autorunner/core/flows/controller.py +71 -1
  13. codex_autorunner/core/flows/reconciler.py +4 -1
  14. codex_autorunner/core/flows/runtime.py +22 -0
  15. codex_autorunner/core/flows/store.py +61 -9
  16. codex_autorunner/core/flows/transition.py +23 -16
  17. codex_autorunner/core/flows/ux_helpers.py +18 -3
  18. codex_autorunner/core/flows/worker_process.py +32 -6
  19. codex_autorunner/core/hub.py +198 -41
  20. codex_autorunner/core/lifecycle_events.py +253 -0
  21. codex_autorunner/core/path_utils.py +2 -1
  22. codex_autorunner/core/pma_audit.py +224 -0
  23. codex_autorunner/core/pma_context.py +496 -0
  24. codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
  25. codex_autorunner/core/pma_lifecycle.py +527 -0
  26. codex_autorunner/core/pma_queue.py +367 -0
  27. codex_autorunner/core/pma_safety.py +221 -0
  28. codex_autorunner/core/pma_state.py +115 -0
  29. codex_autorunner/core/ports/agent_backend.py +2 -5
  30. codex_autorunner/core/ports/run_event.py +1 -4
  31. codex_autorunner/core/prompt.py +0 -80
  32. codex_autorunner/core/prompts.py +56 -172
  33. codex_autorunner/core/redaction.py +0 -4
  34. codex_autorunner/core/review_context.py +11 -9
  35. codex_autorunner/core/runner_controller.py +35 -33
  36. codex_autorunner/core/runner_state.py +147 -0
  37. codex_autorunner/core/runtime.py +829 -0
  38. codex_autorunner/core/sqlite_utils.py +13 -4
  39. codex_autorunner/core/state.py +7 -10
  40. codex_autorunner/core/state_roots.py +5 -0
  41. codex_autorunner/core/templates/__init__.py +39 -0
  42. codex_autorunner/core/templates/git_mirror.py +234 -0
  43. codex_autorunner/core/templates/provenance.py +56 -0
  44. codex_autorunner/core/templates/scan_cache.py +120 -0
  45. codex_autorunner/core/ticket_linter_cli.py +17 -0
  46. codex_autorunner/core/ticket_manager_cli.py +154 -92
  47. codex_autorunner/core/time_utils.py +11 -0
  48. codex_autorunner/core/types.py +18 -0
  49. codex_autorunner/core/utils.py +34 -6
  50. codex_autorunner/flows/review/service.py +23 -25
  51. codex_autorunner/flows/ticket_flow/definition.py +43 -1
  52. codex_autorunner/integrations/agents/__init__.py +2 -0
  53. codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
  54. codex_autorunner/integrations/agents/codex_backend.py +19 -8
  55. codex_autorunner/integrations/agents/runner.py +3 -8
  56. codex_autorunner/integrations/agents/wiring.py +8 -0
  57. codex_autorunner/integrations/telegram/doctor.py +228 -6
  58. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  59. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  60. codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
  61. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  62. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
  63. codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
  64. codex_autorunner/integrations/telegram/handlers/messages.py +26 -1
  65. codex_autorunner/integrations/telegram/helpers.py +1 -3
  66. codex_autorunner/integrations/telegram/runtime.py +9 -4
  67. codex_autorunner/integrations/telegram/service.py +30 -0
  68. codex_autorunner/integrations/telegram/state.py +38 -0
  69. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
  70. codex_autorunner/integrations/telegram/transport.py +10 -3
  71. codex_autorunner/integrations/templates/__init__.py +27 -0
  72. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  73. codex_autorunner/server.py +2 -2
  74. codex_autorunner/static/agentControls.js +21 -5
  75. codex_autorunner/static/app.js +115 -11
  76. codex_autorunner/static/chatUploads.js +137 -0
  77. codex_autorunner/static/docChatCore.js +185 -13
  78. codex_autorunner/static/fileChat.js +68 -40
  79. codex_autorunner/static/fileboxUi.js +159 -0
  80. codex_autorunner/static/hub.js +46 -81
  81. codex_autorunner/static/index.html +303 -24
  82. codex_autorunner/static/messages.js +82 -4
  83. codex_autorunner/static/notifications.js +255 -0
  84. codex_autorunner/static/pma.js +1167 -0
  85. codex_autorunner/static/settings.js +3 -0
  86. codex_autorunner/static/streamUtils.js +57 -0
  87. codex_autorunner/static/styles.css +9125 -6742
  88. codex_autorunner/static/templateReposSettings.js +225 -0
  89. codex_autorunner/static/ticketChatActions.js +165 -3
  90. codex_autorunner/static/ticketChatStream.js +17 -119
  91. codex_autorunner/static/ticketEditor.js +41 -13
  92. codex_autorunner/static/ticketTemplates.js +798 -0
  93. codex_autorunner/static/tickets.js +69 -19
  94. codex_autorunner/static/turnEvents.js +27 -0
  95. codex_autorunner/static/turnResume.js +33 -0
  96. codex_autorunner/static/utils.js +28 -0
  97. codex_autorunner/static/workspace.js +258 -44
  98. codex_autorunner/static/workspaceFileBrowser.js +6 -4
  99. codex_autorunner/surfaces/cli/cli.py +1465 -155
  100. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  101. codex_autorunner/surfaces/web/app.py +253 -49
  102. codex_autorunner/surfaces/web/routes/__init__.py +4 -0
  103. codex_autorunner/surfaces/web/routes/analytics.py +29 -22
  104. codex_autorunner/surfaces/web/routes/file_chat.py +317 -36
  105. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  106. codex_autorunner/surfaces/web/routes/flows.py +219 -29
  107. codex_autorunner/surfaces/web/routes/messages.py +70 -39
  108. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  109. codex_autorunner/surfaces/web/routes/repos.py +1 -1
  110. codex_autorunner/surfaces/web/routes/shared.py +0 -3
  111. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  112. codex_autorunner/surfaces/web/runner_manager.py +2 -2
  113. codex_autorunner/surfaces/web/schemas.py +70 -18
  114. codex_autorunner/tickets/agent_pool.py +27 -0
  115. codex_autorunner/tickets/files.py +33 -16
  116. codex_autorunner/tickets/lint.py +50 -0
  117. codex_autorunner/tickets/models.py +3 -0
  118. codex_autorunner/tickets/outbox.py +41 -5
  119. codex_autorunner/tickets/runner.py +350 -69
  120. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/METADATA +15 -19
  121. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/RECORD +125 -94
  122. codex_autorunner/core/adapter_utils.py +0 -21
  123. codex_autorunner/core/engine.py +0 -3302
  124. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
  125. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
  126. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
  127. {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