codex-autorunner 0.1.1__py3-none-any.whl → 1.0.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/__main__.py +4 -0
- codex_autorunner/agents/__init__.py +20 -0
- codex_autorunner/agents/base.py +2 -2
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/__init__.py +4 -0
- codex_autorunner/agents/opencode/agent_config.py +104 -0
- codex_autorunner/agents/opencode/client.py +305 -28
- codex_autorunner/agents/opencode/harness.py +71 -20
- codex_autorunner/agents/opencode/logging.py +225 -0
- codex_autorunner/agents/opencode/run_prompt.py +261 -0
- codex_autorunner/agents/opencode/runtime.py +1202 -132
- codex_autorunner/agents/opencode/supervisor.py +194 -68
- codex_autorunner/agents/registry.py +258 -0
- codex_autorunner/agents/types.py +2 -2
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +19 -40
- codex_autorunner/cli.py +234 -151
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_events.py +15 -6
- codex_autorunner/core/app_server_logging.py +55 -15
- codex_autorunner/core/app_server_prompts.py +28 -259
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/circuit_breaker.py +183 -0
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +555 -133
- codex_autorunner/core/docs.py +54 -9
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +828 -274
- codex_autorunner/core/exceptions.py +60 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +21 -13
- codex_autorunner/core/locks.py +118 -1
- codex_autorunner/core/logging_utils.py +9 -6
- codex_autorunner/core/path_utils.py +123 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/retry.py +61 -0
- codex_autorunner/core/review.py +888 -0
- codex_autorunner/core/review_context.py +161 -0
- codex_autorunner/core/run_index.py +223 -0
- codex_autorunner/core/runner_controller.py +44 -1
- codex_autorunner/core/runner_process.py +30 -1
- codex_autorunner/core/sqlite_utils.py +32 -0
- codex_autorunner/core/state.py +273 -44
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +43 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +107 -75
- codex_autorunner/core/utils.py +167 -3
- codex_autorunner/discovery.py +3 -3
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +708 -153
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +474 -185
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +239 -1
- codex_autorunner/integrations/telegram/constants.py +19 -1
- codex_autorunner/integrations/telegram/dispatch.py +44 -8
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/approvals.py +12 -10
- codex_autorunner/integrations/telegram/handlers/callbacks.py +15 -1
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +29 -0
- codex_autorunner/integrations/telegram/handlers/commands/approvals.py +173 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +2595 -0
- codex_autorunner/integrations/telegram/handlers/commands/files.py +1408 -0
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +81 -0
- codex_autorunner/integrations/telegram/handlers/commands/github.py +1688 -0
- codex_autorunner/integrations/telegram/handlers/commands/shared.py +190 -0
- codex_autorunner/integrations/telegram/handlers/commands/voice.py +112 -0
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +2043 -0
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +954 -5689
- codex_autorunner/integrations/telegram/handlers/{commands.py → commands_spec.py} +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +374 -49
- codex_autorunner/integrations/telegram/handlers/questions.py +389 -0
- codex_autorunner/integrations/telegram/handlers/selections.py +6 -4
- codex_autorunner/integrations/telegram/handlers/utils.py +171 -0
- codex_autorunner/integrations/telegram/helpers.py +90 -18
- codex_autorunner/integrations/telegram/notifications.py +126 -35
- codex_autorunner/integrations/telegram/outbox.py +214 -43
- codex_autorunner/integrations/telegram/progress_stream.py +42 -19
- codex_autorunner/integrations/telegram/runtime.py +24 -13
- codex_autorunner/integrations/telegram/service.py +500 -129
- codex_autorunner/integrations/telegram/state.py +1278 -330
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +37 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/integrations/telegram/types.py +22 -2
- codex_autorunner/integrations/telegram/voice.py +14 -15
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +25 -14
- codex_autorunner/routes/agents.py +18 -78
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +142 -113
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/repos.py +17 -0
- codex_autorunner/routes/review.py +148 -0
- codex_autorunner/routes/sessions.py +16 -8
- codex_autorunner/routes/settings.py +22 -0
- codex_autorunner/routes/shared.py +33 -3
- codex_autorunner/routes/system.py +22 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/voice.py +5 -13
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +9 -1
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +27 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- 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 +162 -150
- 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 +67 -126
- codex_autorunner/static/index.html +788 -807
- codex_autorunner/static/liveUpdates.js +59 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -205
- codex_autorunner/static/styles.css +7577 -3758
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +53 -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 +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +21 -7
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/voice/capture.py +7 -7
- codex_autorunner/voice/service.py +51 -9
- codex_autorunner/web/app.py +419 -199
- codex_autorunner/web/hub_jobs.py +13 -2
- codex_autorunner/web/middleware.py +47 -13
- codex_autorunner/web/pty_session.py +26 -13
- codex_autorunner/web/schemas.py +114 -109
- codex_autorunner/web/static_assets.py +55 -42
- codex_autorunner/web/static_refresh.py +86 -0
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +20 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/core/doc_chat.py +0 -1415
- 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 -118
- codex_autorunner/spec_ingest.py +0 -788
- codex_autorunner/static/docChatActions.js +0 -279
- 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 -274
- 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 -442
- codex_autorunner/static/logs.js +0 -640
- codex_autorunner/static/runs.js +0 -409
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -86
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.1.dist-info/RECORD +0 -191
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
codex_autorunner/bootstrap.py
CHANGED
|
@@ -10,45 +10,22 @@ from .core.config import (
|
|
|
10
10
|
ConfigError,
|
|
11
11
|
resolve_hub_config_data,
|
|
12
12
|
)
|
|
13
|
+
from .core.state import RunnerState, save_state
|
|
13
14
|
from .core.utils import atomic_write
|
|
14
15
|
from .manifest import load_manifest
|
|
15
16
|
|
|
16
17
|
GITIGNORE_CONTENT = "*"
|
|
18
|
+
GENERATED_CONFIG_HEADER = "# GENERATED by CAR - DO NOT EDIT\n"
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def sample_todo() -> str:
|
|
20
22
|
return """# TODO\n\n- [ ] Replace this item with your first task\n- [ ] Add another task\n- [x] Example completed item\n"""
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
def sample_opinions() -> str:
|
|
24
|
-
return """# Opinions\n\n- Prefer small, well-tested changes.\n- Keep docs in sync with code.\n- Avoid unnecessary dependencies.\n"""
|
|
25
|
-
|
|
26
|
-
|
|
27
25
|
def sample_spec() -> str:
|
|
28
26
|
return """# Spec\n\n## Context\n- Add project background and goals here.\n\n## Requirements\n- Requirement 1\n- Requirement 2\n\n## Non-goals\n- Out of scope items\n"""
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
def sample_summary() -> str:
|
|
32
|
-
return """# Summary
|
|
33
|
-
|
|
34
|
-
This doc is the **user-facing report and handoff** for work done by CAR agents.
|
|
35
|
-
|
|
36
|
-
Use it for:
|
|
37
|
-
- Anything that requires **user action** or an **external party** (not agents).
|
|
38
|
-
- Unresolved decisions or blockers that agents can’t finish autonomously.
|
|
39
|
-
- A final condensed report once TODO is complete.
|
|
40
|
-
|
|
41
|
-
## External/user actions
|
|
42
|
-
- (none)
|
|
43
|
-
|
|
44
|
-
## Open questions / blockers
|
|
45
|
-
- (none)
|
|
46
|
-
|
|
47
|
-
## Final report
|
|
48
|
-
- (pending)
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
|
|
52
29
|
def _seed_doc(path: Path, force: bool, content: str) -> None:
|
|
53
30
|
if path.exists() and not force:
|
|
54
31
|
return
|
|
@@ -81,6 +58,7 @@ def write_hub_config(hub_root: Path, force: bool = False) -> Path:
|
|
|
81
58
|
return config_path
|
|
82
59
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
60
|
with config_path.open("w", encoding="utf-8") as f:
|
|
61
|
+
f.write(GENERATED_CONFIG_HEADER)
|
|
84
62
|
yaml.safe_dump(
|
|
85
63
|
resolve_hub_config_data(hub_root),
|
|
86
64
|
f,
|
|
@@ -107,32 +85,33 @@ def seed_repo_files(
|
|
|
107
85
|
if not gitignore_path.exists() or force:
|
|
108
86
|
gitignore_path.write_text(GITIGNORE_CONTENT, encoding="utf-8")
|
|
109
87
|
|
|
110
|
-
state_path = ca_dir / "state.
|
|
88
|
+
state_path = ca_dir / "state.sqlite3"
|
|
111
89
|
if not state_path.exists() or force:
|
|
112
|
-
|
|
113
|
-
state_path,
|
|
114
|
-
'{\n "last_run_id": null,\n "status": "idle",\n "last_exit_code": null,\n "last_run_started_at": null,\n "last_run_finished_at": null,\n "runner_pid": null\n}\n',
|
|
115
|
-
)
|
|
90
|
+
save_state(state_path, RunnerState(None, "idle", None, None, None))
|
|
116
91
|
|
|
117
92
|
log_path = ca_dir / "codex-autorunner.log"
|
|
118
93
|
if not log_path.exists() or force:
|
|
119
94
|
log_path.write_text("", encoding="utf-8")
|
|
120
95
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
tickets_dir = ca_dir / "tickets"
|
|
97
|
+
if not tickets_dir.exists() or force:
|
|
98
|
+
tickets_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
|
|
100
|
+
workspace_dir = ca_dir / "workspace"
|
|
101
|
+
if not workspace_dir.exists() or force:
|
|
102
|
+
workspace_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
|
|
104
|
+
_seed_doc(workspace_dir / "active_context.md", force, sample_todo())
|
|
105
|
+
_seed_doc(workspace_dir / "decisions.md", force, "# Decisions\n\n")
|
|
106
|
+
_seed_doc(workspace_dir / "spec.md", force, sample_spec())
|
|
126
107
|
|
|
127
108
|
# Seed an always-available briefing doc for interactive Codex sessions.
|
|
128
109
|
ensure_about_car_file_for_repo(
|
|
129
110
|
repo_root,
|
|
130
111
|
doc_paths={
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"spec": ca_dir / "SPEC.md",
|
|
135
|
-
"summary": ca_dir / "SUMMARY.md",
|
|
112
|
+
"active_context": workspace_dir / "active_context.md",
|
|
113
|
+
"decisions": workspace_dir / "decisions.md",
|
|
114
|
+
"spec": workspace_dir / "spec.md",
|
|
136
115
|
},
|
|
137
116
|
force=force,
|
|
138
117
|
)
|
codex_autorunner/cli.py
CHANGED
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import shlex
|
|
7
7
|
import subprocess
|
|
8
|
+
import uuid
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import NoReturn, Optional
|
|
10
11
|
|
|
@@ -18,16 +19,17 @@ from .core.config import (
|
|
|
18
19
|
CONFIG_FILENAME,
|
|
19
20
|
ConfigError,
|
|
20
21
|
HubConfig,
|
|
22
|
+
RepoConfig,
|
|
21
23
|
_normalize_base_path,
|
|
24
|
+
derive_repo_config,
|
|
22
25
|
find_nearest_hub_config_path,
|
|
23
26
|
load_hub_config,
|
|
24
27
|
)
|
|
25
|
-
from .core.engine import Engine, LockError, clear_stale_lock, doctor
|
|
28
|
+
from .core.engine import DoctorReport, Engine, LockError, clear_stale_lock, doctor
|
|
26
29
|
from .core.git_utils import GitError, run_git
|
|
27
30
|
from .core.hub import HubSupervisor
|
|
28
31
|
from .core.logging_utils import log_event, setup_rotating_logger
|
|
29
32
|
from .core.optional_dependencies import require_optional_dependencies
|
|
30
|
-
from .core.snapshot import SnapshotError
|
|
31
33
|
from .core.state import RunnerState, load_state, now_iso, save_state, state_lock
|
|
32
34
|
from .core.usage import (
|
|
33
35
|
UsageError,
|
|
@@ -37,20 +39,21 @@ from .core.usage import (
|
|
|
37
39
|
summarize_repo_usage,
|
|
38
40
|
)
|
|
39
41
|
from .core.utils import RepoNotFoundError, default_editor, find_repo_root
|
|
40
|
-
from .integrations.app_server.env import build_app_server_env
|
|
41
|
-
from .integrations.app_server.supervisor import WorkspaceAppServerSupervisor
|
|
42
42
|
from .integrations.telegram.adapter import TelegramAPIError, TelegramBotClient
|
|
43
|
+
from .integrations.telegram.doctor import telegram_doctor_checks
|
|
43
44
|
from .integrations.telegram.service import (
|
|
44
45
|
TelegramBotConfig,
|
|
45
46
|
TelegramBotConfigError,
|
|
46
47
|
TelegramBotLockError,
|
|
47
48
|
TelegramBotService,
|
|
48
49
|
)
|
|
50
|
+
from .integrations.telegram.state import TelegramStateStore
|
|
49
51
|
from .manifest import load_manifest
|
|
50
52
|
from .server import create_hub_app
|
|
51
|
-
from .spec_ingest import SpecIngestError, SpecIngestService, clear_work_docs
|
|
52
53
|
from .voice import VoiceConfig
|
|
53
54
|
|
|
55
|
+
logger = logging.getLogger("codex_autorunner.cli")
|
|
56
|
+
|
|
54
57
|
app = typer.Typer(add_completion=False)
|
|
55
58
|
hub_app = typer.Typer(add_completion=False)
|
|
56
59
|
telegram_app = typer.Typer(add_completion=False)
|
|
@@ -115,14 +118,16 @@ def _resolve_repo_api_path(repo_root: Path, hub: Optional[Path], path: str) -> s
|
|
|
115
118
|
manifest_value = hub_cfg.get("manifest")
|
|
116
119
|
if isinstance(manifest_value, str) and manifest_value.strip():
|
|
117
120
|
manifest_rel = manifest_value.strip()
|
|
118
|
-
except
|
|
121
|
+
except (OSError, yaml.YAMLError, KeyError, ValueError) as exc:
|
|
122
|
+
logger.debug("Failed to read hub config for manifest: %s", exc)
|
|
119
123
|
manifest_rel = None
|
|
120
124
|
manifest_path = hub_root / (manifest_rel or ".codex-autorunner/manifest.yml")
|
|
121
125
|
if not manifest_path.exists():
|
|
122
126
|
return path
|
|
123
127
|
try:
|
|
124
128
|
manifest = load_manifest(manifest_path, hub_root)
|
|
125
|
-
except
|
|
129
|
+
except (OSError, ValueError, KeyError) as exc:
|
|
130
|
+
logger.debug("Failed to load manifest: %s", exc)
|
|
126
131
|
return path
|
|
127
132
|
repo_root = repo_root.resolve()
|
|
128
133
|
for entry in manifest.repos:
|
|
@@ -287,6 +292,7 @@ def init(
|
|
|
287
292
|
def status(
|
|
288
293
|
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
289
294
|
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
295
|
+
output_json: bool = typer.Option(False, "--json", help="Emit JSON output"),
|
|
290
296
|
):
|
|
291
297
|
"""Show autorunner status."""
|
|
292
298
|
engine = _require_repo_config(repo, hub)
|
|
@@ -301,6 +307,51 @@ def status(
|
|
|
301
307
|
opencode_record = (
|
|
302
308
|
state.sessions.get(opencode_session_id) if opencode_session_id else None
|
|
303
309
|
)
|
|
310
|
+
|
|
311
|
+
if output_json:
|
|
312
|
+
hub_config_path = _resolve_hub_config_path_for_cli(engine.repo_root, hub)
|
|
313
|
+
payload = {
|
|
314
|
+
"repo": str(engine.repo_root),
|
|
315
|
+
"hub": (
|
|
316
|
+
str(hub_config_path.parent.parent.resolve())
|
|
317
|
+
if hub_config_path
|
|
318
|
+
else None
|
|
319
|
+
),
|
|
320
|
+
"status": state.status,
|
|
321
|
+
"last_run_id": state.last_run_id,
|
|
322
|
+
"last_exit_code": state.last_exit_code,
|
|
323
|
+
"last_run_started_at": state.last_run_started_at,
|
|
324
|
+
"last_run_finished_at": state.last_run_finished_at,
|
|
325
|
+
"runner_pid": state.runner_pid,
|
|
326
|
+
"session_id": session_id,
|
|
327
|
+
"session_record": (
|
|
328
|
+
{
|
|
329
|
+
"repo_path": session_record.repo_path,
|
|
330
|
+
"created_at": session_record.created_at,
|
|
331
|
+
"last_seen_at": session_record.last_seen_at,
|
|
332
|
+
"status": session_record.status,
|
|
333
|
+
"agent": session_record.agent,
|
|
334
|
+
}
|
|
335
|
+
if session_record
|
|
336
|
+
else None
|
|
337
|
+
),
|
|
338
|
+
"opencode_session_id": opencode_session_id,
|
|
339
|
+
"opencode_record": (
|
|
340
|
+
{
|
|
341
|
+
"repo_path": opencode_record.repo_path,
|
|
342
|
+
"created_at": opencode_record.created_at,
|
|
343
|
+
"last_seen_at": opencode_record.last_seen_at,
|
|
344
|
+
"status": opencode_record.status,
|
|
345
|
+
"agent": opencode_record.agent,
|
|
346
|
+
}
|
|
347
|
+
if opencode_record
|
|
348
|
+
else None
|
|
349
|
+
),
|
|
350
|
+
"outstanding_todos": len(outstanding),
|
|
351
|
+
}
|
|
352
|
+
typer.echo(json.dumps(payload, indent=2))
|
|
353
|
+
return
|
|
354
|
+
|
|
304
355
|
typer.echo(f"Repo: {engine.repo_root}")
|
|
305
356
|
typer.echo(f"Status: {state.status}")
|
|
306
357
|
typer.echo(f"Last run id: {state.last_run_id}")
|
|
@@ -341,7 +392,15 @@ def sessions(
|
|
|
341
392
|
source = "server"
|
|
342
393
|
try:
|
|
343
394
|
payload = _request_json("GET", url, token_env=config.server_auth_token_env)
|
|
344
|
-
except
|
|
395
|
+
except (
|
|
396
|
+
httpx.HTTPError,
|
|
397
|
+
httpx.ConnectError,
|
|
398
|
+
httpx.TimeoutException,
|
|
399
|
+
OSError,
|
|
400
|
+
) as exc:
|
|
401
|
+
logger.debug(
|
|
402
|
+
"Failed to fetch sessions from server, falling back to state: %s", exc
|
|
403
|
+
)
|
|
345
404
|
state = load_state(engine.state_path)
|
|
346
405
|
payload = {
|
|
347
406
|
"sessions": [
|
|
@@ -407,8 +466,15 @@ def stop_session(
|
|
|
407
466
|
stopped_id = response.get("session_id", payload.get("session_id", ""))
|
|
408
467
|
typer.echo(f"Stopped session {stopped_id}")
|
|
409
468
|
return
|
|
410
|
-
except
|
|
411
|
-
|
|
469
|
+
except (
|
|
470
|
+
httpx.HTTPError,
|
|
471
|
+
httpx.ConnectError,
|
|
472
|
+
httpx.TimeoutException,
|
|
473
|
+
OSError,
|
|
474
|
+
) as exc:
|
|
475
|
+
logger.debug(
|
|
476
|
+
"Failed to stop session via server, falling back to state: %s", exc
|
|
477
|
+
)
|
|
412
478
|
|
|
413
479
|
with state_lock(engine.state_path):
|
|
414
480
|
state = load_state(engine.state_path)
|
|
@@ -467,7 +533,7 @@ def usage(
|
|
|
467
533
|
except RepoNotFoundError:
|
|
468
534
|
repo_root = None
|
|
469
535
|
|
|
470
|
-
if repo_root and (repo_root / ".codex-autorunner" / "state.
|
|
536
|
+
if repo_root and (repo_root / ".codex-autorunner" / "state.sqlite3").exists():
|
|
471
537
|
engine = _require_repo_config(repo, hub)
|
|
472
538
|
else:
|
|
473
539
|
try:
|
|
@@ -568,8 +634,8 @@ def run(
|
|
|
568
634
|
if engine:
|
|
569
635
|
try:
|
|
570
636
|
engine.release_lock()
|
|
571
|
-
except
|
|
572
|
-
|
|
637
|
+
except OSError as exc:
|
|
638
|
+
logger.debug("Failed to release lock in run command: %s", exc)
|
|
573
639
|
|
|
574
640
|
|
|
575
641
|
@app.command()
|
|
@@ -591,8 +657,8 @@ def once(
|
|
|
591
657
|
if engine:
|
|
592
658
|
try:
|
|
593
659
|
engine.release_lock()
|
|
594
|
-
except
|
|
595
|
-
|
|
660
|
+
except OSError as exc:
|
|
661
|
+
logger.debug("Failed to release lock in once command: %s", exc)
|
|
596
662
|
|
|
597
663
|
|
|
598
664
|
@app.command()
|
|
@@ -650,8 +716,8 @@ def resume(
|
|
|
650
716
|
if engine:
|
|
651
717
|
try:
|
|
652
718
|
engine.release_lock()
|
|
653
|
-
except
|
|
654
|
-
|
|
719
|
+
except OSError as exc:
|
|
720
|
+
logger.debug("Failed to release lock in resume command: %s", exc)
|
|
655
721
|
|
|
656
722
|
|
|
657
723
|
@app.command()
|
|
@@ -690,7 +756,7 @@ def log(
|
|
|
690
756
|
|
|
691
757
|
@app.command()
|
|
692
758
|
def edit(
|
|
693
|
-
target: str = typer.Argument(..., help="
|
|
759
|
+
target: str = typer.Argument(..., help="active_context|decisions|spec"),
|
|
694
760
|
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
695
761
|
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
696
762
|
):
|
|
@@ -698,8 +764,8 @@ def edit(
|
|
|
698
764
|
engine = _require_repo_config(repo, hub)
|
|
699
765
|
config = engine.config
|
|
700
766
|
key = target.lower()
|
|
701
|
-
if key not in ("
|
|
702
|
-
_raise_exit("Invalid target; choose
|
|
767
|
+
if key not in ("active_context", "decisions", "spec"):
|
|
768
|
+
_raise_exit("Invalid target; choose active_context, decisions, or spec")
|
|
703
769
|
path = config.doc_path(key)
|
|
704
770
|
editor = os.environ.get("VISUAL") or os.environ.get("EDITOR") or default_editor()
|
|
705
771
|
editor_parts = shlex.split(editor)
|
|
@@ -709,84 +775,6 @@ def edit(
|
|
|
709
775
|
subprocess.run([*editor_parts, str(path)])
|
|
710
776
|
|
|
711
777
|
|
|
712
|
-
@app.command("ingest-spec")
|
|
713
|
-
def ingest_spec_cmd(
|
|
714
|
-
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
715
|
-
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
716
|
-
spec: Optional[Path] = typer.Option(
|
|
717
|
-
None, "--spec", help="Path to SPEC (defaults to configured docs.spec)"
|
|
718
|
-
),
|
|
719
|
-
force: bool = typer.Option(
|
|
720
|
-
False, "--force", help="Overwrite TODO/PROGRESS/OPINIONS"
|
|
721
|
-
),
|
|
722
|
-
):
|
|
723
|
-
"""Generate TODO/PROGRESS/OPINIONS from SPEC using Codex."""
|
|
724
|
-
try:
|
|
725
|
-
engine = _require_repo_config(repo, hub)
|
|
726
|
-
config = engine.config
|
|
727
|
-
if not config.app_server.command:
|
|
728
|
-
raise SpecIngestError("app_server.command must be configured")
|
|
729
|
-
|
|
730
|
-
async def _run_ingest() -> dict:
|
|
731
|
-
logger = logging.getLogger("codex_autorunner.cli.app_server")
|
|
732
|
-
|
|
733
|
-
def _env_builder(
|
|
734
|
-
workspace_root: Path, _workspace_id: str, state_dir: Path
|
|
735
|
-
) -> dict[str, str]:
|
|
736
|
-
state_dir.mkdir(parents=True, exist_ok=True)
|
|
737
|
-
return build_app_server_env(
|
|
738
|
-
config.app_server.command,
|
|
739
|
-
workspace_root,
|
|
740
|
-
state_dir,
|
|
741
|
-
logger=logger,
|
|
742
|
-
event_prefix="cli",
|
|
743
|
-
)
|
|
744
|
-
|
|
745
|
-
supervisor = WorkspaceAppServerSupervisor(
|
|
746
|
-
config.app_server.command,
|
|
747
|
-
state_root=config.app_server.state_root,
|
|
748
|
-
env_builder=_env_builder,
|
|
749
|
-
logger=logger,
|
|
750
|
-
max_handles=config.app_server.max_handles,
|
|
751
|
-
idle_ttl_seconds=config.app_server.idle_ttl_seconds,
|
|
752
|
-
request_timeout=config.app_server.request_timeout,
|
|
753
|
-
)
|
|
754
|
-
service = SpecIngestService(engine, app_server_supervisor=supervisor)
|
|
755
|
-
try:
|
|
756
|
-
await service.execute(force=force, spec_path=spec, message=None)
|
|
757
|
-
return service.apply_patch()
|
|
758
|
-
finally:
|
|
759
|
-
await supervisor.close_all()
|
|
760
|
-
|
|
761
|
-
docs = asyncio.run(_run_ingest())
|
|
762
|
-
except (ConfigError, SpecIngestError) as exc:
|
|
763
|
-
_raise_exit(str(exc), cause=exc)
|
|
764
|
-
|
|
765
|
-
typer.echo("Ingested SPEC into TODO/PROGRESS/OPINIONS.")
|
|
766
|
-
for key, content in docs.items():
|
|
767
|
-
lines = len(content.splitlines())
|
|
768
|
-
typer.echo(f"- {key.upper()}: {lines} lines")
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
@app.command("clear-docs")
|
|
772
|
-
def clear_docs_cmd(
|
|
773
|
-
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
774
|
-
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
775
|
-
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
776
|
-
):
|
|
777
|
-
"""Clear TODO/PROGRESS/OPINIONS to empty templates."""
|
|
778
|
-
if not yes:
|
|
779
|
-
confirm = input("Clear TODO/PROGRESS/OPINIONS? Type CLEAR to confirm: ").strip()
|
|
780
|
-
if confirm.upper() != "CLEAR":
|
|
781
|
-
_raise_exit("Aborted.")
|
|
782
|
-
engine = _require_repo_config(repo, hub)
|
|
783
|
-
try:
|
|
784
|
-
clear_work_docs(engine)
|
|
785
|
-
except ConfigError as exc:
|
|
786
|
-
_raise_exit(str(exc), cause=exc)
|
|
787
|
-
typer.echo("Cleared TODO/PROGRESS/OPINIONS.")
|
|
788
|
-
|
|
789
|
-
|
|
790
778
|
@app.command("doctor")
|
|
791
779
|
def doctor_cmd(
|
|
792
780
|
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo or hub path"),
|
|
@@ -794,7 +782,19 @@ def doctor_cmd(
|
|
|
794
782
|
):
|
|
795
783
|
"""Validate repo or hub setup."""
|
|
796
784
|
try:
|
|
797
|
-
|
|
785
|
+
start_path = repo or Path.cwd()
|
|
786
|
+
report = doctor(start_path)
|
|
787
|
+
|
|
788
|
+
hub_config = load_hub_config(start_path)
|
|
789
|
+
repo_config: Optional[RepoConfig] = None
|
|
790
|
+
try:
|
|
791
|
+
repo_root = find_repo_root(start_path)
|
|
792
|
+
repo_config = derive_repo_config(hub_config, repo_root)
|
|
793
|
+
except RepoNotFoundError:
|
|
794
|
+
repo_config = None
|
|
795
|
+
|
|
796
|
+
telegram_checks = telegram_doctor_checks(repo_config or hub_config)
|
|
797
|
+
report = DoctorReport(checks=report.checks + telegram_checks)
|
|
798
798
|
except ConfigError as exc:
|
|
799
799
|
_raise_exit(str(exc), cause=exc)
|
|
800
800
|
if json_output:
|
|
@@ -812,56 +812,6 @@ def doctor_cmd(
|
|
|
812
812
|
typer.echo("Doctor check passed")
|
|
813
813
|
|
|
814
814
|
|
|
815
|
-
@app.command()
|
|
816
|
-
def snapshot(
|
|
817
|
-
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
818
|
-
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
819
|
-
):
|
|
820
|
-
"""Generate or update `.codex-autorunner/SNAPSHOT.md`."""
|
|
821
|
-
try:
|
|
822
|
-
engine = _require_repo_config(repo, hub)
|
|
823
|
-
config = engine.config
|
|
824
|
-
if not config.app_server.command:
|
|
825
|
-
raise SnapshotError("app_server.command must be configured")
|
|
826
|
-
|
|
827
|
-
async def _run_snapshot() -> None:
|
|
828
|
-
logger = logging.getLogger("codex_autorunner.cli.app_server")
|
|
829
|
-
|
|
830
|
-
def _env_builder(
|
|
831
|
-
workspace_root: Path, _workspace_id: str, state_dir: Path
|
|
832
|
-
) -> dict[str, str]:
|
|
833
|
-
state_dir.mkdir(parents=True, exist_ok=True)
|
|
834
|
-
return build_app_server_env(
|
|
835
|
-
config.app_server.command,
|
|
836
|
-
workspace_root,
|
|
837
|
-
state_dir,
|
|
838
|
-
logger=logger,
|
|
839
|
-
event_prefix="cli",
|
|
840
|
-
)
|
|
841
|
-
|
|
842
|
-
supervisor = WorkspaceAppServerSupervisor(
|
|
843
|
-
config.app_server.command,
|
|
844
|
-
state_root=config.app_server.state_root,
|
|
845
|
-
env_builder=_env_builder,
|
|
846
|
-
logger=logger,
|
|
847
|
-
max_handles=config.app_server.max_handles,
|
|
848
|
-
idle_ttl_seconds=config.app_server.idle_ttl_seconds,
|
|
849
|
-
request_timeout=config.app_server.request_timeout,
|
|
850
|
-
)
|
|
851
|
-
from .core.snapshot import SnapshotService
|
|
852
|
-
|
|
853
|
-
service = SnapshotService(engine, app_server_supervisor=supervisor)
|
|
854
|
-
try:
|
|
855
|
-
await service.generate_snapshot()
|
|
856
|
-
finally:
|
|
857
|
-
await supervisor.close_all()
|
|
858
|
-
|
|
859
|
-
asyncio.run(_run_snapshot())
|
|
860
|
-
except (ConfigError, SnapshotError) as exc:
|
|
861
|
-
_raise_exit(str(exc), cause=exc)
|
|
862
|
-
typer.echo("Snapshot written to .codex-autorunner/SNAPSHOT.md")
|
|
863
|
-
|
|
864
|
-
|
|
865
815
|
@app.command()
|
|
866
816
|
def serve(
|
|
867
817
|
path: Optional[Path] = typer.Option(None, "--path", "--hub", help="Hub root path"),
|
|
@@ -1041,6 +991,8 @@ def telegram_start(
|
|
|
1041
991
|
housekeeping_config=config.housekeeping,
|
|
1042
992
|
update_repo_url=update_repo_url,
|
|
1043
993
|
update_repo_ref=update_repo_ref,
|
|
994
|
+
update_skip_checks=config.update_skip_checks,
|
|
995
|
+
app_server_auto_restart=config.app_server.auto_restart,
|
|
1044
996
|
)
|
|
1045
997
|
await service.run_polling()
|
|
1046
998
|
|
|
@@ -1086,8 +1038,139 @@ def telegram_health(
|
|
|
1086
1038
|
asyncio.run(_run())
|
|
1087
1039
|
except TelegramAPIError as exc:
|
|
1088
1040
|
_raise_exit(f"Telegram health check failed: {exc}", cause=exc)
|
|
1089
|
-
|
|
1090
|
-
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
@telegram_app.command("state-check")
|
|
1044
|
+
def telegram_state_check(
|
|
1045
|
+
path: Optional[Path] = typer.Option(None, "--path", help="Repo or hub root path"),
|
|
1046
|
+
):
|
|
1047
|
+
"""Open the Telegram state DB and ensure schema migrations apply."""
|
|
1048
|
+
try:
|
|
1049
|
+
config = load_hub_config(path or Path.cwd())
|
|
1050
|
+
except ConfigError as exc:
|
|
1051
|
+
_raise_exit(str(exc), cause=exc)
|
|
1052
|
+
telegram_cfg = TelegramBotConfig.from_raw(
|
|
1053
|
+
config.raw.get("telegram_bot") if isinstance(config.raw, dict) else None,
|
|
1054
|
+
root=config.root,
|
|
1055
|
+
agent_binaries=getattr(config, "agents", None)
|
|
1056
|
+
and {name: agent.binary for name, agent in config.agents.items()},
|
|
1057
|
+
)
|
|
1058
|
+
if not telegram_cfg.enabled:
|
|
1059
|
+
_raise_exit("telegram_bot is disabled; set telegram_bot.enabled: true")
|
|
1060
|
+
|
|
1061
|
+
try:
|
|
1062
|
+
store = TelegramStateStore(
|
|
1063
|
+
telegram_cfg.state_file,
|
|
1064
|
+
default_approval_mode=telegram_cfg.defaults.approval_mode,
|
|
1065
|
+
)
|
|
1066
|
+
# This will open the DB and apply schema/migrations.
|
|
1067
|
+
store._connection_sync() # type: ignore[attr-defined]
|
|
1068
|
+
except Exception as exc: # pragma: no cover - defensive runtime check
|
|
1069
|
+
_raise_exit(f"Telegram state check failed: {exc}", cause=exc)
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
@app.command()
|
|
1073
|
+
def flow(
|
|
1074
|
+
action: str = typer.Argument(..., help="worker"),
|
|
1075
|
+
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
1076
|
+
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
1077
|
+
run_id: Optional[str] = typer.Option(
|
|
1078
|
+
None, "--run-id", help="Flow run ID (for worker)"
|
|
1079
|
+
),
|
|
1080
|
+
):
|
|
1081
|
+
"""Flow runtime commands."""
|
|
1082
|
+
engine = _require_repo_config(repo, hub)
|
|
1083
|
+
|
|
1084
|
+
if action == "worker":
|
|
1085
|
+
if not run_id:
|
|
1086
|
+
_raise_exit("--run-id is required for worker command")
|
|
1087
|
+
try:
|
|
1088
|
+
run_id = str(uuid.UUID(str(run_id)))
|
|
1089
|
+
except ValueError:
|
|
1090
|
+
_raise_exit("Invalid run_id format; must be a UUID")
|
|
1091
|
+
|
|
1092
|
+
from .core.flows import FlowController, FlowStore
|
|
1093
|
+
from .core.flows.models import FlowRunStatus
|
|
1094
|
+
from .flows.ticket_flow import build_ticket_flow_definition
|
|
1095
|
+
from .tickets import AgentPool
|
|
1096
|
+
|
|
1097
|
+
db_path = engine.repo_root / ".codex-autorunner" / "flows.db"
|
|
1098
|
+
artifacts_root = engine.repo_root / ".codex-autorunner" / "flows"
|
|
1099
|
+
|
|
1100
|
+
typer.echo(f"Starting flow worker for run {run_id}")
|
|
1101
|
+
|
|
1102
|
+
async def _run_worker():
|
|
1103
|
+
typer.echo(f"Flow worker started for {run_id}")
|
|
1104
|
+
typer.echo(f"DB path: {db_path}")
|
|
1105
|
+
typer.echo(f"Artifacts root: {artifacts_root}")
|
|
1106
|
+
|
|
1107
|
+
store = FlowStore(db_path)
|
|
1108
|
+
store.initialize()
|
|
1109
|
+
|
|
1110
|
+
record = store.get_flow_run(run_id)
|
|
1111
|
+
if not record:
|
|
1112
|
+
typer.echo(f"Flow run {run_id} not found", err=True)
|
|
1113
|
+
store.close()
|
|
1114
|
+
raise typer.Exit(code=1)
|
|
1115
|
+
store.close()
|
|
1116
|
+
|
|
1117
|
+
agent_pool: AgentPool | None = None
|
|
1118
|
+
|
|
1119
|
+
def _build_definition(flow_type: str):
|
|
1120
|
+
nonlocal agent_pool
|
|
1121
|
+
if flow_type == "pr_flow":
|
|
1122
|
+
_raise_exit(
|
|
1123
|
+
"PR flow is no longer supported. Use ticket_flow instead."
|
|
1124
|
+
)
|
|
1125
|
+
if flow_type == "ticket_flow":
|
|
1126
|
+
agent_pool = AgentPool(engine.config)
|
|
1127
|
+
return build_ticket_flow_definition(agent_pool=agent_pool)
|
|
1128
|
+
_raise_exit(f"Unknown flow type for run {run_id}: {flow_type}")
|
|
1129
|
+
return None
|
|
1130
|
+
|
|
1131
|
+
definition = _build_definition(record.flow_type)
|
|
1132
|
+
definition.validate()
|
|
1133
|
+
|
|
1134
|
+
controller = FlowController(
|
|
1135
|
+
definition=definition,
|
|
1136
|
+
db_path=db_path,
|
|
1137
|
+
artifacts_root=artifacts_root,
|
|
1138
|
+
)
|
|
1139
|
+
controller.initialize()
|
|
1140
|
+
|
|
1141
|
+
record = controller.get_status(run_id)
|
|
1142
|
+
if not record:
|
|
1143
|
+
typer.echo(f"Flow run {run_id} not found", err=True)
|
|
1144
|
+
raise typer.Exit(code=1)
|
|
1145
|
+
|
|
1146
|
+
if record.status.is_terminal() and record.status not in {
|
|
1147
|
+
FlowRunStatus.STOPPED,
|
|
1148
|
+
FlowRunStatus.FAILED,
|
|
1149
|
+
}:
|
|
1150
|
+
typer.echo(
|
|
1151
|
+
f"Flow run {run_id} already completed (status={record.status})"
|
|
1152
|
+
)
|
|
1153
|
+
return
|
|
1154
|
+
|
|
1155
|
+
action = (
|
|
1156
|
+
"Resuming" if record.status != FlowRunStatus.PENDING else "Starting"
|
|
1157
|
+
)
|
|
1158
|
+
typer.echo(f"{action} flow run {run_id} from step: {record.current_step}")
|
|
1159
|
+
try:
|
|
1160
|
+
final_record = await controller.run_flow(run_id)
|
|
1161
|
+
typer.echo(
|
|
1162
|
+
f"Flow run {run_id} finished with status {final_record.status}"
|
|
1163
|
+
)
|
|
1164
|
+
finally:
|
|
1165
|
+
if agent_pool is not None:
|
|
1166
|
+
try:
|
|
1167
|
+
await agent_pool.close()
|
|
1168
|
+
except Exception:
|
|
1169
|
+
typer.echo("Failed to close agent pool cleanly", err=True)
|
|
1170
|
+
|
|
1171
|
+
asyncio.run(_run_worker())
|
|
1172
|
+
else:
|
|
1173
|
+
_raise_exit(f"Unknown action: {action}")
|
|
1091
1174
|
|
|
1092
1175
|
|
|
1093
1176
|
if __name__ == "__main__":
|