codex-autorunner 0.1.2__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/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -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 +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- 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 +15 -9
- codex_autorunner/core/locks.py +4 -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/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- 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 +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -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 +227 -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 +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- 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 +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- 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/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -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 -196
- 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 +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -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 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -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 +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 +1 -0
- 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/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.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/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
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,18 +39,17 @@ 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
|
|
|
54
55
|
logger = logging.getLogger("codex_autorunner.cli")
|
|
@@ -755,7 +756,7 @@ def log(
|
|
|
755
756
|
|
|
756
757
|
@app.command()
|
|
757
758
|
def edit(
|
|
758
|
-
target: str = typer.Argument(..., help="
|
|
759
|
+
target: str = typer.Argument(..., help="active_context|decisions|spec"),
|
|
759
760
|
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
760
761
|
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
761
762
|
):
|
|
@@ -763,8 +764,8 @@ def edit(
|
|
|
763
764
|
engine = _require_repo_config(repo, hub)
|
|
764
765
|
config = engine.config
|
|
765
766
|
key = target.lower()
|
|
766
|
-
if key not in ("
|
|
767
|
-
_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")
|
|
768
769
|
path = config.doc_path(key)
|
|
769
770
|
editor = os.environ.get("VISUAL") or os.environ.get("EDITOR") or default_editor()
|
|
770
771
|
editor_parts = shlex.split(editor)
|
|
@@ -774,84 +775,6 @@ def edit(
|
|
|
774
775
|
subprocess.run([*editor_parts, str(path)])
|
|
775
776
|
|
|
776
777
|
|
|
777
|
-
@app.command("ingest-spec")
|
|
778
|
-
def ingest_spec_cmd(
|
|
779
|
-
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
780
|
-
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
781
|
-
spec: Optional[Path] = typer.Option(
|
|
782
|
-
None, "--spec", help="Path to SPEC (defaults to configured docs.spec)"
|
|
783
|
-
),
|
|
784
|
-
force: bool = typer.Option(
|
|
785
|
-
False, "--force", help="Overwrite TODO/PROGRESS/OPINIONS"
|
|
786
|
-
),
|
|
787
|
-
):
|
|
788
|
-
"""Generate TODO/PROGRESS/OPINIONS from SPEC using Codex."""
|
|
789
|
-
try:
|
|
790
|
-
engine = _require_repo_config(repo, hub)
|
|
791
|
-
config = engine.config
|
|
792
|
-
if not config.app_server.command:
|
|
793
|
-
raise SpecIngestError("app_server.command must be configured")
|
|
794
|
-
|
|
795
|
-
async def _run_ingest() -> dict:
|
|
796
|
-
logger = logging.getLogger("codex_autorunner.cli.app_server")
|
|
797
|
-
|
|
798
|
-
def _env_builder(
|
|
799
|
-
workspace_root: Path, _workspace_id: str, state_dir: Path
|
|
800
|
-
) -> dict[str, str]:
|
|
801
|
-
state_dir.mkdir(parents=True, exist_ok=True)
|
|
802
|
-
return build_app_server_env(
|
|
803
|
-
config.app_server.command,
|
|
804
|
-
workspace_root,
|
|
805
|
-
state_dir,
|
|
806
|
-
logger=logger,
|
|
807
|
-
event_prefix="cli",
|
|
808
|
-
)
|
|
809
|
-
|
|
810
|
-
supervisor = WorkspaceAppServerSupervisor(
|
|
811
|
-
config.app_server.command,
|
|
812
|
-
state_root=config.app_server.state_root,
|
|
813
|
-
env_builder=_env_builder,
|
|
814
|
-
logger=logger,
|
|
815
|
-
max_handles=config.app_server.max_handles,
|
|
816
|
-
idle_ttl_seconds=config.app_server.idle_ttl_seconds,
|
|
817
|
-
request_timeout=config.app_server.request_timeout,
|
|
818
|
-
)
|
|
819
|
-
service = SpecIngestService(engine, app_server_supervisor=supervisor)
|
|
820
|
-
try:
|
|
821
|
-
await service.execute(force=force, spec_path=spec, message=None)
|
|
822
|
-
return service.apply_patch()
|
|
823
|
-
finally:
|
|
824
|
-
await supervisor.close_all()
|
|
825
|
-
|
|
826
|
-
docs = asyncio.run(_run_ingest())
|
|
827
|
-
except (ConfigError, SpecIngestError) as exc:
|
|
828
|
-
_raise_exit(str(exc), cause=exc)
|
|
829
|
-
|
|
830
|
-
typer.echo("Ingested SPEC into TODO/PROGRESS/OPINIONS.")
|
|
831
|
-
for key, content in docs.items():
|
|
832
|
-
lines = len(content.splitlines())
|
|
833
|
-
typer.echo(f"- {key.upper()}: {lines} lines")
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
@app.command("clear-docs")
|
|
837
|
-
def clear_docs_cmd(
|
|
838
|
-
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
839
|
-
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
840
|
-
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
841
|
-
):
|
|
842
|
-
"""Clear TODO/PROGRESS/OPINIONS to empty templates."""
|
|
843
|
-
if not yes:
|
|
844
|
-
confirm = input("Clear TODO/PROGRESS/OPINIONS? Type CLEAR to confirm: ").strip()
|
|
845
|
-
if confirm.upper() != "CLEAR":
|
|
846
|
-
_raise_exit("Aborted.")
|
|
847
|
-
engine = _require_repo_config(repo, hub)
|
|
848
|
-
try:
|
|
849
|
-
clear_work_docs(engine)
|
|
850
|
-
except ConfigError as exc:
|
|
851
|
-
_raise_exit(str(exc), cause=exc)
|
|
852
|
-
typer.echo("Cleared TODO/PROGRESS/OPINIONS.")
|
|
853
|
-
|
|
854
|
-
|
|
855
778
|
@app.command("doctor")
|
|
856
779
|
def doctor_cmd(
|
|
857
780
|
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo or hub path"),
|
|
@@ -859,7 +782,19 @@ def doctor_cmd(
|
|
|
859
782
|
):
|
|
860
783
|
"""Validate repo or hub setup."""
|
|
861
784
|
try:
|
|
862
|
-
|
|
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)
|
|
863
798
|
except ConfigError as exc:
|
|
864
799
|
_raise_exit(str(exc), cause=exc)
|
|
865
800
|
if json_output:
|
|
@@ -877,56 +812,6 @@ def doctor_cmd(
|
|
|
877
812
|
typer.echo("Doctor check passed")
|
|
878
813
|
|
|
879
814
|
|
|
880
|
-
@app.command()
|
|
881
|
-
def snapshot(
|
|
882
|
-
repo: Optional[Path] = typer.Option(None, "--repo", help="Repo path"),
|
|
883
|
-
hub: Optional[Path] = typer.Option(None, "--hub", help="Hub root path"),
|
|
884
|
-
):
|
|
885
|
-
"""Generate or update `.codex-autorunner/SNAPSHOT.md`."""
|
|
886
|
-
try:
|
|
887
|
-
engine = _require_repo_config(repo, hub)
|
|
888
|
-
config = engine.config
|
|
889
|
-
if not config.app_server.command:
|
|
890
|
-
raise SnapshotError("app_server.command must be configured")
|
|
891
|
-
|
|
892
|
-
async def _run_snapshot() -> None:
|
|
893
|
-
logger = logging.getLogger("codex_autorunner.cli.app_server")
|
|
894
|
-
|
|
895
|
-
def _env_builder(
|
|
896
|
-
workspace_root: Path, _workspace_id: str, state_dir: Path
|
|
897
|
-
) -> dict[str, str]:
|
|
898
|
-
state_dir.mkdir(parents=True, exist_ok=True)
|
|
899
|
-
return build_app_server_env(
|
|
900
|
-
config.app_server.command,
|
|
901
|
-
workspace_root,
|
|
902
|
-
state_dir,
|
|
903
|
-
logger=logger,
|
|
904
|
-
event_prefix="cli",
|
|
905
|
-
)
|
|
906
|
-
|
|
907
|
-
supervisor = WorkspaceAppServerSupervisor(
|
|
908
|
-
config.app_server.command,
|
|
909
|
-
state_root=config.app_server.state_root,
|
|
910
|
-
env_builder=_env_builder,
|
|
911
|
-
logger=logger,
|
|
912
|
-
max_handles=config.app_server.max_handles,
|
|
913
|
-
idle_ttl_seconds=config.app_server.idle_ttl_seconds,
|
|
914
|
-
request_timeout=config.app_server.request_timeout,
|
|
915
|
-
)
|
|
916
|
-
from .core.snapshot import SnapshotService
|
|
917
|
-
|
|
918
|
-
service = SnapshotService(engine, app_server_supervisor=supervisor)
|
|
919
|
-
try:
|
|
920
|
-
await service.generate_snapshot()
|
|
921
|
-
finally:
|
|
922
|
-
await supervisor.close_all()
|
|
923
|
-
|
|
924
|
-
asyncio.run(_run_snapshot())
|
|
925
|
-
except (ConfigError, SnapshotError) as exc:
|
|
926
|
-
_raise_exit(str(exc), cause=exc)
|
|
927
|
-
typer.echo("Snapshot written to .codex-autorunner/SNAPSHOT.md")
|
|
928
|
-
|
|
929
|
-
|
|
930
815
|
@app.command()
|
|
931
816
|
def serve(
|
|
932
817
|
path: Optional[Path] = typer.Option(None, "--path", "--hub", help="Hub root path"),
|
|
@@ -1106,6 +991,8 @@ def telegram_start(
|
|
|
1106
991
|
housekeeping_config=config.housekeeping,
|
|
1107
992
|
update_repo_url=update_repo_url,
|
|
1108
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,
|
|
1109
996
|
)
|
|
1110
997
|
await service.run_polling()
|
|
1111
998
|
|
|
@@ -1151,8 +1038,139 @@ def telegram_health(
|
|
|
1151
1038
|
asyncio.run(_run())
|
|
1152
1039
|
except TelegramAPIError as exc:
|
|
1153
1040
|
_raise_exit(f"Telegram health check failed: {exc}", cause=exc)
|
|
1154
|
-
|
|
1155
|
-
|
|
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}")
|
|
1156
1174
|
|
|
1157
1175
|
|
|
1158
1176
|
if __name__ == "__main__":
|
|
@@ -17,6 +17,26 @@ ABOUT_CAR_REL_PATH = Path(".codex-autorunner") / ABOUT_CAR_BASENAME
|
|
|
17
17
|
# If this marker is present, codex-autorunner may safely refresh the file content.
|
|
18
18
|
ABOUT_CAR_GENERATED_MARKER = "<!-- CAR:AUTOGENERATED -->"
|
|
19
19
|
|
|
20
|
+
CAR_CONTEXT_KEYWORDS = (
|
|
21
|
+
"car",
|
|
22
|
+
"codex",
|
|
23
|
+
"spec",
|
|
24
|
+
"autorunner",
|
|
25
|
+
"workspace",
|
|
26
|
+
"ticket",
|
|
27
|
+
"tickets",
|
|
28
|
+
"context",
|
|
29
|
+
"decision",
|
|
30
|
+
"decisions",
|
|
31
|
+
"handoff",
|
|
32
|
+
"dispatch",
|
|
33
|
+
"inbox",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
CAR_CONTEXT_HINT = (
|
|
37
|
+
"Context: read .codex-autorunner/ABOUT_CAR.md for repo-specific rules."
|
|
38
|
+
)
|
|
39
|
+
|
|
20
40
|
|
|
21
41
|
def _display_path(repo_root: Path, path: Path) -> str:
|
|
22
42
|
try:
|
|
@@ -28,11 +48,9 @@ def _display_path(repo_root: Path, path: Path) -> str:
|
|
|
28
48
|
def build_about_car_markdown(
|
|
29
49
|
*,
|
|
30
50
|
repo_root: Path,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
opinions_path: Path,
|
|
51
|
+
active_context_path: Path,
|
|
52
|
+
decisions_path: Path,
|
|
34
53
|
spec_path: Path,
|
|
35
|
-
summary_path: Path,
|
|
36
54
|
hub_config_path: Optional[Path] = None,
|
|
37
55
|
repo_override_path: Optional[Path] = None,
|
|
38
56
|
) -> str:
|
|
@@ -44,11 +62,9 @@ def build_about_car_markdown(
|
|
|
44
62
|
repo_override_path = repo_override_path or (repo_root / REPO_OVERRIDE_FILENAME)
|
|
45
63
|
root_config_path = repo_root / ROOT_CONFIG_FILENAME
|
|
46
64
|
root_override_path = repo_root / ROOT_OVERRIDE_FILENAME
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
opinions_disp = _display_path(repo_root, opinions_path)
|
|
65
|
+
active_context_disp = _display_path(repo_root, active_context_path)
|
|
66
|
+
decisions_disp = _display_path(repo_root, decisions_path)
|
|
50
67
|
spec_disp = _display_path(repo_root, spec_path)
|
|
51
|
-
summary_disp = _display_path(repo_root, summary_path)
|
|
52
68
|
hub_config_disp = _display_path(repo_root, hub_config_path)
|
|
53
69
|
repo_override_disp = _display_path(repo_root, repo_override_path)
|
|
54
70
|
root_config_disp = _display_path(repo_root, root_config_path)
|
|
@@ -58,25 +74,25 @@ def build_about_car_markdown(
|
|
|
58
74
|
f"{ABOUT_CAR_GENERATED_MARKER}\n"
|
|
59
75
|
"# ABOUT_CAR — Codex Autorunner (CAR)\n\n"
|
|
60
76
|
"You are running inside **Codex Autorunner (CAR)**.\n\n"
|
|
61
|
-
"CAR uses a
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
f"`{spec_disp}`\n"
|
|
72
|
-
"- **SUMMARY** — user-facing report + external/user action items: "
|
|
73
|
-
f"`{summary_disp}`\n\n"
|
|
77
|
+
"CAR uses a ticket-first workflow.\n\n"
|
|
78
|
+
"## Required for operation\n"
|
|
79
|
+
"- Tickets live under `.codex-autorunner/tickets/`.\n\n"
|
|
80
|
+
"## Optional workspace docs\n"
|
|
81
|
+
"- **Active context**: "
|
|
82
|
+
f"`{active_context_disp}`\n"
|
|
83
|
+
"- **Decisions**: "
|
|
84
|
+
f"`{decisions_disp}`\n"
|
|
85
|
+
"- **Spec**: "
|
|
86
|
+
f"`{spec_disp}`\n\n"
|
|
74
87
|
"## Critical rules\n"
|
|
75
|
-
|
|
76
|
-
"- Do **not** create new copies of TODO/PROGRESS/OPINIONS/SPEC/SUMMARY elsewhere in the repo.\n"
|
|
88
|
+
"- Do **not** create new copies of workspace docs elsewhere in the repo.\n"
|
|
77
89
|
"- Treat `.codex-autorunner/` as intentional project structure even though it is hidden/gitignored.\n\n"
|
|
90
|
+
"## Agent Flow\n"
|
|
91
|
+
"- **Dispatch**: An update or message from the agent.\n"
|
|
92
|
+
"- **Handoff**: Passing control from agent to user (or vice versa).\n"
|
|
93
|
+
"- **Inbox**: Where the agent receives files/messages.\n\n"
|
|
78
94
|
"## How CAR works (short)\n"
|
|
79
|
-
"-
|
|
95
|
+
"- The web UI provides ticket editing + unified file chat.\n"
|
|
80
96
|
"- `car serve` starts the hub web UI. The **Terminal** tab launches the configured `codex` binary in a PTY.\n"
|
|
81
97
|
f"- Hub config lives at `{hub_config_disp}` (generated).\n"
|
|
82
98
|
f"- Repo overrides (optional) live at `{repo_override_disp}`.\n"
|
|
@@ -101,11 +117,9 @@ def ensure_about_car_file_for_repo(
|
|
|
101
117
|
|
|
102
118
|
content = build_about_car_markdown(
|
|
103
119
|
repo_root=repo_root,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
opinions_path=doc_paths["opinions"],
|
|
120
|
+
active_context_path=doc_paths["active_context"],
|
|
121
|
+
decisions_path=doc_paths["decisions"],
|
|
107
122
|
spec_path=doc_paths["spec"],
|
|
108
|
-
summary_path=doc_paths["summary"],
|
|
109
123
|
)
|
|
110
124
|
if content and not content.endswith("\n"):
|
|
111
125
|
content += "\n"
|
|
@@ -129,10 +143,8 @@ def ensure_about_car_file(config: Config, *, force: bool = False) -> Path:
|
|
|
129
143
|
"""Config-aware wrapper that uses configured doc paths."""
|
|
130
144
|
repo_root = config.root
|
|
131
145
|
docs = {
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"opinions": config.doc_path("opinions"),
|
|
146
|
+
"active_context": config.doc_path("active_context"),
|
|
147
|
+
"decisions": config.doc_path("decisions"),
|
|
135
148
|
"spec": config.doc_path("spec"),
|
|
136
|
-
"summary": config.doc_path("summary"),
|
|
137
149
|
}
|
|
138
150
|
return ensure_about_car_file_for_repo(repo_root, doc_paths=docs, force=force)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def handle_agent_output(
|
|
7
|
+
log_app_server_output: Callable[[int, list[str]], None],
|
|
8
|
+
write_run_artifact: Callable[[int, str, str], Any],
|
|
9
|
+
merge_run_index_entry: Callable[[int, dict[str, Any]], None],
|
|
10
|
+
run_id: int,
|
|
11
|
+
output: str | list[str],
|
|
12
|
+
) -> None:
|
|
13
|
+
if isinstance(output, str):
|
|
14
|
+
messages = [output]
|
|
15
|
+
else:
|
|
16
|
+
messages = output
|
|
17
|
+
log_app_server_output(run_id, messages)
|
|
18
|
+
output_text = "\n\n".join(messages).strip() if messages else ""
|
|
19
|
+
if output_text:
|
|
20
|
+
output_path = write_run_artifact(run_id, "output.txt", output_text)
|
|
21
|
+
merge_run_index_entry(run_id, {"artifacts": {"output_path": str(output_path)}})
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, cast
|
|
4
4
|
|
|
5
|
+
from .redaction import redact_text
|
|
5
6
|
from .text_delta_coalescer import TextDeltaCoalescer
|
|
6
7
|
|
|
7
8
|
|
|
@@ -69,7 +70,8 @@ def _extract_error_message(params: Any) -> str:
|
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
class AppServerEventFormatter:
|
|
72
|
-
def __init__(self) -> None:
|
|
73
|
+
def __init__(self, redact_enabled: bool = True) -> None:
|
|
74
|
+
self._redact_enabled = redact_enabled
|
|
73
75
|
self._thinking_items: set[str] = set()
|
|
74
76
|
self._reasoning_coalescers: dict[str, TextDeltaCoalescer] = {}
|
|
75
77
|
|
|
@@ -187,7 +189,8 @@ class AppServerEventFormatter:
|
|
|
187
189
|
or params.get("value")
|
|
188
190
|
)
|
|
189
191
|
if isinstance(diff, str) and diff:
|
|
190
|
-
|
|
192
|
+
diff_text = redact_text(diff) if self._redact_enabled else diff
|
|
193
|
+
lines.extend(diff_text.splitlines())
|
|
191
194
|
return lines
|
|
192
195
|
|
|
193
196
|
if method == "error":
|
|
@@ -199,7 +202,8 @@ class AppServerEventFormatter:
|
|
|
199
202
|
if "outputdelta" in method.lower():
|
|
200
203
|
delta = params.get("delta") or params.get("text") or params.get("output")
|
|
201
204
|
if isinstance(delta, str) and delta:
|
|
202
|
-
|
|
205
|
+
delta_text = redact_text(delta) if self._redact_enabled else delta
|
|
206
|
+
lines.extend(delta_text.splitlines())
|
|
203
207
|
return lines
|
|
204
208
|
|
|
205
209
|
return lines
|