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,271 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import logging
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any, Awaitable, Callable, Optional
8
+
9
+ from ...core.config import RepoConfig
10
+ from ...core.ports.agent_backend import AgentBackend
11
+ from ...core.state import RunnerState
12
+ from ...core.utils import build_opencode_supervisor
13
+ from ...workspace import canonical_workspace_root, workspace_id_for_path
14
+ from ..app_server.env import build_app_server_env
15
+ from ..app_server.supervisor import WorkspaceAppServerSupervisor
16
+ from .codex_backend import CodexAppServerBackend
17
+ from .opencode_backend import OpenCodeBackend
18
+
19
+ NotificationHandler = Callable[[dict[str, Any]], Awaitable[None]]
20
+ BackendFactory = Callable[
21
+ [str, RunnerState, Optional[NotificationHandler]], AgentBackend
22
+ ]
23
+ SupervisorFactory = Callable[[str, Optional[NotificationHandler]], Any]
24
+
25
+
26
+ def _build_workspace_env(
27
+ repo_root: Path,
28
+ config: RepoConfig,
29
+ *,
30
+ event_prefix: str,
31
+ logger: logging.Logger,
32
+ ) -> dict[str, str]:
33
+ workspace_root = canonical_workspace_root(repo_root)
34
+ workspace_id = workspace_id_for_path(workspace_root)
35
+ state_dir = config.app_server.state_root / workspace_id
36
+ state_dir.mkdir(parents=True, exist_ok=True)
37
+ return build_app_server_env(
38
+ config.app_server.command,
39
+ workspace_root,
40
+ state_dir,
41
+ logger=logger,
42
+ event_prefix=event_prefix,
43
+ )
44
+
45
+
46
+ class AgentBackendFactory:
47
+ def __init__(self, repo_root: Path, config: RepoConfig) -> None:
48
+ self._repo_root = repo_root
49
+ self._config = config
50
+ self._logger = logging.getLogger("codex_autorunner.app_server")
51
+ self._backend_cache: dict[str, AgentBackend] = {}
52
+ self._opencode_supervisor: Optional[Any] = None
53
+
54
+ def __call__(
55
+ self,
56
+ agent_id: str,
57
+ state: RunnerState,
58
+ notification_handler: Optional[NotificationHandler],
59
+ ) -> AgentBackend:
60
+ if agent_id == "codex":
61
+ if not self._config.app_server.command:
62
+ raise ValueError("app_server.command is required for codex backend")
63
+
64
+ approval_policy = state.autorunner_approval_policy or "never"
65
+ sandbox_mode = state.autorunner_sandbox_mode or "dangerFullAccess"
66
+ if sandbox_mode == "workspaceWrite":
67
+ sandbox_policy: Any = {
68
+ "type": "workspaceWrite",
69
+ "writableRoots": [str(self._repo_root)],
70
+ "networkAccess": bool(state.autorunner_workspace_write_network),
71
+ }
72
+ else:
73
+ sandbox_policy = sandbox_mode
74
+
75
+ model = state.autorunner_model_override or self._config.codex_model
76
+ reasoning_effort = (
77
+ state.autorunner_effort_override or self._config.codex_reasoning
78
+ )
79
+
80
+ env = _build_workspace_env(
81
+ self._repo_root,
82
+ self._config,
83
+ event_prefix="autorunner",
84
+ logger=self._logger,
85
+ )
86
+
87
+ cached = self._backend_cache.get(agent_id)
88
+ if cached is None:
89
+ cached = CodexAppServerBackend(
90
+ command=self._config.app_server.command,
91
+ cwd=self._repo_root,
92
+ env=env,
93
+ approval_policy=approval_policy,
94
+ sandbox_policy=sandbox_policy,
95
+ model=model,
96
+ reasoning_effort=reasoning_effort,
97
+ turn_timeout_seconds=None,
98
+ auto_restart=self._config.app_server.auto_restart,
99
+ request_timeout=self._config.app_server.request_timeout,
100
+ turn_stall_timeout_seconds=self._config.app_server.turn_stall_timeout_seconds,
101
+ turn_stall_poll_interval_seconds=self._config.app_server.turn_stall_poll_interval_seconds,
102
+ turn_stall_recovery_min_interval_seconds=self._config.app_server.turn_stall_recovery_min_interval_seconds,
103
+ max_message_bytes=self._config.app_server.client.max_message_bytes,
104
+ oversize_preview_bytes=self._config.app_server.client.oversize_preview_bytes,
105
+ max_oversize_drain_bytes=self._config.app_server.client.max_oversize_drain_bytes,
106
+ restart_backoff_initial_seconds=self._config.app_server.client.restart_backoff_initial_seconds,
107
+ restart_backoff_max_seconds=self._config.app_server.client.restart_backoff_max_seconds,
108
+ restart_backoff_jitter_ratio=self._config.app_server.client.restart_backoff_jitter_ratio,
109
+ notification_handler=notification_handler,
110
+ logger=self._logger,
111
+ )
112
+ self._backend_cache[agent_id] = cached
113
+ else:
114
+ if isinstance(cached, CodexAppServerBackend):
115
+ cached.configure(
116
+ approval_policy=approval_policy,
117
+ sandbox_policy=sandbox_policy,
118
+ model=model,
119
+ reasoning_effort=reasoning_effort,
120
+ turn_timeout_seconds=None,
121
+ notification_handler=notification_handler,
122
+ )
123
+ return cached
124
+
125
+ if agent_id == "opencode":
126
+ agent_cfg = self._config.agents.get("opencode")
127
+ base_url = agent_cfg.base_url if agent_cfg else None
128
+ username = os.environ.get("OPENCODE_SERVER_USERNAME")
129
+ password = os.environ.get("OPENCODE_SERVER_PASSWORD")
130
+ if password and not username:
131
+ username = "opencode"
132
+ auth = (username, password) if username and password else None
133
+
134
+ cached = self._backend_cache.get(agent_id)
135
+ if cached is None:
136
+ if not base_url:
137
+ supervisor = self._ensure_opencode_supervisor()
138
+ if supervisor is None:
139
+ raise ValueError("opencode backend is not configured")
140
+ cached = OpenCodeBackend(
141
+ supervisor=supervisor,
142
+ workspace_root=self._repo_root,
143
+ auth=auth,
144
+ timeout=self._config.app_server.request_timeout,
145
+ model=state.autorunner_model_override,
146
+ reasoning=state.autorunner_effort_override,
147
+ approval_policy=state.autorunner_approval_policy,
148
+ session_stall_timeout_seconds=self._config.opencode.session_stall_timeout_seconds,
149
+ logger=self._logger,
150
+ )
151
+ else:
152
+ cached = OpenCodeBackend(
153
+ base_url=base_url,
154
+ workspace_root=self._repo_root,
155
+ auth=auth,
156
+ timeout=self._config.app_server.request_timeout,
157
+ model=state.autorunner_model_override,
158
+ reasoning=state.autorunner_effort_override,
159
+ approval_policy=state.autorunner_approval_policy,
160
+ session_stall_timeout_seconds=self._config.opencode.session_stall_timeout_seconds,
161
+ logger=self._logger,
162
+ )
163
+ self._backend_cache[agent_id] = cached
164
+ else:
165
+ if isinstance(cached, OpenCodeBackend):
166
+ cached.configure(
167
+ model=state.autorunner_model_override,
168
+ reasoning=state.autorunner_effort_override,
169
+ approval_policy=state.autorunner_approval_policy,
170
+ )
171
+ return cached
172
+
173
+ raise ValueError(f"Unsupported agent backend: {agent_id}")
174
+
175
+ def _ensure_opencode_supervisor(self) -> Optional[Any]:
176
+ if self._opencode_supervisor is not None:
177
+ return self._opencode_supervisor
178
+ opencode_command = self._config.agent_serve_command("opencode")
179
+ opencode_binary = None
180
+ try:
181
+ opencode_binary = self._config.agent_binary("opencode")
182
+ except Exception:
183
+ opencode_binary = None
184
+ agent_config = self._config.agents.get("opencode")
185
+ subagent_models = agent_config.subagent_models if agent_config else None
186
+ supervisor = build_opencode_supervisor(
187
+ opencode_command=opencode_command,
188
+ opencode_binary=opencode_binary,
189
+ workspace_root=self._repo_root,
190
+ logger=self._logger,
191
+ request_timeout=self._config.app_server.request_timeout,
192
+ max_handles=self._config.app_server.max_handles,
193
+ idle_ttl_seconds=self._config.app_server.idle_ttl_seconds,
194
+ session_stall_timeout_seconds=self._config.opencode.session_stall_timeout_seconds,
195
+ base_env=None,
196
+ subagent_models=subagent_models,
197
+ )
198
+ self._opencode_supervisor = supervisor
199
+ return supervisor
200
+
201
+ async def close_all(self) -> None:
202
+ backends = list(self._backend_cache.values())
203
+ self._backend_cache = {}
204
+ for backend in backends:
205
+ close = getattr(backend, "close", None)
206
+ if close is None:
207
+ continue
208
+ result = close()
209
+ if inspect.isawaitable(result):
210
+ await result
211
+ if self._opencode_supervisor is not None:
212
+ try:
213
+ await self._opencode_supervisor.close_all()
214
+ except Exception:
215
+ self._logger.warning(
216
+ "Failed closing opencode supervisor", exc_info=True
217
+ )
218
+ self._opencode_supervisor = None
219
+
220
+
221
+ def build_agent_backend_factory(repo_root: Path, config: RepoConfig) -> BackendFactory:
222
+ return AgentBackendFactory(repo_root, config)
223
+
224
+
225
+ def build_app_server_supervisor_factory(
226
+ config: RepoConfig,
227
+ *,
228
+ logger: Optional[logging.Logger] = None,
229
+ ) -> SupervisorFactory:
230
+ app_logger = logger or logging.getLogger("codex_autorunner.app_server")
231
+
232
+ def factory(
233
+ event_prefix: str, notification_handler: Optional[NotificationHandler]
234
+ ) -> WorkspaceAppServerSupervisor:
235
+ if not config.app_server.command:
236
+ raise ValueError("app_server.command is required for supervisor")
237
+
238
+ def _env_builder(
239
+ workspace_root: Path, _workspace_id: str, state_dir: Path
240
+ ) -> dict[str, str]:
241
+ state_dir.mkdir(parents=True, exist_ok=True)
242
+ return build_app_server_env(
243
+ config.app_server.command,
244
+ workspace_root,
245
+ state_dir,
246
+ logger=app_logger,
247
+ event_prefix=event_prefix,
248
+ )
249
+
250
+ return WorkspaceAppServerSupervisor(
251
+ config.app_server.command,
252
+ state_root=config.app_server.state_root,
253
+ env_builder=_env_builder,
254
+ logger=app_logger,
255
+ notification_handler=notification_handler,
256
+ auto_restart=config.app_server.auto_restart,
257
+ max_handles=config.app_server.max_handles,
258
+ idle_ttl_seconds=config.app_server.idle_ttl_seconds,
259
+ request_timeout=config.app_server.request_timeout,
260
+ turn_stall_timeout_seconds=config.app_server.turn_stall_timeout_seconds,
261
+ turn_stall_poll_interval_seconds=config.app_server.turn_stall_poll_interval_seconds,
262
+ turn_stall_recovery_min_interval_seconds=config.app_server.turn_stall_recovery_min_interval_seconds,
263
+ max_message_bytes=config.app_server.client.max_message_bytes,
264
+ oversize_preview_bytes=config.app_server.client.oversize_preview_bytes,
265
+ max_oversize_drain_bytes=config.app_server.client.max_oversize_drain_bytes,
266
+ restart_backoff_initial_seconds=config.app_server.client.restart_backoff_initial_seconds,
267
+ restart_backoff_max_seconds=config.app_server.client.restart_backoff_max_seconds,
268
+ restart_backoff_jitter_ratio=config.app_server.client.restart_backoff_jitter_ratio,
269
+ )
270
+
271
+ return factory