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.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +344 -325
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {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
|