codex-autorunner 0.1.2__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 (276) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/__main__.py +4 -0
  3. codex_autorunner/agents/codex/harness.py +1 -1
  4. codex_autorunner/agents/opencode/client.py +68 -35
  5. codex_autorunner/agents/opencode/constants.py +3 -0
  6. codex_autorunner/agents/opencode/harness.py +6 -1
  7. codex_autorunner/agents/opencode/logging.py +21 -5
  8. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  9. codex_autorunner/agents/opencode/runtime.py +176 -47
  10. codex_autorunner/agents/opencode/supervisor.py +36 -48
  11. codex_autorunner/agents/registry.py +155 -8
  12. codex_autorunner/api.py +25 -0
  13. codex_autorunner/bootstrap.py +22 -37
  14. codex_autorunner/cli.py +5 -1156
  15. codex_autorunner/codex_cli.py +20 -84
  16. codex_autorunner/core/__init__.py +4 -0
  17. codex_autorunner/core/about_car.py +49 -32
  18. codex_autorunner/core/adapter_utils.py +21 -0
  19. codex_autorunner/core/app_server_ids.py +59 -0
  20. codex_autorunner/core/app_server_logging.py +7 -3
  21. codex_autorunner/core/app_server_prompts.py +27 -260
  22. codex_autorunner/core/app_server_threads.py +26 -28
  23. codex_autorunner/core/app_server_utils.py +165 -0
  24. codex_autorunner/core/archive.py +349 -0
  25. codex_autorunner/core/codex_runner.py +12 -2
  26. codex_autorunner/core/config.py +587 -103
  27. codex_autorunner/core/docs.py +10 -2
  28. codex_autorunner/core/drafts.py +136 -0
  29. codex_autorunner/core/engine.py +1531 -866
  30. codex_autorunner/core/exceptions.py +4 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +202 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +88 -0
  35. codex_autorunner/core/flows/reasons.py +52 -0
  36. codex_autorunner/core/flows/reconciler.py +131 -0
  37. codex_autorunner/core/flows/runtime.py +382 -0
  38. codex_autorunner/core/flows/store.py +568 -0
  39. codex_autorunner/core/flows/transition.py +138 -0
  40. codex_autorunner/core/flows/ux_helpers.py +257 -0
  41. codex_autorunner/core/flows/worker_process.py +242 -0
  42. codex_autorunner/core/git_utils.py +62 -0
  43. codex_autorunner/core/hub.py +136 -16
  44. codex_autorunner/core/locks.py +4 -0
  45. codex_autorunner/core/notifications.py +14 -2
  46. codex_autorunner/core/ports/__init__.py +28 -0
  47. codex_autorunner/core/ports/agent_backend.py +150 -0
  48. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  49. codex_autorunner/core/ports/run_event.py +91 -0
  50. codex_autorunner/core/prompt.py +15 -7
  51. codex_autorunner/core/redaction.py +29 -0
  52. codex_autorunner/core/review_context.py +5 -8
  53. codex_autorunner/core/run_index.py +6 -0
  54. codex_autorunner/core/runner_process.py +5 -2
  55. codex_autorunner/core/state.py +0 -88
  56. codex_autorunner/core/state_roots.py +57 -0
  57. codex_autorunner/core/supervisor_protocol.py +15 -0
  58. codex_autorunner/core/supervisor_utils.py +67 -0
  59. codex_autorunner/core/text_delta_coalescer.py +54 -0
  60. codex_autorunner/core/ticket_linter_cli.py +201 -0
  61. codex_autorunner/core/ticket_manager_cli.py +432 -0
  62. codex_autorunner/core/update.py +24 -16
  63. codex_autorunner/core/update_paths.py +28 -0
  64. codex_autorunner/core/update_runner.py +2 -0
  65. codex_autorunner/core/usage.py +164 -12
  66. codex_autorunner/core/utils.py +120 -11
  67. codex_autorunner/discovery.py +2 -4
  68. codex_autorunner/flows/review/__init__.py +17 -0
  69. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  70. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  71. codex_autorunner/flows/ticket_flow/definition.py +98 -0
  72. codex_autorunner/integrations/agents/__init__.py +17 -0
  73. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  74. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  75. codex_autorunner/integrations/agents/codex_backend.py +448 -0
  76. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  77. codex_autorunner/integrations/agents/opencode_backend.py +598 -0
  78. codex_autorunner/integrations/agents/runner.py +91 -0
  79. codex_autorunner/integrations/agents/wiring.py +271 -0
  80. codex_autorunner/integrations/app_server/client.py +583 -152
  81. codex_autorunner/integrations/app_server/env.py +2 -107
  82. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  83. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  84. codex_autorunner/integrations/telegram/adapter.py +204 -165
  85. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  86. codex_autorunner/integrations/telegram/config.py +221 -0
  87. codex_autorunner/integrations/telegram/constants.py +17 -2
  88. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  89. codex_autorunner/integrations/telegram/doctor.py +47 -0
  90. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
  91. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  92. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  93. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  94. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
  95. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  96. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  97. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  98. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
  99. codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
  100. codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
  101. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  102. codex_autorunner/integrations/telegram/helpers.py +111 -16
  103. codex_autorunner/integrations/telegram/outbox.py +208 -37
  104. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  105. codex_autorunner/integrations/telegram/service.py +221 -42
  106. codex_autorunner/integrations/telegram/state.py +100 -2
  107. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
  108. codex_autorunner/integrations/telegram/transport.py +39 -4
  109. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  110. codex_autorunner/manifest.py +2 -0
  111. codex_autorunner/plugin_api.py +22 -0
  112. codex_autorunner/routes/__init__.py +37 -67
  113. codex_autorunner/routes/agents.py +2 -137
  114. codex_autorunner/routes/analytics.py +3 -0
  115. codex_autorunner/routes/app_server.py +2 -131
  116. codex_autorunner/routes/base.py +2 -624
  117. codex_autorunner/routes/file_chat.py +7 -0
  118. codex_autorunner/routes/flows.py +7 -0
  119. codex_autorunner/routes/messages.py +7 -0
  120. codex_autorunner/routes/repos.py +2 -196
  121. codex_autorunner/routes/review.py +2 -147
  122. codex_autorunner/routes/sessions.py +2 -175
  123. codex_autorunner/routes/settings.py +2 -168
  124. codex_autorunner/routes/shared.py +2 -275
  125. codex_autorunner/routes/system.py +4 -188
  126. codex_autorunner/routes/usage.py +3 -0
  127. codex_autorunner/routes/voice.py +2 -119
  128. codex_autorunner/routes/workspace.py +3 -0
  129. codex_autorunner/server.py +3 -2
  130. codex_autorunner/static/agentControls.js +41 -11
  131. codex_autorunner/static/agentEvents.js +248 -0
  132. codex_autorunner/static/app.js +35 -24
  133. codex_autorunner/static/archive.js +826 -0
  134. codex_autorunner/static/archiveApi.js +37 -0
  135. codex_autorunner/static/autoRefresh.js +36 -8
  136. codex_autorunner/static/bootstrap.js +1 -0
  137. codex_autorunner/static/bus.js +1 -0
  138. codex_autorunner/static/cache.js +1 -0
  139. codex_autorunner/static/constants.js +20 -4
  140. codex_autorunner/static/dashboard.js +344 -325
  141. codex_autorunner/static/diffRenderer.js +37 -0
  142. codex_autorunner/static/docChatCore.js +324 -0
  143. codex_autorunner/static/docChatStorage.js +65 -0
  144. codex_autorunner/static/docChatVoice.js +65 -0
  145. codex_autorunner/static/docEditor.js +133 -0
  146. codex_autorunner/static/env.js +1 -0
  147. codex_autorunner/static/eventSummarizer.js +166 -0
  148. codex_autorunner/static/fileChat.js +182 -0
  149. codex_autorunner/static/health.js +155 -0
  150. codex_autorunner/static/hub.js +126 -185
  151. codex_autorunner/static/index.html +839 -863
  152. codex_autorunner/static/liveUpdates.js +1 -0
  153. codex_autorunner/static/loader.js +1 -0
  154. codex_autorunner/static/messages.js +873 -0
  155. codex_autorunner/static/mobileCompact.js +2 -1
  156. codex_autorunner/static/preserve.js +17 -0
  157. codex_autorunner/static/settings.js +149 -217
  158. codex_autorunner/static/smartRefresh.js +52 -0
  159. codex_autorunner/static/styles.css +8850 -3876
  160. codex_autorunner/static/tabs.js +175 -11
  161. codex_autorunner/static/terminal.js +32 -0
  162. codex_autorunner/static/terminalManager.js +34 -59
  163. codex_autorunner/static/ticketChatActions.js +333 -0
  164. codex_autorunner/static/ticketChatEvents.js +16 -0
  165. codex_autorunner/static/ticketChatStorage.js +16 -0
  166. codex_autorunner/static/ticketChatStream.js +264 -0
  167. codex_autorunner/static/ticketEditor.js +844 -0
  168. codex_autorunner/static/ticketVoice.js +9 -0
  169. codex_autorunner/static/tickets.js +1988 -0
  170. codex_autorunner/static/utils.js +43 -3
  171. codex_autorunner/static/voice.js +1 -0
  172. codex_autorunner/static/workspace.js +765 -0
  173. codex_autorunner/static/workspaceApi.js +53 -0
  174. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  175. codex_autorunner/surfaces/__init__.py +5 -0
  176. codex_autorunner/surfaces/cli/__init__.py +6 -0
  177. codex_autorunner/surfaces/cli/cli.py +1224 -0
  178. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  179. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  180. codex_autorunner/surfaces/web/__init__.py +1 -0
  181. codex_autorunner/surfaces/web/app.py +2019 -0
  182. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  183. codex_autorunner/surfaces/web/middleware.py +587 -0
  184. codex_autorunner/surfaces/web/pty_session.py +370 -0
  185. codex_autorunner/surfaces/web/review.py +6 -0
  186. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  187. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  188. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  189. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  190. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  191. codex_autorunner/surfaces/web/routes/base.py +615 -0
  192. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  193. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  194. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  195. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  196. codex_autorunner/surfaces/web/routes/review.py +148 -0
  197. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  198. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  199. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  200. codex_autorunner/surfaces/web/routes/system.py +196 -0
  201. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  202. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  203. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  204. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  205. codex_autorunner/surfaces/web/schemas.py +417 -0
  206. codex_autorunner/surfaces/web/static_assets.py +490 -0
  207. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  208. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  209. codex_autorunner/tickets/__init__.py +27 -0
  210. codex_autorunner/tickets/agent_pool.py +399 -0
  211. codex_autorunner/tickets/files.py +89 -0
  212. codex_autorunner/tickets/frontmatter.py +55 -0
  213. codex_autorunner/tickets/lint.py +102 -0
  214. codex_autorunner/tickets/models.py +97 -0
  215. codex_autorunner/tickets/outbox.py +244 -0
  216. codex_autorunner/tickets/replies.py +179 -0
  217. codex_autorunner/tickets/runner.py +881 -0
  218. codex_autorunner/tickets/spec_ingest.py +77 -0
  219. codex_autorunner/web/__init__.py +5 -1
  220. codex_autorunner/web/app.py +2 -1771
  221. codex_autorunner/web/hub_jobs.py +2 -191
  222. codex_autorunner/web/middleware.py +2 -587
  223. codex_autorunner/web/pty_session.py +2 -369
  224. codex_autorunner/web/runner_manager.py +2 -24
  225. codex_autorunner/web/schemas.py +2 -396
  226. codex_autorunner/web/static_assets.py +4 -484
  227. codex_autorunner/web/static_refresh.py +2 -85
  228. codex_autorunner/web/terminal_sessions.py +2 -77
  229. codex_autorunner/workspace/__init__.py +40 -0
  230. codex_autorunner/workspace/paths.py +335 -0
  231. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  232. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  233. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
  234. codex_autorunner/agents/execution/policy.py +0 -292
  235. codex_autorunner/agents/factory.py +0 -52
  236. codex_autorunner/agents/orchestrator.py +0 -358
  237. codex_autorunner/core/doc_chat.py +0 -1446
  238. codex_autorunner/core/snapshot.py +0 -580
  239. codex_autorunner/integrations/github/chatops.py +0 -268
  240. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  241. codex_autorunner/routes/docs.py +0 -381
  242. codex_autorunner/routes/github.py +0 -327
  243. codex_autorunner/routes/runs.py +0 -250
  244. codex_autorunner/spec_ingest.py +0 -812
  245. codex_autorunner/static/docChatActions.js +0 -287
  246. codex_autorunner/static/docChatEvents.js +0 -300
  247. codex_autorunner/static/docChatRender.js +0 -205
  248. codex_autorunner/static/docChatStream.js +0 -361
  249. codex_autorunner/static/docs.js +0 -20
  250. codex_autorunner/static/docsClipboard.js +0 -69
  251. codex_autorunner/static/docsCrud.js +0 -257
  252. codex_autorunner/static/docsDocUpdates.js +0 -62
  253. codex_autorunner/static/docsDrafts.js +0 -16
  254. codex_autorunner/static/docsElements.js +0 -69
  255. codex_autorunner/static/docsInit.js +0 -285
  256. codex_autorunner/static/docsParse.js +0 -160
  257. codex_autorunner/static/docsSnapshot.js +0 -87
  258. codex_autorunner/static/docsSpecIngest.js +0 -263
  259. codex_autorunner/static/docsState.js +0 -127
  260. codex_autorunner/static/docsThreadRegistry.js +0 -44
  261. codex_autorunner/static/docsUi.js +0 -153
  262. codex_autorunner/static/docsVoice.js +0 -56
  263. codex_autorunner/static/github.js +0 -504
  264. codex_autorunner/static/logs.js +0 -678
  265. codex_autorunner/static/review.js +0 -157
  266. codex_autorunner/static/runs.js +0 -418
  267. codex_autorunner/static/snapshot.js +0 -124
  268. codex_autorunner/static/state.js +0 -94
  269. codex_autorunner/static/todoPreview.js +0 -27
  270. codex_autorunner/workspace.py +0 -16
  271. codex_autorunner-0.1.2.dist-info/METADATA +0 -249
  272. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  273. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  274. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  275. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  276. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,349 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ import shutil
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Iterable, Literal, Optional
10
+
11
+ from .git_utils import git_branch, git_head_sha
12
+ from .state import now_iso
13
+ from .utils import atomic_write
14
+
15
+ ArchiveStatus = Literal["complete", "partial", "failed"]
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ArchiveResult:
20
+ snapshot_id: str
21
+ snapshot_path: Path
22
+ meta_path: Path
23
+ status: ArchiveStatus
24
+ file_count: int
25
+ total_bytes: int
26
+ flow_run_count: int
27
+ latest_flow_run_id: Optional[str]
28
+ missing_paths: tuple[str, ...]
29
+ skipped_symlinks: tuple[str, ...]
30
+
31
+
32
+ def _snapshot_timestamp() -> str:
33
+ return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
34
+
35
+
36
+ _BRANCH_SANITIZE_RE = re.compile(r"[^a-zA-Z0-9._-]+")
37
+
38
+
39
+ def _sanitize_branch(branch: Optional[str]) -> str:
40
+ if not branch:
41
+ return "unknown"
42
+ cleaned = _BRANCH_SANITIZE_RE.sub("-", branch.strip())
43
+ cleaned = cleaned.strip("-")
44
+ return cleaned or "unknown"
45
+
46
+
47
+ def _is_within(root: Path, target: Path) -> bool:
48
+ try:
49
+ return target.resolve().is_relative_to(root.resolve())
50
+ except FileNotFoundError:
51
+ return False
52
+
53
+
54
+ def _copy_file(src: Path, dest: Path, stats: dict[str, int]) -> None:
55
+ dest.parent.mkdir(parents=True, exist_ok=True)
56
+ shutil.copy2(src, dest)
57
+ stats["file_count"] += 1
58
+ stats["total_bytes"] += dest.stat().st_size
59
+
60
+
61
+ def _copy_tree(
62
+ src_dir: Path,
63
+ dest_dir: Path,
64
+ worktree_root: Path,
65
+ stats: dict[str, int],
66
+ *,
67
+ visited: set[Path],
68
+ skipped_symlinks: list[str],
69
+ ) -> None:
70
+ real_dir = src_dir.resolve()
71
+ if real_dir in visited:
72
+ return
73
+ visited.add(real_dir)
74
+ try:
75
+ dest_dir.mkdir(parents=True, exist_ok=True)
76
+ for child in sorted(src_dir.iterdir(), key=lambda p: p.name):
77
+ _copy_entry(
78
+ child,
79
+ dest_dir / child.name,
80
+ worktree_root,
81
+ stats,
82
+ visited=visited,
83
+ skipped_symlinks=skipped_symlinks,
84
+ )
85
+ try:
86
+ shutil.copystat(src_dir, dest_dir, follow_symlinks=False)
87
+ except OSError:
88
+ pass
89
+ finally:
90
+ visited.remove(real_dir)
91
+
92
+
93
+ def _copy_entry(
94
+ src: Path,
95
+ dest: Path,
96
+ worktree_root: Path,
97
+ stats: dict[str, int],
98
+ *,
99
+ visited: set[Path],
100
+ skipped_symlinks: list[str],
101
+ ) -> bool:
102
+ if src.is_symlink():
103
+ try:
104
+ resolved = src.resolve()
105
+ except FileNotFoundError:
106
+ skipped_symlinks.append(str(src))
107
+ return False
108
+ if not _is_within(worktree_root, resolved):
109
+ skipped_symlinks.append(str(src))
110
+ return False
111
+ if resolved.is_dir():
112
+ _copy_tree(
113
+ resolved,
114
+ dest,
115
+ worktree_root,
116
+ stats,
117
+ visited=visited,
118
+ skipped_symlinks=skipped_symlinks,
119
+ )
120
+ return True
121
+ if resolved.is_file():
122
+ _copy_file(resolved, dest, stats)
123
+ return True
124
+ return False
125
+
126
+ if src.is_dir():
127
+ _copy_tree(
128
+ src,
129
+ dest,
130
+ worktree_root,
131
+ stats,
132
+ visited=visited,
133
+ skipped_symlinks=skipped_symlinks,
134
+ )
135
+ return True
136
+
137
+ if src.is_file():
138
+ _copy_file(src, dest, stats)
139
+ return True
140
+
141
+ return False
142
+
143
+
144
+ def _flow_summary(flows_dir: Path) -> tuple[int, Optional[str]]:
145
+ if not flows_dir.exists() or not flows_dir.is_dir():
146
+ return 0, None
147
+ runs: list[Path] = [
148
+ path
149
+ for path in sorted(flows_dir.iterdir(), key=lambda p: p.name)
150
+ if path.is_dir()
151
+ ]
152
+ if not runs:
153
+ return 0, None
154
+ latest = max(
155
+ runs,
156
+ key=lambda p: (p.stat().st_mtime, p.name),
157
+ )
158
+ return len(runs), latest.name
159
+
160
+
161
+ def _build_meta(
162
+ *,
163
+ snapshot_id: str,
164
+ created_at: str,
165
+ status: ArchiveStatus,
166
+ base_repo_id: str,
167
+ worktree_repo_id: str,
168
+ worktree_of: str,
169
+ branch: str,
170
+ head_sha: str,
171
+ source_path: Path,
172
+ copied_paths: Iterable[str],
173
+ missing_paths: Iterable[str],
174
+ skipped_symlinks: Iterable[str],
175
+ summary: dict[str, object],
176
+ note: Optional[str] = None,
177
+ error: Optional[str] = None,
178
+ ) -> dict[str, object]:
179
+ payload: dict[str, object] = {
180
+ "schema_version": 1,
181
+ "snapshot_id": snapshot_id,
182
+ "created_at": created_at,
183
+ "status": status,
184
+ "base_repo_id": base_repo_id,
185
+ "worktree_repo_id": worktree_repo_id,
186
+ "worktree_of": worktree_of,
187
+ "branch": branch,
188
+ "head_sha": head_sha,
189
+ "source": {
190
+ "path": str(source_path),
191
+ "copied_paths": list(copied_paths),
192
+ "missing_paths": list(missing_paths),
193
+ "skipped_symlinks": list(skipped_symlinks),
194
+ },
195
+ "summary": summary,
196
+ }
197
+ if note:
198
+ payload["note"] = note
199
+ if error:
200
+ payload["error"] = error
201
+ return payload
202
+
203
+
204
+ def build_snapshot_id(branch: Optional[str], head_sha: str) -> str:
205
+ head_short = head_sha[:7] if head_sha and head_sha != "unknown" else "unknown"
206
+ return f"{_snapshot_timestamp()}--{_sanitize_branch(branch)}--{head_short}"
207
+
208
+
209
+ def archive_worktree_snapshot(
210
+ *,
211
+ base_repo_root: Path,
212
+ base_repo_id: str,
213
+ worktree_repo_root: Path,
214
+ worktree_repo_id: str,
215
+ branch: Optional[str],
216
+ worktree_of: str,
217
+ note: Optional[str] = None,
218
+ snapshot_id: Optional[str] = None,
219
+ head_sha: Optional[str] = None,
220
+ source_path: Optional[Path | str] = None,
221
+ ) -> ArchiveResult:
222
+ base_repo_root = base_repo_root.resolve()
223
+ worktree_repo_root = worktree_repo_root.resolve()
224
+ branch_name = branch or git_branch(worktree_repo_root) or "unknown"
225
+ resolved_head_sha = head_sha or git_head_sha(worktree_repo_root) or "unknown"
226
+ snapshot_id = snapshot_id or build_snapshot_id(branch_name, resolved_head_sha)
227
+ snapshot_root = (
228
+ base_repo_root
229
+ / ".codex-autorunner"
230
+ / "archive"
231
+ / "worktrees"
232
+ / worktree_repo_id
233
+ / snapshot_id
234
+ )
235
+ snapshot_root.mkdir(parents=True, exist_ok=False)
236
+
237
+ source_root = worktree_repo_root / ".codex-autorunner"
238
+ curated: list[tuple[Path, Path]] = [
239
+ (source_root / "workspace", snapshot_root / "workspace"),
240
+ (source_root / "tickets", snapshot_root / "tickets"),
241
+ (source_root / "runs", snapshot_root / "runs"),
242
+ (source_root / "flows", snapshot_root / "flows"),
243
+ (source_root / "flows.db", snapshot_root / "flows.db"),
244
+ (source_root / "config.yml", snapshot_root / "config" / "config.yml"),
245
+ (source_root / "state.sqlite3", snapshot_root / "state" / "state.sqlite3"),
246
+ (
247
+ source_root / "codex-autorunner.log",
248
+ snapshot_root / "logs" / "codex-autorunner.log",
249
+ ),
250
+ (
251
+ source_root / "codex-server.log",
252
+ snapshot_root / "logs" / "codex-server.log",
253
+ ),
254
+ ]
255
+
256
+ stats = {"file_count": 0, "total_bytes": 0}
257
+ copied_paths: list[str] = []
258
+ missing_paths: list[str] = []
259
+ skipped_symlinks: list[str] = []
260
+ visited: set[Path] = set()
261
+ created_at = now_iso()
262
+ meta_path = snapshot_root / "META.json"
263
+ summary: dict[str, object] = {}
264
+
265
+ try:
266
+ for src, dest in curated:
267
+ rel = src.relative_to(source_root)
268
+ if not src.exists() and not src.is_symlink():
269
+ missing_paths.append(str(rel))
270
+ continue
271
+ copied = _copy_entry(
272
+ src,
273
+ dest,
274
+ worktree_repo_root,
275
+ stats,
276
+ visited=visited,
277
+ skipped_symlinks=skipped_symlinks,
278
+ )
279
+ if copied:
280
+ copied_paths.append(str(rel))
281
+
282
+ flow_run_count, latest_flow_run_id = _flow_summary(snapshot_root / "flows")
283
+ status: ArchiveStatus = "complete" if not missing_paths else "partial"
284
+ summary = {
285
+ "file_count": stats["file_count"],
286
+ "total_bytes": stats["total_bytes"],
287
+ "flow_run_count": flow_run_count,
288
+ "latest_flow_run_id": latest_flow_run_id,
289
+ }
290
+ meta = _build_meta(
291
+ snapshot_id=snapshot_id,
292
+ created_at=created_at,
293
+ status=status,
294
+ base_repo_id=base_repo_id,
295
+ worktree_repo_id=worktree_repo_id,
296
+ worktree_of=worktree_of,
297
+ branch=branch_name,
298
+ head_sha=resolved_head_sha,
299
+ source_path=(
300
+ Path(source_path) if source_path is not None else worktree_repo_root
301
+ ),
302
+ copied_paths=copied_paths,
303
+ missing_paths=missing_paths,
304
+ skipped_symlinks=skipped_symlinks,
305
+ summary=summary,
306
+ note=note,
307
+ )
308
+ atomic_write(meta_path, json.dumps(meta, indent=2) + "\n")
309
+ except Exception as exc:
310
+ summary = {
311
+ "file_count": stats["file_count"],
312
+ "total_bytes": stats["total_bytes"],
313
+ "flow_run_count": 0,
314
+ "latest_flow_run_id": None,
315
+ }
316
+ meta = _build_meta(
317
+ snapshot_id=snapshot_id,
318
+ created_at=created_at,
319
+ status="failed",
320
+ base_repo_id=base_repo_id,
321
+ worktree_repo_id=worktree_repo_id,
322
+ worktree_of=worktree_of,
323
+ branch=branch_name,
324
+ head_sha=resolved_head_sha,
325
+ source_path=(
326
+ Path(source_path) if source_path is not None else worktree_repo_root
327
+ ),
328
+ copied_paths=copied_paths,
329
+ missing_paths=missing_paths,
330
+ skipped_symlinks=skipped_symlinks,
331
+ summary=summary,
332
+ note=note,
333
+ error=str(exc),
334
+ )
335
+ atomic_write(meta_path, json.dumps(meta, indent=2) + "\n")
336
+ raise
337
+
338
+ return ArchiveResult(
339
+ snapshot_id=snapshot_id,
340
+ snapshot_path=snapshot_root,
341
+ meta_path=meta_path,
342
+ status=status,
343
+ file_count=stats["file_count"],
344
+ total_bytes=stats["total_bytes"],
345
+ flow_run_count=flow_run_count,
346
+ latest_flow_run_id=latest_flow_run_id,
347
+ missing_paths=tuple(missing_paths),
348
+ skipped_symlinks=tuple(skipped_symlinks),
349
+ )
@@ -1,11 +1,21 @@
1
+ # DEPRECATED: This module implements a Codex CLI subprocess runner.
2
+ # The primary execution path now uses the Codex app-server via OpenCode runtime.
3
+ # This file is kept for potential future CLI-as-backend support but is currently
4
+ # not referenced by the main engine. See src/codex_autorunner/core/engine.py for
5
+ # the current execution path (_run_codex_app_server_async).
6
+
1
7
  import asyncio
2
8
  import subprocess
3
9
  from pathlib import Path
4
10
  from typing import Callable, Optional
5
11
 
6
- from ..codex_cli import apply_codex_options, supports_reasoning
7
12
  from .config import Config, ConfigError
8
- from .utils import resolve_executable, subprocess_env
13
+ from .utils import (
14
+ apply_codex_options,
15
+ resolve_executable,
16
+ subprocess_env,
17
+ supports_reasoning,
18
+ )
9
19
 
10
20
 
11
21
  class CodexRunnerError(Exception):