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
codex_autorunner/core/hub.py
CHANGED
|
@@ -5,7 +5,7 @@ import re
|
|
|
5
5
|
import shutil
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Dict, List, Optional, Tuple
|
|
8
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
9
9
|
|
|
10
10
|
from ..bootstrap import seed_repo_files
|
|
11
11
|
from ..discovery import DiscoveryRecord, discover_and_init
|
|
@@ -16,12 +16,15 @@ from ..manifest import (
|
|
|
16
16
|
sanitize_repo_id,
|
|
17
17
|
save_manifest,
|
|
18
18
|
)
|
|
19
|
+
from .archive import archive_worktree_snapshot, build_snapshot_id
|
|
19
20
|
from .config import HubConfig, RepoConfig, derive_repo_config, load_hub_config
|
|
20
|
-
from .engine import Engine
|
|
21
|
+
from .engine import AppServerSupervisorFactory, BackendFactory, Engine
|
|
21
22
|
from .git_utils import (
|
|
22
23
|
GitError,
|
|
23
24
|
git_available,
|
|
25
|
+
git_branch,
|
|
24
26
|
git_default_branch,
|
|
27
|
+
git_head_sha,
|
|
25
28
|
git_is_clean,
|
|
26
29
|
git_upstream_status,
|
|
27
30
|
run_git,
|
|
@@ -33,6 +36,9 @@ from .utils import atomic_write
|
|
|
33
36
|
|
|
34
37
|
logger = logging.getLogger("codex_autorunner.hub")
|
|
35
38
|
|
|
39
|
+
BackendFactoryBuilder = Callable[[Path, RepoConfig], BackendFactory]
|
|
40
|
+
AppServerSupervisorFactoryBuilder = Callable[[RepoConfig], AppServerSupervisorFactory]
|
|
41
|
+
|
|
36
42
|
|
|
37
43
|
def _git_failure_detail(proc) -> str:
|
|
38
44
|
return (proc.stderr or proc.stdout or "").strip() or f"exit {proc.returncode}"
|
|
@@ -137,7 +143,8 @@ def load_hub_state(state_path: Path, hub_root: Path) -> HubState:
|
|
|
137
143
|
import json
|
|
138
144
|
|
|
139
145
|
payload = json.loads(data)
|
|
140
|
-
except Exception:
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
logger.warning("Failed to parse hub state from %s: %s", state_path, exc)
|
|
141
148
|
return HubState(last_scan_at=None, repos=[])
|
|
142
149
|
last_scan_at = payload.get("last_scan_at")
|
|
143
150
|
repos_payload = payload.get("repos") or []
|
|
@@ -168,7 +175,13 @@ def load_hub_state(state_path: Path, hub_root: Path) -> HubState:
|
|
|
168
175
|
runner_pid=entry.get("runner_pid"),
|
|
169
176
|
)
|
|
170
177
|
repos.append(repo)
|
|
171
|
-
except Exception:
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
repo_id = entry.get("id", "unknown")
|
|
180
|
+
logger.warning(
|
|
181
|
+
"Failed to load repo snapshot for id=%s from hub state: %s",
|
|
182
|
+
repo_id,
|
|
183
|
+
exc,
|
|
184
|
+
)
|
|
172
185
|
continue
|
|
173
186
|
return HubState(last_scan_at=last_scan_at, repos=repos)
|
|
174
187
|
|
|
@@ -188,9 +201,30 @@ class RepoRunner:
|
|
|
188
201
|
*,
|
|
189
202
|
repo_config: RepoConfig,
|
|
190
203
|
spawn_fn: Optional[SpawnRunnerFn] = None,
|
|
204
|
+
backend_factory_builder: Optional[BackendFactoryBuilder] = None,
|
|
205
|
+
app_server_supervisor_factory_builder: Optional[
|
|
206
|
+
AppServerSupervisorFactoryBuilder
|
|
207
|
+
] = None,
|
|
208
|
+
agent_id_validator: Optional[Callable[[str], str]] = None,
|
|
191
209
|
):
|
|
192
210
|
self.repo_id = repo_id
|
|
193
|
-
|
|
211
|
+
backend_factory = (
|
|
212
|
+
backend_factory_builder(repo_root, repo_config)
|
|
213
|
+
if backend_factory_builder is not None
|
|
214
|
+
else None
|
|
215
|
+
)
|
|
216
|
+
app_server_supervisor_factory = (
|
|
217
|
+
app_server_supervisor_factory_builder(repo_config)
|
|
218
|
+
if app_server_supervisor_factory_builder is not None
|
|
219
|
+
else None
|
|
220
|
+
)
|
|
221
|
+
self._engine = Engine(
|
|
222
|
+
repo_root,
|
|
223
|
+
config=repo_config,
|
|
224
|
+
backend_factory=backend_factory,
|
|
225
|
+
app_server_supervisor_factory=app_server_supervisor_factory,
|
|
226
|
+
agent_id_validator=agent_id_validator,
|
|
227
|
+
)
|
|
194
228
|
self._controller = ProcessRunnerController(self._engine, spawn_fn=spawn_fn)
|
|
195
229
|
|
|
196
230
|
@property
|
|
@@ -212,21 +246,46 @@ class RepoRunner:
|
|
|
212
246
|
|
|
213
247
|
class HubSupervisor:
|
|
214
248
|
def __init__(
|
|
215
|
-
self,
|
|
249
|
+
self,
|
|
250
|
+
hub_config: HubConfig,
|
|
251
|
+
*,
|
|
252
|
+
spawn_fn: Optional[SpawnRunnerFn] = None,
|
|
253
|
+
backend_factory_builder: Optional[BackendFactoryBuilder] = None,
|
|
254
|
+
app_server_supervisor_factory_builder: Optional[
|
|
255
|
+
AppServerSupervisorFactoryBuilder
|
|
256
|
+
] = None,
|
|
257
|
+
agent_id_validator: Optional[Callable[[str], str]] = None,
|
|
216
258
|
):
|
|
217
259
|
self.hub_config = hub_config
|
|
218
260
|
self.state_path = hub_config.root / ".codex-autorunner" / "hub_state.json"
|
|
219
261
|
self._runners: Dict[str, RepoRunner] = {}
|
|
220
262
|
self._spawn_fn = spawn_fn
|
|
263
|
+
self._backend_factory_builder = backend_factory_builder
|
|
264
|
+
self._app_server_supervisor_factory_builder = (
|
|
265
|
+
app_server_supervisor_factory_builder
|
|
266
|
+
)
|
|
267
|
+
self._agent_id_validator = agent_id_validator
|
|
221
268
|
self.state = load_hub_state(self.state_path, self.hub_config.root)
|
|
222
269
|
self._list_cache_at: Optional[float] = None
|
|
223
270
|
self._list_cache: Optional[List[RepoSnapshot]] = None
|
|
224
271
|
self._reconcile_startup()
|
|
225
272
|
|
|
226
273
|
@classmethod
|
|
227
|
-
def from_path(
|
|
274
|
+
def from_path(
|
|
275
|
+
cls,
|
|
276
|
+
path: Path,
|
|
277
|
+
*,
|
|
278
|
+
backend_factory_builder: Optional[BackendFactoryBuilder] = None,
|
|
279
|
+
app_server_supervisor_factory_builder: Optional[
|
|
280
|
+
AppServerSupervisorFactoryBuilder
|
|
281
|
+
] = None,
|
|
282
|
+
) -> "HubSupervisor":
|
|
228
283
|
config = load_hub_config(path)
|
|
229
|
-
return cls(
|
|
284
|
+
return cls(
|
|
285
|
+
config,
|
|
286
|
+
backend_factory_builder=backend_factory_builder,
|
|
287
|
+
app_server_supervisor_factory_builder=app_server_supervisor_factory_builder,
|
|
288
|
+
)
|
|
230
289
|
|
|
231
290
|
def scan(self) -> List[RepoSnapshot]:
|
|
232
291
|
self._invalidate_list_cache()
|
|
@@ -261,8 +320,24 @@ class HubSupervisor:
|
|
|
261
320
|
repo_config = derive_repo_config(
|
|
262
321
|
self.hub_config, record.absolute_path, load_env=False
|
|
263
322
|
)
|
|
323
|
+
backend_factory = (
|
|
324
|
+
self._backend_factory_builder(record.absolute_path, repo_config)
|
|
325
|
+
if self._backend_factory_builder is not None
|
|
326
|
+
else None
|
|
327
|
+
)
|
|
328
|
+
app_server_supervisor_factory = (
|
|
329
|
+
self._app_server_supervisor_factory_builder(repo_config)
|
|
330
|
+
if self._app_server_supervisor_factory_builder is not None
|
|
331
|
+
else None
|
|
332
|
+
)
|
|
264
333
|
controller = ProcessRunnerController(
|
|
265
|
-
Engine(
|
|
334
|
+
Engine(
|
|
335
|
+
record.absolute_path,
|
|
336
|
+
config=repo_config,
|
|
337
|
+
backend_factory=backend_factory,
|
|
338
|
+
app_server_supervisor_factory=app_server_supervisor_factory,
|
|
339
|
+
agent_id_validator=self._agent_id_validator,
|
|
340
|
+
)
|
|
266
341
|
)
|
|
267
342
|
controller.reconcile()
|
|
268
343
|
except Exception as exc:
|
|
@@ -586,6 +661,9 @@ class HubSupervisor:
|
|
|
586
661
|
worktree_repo_id: str,
|
|
587
662
|
delete_branch: bool = False,
|
|
588
663
|
delete_remote: bool = False,
|
|
664
|
+
archive: bool = True,
|
|
665
|
+
force_archive: bool = False,
|
|
666
|
+
archive_note: Optional[str] = None,
|
|
589
667
|
) -> None:
|
|
590
668
|
self._invalidate_list_cache()
|
|
591
669
|
manifest = load_manifest(self.hub_config.manifest_path, self.hub_config.root)
|
|
@@ -606,6 +684,44 @@ class HubSupervisor:
|
|
|
606
684
|
if runner:
|
|
607
685
|
runner.stop()
|
|
608
686
|
|
|
687
|
+
if archive:
|
|
688
|
+
branch_name = entry.branch or git_branch(worktree_path) or "unknown"
|
|
689
|
+
head_sha = git_head_sha(worktree_path) or "unknown"
|
|
690
|
+
snapshot_id = build_snapshot_id(branch_name, head_sha)
|
|
691
|
+
logger.info(
|
|
692
|
+
"Hub archive worktree start id=%s snapshot_id=%s",
|
|
693
|
+
worktree_repo_id,
|
|
694
|
+
snapshot_id,
|
|
695
|
+
)
|
|
696
|
+
try:
|
|
697
|
+
result = archive_worktree_snapshot(
|
|
698
|
+
base_repo_root=base_path,
|
|
699
|
+
base_repo_id=base.id,
|
|
700
|
+
worktree_repo_root=worktree_path,
|
|
701
|
+
worktree_repo_id=worktree_repo_id,
|
|
702
|
+
branch=branch_name,
|
|
703
|
+
worktree_of=entry.worktree_of,
|
|
704
|
+
note=archive_note,
|
|
705
|
+
snapshot_id=snapshot_id,
|
|
706
|
+
head_sha=head_sha,
|
|
707
|
+
source_path=entry.path,
|
|
708
|
+
)
|
|
709
|
+
except Exception as exc:
|
|
710
|
+
logger.exception(
|
|
711
|
+
"Hub archive worktree failed id=%s snapshot_id=%s",
|
|
712
|
+
worktree_repo_id,
|
|
713
|
+
snapshot_id,
|
|
714
|
+
)
|
|
715
|
+
if not force_archive:
|
|
716
|
+
raise ValueError(f"Worktree archive failed: {exc}") from exc
|
|
717
|
+
else:
|
|
718
|
+
logger.info(
|
|
719
|
+
"Hub archive worktree complete id=%s snapshot_id=%s status=%s",
|
|
720
|
+
worktree_repo_id,
|
|
721
|
+
result.snapshot_id,
|
|
722
|
+
result.status,
|
|
723
|
+
)
|
|
724
|
+
|
|
609
725
|
# Remove worktree from base repo.
|
|
610
726
|
try:
|
|
611
727
|
proc = run_git(
|
|
@@ -759,10 +875,10 @@ class HubSupervisor:
|
|
|
759
875
|
if not repo:
|
|
760
876
|
raise ValueError(f"Repo {repo_id} not found in manifest")
|
|
761
877
|
repo_root = (self.hub_config.root / repo.path).resolve()
|
|
762
|
-
|
|
763
|
-
if not allow_uninitialized and not
|
|
878
|
+
tickets_dir = repo_root / ".codex-autorunner" / "tickets"
|
|
879
|
+
if not allow_uninitialized and not tickets_dir.exists():
|
|
764
880
|
raise ValueError(f"Repo {repo_id} is not initialized")
|
|
765
|
-
if not
|
|
881
|
+
if not tickets_dir.exists():
|
|
766
882
|
return None
|
|
767
883
|
repo_config = derive_repo_config(self.hub_config, repo_root, load_env=False)
|
|
768
884
|
runner = RepoRunner(
|
|
@@ -770,6 +886,11 @@ class HubSupervisor:
|
|
|
770
886
|
repo_root,
|
|
771
887
|
repo_config=repo_config,
|
|
772
888
|
spawn_fn=self._spawn_fn,
|
|
889
|
+
backend_factory_builder=self._backend_factory_builder,
|
|
890
|
+
app_server_supervisor_factory_builder=(
|
|
891
|
+
self._app_server_supervisor_factory_builder
|
|
892
|
+
),
|
|
893
|
+
agent_id_validator=self._agent_id_validator,
|
|
773
894
|
)
|
|
774
895
|
self._runners[repo_id] = runner
|
|
775
896
|
return runner
|
|
@@ -781,7 +902,7 @@ class HubSupervisor:
|
|
|
781
902
|
records: List[DiscoveryRecord] = []
|
|
782
903
|
for entry in manifest.repos:
|
|
783
904
|
repo_path = (self.hub_config.root / entry.path).resolve()
|
|
784
|
-
initialized = (repo_path / ".codex-autorunner" / "
|
|
905
|
+
initialized = (repo_path / ".codex-autorunner" / "tickets").exists()
|
|
785
906
|
records.append(
|
|
786
907
|
DiscoveryRecord(
|
|
787
908
|
repo=entry,
|
|
@@ -821,9 +942,8 @@ class HubSupervisor:
|
|
|
821
942
|
lock_status = read_lock_status(lock_path)
|
|
822
943
|
|
|
823
944
|
runner_state: Optional[RunnerState] = None
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
runner_state = load_state(state_path)
|
|
945
|
+
if record.initialized:
|
|
946
|
+
runner_state = load_state(repo_path / ".codex-autorunner" / "state.sqlite3")
|
|
827
947
|
|
|
828
948
|
is_clean: Optional[bool] = None
|
|
829
949
|
if record.exists_on_disk and git_available(repo_path):
|
codex_autorunner/core/locks.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import errno
|
|
2
2
|
import json
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
import socket
|
|
5
6
|
import subprocess
|
|
@@ -27,6 +28,7 @@ class LockAssessment:
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
DEFAULT_RUNNER_CMD_HINTS = ("codex_autorunner.cli", "codex-autorunner", "car ")
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def process_alive(pid: int) -> bool:
|
|
@@ -48,6 +50,7 @@ def process_is_zombie(pid: int) -> bool:
|
|
|
48
50
|
check=False,
|
|
49
51
|
)
|
|
50
52
|
except Exception:
|
|
53
|
+
logger.debug("Failed to check process status for pid %s", pid, exc_info=True)
|
|
51
54
|
return False
|
|
52
55
|
if result.returncode != 0:
|
|
53
56
|
return False
|
|
@@ -65,6 +68,7 @@ def process_command(pid: int) -> Optional[str]:
|
|
|
65
68
|
check=False,
|
|
66
69
|
)
|
|
67
70
|
except Exception:
|
|
71
|
+
logger.debug("Failed to inspect process command for pid %s", pid, exc_info=True)
|
|
68
72
|
return None
|
|
69
73
|
if result.returncode != 0:
|
|
70
74
|
return None
|
|
@@ -23,6 +23,18 @@ class NotificationManager:
|
|
|
23
23
|
self._warned_missing: set[str] = set()
|
|
24
24
|
self._enabled_mode = self._parse_enabled(self._cfg.get("enabled"))
|
|
25
25
|
self._events = self._normalize_events(self._cfg.get("events"))
|
|
26
|
+
timeout_raw = self._cfg.get("timeout_seconds", DEFAULT_TIMEOUT_SECONDS)
|
|
27
|
+
try:
|
|
28
|
+
timeout_seconds = (
|
|
29
|
+
float(timeout_raw)
|
|
30
|
+
if timeout_raw is not None
|
|
31
|
+
else DEFAULT_TIMEOUT_SECONDS
|
|
32
|
+
)
|
|
33
|
+
except (TypeError, ValueError):
|
|
34
|
+
timeout_seconds = DEFAULT_TIMEOUT_SECONDS
|
|
35
|
+
if timeout_seconds <= 0:
|
|
36
|
+
timeout_seconds = DEFAULT_TIMEOUT_SECONDS
|
|
37
|
+
self._timeout_seconds = timeout_seconds
|
|
26
38
|
self._warn_unknown_events(self._events)
|
|
27
39
|
discord_cfg = self._cfg.get("discord")
|
|
28
40
|
self._discord: Dict[str, Any] = (
|
|
@@ -202,7 +214,7 @@ class NotificationManager:
|
|
|
202
214
|
if not targets:
|
|
203
215
|
return
|
|
204
216
|
try:
|
|
205
|
-
with httpx.Client(timeout=
|
|
217
|
+
with httpx.Client(timeout=self._timeout_seconds) as client:
|
|
206
218
|
self._send_sync(client, targets, message)
|
|
207
219
|
except Exception as exc:
|
|
208
220
|
self._log_warning("Notification delivery failed", exc)
|
|
@@ -216,7 +228,7 @@ class NotificationManager:
|
|
|
216
228
|
if not targets:
|
|
217
229
|
return
|
|
218
230
|
try:
|
|
219
|
-
async with httpx.AsyncClient(timeout=
|
|
231
|
+
async with httpx.AsyncClient(timeout=self._timeout_seconds) as client:
|
|
220
232
|
await self._send_async(client, targets, message)
|
|
221
233
|
except Exception as exc:
|
|
222
234
|
self._log_warning("Notification delivery failed", exc)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .agent_backend import AgentBackend, AgentEvent, AgentEventType, now_iso
|
|
2
|
+
from .run_event import (
|
|
3
|
+
ApprovalRequested,
|
|
4
|
+
Completed,
|
|
5
|
+
Failed,
|
|
6
|
+
OutputDelta,
|
|
7
|
+
RunEvent,
|
|
8
|
+
RunNotice,
|
|
9
|
+
Started,
|
|
10
|
+
TokenUsage,
|
|
11
|
+
ToolCall,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AgentBackend",
|
|
16
|
+
"AgentEvent",
|
|
17
|
+
"AgentEventType",
|
|
18
|
+
"now_iso",
|
|
19
|
+
"RunEvent",
|
|
20
|
+
"Started",
|
|
21
|
+
"OutputDelta",
|
|
22
|
+
"ToolCall",
|
|
23
|
+
"ApprovalRequested",
|
|
24
|
+
"TokenUsage",
|
|
25
|
+
"RunNotice",
|
|
26
|
+
"Completed",
|
|
27
|
+
"Failed",
|
|
28
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, AsyncGenerator, Dict, Optional
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def now_iso() -> str:
|
|
11
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentEventType(str, Enum):
|
|
15
|
+
STREAM_DELTA = "stream_delta"
|
|
16
|
+
TOOL_CALL = "tool_call"
|
|
17
|
+
TOOL_RESULT = "tool_result"
|
|
18
|
+
MESSAGE_COMPLETE = "message_complete"
|
|
19
|
+
ERROR = "error"
|
|
20
|
+
APPROVAL_REQUESTED = "approval_requested"
|
|
21
|
+
APPROVAL_GRANTED = "approval_granted"
|
|
22
|
+
APPROVAL_DENIED = "approval_denied"
|
|
23
|
+
SESSION_STARTED = "session_started"
|
|
24
|
+
SESSION_ENDED = "session_ended"
|
|
25
|
+
SESION_STARTED = "session_started" # legacy typo kept for backward tests
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class AgentEvent:
|
|
30
|
+
type: str
|
|
31
|
+
timestamp: str
|
|
32
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def event_type(self) -> AgentEventType:
|
|
36
|
+
try:
|
|
37
|
+
return AgentEventType(self.type)
|
|
38
|
+
except ValueError:
|
|
39
|
+
return AgentEventType.ERROR
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def stream_delta(cls, content: str, delta_type: str = "text") -> "AgentEvent":
|
|
43
|
+
return cls(
|
|
44
|
+
type=AgentEventType.STREAM_DELTA.value,
|
|
45
|
+
timestamp=now_iso(),
|
|
46
|
+
data={"content": content, "delta_type": delta_type},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def tool_call(cls, tool_name: str, tool_input: Dict[str, Any]) -> "AgentEvent":
|
|
51
|
+
return cls(
|
|
52
|
+
type=AgentEventType.TOOL_CALL.value,
|
|
53
|
+
timestamp=now_iso(),
|
|
54
|
+
data={"tool_name": tool_name, "tool_input": tool_input},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def tool_result(
|
|
59
|
+
cls, tool_name: str, result: Any, error: Optional[str] = None
|
|
60
|
+
) -> "AgentEvent":
|
|
61
|
+
return cls(
|
|
62
|
+
type=AgentEventType.TOOL_RESULT.value,
|
|
63
|
+
timestamp=now_iso(),
|
|
64
|
+
data={"tool_name": tool_name, "result": result, "error": error},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def message_complete(cls, final_message: str) -> "AgentEvent":
|
|
69
|
+
return cls(
|
|
70
|
+
type=AgentEventType.MESSAGE_COMPLETE.value,
|
|
71
|
+
timestamp=now_iso(),
|
|
72
|
+
data={"final_message": final_message},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def error(cls, error_message: str) -> "AgentEvent":
|
|
77
|
+
return cls(
|
|
78
|
+
type=AgentEventType.ERROR.value,
|
|
79
|
+
timestamp=now_iso(),
|
|
80
|
+
data={"error": error_message},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def approval_requested(
|
|
85
|
+
cls, request_id: str, description: str, context: Optional[Dict[str, Any]] = None
|
|
86
|
+
) -> "AgentEvent":
|
|
87
|
+
return cls(
|
|
88
|
+
type=AgentEventType.APPROVAL_REQUESTED.value,
|
|
89
|
+
timestamp=now_iso(),
|
|
90
|
+
data={
|
|
91
|
+
"request_id": request_id,
|
|
92
|
+
"description": description,
|
|
93
|
+
"context": context or {},
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def approval_granted(cls, request_id: str) -> "AgentEvent":
|
|
99
|
+
return cls(
|
|
100
|
+
type=AgentEventType.APPROVAL_GRANTED.value,
|
|
101
|
+
timestamp=now_iso(),
|
|
102
|
+
data={"request_id": request_id},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def approval_denied(
|
|
107
|
+
cls, request_id: str, reason: Optional[str] = None
|
|
108
|
+
) -> "AgentEvent":
|
|
109
|
+
return cls(
|
|
110
|
+
type=AgentEventType.APPROVAL_DENIED.value,
|
|
111
|
+
timestamp=now_iso(),
|
|
112
|
+
data={"request_id": request_id, "reason": reason},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AgentBackend:
|
|
117
|
+
async def start_session(self, target: dict, context: dict) -> str:
|
|
118
|
+
raise NotImplementedError
|
|
119
|
+
|
|
120
|
+
def run_turn(
|
|
121
|
+
self, session_id: str, message: str
|
|
122
|
+
) -> AsyncGenerator[AgentEvent, None]:
|
|
123
|
+
raise NotImplementedError
|
|
124
|
+
|
|
125
|
+
def stream_events(self, session_id: str) -> AsyncGenerator[AgentEvent, None]:
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
def run_turn_events(
|
|
129
|
+
self, session_id: str, message: str
|
|
130
|
+
) -> AsyncGenerator[Any, None]:
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
133
|
+
async def interrupt(self, session_id: str) -> None:
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
async def final_messages(self, session_id: str) -> list[str]:
|
|
137
|
+
raise NotImplementedError
|
|
138
|
+
|
|
139
|
+
async def request_approval(
|
|
140
|
+
self, description: str, context: Optional[Dict[str, Any]] = None
|
|
141
|
+
) -> bool:
|
|
142
|
+
raise NotImplementedError
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
__all__ = [
|
|
146
|
+
"AgentBackend",
|
|
147
|
+
"AgentEvent",
|
|
148
|
+
"AgentEventType",
|
|
149
|
+
"now_iso",
|
|
150
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Protocol for backend orchestrators used by the Engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, AsyncGenerator, Optional, Protocol
|
|
6
|
+
|
|
7
|
+
from .run_event import RunEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BackendOrchestrator(Protocol):
|
|
11
|
+
def run_turn(
|
|
12
|
+
self,
|
|
13
|
+
*,
|
|
14
|
+
agent_id: str,
|
|
15
|
+
state: Any,
|
|
16
|
+
prompt: str,
|
|
17
|
+
model: Optional[str],
|
|
18
|
+
reasoning: Optional[str],
|
|
19
|
+
session_key: str,
|
|
20
|
+
) -> AsyncGenerator[RunEvent, None]: ...
|
|
21
|
+
|
|
22
|
+
async def interrupt(self, agent_id: str, state: Any) -> None: ...
|
|
23
|
+
|
|
24
|
+
def get_thread_id(self, session_key: str) -> Optional[str]: ...
|
|
25
|
+
|
|
26
|
+
def set_thread_id(self, session_key: str, thread_id: str) -> None: ...
|
|
27
|
+
|
|
28
|
+
def build_app_server_supervisor(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
event_prefix: str,
|
|
32
|
+
notification_handler: Optional[Any] = None,
|
|
33
|
+
) -> Optional[Any]: ...
|
|
34
|
+
|
|
35
|
+
def ensure_opencode_supervisor(self) -> Optional[Any]: ...
|
|
36
|
+
|
|
37
|
+
def get_last_turn_id(self) -> Optional[str]: ...
|
|
38
|
+
|
|
39
|
+
def get_last_thread_info(self) -> Optional[dict[str, Any]]: ...
|
|
40
|
+
|
|
41
|
+
def get_last_token_total(self) -> Optional[dict[str, Any]]: ...
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Optional, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def now_iso() -> str:
|
|
9
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Started:
|
|
14
|
+
timestamp: str
|
|
15
|
+
session_id: str
|
|
16
|
+
thread_id: Optional[str] = None
|
|
17
|
+
turn_id: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class OutputDelta:
|
|
22
|
+
timestamp: str
|
|
23
|
+
content: str
|
|
24
|
+
delta_type: str = "text"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class ToolCall:
|
|
29
|
+
timestamp: str
|
|
30
|
+
tool_name: str
|
|
31
|
+
tool_input: dict[str, Any]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class ApprovalRequested:
|
|
36
|
+
timestamp: str
|
|
37
|
+
request_id: str
|
|
38
|
+
description: str
|
|
39
|
+
context: dict[str, Any]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class TokenUsage:
|
|
44
|
+
timestamp: str
|
|
45
|
+
usage: dict[str, Any]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class RunNotice:
|
|
50
|
+
timestamp: str
|
|
51
|
+
kind: str
|
|
52
|
+
message: str = ""
|
|
53
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class Completed:
|
|
58
|
+
timestamp: str
|
|
59
|
+
final_message: str = ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class Failed:
|
|
64
|
+
timestamp: str
|
|
65
|
+
error_message: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
RunEvent = Union[
|
|
69
|
+
Started,
|
|
70
|
+
OutputDelta,
|
|
71
|
+
ToolCall,
|
|
72
|
+
ApprovalRequested,
|
|
73
|
+
TokenUsage,
|
|
74
|
+
RunNotice,
|
|
75
|
+
Completed,
|
|
76
|
+
Failed,
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"RunEvent",
|
|
82
|
+
"Started",
|
|
83
|
+
"OutputDelta",
|
|
84
|
+
"ToolCall",
|
|
85
|
+
"ApprovalRequested",
|
|
86
|
+
"TokenUsage",
|
|
87
|
+
"RunNotice",
|
|
88
|
+
"Completed",
|
|
89
|
+
"Failed",
|
|
90
|
+
"now_iso",
|
|
91
|
+
]
|
codex_autorunner/core/prompt.py
CHANGED
|
@@ -15,12 +15,20 @@ def _display_path(root: Path, path: Path) -> str:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def build_doc_paths(config: Config) -> Mapping[str, str]:
|
|
18
|
+
def _safe_path(*keys: str) -> str:
|
|
19
|
+
for key in keys:
|
|
20
|
+
try:
|
|
21
|
+
return _display_path(config.root, config.doc_path(key))
|
|
22
|
+
except KeyError:
|
|
23
|
+
continue
|
|
24
|
+
return ""
|
|
25
|
+
|
|
18
26
|
return {
|
|
19
|
-
"todo":
|
|
20
|
-
"progress":
|
|
21
|
-
"opinions":
|
|
22
|
-
"spec":
|
|
23
|
-
"summary":
|
|
27
|
+
"todo": _safe_path("todo", "active_context"),
|
|
28
|
+
"progress": _safe_path("progress", "decisions"),
|
|
29
|
+
"opinions": _safe_path("opinions"),
|
|
30
|
+
"spec": _safe_path("spec"),
|
|
31
|
+
"summary": _safe_path("summary"),
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
|
|
@@ -64,8 +72,8 @@ def build_final_summary_prompt(
|
|
|
64
72
|
|
|
65
73
|
doc_paths = build_doc_paths(config)
|
|
66
74
|
doc_contents = {
|
|
67
|
-
"todo": docs.read_doc("todo"),
|
|
68
|
-
"progress": docs.read_doc("progress"),
|
|
75
|
+
"todo": docs.read_doc("todo") or docs.read_doc("active_context"),
|
|
76
|
+
"progress": docs.read_doc("progress") or docs.read_doc("decisions"),
|
|
69
77
|
"opinions": docs.read_doc("opinions"),
|
|
70
78
|
"spec": docs.read_doc("spec"),
|
|
71
79
|
"summary": docs.read_doc("summary"),
|