codex-autorunner 0.1.2__py3-none-any.whl → 1.0.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 (189) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/opencode/client.py +68 -35
  3. codex_autorunner/agents/opencode/logging.py +21 -5
  4. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  5. codex_autorunner/agents/opencode/runtime.py +118 -30
  6. codex_autorunner/agents/opencode/supervisor.py +36 -48
  7. codex_autorunner/agents/registry.py +136 -8
  8. codex_autorunner/api.py +25 -0
  9. codex_autorunner/bootstrap.py +16 -35
  10. codex_autorunner/cli.py +157 -139
  11. codex_autorunner/core/about_car.py +44 -32
  12. codex_autorunner/core/adapter_utils.py +21 -0
  13. codex_autorunner/core/app_server_logging.py +7 -3
  14. codex_autorunner/core/app_server_prompts.py +27 -260
  15. codex_autorunner/core/app_server_threads.py +15 -26
  16. codex_autorunner/core/codex_runner.py +6 -0
  17. codex_autorunner/core/config.py +390 -100
  18. codex_autorunner/core/docs.py +10 -2
  19. codex_autorunner/core/drafts.py +82 -0
  20. codex_autorunner/core/engine.py +278 -262
  21. codex_autorunner/core/flows/__init__.py +25 -0
  22. codex_autorunner/core/flows/controller.py +178 -0
  23. codex_autorunner/core/flows/definition.py +82 -0
  24. codex_autorunner/core/flows/models.py +75 -0
  25. codex_autorunner/core/flows/runtime.py +351 -0
  26. codex_autorunner/core/flows/store.py +485 -0
  27. codex_autorunner/core/flows/transition.py +133 -0
  28. codex_autorunner/core/flows/worker_process.py +242 -0
  29. codex_autorunner/core/hub.py +15 -9
  30. codex_autorunner/core/locks.py +4 -0
  31. codex_autorunner/core/prompt.py +15 -7
  32. codex_autorunner/core/redaction.py +29 -0
  33. codex_autorunner/core/review_context.py +5 -8
  34. codex_autorunner/core/run_index.py +6 -0
  35. codex_autorunner/core/runner_process.py +5 -2
  36. codex_autorunner/core/state.py +0 -88
  37. codex_autorunner/core/static_assets.py +55 -0
  38. codex_autorunner/core/supervisor_utils.py +67 -0
  39. codex_autorunner/core/update.py +20 -11
  40. codex_autorunner/core/update_runner.py +2 -0
  41. codex_autorunner/core/utils.py +29 -2
  42. codex_autorunner/discovery.py +2 -4
  43. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  44. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  45. codex_autorunner/integrations/agents/__init__.py +27 -0
  46. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  47. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  48. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  49. codex_autorunner/integrations/agents/run_event.py +71 -0
  50. codex_autorunner/integrations/app_server/client.py +576 -92
  51. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  52. codex_autorunner/integrations/telegram/adapter.py +141 -167
  53. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  54. codex_autorunner/integrations/telegram/config.py +175 -0
  55. codex_autorunner/integrations/telegram/constants.py +16 -1
  56. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  57. codex_autorunner/integrations/telegram/doctor.py +47 -0
  58. codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
  59. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  63. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  64. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  65. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  66. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
  67. codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
  68. codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
  69. codex_autorunner/integrations/telegram/helpers.py +88 -16
  70. codex_autorunner/integrations/telegram/outbox.py +208 -37
  71. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  72. codex_autorunner/integrations/telegram/service.py +214 -40
  73. codex_autorunner/integrations/telegram/state.py +100 -2
  74. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  75. codex_autorunner/integrations/telegram/transport.py +36 -3
  76. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  77. codex_autorunner/manifest.py +2 -0
  78. codex_autorunner/plugin_api.py +22 -0
  79. codex_autorunner/routes/__init__.py +23 -14
  80. codex_autorunner/routes/analytics.py +239 -0
  81. codex_autorunner/routes/base.py +81 -109
  82. codex_autorunner/routes/file_chat.py +836 -0
  83. codex_autorunner/routes/flows.py +980 -0
  84. codex_autorunner/routes/messages.py +459 -0
  85. codex_autorunner/routes/system.py +6 -1
  86. codex_autorunner/routes/usage.py +87 -0
  87. codex_autorunner/routes/workspace.py +271 -0
  88. codex_autorunner/server.py +2 -1
  89. codex_autorunner/static/agentControls.js +1 -0
  90. codex_autorunner/static/agentEvents.js +248 -0
  91. codex_autorunner/static/app.js +25 -22
  92. codex_autorunner/static/autoRefresh.js +29 -1
  93. codex_autorunner/static/bootstrap.js +1 -0
  94. codex_autorunner/static/bus.js +1 -0
  95. codex_autorunner/static/cache.js +1 -0
  96. codex_autorunner/static/constants.js +20 -4
  97. codex_autorunner/static/dashboard.js +162 -196
  98. codex_autorunner/static/diffRenderer.js +37 -0
  99. codex_autorunner/static/docChatCore.js +324 -0
  100. codex_autorunner/static/docChatStorage.js +65 -0
  101. codex_autorunner/static/docChatVoice.js +65 -0
  102. codex_autorunner/static/docEditor.js +133 -0
  103. codex_autorunner/static/env.js +1 -0
  104. codex_autorunner/static/eventSummarizer.js +166 -0
  105. codex_autorunner/static/fileChat.js +182 -0
  106. codex_autorunner/static/health.js +155 -0
  107. codex_autorunner/static/hub.js +41 -118
  108. codex_autorunner/static/index.html +787 -858
  109. codex_autorunner/static/liveUpdates.js +1 -0
  110. codex_autorunner/static/loader.js +1 -0
  111. codex_autorunner/static/messages.js +470 -0
  112. codex_autorunner/static/mobileCompact.js +2 -1
  113. codex_autorunner/static/settings.js +24 -211
  114. codex_autorunner/static/styles.css +7567 -3865
  115. codex_autorunner/static/tabs.js +28 -5
  116. codex_autorunner/static/terminal.js +14 -0
  117. codex_autorunner/static/terminalManager.js +34 -59
  118. codex_autorunner/static/ticketChatActions.js +333 -0
  119. codex_autorunner/static/ticketChatEvents.js +16 -0
  120. codex_autorunner/static/ticketChatStorage.js +16 -0
  121. codex_autorunner/static/ticketChatStream.js +264 -0
  122. codex_autorunner/static/ticketEditor.js +750 -0
  123. codex_autorunner/static/ticketVoice.js +9 -0
  124. codex_autorunner/static/tickets.js +1315 -0
  125. codex_autorunner/static/utils.js +32 -3
  126. codex_autorunner/static/voice.js +1 -0
  127. codex_autorunner/static/workspace.js +672 -0
  128. codex_autorunner/static/workspaceApi.js +53 -0
  129. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  130. codex_autorunner/tickets/__init__.py +20 -0
  131. codex_autorunner/tickets/agent_pool.py +377 -0
  132. codex_autorunner/tickets/files.py +85 -0
  133. codex_autorunner/tickets/frontmatter.py +55 -0
  134. codex_autorunner/tickets/lint.py +102 -0
  135. codex_autorunner/tickets/models.py +95 -0
  136. codex_autorunner/tickets/outbox.py +232 -0
  137. codex_autorunner/tickets/replies.py +179 -0
  138. codex_autorunner/tickets/runner.py +823 -0
  139. codex_autorunner/tickets/spec_ingest.py +77 -0
  140. codex_autorunner/web/app.py +269 -91
  141. codex_autorunner/web/middleware.py +3 -4
  142. codex_autorunner/web/schemas.py +89 -109
  143. codex_autorunner/web/static_assets.py +1 -44
  144. codex_autorunner/workspace/__init__.py +40 -0
  145. codex_autorunner/workspace/paths.py +319 -0
  146. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
  147. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  148. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  149. codex_autorunner/agents/execution/policy.py +0 -292
  150. codex_autorunner/agents/factory.py +0 -52
  151. codex_autorunner/agents/orchestrator.py +0 -358
  152. codex_autorunner/core/doc_chat.py +0 -1446
  153. codex_autorunner/core/snapshot.py +0 -580
  154. codex_autorunner/integrations/github/chatops.py +0 -268
  155. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  156. codex_autorunner/routes/docs.py +0 -381
  157. codex_autorunner/routes/github.py +0 -327
  158. codex_autorunner/routes/runs.py +0 -250
  159. codex_autorunner/spec_ingest.py +0 -812
  160. codex_autorunner/static/docChatActions.js +0 -287
  161. codex_autorunner/static/docChatEvents.js +0 -300
  162. codex_autorunner/static/docChatRender.js +0 -205
  163. codex_autorunner/static/docChatStream.js +0 -361
  164. codex_autorunner/static/docs.js +0 -20
  165. codex_autorunner/static/docsClipboard.js +0 -69
  166. codex_autorunner/static/docsCrud.js +0 -257
  167. codex_autorunner/static/docsDocUpdates.js +0 -62
  168. codex_autorunner/static/docsDrafts.js +0 -16
  169. codex_autorunner/static/docsElements.js +0 -69
  170. codex_autorunner/static/docsInit.js +0 -285
  171. codex_autorunner/static/docsParse.js +0 -160
  172. codex_autorunner/static/docsSnapshot.js +0 -87
  173. codex_autorunner/static/docsSpecIngest.js +0 -263
  174. codex_autorunner/static/docsState.js +0 -127
  175. codex_autorunner/static/docsThreadRegistry.js +0 -44
  176. codex_autorunner/static/docsUi.js +0 -153
  177. codex_autorunner/static/docsVoice.js +0 -56
  178. codex_autorunner/static/github.js +0 -504
  179. codex_autorunner/static/logs.js +0 -678
  180. codex_autorunner/static/review.js +0 -157
  181. codex_autorunner/static/runs.js +0 -418
  182. codex_autorunner/static/snapshot.js +0 -124
  183. codex_autorunner/static/state.js +0 -94
  184. codex_autorunner/static/todoPreview.js +0 -27
  185. codex_autorunner/workspace.py +0 -16
  186. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  187. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  188. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  189. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,268 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import json
5
- import logging
6
- import re
7
- import shlex
8
- from pathlib import Path
9
- from typing import Any, Optional
10
-
11
- from ...core.logging_utils import log_event
12
- from ...core.state import now_iso
13
- from ...core.utils import atomic_write, read_json
14
- from .pr_flow import PrFlowError, PrFlowManager
15
- from .service import GitHubService
16
-
17
- COMMANDS = {"implement", "fix", "status", "stop", "resume"}
18
- ISSUE_URL_RE = re.compile(r"/issues/(?P<num>\d+)")
19
-
20
-
21
- def _chatops_state_path(repo_root: Path) -> Path:
22
- return repo_root / ".codex-autorunner" / "pr_flow" / "chatops_state.json"
23
-
24
-
25
- def _parse_command(text: str) -> Optional[tuple[str, list[str]]]:
26
- for line in (text or "").splitlines():
27
- if "@car" not in line and "/car" not in line:
28
- continue
29
- try:
30
- tokens = [tok for tok in shlex.split(line) if tok]
31
- except ValueError:
32
- tokens = [tok for tok in line.split() if tok]
33
- for idx, token in enumerate(tokens):
34
- raw = token.strip().rstrip(":,")
35
- if raw.startswith("@car") or raw == "/car":
36
- if idx + 1 >= len(tokens):
37
- return None
38
- cmd = tokens[idx + 1].strip().lower()
39
- args = tokens[idx + 2 :]
40
- if cmd in COMMANDS:
41
- return cmd, args
42
- return None
43
-
44
-
45
- def _parse_flags(args: list[str]) -> dict[str, Any]:
46
- flags: dict[str, Any] = {}
47
- idx = 0
48
- while idx < len(args):
49
- token = args[idx]
50
- if token == "--until" and idx + 1 < len(args):
51
- flags["stop_condition"] = args[idx + 1]
52
- idx += 2
53
- continue
54
- if token == "--draft":
55
- flags["draft"] = True
56
- idx += 1
57
- continue
58
- if token == "--ready":
59
- flags["draft"] = False
60
- idx += 1
61
- continue
62
- if token == "--base" and idx + 1 < len(args):
63
- flags["base_branch"] = args[idx + 1]
64
- idx += 2
65
- continue
66
- if token in ("--max-cycles", "--max_cycles") and idx + 1 < len(args):
67
- try:
68
- flags["max_cycles"] = int(args[idx + 1])
69
- except ValueError:
70
- pass
71
- idx += 2
72
- continue
73
- idx += 1
74
- return flags
75
-
76
-
77
- def _extract_issue_number(issue_url: str) -> Optional[int]:
78
- if not issue_url:
79
- return None
80
- match = ISSUE_URL_RE.search(issue_url)
81
- if not match:
82
- return None
83
- try:
84
- return int(match.group("num"))
85
- except ValueError:
86
- return None
87
-
88
-
89
- class GitHubChatOpsPoller:
90
- def __init__(
91
- self,
92
- repo_root: Path,
93
- pr_flow: PrFlowManager,
94
- *,
95
- logger: Optional[logging.Logger] = None,
96
- ) -> None:
97
- self._repo_root = repo_root
98
- self._pr_flow = pr_flow
99
- self._logger = logger or logging.getLogger("codex_autorunner.github_chatops")
100
- self._stop_event = asyncio.Event()
101
-
102
- async def run(self) -> None:
103
- cfg = self._pr_flow.chatops_config()
104
- if not cfg.get("enabled", False):
105
- return
106
- poll_interval = int(cfg.get("poll_interval_seconds", 60))
107
- while not self._stop_event.is_set():
108
- try:
109
- await self.poll_once()
110
- except Exception as exc:
111
- log_event(
112
- self._logger,
113
- logging.WARNING,
114
- "github.chatops.poll.failed",
115
- exc=exc,
116
- )
117
- try:
118
- await asyncio.wait_for(self._stop_event.wait(), timeout=poll_interval)
119
- except asyncio.TimeoutError:
120
- continue
121
-
122
- async def stop(self) -> None:
123
- self._stop_event.set()
124
-
125
- async def poll_once(self) -> None:
126
- gh = GitHubService(self._repo_root)
127
- if not gh.gh_available() or not gh.gh_authenticated():
128
- return
129
- repo = gh.repo_info()
130
- owner, repo_name = repo.name_with_owner.split("/", 1)
131
- state = self._load_state()
132
- since = state.get("last_seen")
133
- comments = gh.issue_comments(
134
- owner=owner, repo=repo_name, since=since, limit=100
135
- )
136
- if not comments:
137
- return
138
- comments.sort(key=lambda item: item.get("created_at") or "")
139
- processed = set(state.get("processed_ids") or [])
140
- max_seen = since or ""
141
- for comment in comments:
142
- comment_id = comment.get("id")
143
- if comment_id in processed:
144
- continue
145
- processed.add(comment_id)
146
- created_at = comment.get("created_at") or ""
147
- if created_at and created_at > max_seen:
148
- max_seen = created_at
149
- if not self._authorized(comment):
150
- continue
151
- parsed = _parse_command(comment.get("body") or "")
152
- if not parsed:
153
- continue
154
- command, args = parsed
155
- issue_number = _extract_issue_number(comment.get("issue_url") or "")
156
- if not issue_number:
157
- continue
158
- issue_meta = gh.issue_meta(owner=owner, repo=repo_name, number=issue_number)
159
- is_pr = bool(issue_meta.get("pull_request"))
160
- response = await self._handle_command(
161
- gh,
162
- command,
163
- args,
164
- issue_number=issue_number,
165
- is_pr=is_pr,
166
- )
167
- if response:
168
- gh.create_issue_comment(
169
- owner=owner,
170
- repo=repo_name,
171
- number=issue_number,
172
- body=response,
173
- )
174
- self._save_state(
175
- {
176
- "processed_ids": list(processed)[-500:],
177
- "last_seen": max_seen or now_iso(),
178
- }
179
- )
180
-
181
- def _authorized(self, comment: dict[str, Any]) -> bool:
182
- cfg = self._pr_flow.chatops_config()
183
- user = comment.get("user") or {}
184
- login = user.get("login") if isinstance(user, dict) else None
185
- if cfg.get("ignore_bots", True):
186
- if isinstance(user, dict) and user.get("type") == "Bot":
187
- return False
188
- if isinstance(login, str) and login.endswith("[bot]"):
189
- return False
190
- allow_users = cfg.get("allow_users") or []
191
- allow_assoc = cfg.get("allow_associations") or []
192
- allowed = False
193
- if allow_users:
194
- allowed = allowed or (login in allow_users)
195
- if allow_assoc:
196
- assoc = str(comment.get("author_association") or "").upper()
197
- allowed = allowed or (assoc in {str(a).upper() for a in allow_assoc})
198
- return allowed
199
-
200
- async def _handle_command(
201
- self,
202
- gh: GitHubService,
203
- command: str,
204
- args: list[str],
205
- *,
206
- issue_number: int,
207
- is_pr: bool,
208
- ) -> Optional[str]:
209
- flags = _parse_flags(args)
210
- try:
211
- if command == "status":
212
- flow = self._pr_flow.status()
213
- return self._format_status(flow)
214
- if command == "stop":
215
- flow = self._pr_flow.stop()
216
- return f"Stopped workflow {flow.get('id') or '(unknown)'}."
217
- if command == "resume":
218
- flow = self._pr_flow.resume()
219
- return self._format_status(flow, prefix="Resumed")
220
- if command == "implement":
221
- if is_pr:
222
- return "Command ignored: implement is for issues, not PRs."
223
- payload = {
224
- "mode": "issue",
225
- "issue": str(issue_number),
226
- **flags,
227
- }
228
- flow = self._pr_flow.start(payload=payload)
229
- return self._format_status(flow, prefix="Started")
230
- if command == "fix":
231
- if not is_pr:
232
- return "Command ignored: fix is for PRs."
233
- payload = {
234
- "mode": "pr",
235
- "pr": str(issue_number),
236
- **flags,
237
- }
238
- flow = self._pr_flow.start(payload=payload)
239
- return self._format_status(flow, prefix="Started")
240
- except PrFlowError as exc:
241
- return f"PR flow error: {exc}"
242
- except Exception as exc:
243
- return f"PR flow error: {exc}"
244
- return None
245
-
246
- def _format_status(self, flow: dict[str, Any], *, prefix: str = "Status") -> str:
247
- status = flow.get("status") or "unknown"
248
- step = flow.get("step") or "unknown"
249
- wf_id = flow.get("id") or "unknown"
250
- pr_url = flow.get("pr_url")
251
- line = f"{prefix} workflow {wf_id}: {status} (step: {step})"
252
- if pr_url:
253
- line = f"{line}\nPR: {pr_url}"
254
- return line
255
-
256
- def _load_state(self) -> dict[str, Any]:
257
- path = _chatops_state_path(self._repo_root)
258
- data = read_json(path) or {}
259
- if not isinstance(data, dict):
260
- data = {}
261
- data.setdefault("processed_ids", [])
262
- data.setdefault("last_seen", "")
263
- return data
264
-
265
- def _save_state(self, state: dict[str, Any]) -> None:
266
- path = _chatops_state_path(self._repo_root)
267
- path.parent.mkdir(parents=True, exist_ok=True)
268
- atomic_write(path, json.dumps(state, indent=2) + "\n")