codex-autorunner 1.0.0__py3-none-any.whl → 1.1.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 (170) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/agents/codex/harness.py +1 -1
  3. codex_autorunner/agents/opencode/constants.py +3 -0
  4. codex_autorunner/agents/opencode/harness.py +6 -1
  5. codex_autorunner/agents/opencode/runtime.py +59 -18
  6. codex_autorunner/agents/registry.py +22 -3
  7. codex_autorunner/bootstrap.py +7 -3
  8. codex_autorunner/cli.py +5 -1174
  9. codex_autorunner/codex_cli.py +20 -84
  10. codex_autorunner/core/__init__.py +4 -0
  11. codex_autorunner/core/about_car.py +6 -1
  12. codex_autorunner/core/app_server_ids.py +59 -0
  13. codex_autorunner/core/app_server_threads.py +11 -2
  14. codex_autorunner/core/app_server_utils.py +165 -0
  15. codex_autorunner/core/archive.py +349 -0
  16. codex_autorunner/core/codex_runner.py +6 -2
  17. codex_autorunner/core/config.py +197 -3
  18. codex_autorunner/core/drafts.py +58 -4
  19. codex_autorunner/core/engine.py +1329 -680
  20. codex_autorunner/core/exceptions.py +4 -0
  21. codex_autorunner/core/flows/controller.py +25 -1
  22. codex_autorunner/core/flows/models.py +13 -0
  23. codex_autorunner/core/flows/reasons.py +52 -0
  24. codex_autorunner/core/flows/reconciler.py +131 -0
  25. codex_autorunner/core/flows/runtime.py +35 -4
  26. codex_autorunner/core/flows/store.py +83 -0
  27. codex_autorunner/core/flows/transition.py +5 -0
  28. codex_autorunner/core/flows/ux_helpers.py +257 -0
  29. codex_autorunner/core/git_utils.py +62 -0
  30. codex_autorunner/core/hub.py +121 -7
  31. codex_autorunner/core/notifications.py +14 -2
  32. codex_autorunner/core/ports/__init__.py +28 -0
  33. codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +11 -3
  34. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  35. codex_autorunner/{integrations/agents → core/ports}/run_event.py +22 -2
  36. codex_autorunner/core/state_roots.py +57 -0
  37. codex_autorunner/core/supervisor_protocol.py +15 -0
  38. codex_autorunner/core/text_delta_coalescer.py +54 -0
  39. codex_autorunner/core/ticket_linter_cli.py +201 -0
  40. codex_autorunner/core/ticket_manager_cli.py +432 -0
  41. codex_autorunner/core/update.py +4 -5
  42. codex_autorunner/core/update_paths.py +28 -0
  43. codex_autorunner/core/usage.py +164 -12
  44. codex_autorunner/core/utils.py +91 -9
  45. codex_autorunner/flows/review/__init__.py +17 -0
  46. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  47. codex_autorunner/flows/ticket_flow/definition.py +9 -2
  48. codex_autorunner/integrations/agents/__init__.py +9 -19
  49. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  50. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  51. codex_autorunner/integrations/agents/codex_backend.py +158 -17
  52. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  53. codex_autorunner/integrations/agents/opencode_backend.py +305 -32
  54. codex_autorunner/integrations/agents/runner.py +91 -0
  55. codex_autorunner/integrations/agents/wiring.py +271 -0
  56. codex_autorunner/integrations/app_server/client.py +7 -60
  57. codex_autorunner/integrations/app_server/env.py +2 -107
  58. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  59. codex_autorunner/integrations/telegram/adapter.py +65 -0
  60. codex_autorunner/integrations/telegram/config.py +46 -0
  61. codex_autorunner/integrations/telegram/constants.py +1 -1
  62. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
  63. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1203 -66
  64. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +4 -3
  65. codex_autorunner/integrations/telegram/handlers/commands_spec.py +8 -2
  66. codex_autorunner/integrations/telegram/handlers/messages.py +1 -0
  67. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  68. codex_autorunner/integrations/telegram/helpers.py +24 -1
  69. codex_autorunner/integrations/telegram/service.py +15 -10
  70. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +329 -40
  71. codex_autorunner/integrations/telegram/transport.py +3 -1
  72. codex_autorunner/routes/__init__.py +37 -76
  73. codex_autorunner/routes/agents.py +2 -137
  74. codex_autorunner/routes/analytics.py +2 -238
  75. codex_autorunner/routes/app_server.py +2 -131
  76. codex_autorunner/routes/base.py +2 -596
  77. codex_autorunner/routes/file_chat.py +4 -833
  78. codex_autorunner/routes/flows.py +4 -977
  79. codex_autorunner/routes/messages.py +4 -456
  80. codex_autorunner/routes/repos.py +2 -196
  81. codex_autorunner/routes/review.py +2 -147
  82. codex_autorunner/routes/sessions.py +2 -175
  83. codex_autorunner/routes/settings.py +2 -168
  84. codex_autorunner/routes/shared.py +2 -275
  85. codex_autorunner/routes/system.py +4 -193
  86. codex_autorunner/routes/usage.py +2 -86
  87. codex_autorunner/routes/voice.py +2 -119
  88. codex_autorunner/routes/workspace.py +2 -270
  89. codex_autorunner/server.py +2 -2
  90. codex_autorunner/static/agentControls.js +40 -11
  91. codex_autorunner/static/app.js +11 -3
  92. codex_autorunner/static/archive.js +826 -0
  93. codex_autorunner/static/archiveApi.js +37 -0
  94. codex_autorunner/static/autoRefresh.js +7 -7
  95. codex_autorunner/static/dashboard.js +224 -171
  96. codex_autorunner/static/hub.js +112 -94
  97. codex_autorunner/static/index.html +80 -33
  98. codex_autorunner/static/messages.js +486 -83
  99. codex_autorunner/static/preserve.js +17 -0
  100. codex_autorunner/static/settings.js +125 -6
  101. codex_autorunner/static/smartRefresh.js +52 -0
  102. codex_autorunner/static/styles.css +1373 -101
  103. codex_autorunner/static/tabs.js +152 -11
  104. codex_autorunner/static/terminal.js +18 -0
  105. codex_autorunner/static/ticketEditor.js +99 -5
  106. codex_autorunner/static/tickets.js +760 -87
  107. codex_autorunner/static/utils.js +11 -0
  108. codex_autorunner/static/workspace.js +133 -40
  109. codex_autorunner/static/workspaceFileBrowser.js +9 -9
  110. codex_autorunner/surfaces/__init__.py +5 -0
  111. codex_autorunner/surfaces/cli/__init__.py +6 -0
  112. codex_autorunner/surfaces/cli/cli.py +1224 -0
  113. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  114. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  115. codex_autorunner/surfaces/web/__init__.py +1 -0
  116. codex_autorunner/surfaces/web/app.py +2019 -0
  117. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  118. codex_autorunner/surfaces/web/middleware.py +587 -0
  119. codex_autorunner/surfaces/web/pty_session.py +370 -0
  120. codex_autorunner/surfaces/web/review.py +6 -0
  121. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  122. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  123. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  124. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  125. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  126. codex_autorunner/surfaces/web/routes/base.py +615 -0
  127. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  128. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  129. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  130. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  131. codex_autorunner/surfaces/web/routes/review.py +148 -0
  132. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  133. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  134. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  135. codex_autorunner/surfaces/web/routes/system.py +196 -0
  136. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  137. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  138. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  139. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  140. codex_autorunner/surfaces/web/schemas.py +417 -0
  141. codex_autorunner/surfaces/web/static_assets.py +490 -0
  142. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  143. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  144. codex_autorunner/tickets/__init__.py +8 -1
  145. codex_autorunner/tickets/agent_pool.py +26 -4
  146. codex_autorunner/tickets/files.py +6 -2
  147. codex_autorunner/tickets/models.py +3 -1
  148. codex_autorunner/tickets/outbox.py +12 -0
  149. codex_autorunner/tickets/runner.py +63 -5
  150. codex_autorunner/web/__init__.py +5 -1
  151. codex_autorunner/web/app.py +2 -1949
  152. codex_autorunner/web/hub_jobs.py +2 -191
  153. codex_autorunner/web/middleware.py +2 -586
  154. codex_autorunner/web/pty_session.py +2 -369
  155. codex_autorunner/web/runner_manager.py +2 -24
  156. codex_autorunner/web/schemas.py +2 -376
  157. codex_autorunner/web/static_assets.py +4 -441
  158. codex_autorunner/web/static_refresh.py +2 -85
  159. codex_autorunner/web/terminal_sessions.py +2 -77
  160. codex_autorunner/workspace/paths.py +49 -33
  161. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  162. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  163. codex_autorunner/core/static_assets.py +0 -55
  164. codex_autorunner-1.0.0.dist-info/METADATA +0 -246
  165. codex_autorunner-1.0.0.dist-info/RECORD +0 -251
  166. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  167. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +0 -0
  168. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  169. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  170. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,192 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import dataclasses
5
+ import logging
6
+ import time
7
+ import uuid
8
+ from collections import deque
9
+ from typing import Any, Awaitable, Callable, Deque, Dict, Optional
10
+
11
+ from ...core.logging_utils import log_event
12
+ from ...core.state import now_iso
13
+
14
+
15
+ @dataclasses.dataclass
16
+ class HubJob:
17
+ job_id: str
18
+ kind: str
19
+ status: str
20
+ created_at: str
21
+ started_at: Optional[str] = None
22
+ finished_at: Optional[str] = None
23
+ result: Optional[dict] = None
24
+ error: Optional[str] = None
25
+
26
+ def to_dict(self) -> dict:
27
+ return {
28
+ "job_id": self.job_id,
29
+ "kind": self.kind,
30
+ "status": self.status,
31
+ "created_at": self.created_at,
32
+ "started_at": self.started_at,
33
+ "finished_at": self.finished_at,
34
+ "result": self.result,
35
+ "error": self.error,
36
+ }
37
+
38
+
39
+ class HubJobManager:
40
+ def __init__(
41
+ self,
42
+ *,
43
+ logger: logging.Logger,
44
+ max_jobs: int = 200,
45
+ max_age_seconds: int = 3600,
46
+ max_concurrent_jobs: int = 10,
47
+ ) -> None:
48
+ self._logger = logger
49
+ self._max_jobs = max(10, max_jobs)
50
+ self._max_age_seconds = max(300, max_age_seconds)
51
+ self._max_concurrent_jobs = max(1, max_concurrent_jobs)
52
+ self._jobs: Dict[str, HubJob] = {}
53
+ self._order: Deque[str] = deque()
54
+ try:
55
+ self._lock = asyncio.Lock()
56
+ except RuntimeError:
57
+ asyncio.set_event_loop(asyncio.new_event_loop())
58
+ self._lock = asyncio.Lock()
59
+
60
+ async def submit(
61
+ self,
62
+ kind: str,
63
+ func: Callable[[], Any] | Callable[[], Awaitable[Any]],
64
+ *,
65
+ request_id: Optional[str] = None,
66
+ ) -> HubJob:
67
+ async with self._lock:
68
+ running_count = sum(
69
+ 1 for job in self._jobs.values() if job.status == "running"
70
+ )
71
+ if running_count >= self._max_concurrent_jobs:
72
+ raise Exception(
73
+ f"Too many concurrent jobs: {running_count} (max {self._max_concurrent_jobs})"
74
+ )
75
+ job_id = uuid.uuid4().hex
76
+ job = HubJob(
77
+ job_id=job_id, kind=kind, status="queued", created_at=now_iso()
78
+ )
79
+ self._jobs[job_id] = job
80
+ self._order.append(job_id)
81
+ self._prune_locked()
82
+ asyncio.create_task(self._run_job(job_id, func, request_id=request_id))
83
+ return job
84
+
85
+ async def get(self, job_id: str) -> Optional[HubJob]:
86
+ async with self._lock:
87
+ return self._jobs.get(job_id)
88
+
89
+ async def _run_job(
90
+ self,
91
+ job_id: str,
92
+ func: Callable[[], Any] | Callable[[], Awaitable[Any]],
93
+ *,
94
+ request_id: Optional[str],
95
+ ) -> None:
96
+ async with self._lock:
97
+ job = self._jobs.get(job_id)
98
+ if not job:
99
+ return
100
+ job.status = "running"
101
+ job.started_at = now_iso()
102
+ log_event(
103
+ self._logger,
104
+ logging.INFO,
105
+ "hub.job.start",
106
+ job_id=job_id,
107
+ job_kind=job.kind,
108
+ request_id=request_id,
109
+ )
110
+ started = time.monotonic()
111
+ try:
112
+ if asyncio.iscoroutinefunction(func):
113
+ result = await func()
114
+ else:
115
+ result = await asyncio.to_thread(func)
116
+ except Exception as exc:
117
+ duration_ms = (time.monotonic() - started) * 1000
118
+ async with self._lock:
119
+ job = self._jobs.get(job_id)
120
+ if job:
121
+ job.status = "failed"
122
+ job.finished_at = now_iso()
123
+ job.error = str(exc)
124
+ log_event(
125
+ self._logger,
126
+ logging.ERROR,
127
+ "hub.job.finish",
128
+ job_id=job_id,
129
+ job_kind=job.kind if job else "",
130
+ request_id=request_id,
131
+ status="failed",
132
+ duration_ms=round(duration_ms, 2),
133
+ exc=exc,
134
+ )
135
+ return
136
+ duration_ms = (time.monotonic() - started) * 1000
137
+ async with self._lock:
138
+ job = self._jobs.get(job_id)
139
+ if job:
140
+ job.status = "succeeded"
141
+ job.finished_at = now_iso()
142
+ if isinstance(result, dict):
143
+ job.result = result
144
+ log_event(
145
+ self._logger,
146
+ logging.INFO,
147
+ "hub.job.finish",
148
+ job_id=job_id,
149
+ job_kind=job.kind if job else "",
150
+ request_id=request_id,
151
+ status="succeeded",
152
+ duration_ms=round(duration_ms, 2),
153
+ )
154
+
155
+ def _prune_locked(self) -> None:
156
+ now = time.time()
157
+ attempts = 0
158
+ while len(self._jobs) > self._max_jobs and self._order:
159
+ job_id = self._order.popleft()
160
+ job = self._jobs.get(job_id)
161
+ if (
162
+ job
163
+ and job.status in ("queued", "running")
164
+ and attempts < len(self._order)
165
+ ):
166
+ self._order.append(job_id)
167
+ attempts += 1
168
+ continue
169
+ self._jobs.pop(job_id, None)
170
+ stale_ids = []
171
+ for job_id, job in self._jobs.items():
172
+ if (
173
+ job.finished_at
174
+ and now - self._parse_age(job.finished_at) > self._max_age_seconds
175
+ ):
176
+ stale_ids.append(job_id)
177
+ for job_id in stale_ids:
178
+ self._jobs.pop(job_id, None)
179
+ try:
180
+ self._order.remove(job_id)
181
+ except ValueError:
182
+ pass
183
+
184
+ def _parse_age(self, iso_ts: str) -> float:
185
+ # Best-effort: treat missing/invalid timestamps as now to avoid mis-pruning.
186
+ try:
187
+ import datetime
188
+
189
+ dt = datetime.datetime.fromisoformat(iso_ts.replace("Z", "+00:00"))
190
+ return dt.timestamp()
191
+ except Exception:
192
+ return time.time()