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,284 @@
1
+ """
2
+ Backend orchestrator that manages protocol-agnostic backend lifecycle.
3
+
4
+ The orchestrator sits between the Engine and backend adapters, handling
5
+ backend-specific concerns like supervisor management, event handling,
6
+ and session/thread tracking while exposing a clean, protocol-neutral
7
+ interface to the Engine.
8
+ """
9
+
10
+ import asyncio
11
+ import logging
12
+ import threading
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any, AsyncGenerator, Awaitable, Callable, Optional
16
+
17
+ from ...core.app_server_threads import (
18
+ AppServerThreadRegistry,
19
+ default_app_server_threads_path,
20
+ )
21
+ from ...core.config import RepoConfig
22
+ from ...core.ports.agent_backend import AgentBackend
23
+ from ...core.ports.run_event import RunEvent
24
+ from ...core.state import RunnerState
25
+ from .codex_backend import CodexAppServerBackend
26
+ from .opencode_backend import OpenCodeBackend
27
+ from .wiring import AgentBackendFactory, BackendFactory
28
+
29
+ NotificationHandler = Callable[[dict[str, Any]], Awaitable[None]]
30
+ SessionIdGetter = Callable[[str], Optional[str]]
31
+ SessionIdSetter = Callable[[str, str], None]
32
+
33
+
34
+ @dataclass
35
+ class BackendContext:
36
+ """Context for a backend run."""
37
+
38
+ agent_id: str
39
+ session_id: Optional[str]
40
+ turn_id: Optional[str]
41
+ thread_info: Optional[dict[str, Any]]
42
+
43
+
44
+ class BackendOrchestrator:
45
+ """
46
+ Orchestrates backend operations, keeping Engine protocol-agnostic.
47
+
48
+ This class manages:
49
+ - Backend factory and lifecycle
50
+ - Backend-specific supervisors (Codex app server, OpenCode)
51
+ - Backend-specific event handling and notification routing
52
+ - Session/thread tracking for backends that support it
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ repo_root: Path,
58
+ config: RepoConfig,
59
+ *,
60
+ notification_handler: Optional[NotificationHandler] = None,
61
+ logger: Optional[logging.Logger] = None,
62
+ ):
63
+ from .wiring import build_agent_backend_factory
64
+
65
+ self._repo_root = repo_root
66
+ self._config = config
67
+ self._logger = logger or logging.getLogger("codex_autorunner.backend")
68
+ self._notification_handler = notification_handler
69
+
70
+ # Backend factory manages creation and caching of backends
71
+ self._backend_factory: BackendFactory = build_agent_backend_factory(
72
+ repo_root, config
73
+ )
74
+
75
+ # Active backend for current run
76
+ self._active_backend: Optional[AgentBackend] = None
77
+
78
+ # Context tracking
79
+ self._context: Optional[BackendContext] = None
80
+
81
+ # Session registry for backend-specific session tracking
82
+ self._app_server_threads = AppServerThreadRegistry(
83
+ default_app_server_threads_path(repo_root)
84
+ )
85
+ self._app_server_threads_lock = threading.Lock()
86
+
87
+ async def get_backend(
88
+ self,
89
+ agent_id: str,
90
+ state: RunnerState,
91
+ ) -> AgentBackend:
92
+ """Get a backend instance for the given agent."""
93
+ backend = self._backend_factory(agent_id, state, self._notification_handler)
94
+ self._active_backend = backend
95
+ return backend
96
+
97
+ async def start_session(
98
+ self,
99
+ agent_id: str,
100
+ state: RunnerState,
101
+ session_id: Optional[str] = None,
102
+ ) -> str:
103
+ """
104
+ Start a backend session.
105
+
106
+ Returns the session/thread ID.
107
+ """
108
+ backend = await self.get_backend(agent_id, state)
109
+
110
+ context: dict[str, Any] = {"workspace": str(self._repo_root)}
111
+ if session_id:
112
+ context["session_id"] = session_id
113
+
114
+ target = {"workspace": str(self._repo_root)}
115
+
116
+ session = await backend.start_session(target, context)
117
+
118
+ # Track context
119
+ self._context = BackendContext(
120
+ agent_id=agent_id,
121
+ session_id=session,
122
+ turn_id=None,
123
+ thread_info=None,
124
+ )
125
+
126
+ return session
127
+
128
+ async def run_turn(
129
+ self,
130
+ agent_id: str,
131
+ state: RunnerState,
132
+ prompt: str,
133
+ *,
134
+ model: Optional[str] = None,
135
+ reasoning: Optional[str] = None,
136
+ session_key: Optional[str] = None,
137
+ ) -> AsyncGenerator[RunEvent, None]:
138
+ """
139
+ Run a turn on the backend.
140
+
141
+ Yields RunEvent objects.
142
+ """
143
+ reuse_session = bool(getattr(self._config, "autorunner_reuse_session", False))
144
+ session_id: Optional[str] = None
145
+ if reuse_session and session_key:
146
+ session_id = self.get_thread_id(session_key)
147
+ if reuse_session and session_id is None and self._context is not None:
148
+ session_id = self._context.session_id
149
+
150
+ session_id = await self.start_session(agent_id, state, session_id=session_id)
151
+ if reuse_session and session_key and session_id:
152
+ self.set_thread_id(session_key, session_id)
153
+
154
+ backend = self._active_backend
155
+ assert backend is not None, "backend should be initialized before run_turn"
156
+
157
+ # Configure backend if supported
158
+ if isinstance(backend, CodexAppServerBackend):
159
+ backend.configure(
160
+ approval_policy=state.autorunner_approval_policy or "never",
161
+ sandbox_policy=state.autorunner_sandbox_mode or "dangerFullAccess",
162
+ model=model,
163
+ reasoning_effort=reasoning,
164
+ turn_timeout_seconds=None,
165
+ notification_handler=self._notification_handler,
166
+ )
167
+ elif isinstance(backend, OpenCodeBackend):
168
+ backend.configure(
169
+ model=model,
170
+ reasoning=reasoning,
171
+ approval_policy=state.autorunner_approval_policy,
172
+ )
173
+
174
+ async for event in backend.run_turn_events(session_id, prompt):
175
+ yield event
176
+
177
+ # Update context from events
178
+ if hasattr(event, "session_id") and event.session_id:
179
+ if self._context:
180
+ self._context.session_id = event.session_id
181
+
182
+ async def interrupt(self, agent_id: str, state: RunnerState) -> None:
183
+ """Interrupt the current backend session."""
184
+ if self._context and self._context.session_id:
185
+ backend = await self.get_backend(agent_id, state)
186
+ await backend.interrupt(self._context.session_id)
187
+
188
+ def get_context(self) -> Optional[BackendContext]:
189
+ """Get the current backend context."""
190
+ return self._context
191
+
192
+ def get_last_turn_id(self) -> Optional[str]:
193
+ """Get the last turn ID from the active backend."""
194
+ if self._active_backend:
195
+ return getattr(self._active_backend, "last_turn_id", None)
196
+ if self._context:
197
+ return self._context.turn_id
198
+ return None
199
+
200
+ def get_last_thread_info(self) -> Optional[dict[str, Any]]:
201
+ """Get the last thread info from the active backend."""
202
+ if self._active_backend:
203
+ return getattr(self._active_backend, "last_thread_info", None)
204
+ if self._context:
205
+ return self._context.thread_info
206
+ return None
207
+
208
+ def get_last_token_total(self) -> Optional[dict[str, Any]]:
209
+ """Get the last token total from the active backend."""
210
+ if self._active_backend:
211
+ return getattr(self._active_backend, "last_token_total", None)
212
+ return None
213
+
214
+ async def close_all(self) -> None:
215
+ """Close all backends and clean up resources."""
216
+ close_all = getattr(self._backend_factory, "close_all", None)
217
+ if close_all:
218
+ result = close_all()
219
+ if asyncio.iscoroutine(result):
220
+ await result
221
+ self._active_backend = None
222
+ self._context = None
223
+
224
+ def update_context(
225
+ self,
226
+ *,
227
+ turn_id: Optional[str] = None,
228
+ thread_info: Optional[dict[str, Any]] = None,
229
+ ) -> None:
230
+ """Update the backend context with new information."""
231
+ if self._context:
232
+ if turn_id:
233
+ self._context.turn_id = turn_id
234
+ if thread_info:
235
+ self._context.thread_info = thread_info
236
+
237
+ def get_thread_id(self, session_key: str) -> Optional[str]:
238
+ """Get the thread ID for a given session key."""
239
+ with self._app_server_threads_lock:
240
+ return self._app_server_threads.get_thread_id(session_key)
241
+
242
+ def set_thread_id(self, session_key: str, thread_id: str) -> None:
243
+ """Set the thread ID for a given session key."""
244
+ with self._app_server_threads_lock:
245
+ self._app_server_threads.set_thread_id(session_key, thread_id)
246
+
247
+ def _agent_backend_factory(self) -> Optional[AgentBackendFactory]:
248
+ if isinstance(self._backend_factory, AgentBackendFactory):
249
+ return self._backend_factory
250
+ return None
251
+
252
+ def ensure_opencode_supervisor(self) -> Optional[Any]:
253
+ """
254
+ Ensure OpenCode supervisor exists.
255
+
256
+ This method delegates to the backend factory for supervisor management,
257
+ keeping Engine protocol-agnostic.
258
+ """
259
+ factory = self._agent_backend_factory()
260
+ if factory is not None:
261
+ return factory._ensure_opencode_supervisor()
262
+ return None
263
+
264
+ def build_app_server_supervisor(
265
+ self, *, event_prefix: str, notification_handler: Optional[NotificationHandler]
266
+ ) -> Optional[Any]:
267
+ """
268
+ Build a Codex app server supervisor factory.
269
+
270
+ This method centralizes backend-specific supervisor creation, keeping
271
+ Engine protocol-agnostic.
272
+ """
273
+ from .wiring import build_app_server_supervisor_factory
274
+
275
+ factory_fn = build_app_server_supervisor_factory(
276
+ self._config, logger=self._logger
277
+ )
278
+ return factory_fn(event_prefix, notification_handler)
279
+
280
+
281
+ __all__ = [
282
+ "BackendOrchestrator",
283
+ "BackendContext",
284
+ ]
@@ -0,0 +1,90 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Any, Callable, Dict, Optional, Sequence
4
+
5
+ from ...integrations.app_server.client import CodexAppServerClient
6
+ from ...integrations.app_server.supervisor import WorkspaceAppServerSupervisor
7
+
8
+ _logger = logging.getLogger(__name__)
9
+
10
+ EnvBuilder = Callable[[Path, str, Path], Dict[str, str]]
11
+
12
+
13
+ class CodexAdapterOrchestrator:
14
+ """
15
+ Orchestrates Codex app-server backend sessions using WorkspaceAppServerSupervisor.
16
+
17
+ This adapter wraps the WorkspaceAppServerSupervisor to provide an AgentBackend-compatible
18
+ interface for use by the Engine.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ command: Sequence[str],
24
+ *,
25
+ state_root: Path,
26
+ env_builder: EnvBuilder,
27
+ approval_handler: Optional[Any] = None,
28
+ notification_handler: Optional[Any] = None,
29
+ logger: Optional[logging.Logger] = None,
30
+ auto_restart: bool = True,
31
+ request_timeout: Optional[float] = None,
32
+ turn_stall_timeout_seconds: Optional[float] = None,
33
+ turn_stall_poll_interval_seconds: Optional[float] = None,
34
+ turn_stall_recovery_min_interval_seconds: Optional[float] = None,
35
+ default_approval_decision: str = "cancel",
36
+ max_handles: Optional[int] = None,
37
+ idle_ttl_seconds: Optional[float] = None,
38
+ ):
39
+ self._command = command
40
+ self._state_root = state_root
41
+ self._env_builder = env_builder
42
+ self._approval_handler = approval_handler
43
+ self._notification_handler = notification_handler
44
+ self._logger = logger or _logger
45
+ self._auto_restart = auto_restart
46
+ self._request_timeout = request_timeout
47
+ self._turn_stall_timeout_seconds = turn_stall_timeout_seconds
48
+ self._turn_stall_poll_interval_seconds = turn_stall_poll_interval_seconds
49
+ self._turn_stall_recovery_min_interval_seconds = (
50
+ turn_stall_recovery_min_interval_seconds
51
+ )
52
+ self._default_approval_decision = default_approval_decision
53
+ self._max_handles = max_handles
54
+ self._idle_ttl_seconds = idle_ttl_seconds
55
+
56
+ self._supervisor: Optional[WorkspaceAppServerSupervisor] = None
57
+ self._client: Optional[CodexAppServerClient] = None
58
+
59
+ async def ensure_supervisor(self) -> WorkspaceAppServerSupervisor:
60
+ """Ensure the Codex app-server supervisor is initialized."""
61
+ if self._supervisor is None:
62
+ self._supervisor = WorkspaceAppServerSupervisor(
63
+ self._command,
64
+ state_root=self._state_root,
65
+ env_builder=self._env_builder,
66
+ approval_handler=self._approval_handler,
67
+ notification_handler=self._notification_handler,
68
+ logger=self._logger,
69
+ auto_restart=self._auto_restart,
70
+ request_timeout=self._request_timeout,
71
+ turn_stall_timeout_seconds=self._turn_stall_timeout_seconds,
72
+ turn_stall_poll_interval_seconds=self._turn_stall_poll_interval_seconds,
73
+ turn_stall_recovery_min_interval_seconds=self._turn_stall_recovery_min_interval_seconds,
74
+ default_approval_decision=self._default_approval_decision,
75
+ max_handles=self._max_handles,
76
+ idle_ttl_seconds=self._idle_ttl_seconds,
77
+ )
78
+ return self._supervisor
79
+
80
+ async def get_client(self, workspace_root: Path) -> CodexAppServerClient:
81
+ """Get or create a Codex app-server client for the given workspace."""
82
+ supervisor = await self.ensure_supervisor()
83
+ return await supervisor.get_client(workspace_root)
84
+
85
+ async def close_all(self) -> None:
86
+ """Close the supervisor and clean up resources."""
87
+ if self._supervisor is not None:
88
+ await self._supervisor.close_all()
89
+ self._supervisor = None
90
+ self._client = None