codex-autorunner 1.0.0__py3-none-any.whl → 1.2.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/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +113 -4
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/runtime.py +59 -18
- codex_autorunner/agents/opencode/supervisor.py +4 -0
- codex_autorunner/agents/registry.py +36 -7
- codex_autorunner/bootstrap.py +226 -4
- codex_autorunner/cli.py +5 -1174
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +20 -0
- codex_autorunner/core/about_car.py +119 -1
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_threads.py +17 -2
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +6 -2
- codex_autorunner/core/config.py +433 -4
- codex_autorunner/core/context_awareness.py +38 -0
- codex_autorunner/core/docs.py +0 -122
- codex_autorunner/core/drafts.py +58 -4
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/filebox.py +265 -0
- codex_autorunner/core/flows/controller.py +96 -2
- codex_autorunner/core/flows/models.py +13 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +134 -0
- codex_autorunner/core/flows/runtime.py +57 -4
- codex_autorunner/core/flows/store.py +142 -7
- codex_autorunner/core/flows/transition.py +27 -15
- codex_autorunner/core/flows/ux_helpers.py +272 -0
- codex_autorunner/core/flows/worker_process.py +32 -6
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +291 -20
- codex_autorunner/core/lifecycle_events.py +253 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/path_utils.py +2 -1
- codex_autorunner/core/pma_audit.py +224 -0
- codex_autorunner/core/pma_context.py +496 -0
- codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
- codex_autorunner/core/pma_lifecycle.py +527 -0
- codex_autorunner/core/pma_queue.py +367 -0
- codex_autorunner/core/pma_safety.py +221 -0
- codex_autorunner/core/pma_state.py +115 -0
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +13 -8
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/{integrations/agents → core/ports}/run_event.py +23 -6
- codex_autorunner/core/prompt.py +0 -80
- codex_autorunner/core/prompts.py +56 -172
- codex_autorunner/core/redaction.py +0 -4
- codex_autorunner/core/review_context.py +11 -9
- codex_autorunner/core/runner_controller.py +35 -33
- codex_autorunner/core/runner_state.py +147 -0
- codex_autorunner/core/runtime.py +829 -0
- codex_autorunner/core/sqlite_utils.py +13 -4
- codex_autorunner/core/state.py +7 -10
- codex_autorunner/core/state_roots.py +62 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/templates/__init__.py +39 -0
- codex_autorunner/core/templates/git_mirror.py +234 -0
- codex_autorunner/core/templates/provenance.py +56 -0
- codex_autorunner/core/templates/scan_cache.py +120 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +218 -0
- codex_autorunner/core/ticket_manager_cli.py +494 -0
- codex_autorunner/core/time_utils.py +11 -0
- codex_autorunner/core/types.py +18 -0
- codex_autorunner/core/update.py +4 -5
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +125 -15
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +37 -34
- codex_autorunner/flows/ticket_flow/definition.py +52 -3
- codex_autorunner/integrations/agents/__init__.py +11 -19
- codex_autorunner/integrations/agents/backend_orchestrator.py +302 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +177 -25
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +305 -32
- codex_autorunner/integrations/agents/runner.py +86 -0
- codex_autorunner/integrations/agents/wiring.py +279 -0
- codex_autorunner/integrations/app_server/client.py +7 -60
- 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/telegram/adapter.py +65 -0
- codex_autorunner/integrations/telegram/config.py +46 -0
- codex_autorunner/integrations/telegram/constants.py +1 -1
- codex_autorunner/integrations/telegram/doctor.py +228 -6
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
- codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1496 -71
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +206 -48
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +20 -3
- codex_autorunner/integrations/telegram/handlers/messages.py +27 -1
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +22 -1
- codex_autorunner/integrations/telegram/runtime.py +9 -4
- codex_autorunner/integrations/telegram/service.py +45 -10
- codex_autorunner/integrations/telegram/state.py +38 -0
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +338 -43
- codex_autorunner/integrations/telegram/transport.py +13 -4
- codex_autorunner/integrations/templates/__init__.py +27 -0
- codex_autorunner/integrations/templates/scan_agent.py +312 -0
- codex_autorunner/routes/__init__.py +37 -76
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +2 -238
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -596
- codex_autorunner/routes/file_chat.py +4 -833
- codex_autorunner/routes/flows.py +4 -977
- codex_autorunner/routes/messages.py +4 -456
- 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 -193
- codex_autorunner/routes/usage.py +2 -86
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +2 -270
- codex_autorunner/server.py +4 -4
- codex_autorunner/static/agentControls.js +61 -16
- codex_autorunner/static/app.js +126 -14
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +7 -7
- codex_autorunner/static/chatUploads.js +137 -0
- codex_autorunner/static/dashboard.js +224 -171
- codex_autorunner/static/docChatCore.js +185 -13
- codex_autorunner/static/fileChat.js +68 -40
- codex_autorunner/static/fileboxUi.js +159 -0
- codex_autorunner/static/hub.js +114 -131
- codex_autorunner/static/index.html +375 -49
- codex_autorunner/static/messages.js +568 -87
- codex_autorunner/static/notifications.js +255 -0
- codex_autorunner/static/pma.js +1167 -0
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +128 -6
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/streamUtils.js +57 -0
- codex_autorunner/static/styles.css +9798 -6143
- codex_autorunner/static/tabs.js +152 -11
- codex_autorunner/static/templateReposSettings.js +225 -0
- codex_autorunner/static/terminal.js +18 -0
- codex_autorunner/static/ticketChatActions.js +165 -3
- codex_autorunner/static/ticketChatStream.js +17 -119
- codex_autorunner/static/ticketEditor.js +137 -15
- codex_autorunner/static/ticketTemplates.js +798 -0
- codex_autorunner/static/tickets.js +821 -98
- codex_autorunner/static/turnEvents.js +27 -0
- codex_autorunner/static/turnResume.js +33 -0
- codex_autorunner/static/utils.js +39 -0
- codex_autorunner/static/workspace.js +389 -82
- codex_autorunner/static/workspaceFileBrowser.js +15 -13
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +2534 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/cli/pma_cli.py +817 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2223 -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 +82 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +284 -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 +1117 -0
- codex_autorunner/surfaces/web/routes/filebox.py +227 -0
- codex_autorunner/surfaces/web/routes/flows.py +1354 -0
- codex_autorunner/surfaces/web/routes/messages.py +490 -0
- codex_autorunner/surfaces/web/routes/pma.py +1652 -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 +277 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/templates.py +634 -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 +469 -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 +8 -1
- codex_autorunner/tickets/agent_pool.py +53 -4
- codex_autorunner/tickets/files.py +37 -16
- codex_autorunner/tickets/lint.py +50 -0
- codex_autorunner/tickets/models.py +6 -1
- codex_autorunner/tickets/outbox.py +50 -2
- codex_autorunner/tickets/runner.py +396 -57
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1949
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -586
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -376
- codex_autorunner/web/static_assets.py +4 -441
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/paths.py +49 -33
- codex_autorunner-1.2.0.dist-info/METADATA +150 -0
- codex_autorunner-1.2.0.dist-info/RECORD +339 -0
- codex_autorunner/core/adapter_utils.py +0 -21
- codex_autorunner/core/engine.py +0 -2653
- codex_autorunner/core/static_assets.py +0 -55
- codex_autorunner-1.0.0.dist-info/METADATA +0 -246
- codex_autorunner-1.0.0.dist-info/RECORD +0 -251
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Core type definitions.
|
|
2
|
+
|
|
3
|
+
This module provides type definitions that were previously in Engine.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Callable, Optional
|
|
7
|
+
|
|
8
|
+
# Type aliases for factory functions
|
|
9
|
+
BackendFactory = Callable[[str, Any, Optional[Callable[[dict[str, Any]], Any]]], Any]
|
|
10
|
+
AppServerSupervisorFactory = Callable[
|
|
11
|
+
[str, Optional[Callable[[dict[str, Any]], Any]]], Any
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BackendFactory",
|
|
17
|
+
"AppServerSupervisorFactory",
|
|
18
|
+
]
|
codex_autorunner/core/update.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import Optional
|
|
|
11
11
|
from urllib.parse import unquote, urlparse
|
|
12
12
|
|
|
13
13
|
from .git_utils import GitError, run_git
|
|
14
|
+
from .update_paths import resolve_update_paths
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class UpdateInProgressError(RuntimeError):
|
|
@@ -55,7 +56,7 @@ def _normalize_update_ref(raw: Optional[str]) -> str:
|
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
def _update_status_path() -> Path:
|
|
58
|
-
return
|
|
59
|
+
return resolve_update_paths().status_path
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def _write_update_status(status: str, message: str, **extra) -> None:
|
|
@@ -134,7 +135,7 @@ def _read_update_status() -> Optional[dict[str, object]]:
|
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
def _update_lock_path() -> Path:
|
|
137
|
-
return
|
|
138
|
+
return resolve_update_paths().lock_path
|
|
138
139
|
|
|
139
140
|
|
|
140
141
|
def _read_update_lock() -> Optional[dict[str, object]]:
|
|
@@ -281,9 +282,7 @@ def _system_update_check(
|
|
|
281
282
|
update_cache_dir: Optional[Path] = None,
|
|
282
283
|
) -> dict:
|
|
283
284
|
module_dir = module_dir or Path(__file__).resolve().parent
|
|
284
|
-
update_cache_dir = update_cache_dir or (
|
|
285
|
-
Path.home() / ".codex-autorunner" / "update_cache"
|
|
286
|
-
)
|
|
285
|
+
update_cache_dir = update_cache_dir or resolve_update_paths().cache_dir
|
|
287
286
|
repo_ref = _normalize_update_ref(repo_ref)
|
|
288
287
|
|
|
289
288
|
repo_root = _resolve_local_repo_root(
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .state_roots import resolve_global_state_root
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class UpdatePaths:
|
|
12
|
+
status_path: Path
|
|
13
|
+
lock_path: Path
|
|
14
|
+
cache_dir: Path
|
|
15
|
+
compact_status_path: Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_update_paths(
|
|
19
|
+
*, config: Optional[Any] = None, repo_root: Optional[Path] = None
|
|
20
|
+
) -> UpdatePaths:
|
|
21
|
+
"""Resolve update status, lock, cache, and compact status paths."""
|
|
22
|
+
root = resolve_global_state_root(config=config, repo_root=repo_root)
|
|
23
|
+
return UpdatePaths(
|
|
24
|
+
status_path=root / "update_status.json",
|
|
25
|
+
lock_path=root / "update.lock",
|
|
26
|
+
cache_dir=root / "update_cache",
|
|
27
|
+
compact_status_path=root / "compact_status.json",
|
|
28
|
+
)
|
codex_autorunner/core/usage.py
CHANGED
|
@@ -522,8 +522,35 @@ def summarize_hub_usage(
|
|
|
522
522
|
return repo_id
|
|
523
523
|
return None
|
|
524
524
|
|
|
525
|
+
base_repo_ids = sorted(
|
|
526
|
+
{repo_id for repo_id, _ in repo_map}, key=lambda rid: (-len(rid), rid)
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
def _heuristic_match_base(cwd: Optional[Path]) -> Optional[str]:
|
|
530
|
+
if not cwd:
|
|
531
|
+
return None
|
|
532
|
+
for repo_id in base_repo_ids:
|
|
533
|
+
prefix = f"{repo_id}--"
|
|
534
|
+
if cwd.name.startswith(prefix):
|
|
535
|
+
logger.debug(
|
|
536
|
+
"Heuristic matched cwd %s to base %s via name", cwd, repo_id
|
|
537
|
+
)
|
|
538
|
+
return repo_id
|
|
539
|
+
for part in cwd.parts:
|
|
540
|
+
if part.startswith(prefix):
|
|
541
|
+
logger.debug(
|
|
542
|
+
"Heuristic matched cwd %s to base %s via path part %s",
|
|
543
|
+
cwd,
|
|
544
|
+
repo_id,
|
|
545
|
+
part,
|
|
546
|
+
)
|
|
547
|
+
return repo_id
|
|
548
|
+
return None
|
|
549
|
+
|
|
525
550
|
for event in iter_token_events(codex_home, since=since, until=until):
|
|
526
551
|
repo_id = _match_repo(event.cwd)
|
|
552
|
+
if repo_id is None:
|
|
553
|
+
repo_id = _heuristic_match_base(event.cwd)
|
|
527
554
|
if repo_id is None:
|
|
528
555
|
unmatched.totals.add(event.delta)
|
|
529
556
|
unmatched.events += 1
|
|
@@ -540,6 +567,8 @@ def summarize_hub_usage(
|
|
|
540
567
|
[path for _, path in repo_map], since=since, until=until
|
|
541
568
|
):
|
|
542
569
|
repo_id = _match_repo(event.cwd)
|
|
570
|
+
if repo_id is None:
|
|
571
|
+
repo_id = _heuristic_match_base(event.cwd)
|
|
543
572
|
if repo_id is None:
|
|
544
573
|
continue
|
|
545
574
|
summary = per_repo[repo_id]
|
|
@@ -1323,6 +1352,31 @@ class UsageSeriesCache:
|
|
|
1323
1352
|
return repo_id
|
|
1324
1353
|
return None
|
|
1325
1354
|
|
|
1355
|
+
base_repo_ids = sorted(
|
|
1356
|
+
{repo_id for repo_id, _ in repo_map}, key=lambda rid: (-len(rid), rid)
|
|
1357
|
+
)
|
|
1358
|
+
|
|
1359
|
+
def _heuristic_match_base(cwd: Optional[Path]) -> Optional[str]:
|
|
1360
|
+
if not cwd:
|
|
1361
|
+
return None
|
|
1362
|
+
for repo_id in base_repo_ids:
|
|
1363
|
+
prefix = f"{repo_id}--"
|
|
1364
|
+
if cwd.name.startswith(prefix):
|
|
1365
|
+
logger.debug(
|
|
1366
|
+
"Heuristic matched cwd %s to base %s via name", cwd, repo_id
|
|
1367
|
+
)
|
|
1368
|
+
return repo_id
|
|
1369
|
+
for part in cwd.parts:
|
|
1370
|
+
if part.startswith(prefix):
|
|
1371
|
+
logger.debug(
|
|
1372
|
+
"Heuristic matched cwd %s to base %s via path part %s",
|
|
1373
|
+
cwd,
|
|
1374
|
+
repo_id,
|
|
1375
|
+
part,
|
|
1376
|
+
)
|
|
1377
|
+
return repo_id
|
|
1378
|
+
return None
|
|
1379
|
+
|
|
1326
1380
|
rollups = cast(Dict[str, Any], payload.get("summary", {}).get("by_cwd", {}))
|
|
1327
1381
|
per_repo: Dict[str, _SummaryAccumulator] = {
|
|
1328
1382
|
repo_id: _SummaryAccumulator() for repo_id, _ in repo_map
|
|
@@ -1336,6 +1390,8 @@ class UsageSeriesCache:
|
|
|
1336
1390
|
logger.debug("Failed to create Path from cwd %r: %s", cwd, exc)
|
|
1337
1391
|
cwd_path = None
|
|
1338
1392
|
repo_id = _match_repo(cwd_path)
|
|
1393
|
+
if repo_id is None:
|
|
1394
|
+
repo_id = _heuristic_match_base(cwd_path)
|
|
1339
1395
|
if repo_id is None:
|
|
1340
1396
|
unmatched.add_entry(entry)
|
|
1341
1397
|
else:
|
|
@@ -1513,7 +1569,8 @@ class UsageSeriesCache:
|
|
|
1513
1569
|
}
|
|
1514
1570
|
|
|
1515
1571
|
|
|
1516
|
-
_USAGE_SERIES_CACHES: Dict[str, UsageSeriesCache] = {}
|
|
1572
|
+
_USAGE_SERIES_CACHES: Dict[Tuple[str, str], UsageSeriesCache] = {}
|
|
1573
|
+
_REPO_USAGE_CACHE_MIGRATED: set[str] = set()
|
|
1517
1574
|
|
|
1518
1575
|
|
|
1519
1576
|
def _build_series_entries(
|
|
@@ -1781,9 +1838,80 @@ def _build_hub_opencode_series(
|
|
|
1781
1838
|
}
|
|
1782
1839
|
|
|
1783
1840
|
|
|
1784
|
-
def
|
|
1785
|
-
|
|
1786
|
-
|
|
1841
|
+
def _resolve_usage_cache_paths(
|
|
1842
|
+
*,
|
|
1843
|
+
config: Optional[Any] = None,
|
|
1844
|
+
repo_root: Optional[Path] = None,
|
|
1845
|
+
codex_home: Optional[Path] = None,
|
|
1846
|
+
) -> Tuple[Path, Path, str, Path]:
|
|
1847
|
+
codex_root = (codex_home or default_codex_home()).expanduser()
|
|
1848
|
+
cache_scope = "global"
|
|
1849
|
+
cache_path = _default_usage_series_cache_path(codex_root)
|
|
1850
|
+
global_cache_root = codex_root
|
|
1851
|
+
usage_cfg: Optional[Any] = None
|
|
1852
|
+
if config is not None:
|
|
1853
|
+
usage_cfg = getattr(config, "usage", None)
|
|
1854
|
+
if usage_cfg is None:
|
|
1855
|
+
raw = getattr(config, "raw", None)
|
|
1856
|
+
if isinstance(raw, dict):
|
|
1857
|
+
usage_cfg = raw.get("usage")
|
|
1858
|
+
if usage_cfg:
|
|
1859
|
+
cache_scope = str(getattr(usage_cfg, "cache_scope", "global") or "global")
|
|
1860
|
+
cache_scope = cache_scope.lower().strip() or "global"
|
|
1861
|
+
global_root = getattr(usage_cfg, "global_cache_root", None)
|
|
1862
|
+
repo_cache_path = getattr(usage_cfg, "repo_cache_path", None)
|
|
1863
|
+
if global_root:
|
|
1864
|
+
global_cache_root = Path(global_root)
|
|
1865
|
+
if cache_scope == "repo":
|
|
1866
|
+
if repo_cache_path:
|
|
1867
|
+
cache_path = Path(repo_cache_path)
|
|
1868
|
+
elif repo_root:
|
|
1869
|
+
cache_path = (
|
|
1870
|
+
repo_root
|
|
1871
|
+
/ ".codex-autorunner"
|
|
1872
|
+
/ "usage"
|
|
1873
|
+
/ "usage_series_cache.json"
|
|
1874
|
+
)
|
|
1875
|
+
else:
|
|
1876
|
+
if global_root:
|
|
1877
|
+
cache_path = _default_usage_series_cache_path(global_cache_root)
|
|
1878
|
+
else:
|
|
1879
|
+
cache_path = _default_usage_series_cache_path(codex_root)
|
|
1880
|
+
return codex_root, cache_path, cache_scope, Path(global_cache_root)
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
def _maybe_migrate_usage_cache(cache_path: Path, global_cache_path: Path) -> None:
|
|
1884
|
+
cache_key = str(cache_path)
|
|
1885
|
+
if cache_key in _REPO_USAGE_CACHE_MIGRATED:
|
|
1886
|
+
return
|
|
1887
|
+
_REPO_USAGE_CACHE_MIGRATED.add(cache_key)
|
|
1888
|
+
if cache_path.exists() or not global_cache_path.exists():
|
|
1889
|
+
return
|
|
1890
|
+
try:
|
|
1891
|
+
payload = global_cache_path.read_text(encoding="utf-8")
|
|
1892
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1893
|
+
tmp_path = cache_path.with_suffix(".tmp")
|
|
1894
|
+
tmp_path.write_text(payload, encoding="utf-8")
|
|
1895
|
+
tmp_path.replace(cache_path)
|
|
1896
|
+
logger.warning(
|
|
1897
|
+
"Imported global usage cache into repo cache at %s from %s",
|
|
1898
|
+
cache_path,
|
|
1899
|
+
global_cache_path,
|
|
1900
|
+
)
|
|
1901
|
+
except OSError as exc:
|
|
1902
|
+
logger.warning(
|
|
1903
|
+
"Failed to import global usage cache from %s to %s: %s",
|
|
1904
|
+
global_cache_path,
|
|
1905
|
+
cache_path,
|
|
1906
|
+
exc,
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
def get_usage_series_cache(
|
|
1911
|
+
codex_home: Path, *, cache_path: Optional[Path] = None
|
|
1912
|
+
) -> UsageSeriesCache:
|
|
1913
|
+
cache_path = cache_path or _default_usage_series_cache_path(codex_home)
|
|
1914
|
+
key = (str(cache_path), str(codex_home))
|
|
1787
1915
|
cache = _USAGE_SERIES_CACHES.get(key)
|
|
1788
1916
|
if cache is None:
|
|
1789
1917
|
cache = UsageSeriesCache(codex_home, cache_path)
|
|
@@ -1795,13 +1923,19 @@ def get_repo_usage_series_cached(
|
|
|
1795
1923
|
repo_root: Path,
|
|
1796
1924
|
codex_home: Optional[Path] = None,
|
|
1797
1925
|
*,
|
|
1926
|
+
config: Optional[Any] = None,
|
|
1798
1927
|
since: Optional[datetime] = None,
|
|
1799
1928
|
until: Optional[datetime] = None,
|
|
1800
1929
|
bucket: str = "day",
|
|
1801
1930
|
segment: str = "none",
|
|
1802
1931
|
) -> Tuple[Dict[str, object], str]:
|
|
1803
|
-
codex_root
|
|
1804
|
-
|
|
1932
|
+
codex_root, cache_path, cache_scope, global_cache_root = _resolve_usage_cache_paths(
|
|
1933
|
+
config=config, repo_root=repo_root, codex_home=codex_home
|
|
1934
|
+
)
|
|
1935
|
+
if cache_scope == "repo":
|
|
1936
|
+
global_cache_path = _default_usage_series_cache_path(global_cache_root)
|
|
1937
|
+
_maybe_migrate_usage_cache(cache_path, global_cache_path)
|
|
1938
|
+
cache = get_usage_series_cache(codex_root, cache_path=cache_path)
|
|
1805
1939
|
if segment == "agent":
|
|
1806
1940
|
codex_series, status = cache.get_repo_series(
|
|
1807
1941
|
repo_root, since=since, until=until, bucket=bucket, segment="none"
|
|
@@ -1829,11 +1963,17 @@ def get_repo_usage_summary_cached(
|
|
|
1829
1963
|
repo_root: Path,
|
|
1830
1964
|
codex_home: Optional[Path] = None,
|
|
1831
1965
|
*,
|
|
1966
|
+
config: Optional[Any] = None,
|
|
1832
1967
|
since: Optional[datetime] = None,
|
|
1833
1968
|
until: Optional[datetime] = None,
|
|
1834
1969
|
) -> Tuple[UsageSummary, str]:
|
|
1835
|
-
codex_root
|
|
1836
|
-
|
|
1970
|
+
codex_root, cache_path, cache_scope, global_cache_root = _resolve_usage_cache_paths(
|
|
1971
|
+
config=config, repo_root=repo_root, codex_home=codex_home
|
|
1972
|
+
)
|
|
1973
|
+
if cache_scope == "repo":
|
|
1974
|
+
global_cache_path = _default_usage_series_cache_path(global_cache_root)
|
|
1975
|
+
_maybe_migrate_usage_cache(cache_path, global_cache_path)
|
|
1976
|
+
cache = get_usage_series_cache(codex_root, cache_path=cache_path)
|
|
1837
1977
|
summary, status = cache.get_repo_summary(repo_root, since=since, until=until)
|
|
1838
1978
|
opencode_summary = summarize_opencode_repo_usage(
|
|
1839
1979
|
repo_root, since=since, until=until
|
|
@@ -1852,13 +1992,19 @@ def get_hub_usage_series_cached(
|
|
|
1852
1992
|
repo_map: List[Tuple[str, Path]],
|
|
1853
1993
|
codex_home: Optional[Path] = None,
|
|
1854
1994
|
*,
|
|
1995
|
+
config: Optional[Any] = None,
|
|
1855
1996
|
since: Optional[datetime] = None,
|
|
1856
1997
|
until: Optional[datetime] = None,
|
|
1857
1998
|
bucket: str = "day",
|
|
1858
1999
|
segment: str = "none",
|
|
1859
2000
|
) -> Tuple[Dict[str, object], str]:
|
|
1860
|
-
codex_root
|
|
1861
|
-
|
|
2001
|
+
codex_root, cache_path, cache_scope, global_cache_root = _resolve_usage_cache_paths(
|
|
2002
|
+
config=config, repo_root=None, codex_home=codex_home
|
|
2003
|
+
)
|
|
2004
|
+
if cache_scope == "repo":
|
|
2005
|
+
global_cache_path = _default_usage_series_cache_path(global_cache_root)
|
|
2006
|
+
_maybe_migrate_usage_cache(cache_path, global_cache_path)
|
|
2007
|
+
cache = get_usage_series_cache(codex_root, cache_path=cache_path)
|
|
1862
2008
|
if segment == "agent":
|
|
1863
2009
|
codex_series, status = cache.get_hub_series(
|
|
1864
2010
|
repo_map, since=since, until=until, bucket=bucket, segment="none"
|
|
@@ -1886,11 +2032,17 @@ def get_hub_usage_summary_cached(
|
|
|
1886
2032
|
repo_map: List[Tuple[str, Path]],
|
|
1887
2033
|
codex_home: Optional[Path] = None,
|
|
1888
2034
|
*,
|
|
2035
|
+
config: Optional[Any] = None,
|
|
1889
2036
|
since: Optional[datetime] = None,
|
|
1890
2037
|
until: Optional[datetime] = None,
|
|
1891
2038
|
) -> Tuple[Dict[str, UsageSummary], UsageSummary, str]:
|
|
1892
|
-
codex_root
|
|
1893
|
-
|
|
2039
|
+
codex_root, cache_path, cache_scope, global_cache_root = _resolve_usage_cache_paths(
|
|
2040
|
+
config=config, repo_root=None, codex_home=codex_home
|
|
2041
|
+
)
|
|
2042
|
+
if cache_scope == "repo":
|
|
2043
|
+
global_cache_path = _default_usage_series_cache_path(global_cache_root)
|
|
2044
|
+
_maybe_migrate_usage_cache(cache_path, global_cache_path)
|
|
2045
|
+
cache = get_usage_series_cache(codex_root, cache_path=cache_path)
|
|
1894
2046
|
per_repo, unmatched, status = cache.get_hub_summary(
|
|
1895
2047
|
repo_map, since=since, until=until
|
|
1896
2048
|
)
|
codex_autorunner/core/utils.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import contextvars
|
|
2
|
+
import importlib
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
6
|
import shlex
|
|
6
7
|
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from functools import lru_cache
|
|
7
10
|
from pathlib import Path
|
|
8
11
|
from typing import (
|
|
9
|
-
|
|
12
|
+
Any,
|
|
10
13
|
Dict,
|
|
14
|
+
Iterable,
|
|
11
15
|
Mapping,
|
|
12
16
|
MutableMapping,
|
|
13
17
|
Optional,
|
|
@@ -16,8 +20,83 @@ from typing import (
|
|
|
16
20
|
cast,
|
|
17
21
|
)
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
SUBCOMMAND_HINTS = ("exec", "resume")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def extract_flag_value(args: Iterable[str], flag: str) -> Optional[str]:
|
|
27
|
+
if not args:
|
|
28
|
+
return None
|
|
29
|
+
for arg in args:
|
|
30
|
+
if not isinstance(arg, str):
|
|
31
|
+
continue
|
|
32
|
+
if arg.startswith(f"{flag}="):
|
|
33
|
+
return arg.split("=", 1)[1] or None
|
|
34
|
+
args_list = [str(a) for a in args]
|
|
35
|
+
for idx, arg in enumerate(args_list):
|
|
36
|
+
if arg == flag and idx + 1 < len(args_list):
|
|
37
|
+
return args_list[idx + 1]
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def inject_flag(
|
|
42
|
+
args: Iterable[str],
|
|
43
|
+
flag: str,
|
|
44
|
+
value: Optional[str],
|
|
45
|
+
*,
|
|
46
|
+
subcommands: Iterable[str] = SUBCOMMAND_HINTS,
|
|
47
|
+
) -> list[str]:
|
|
48
|
+
if not value:
|
|
49
|
+
return [str(a) for a in args]
|
|
50
|
+
args_list = [str(a) for a in args]
|
|
51
|
+
if extract_flag_value(args_list, flag):
|
|
52
|
+
return args_list
|
|
53
|
+
insert_at = None
|
|
54
|
+
for cmd in subcommands:
|
|
55
|
+
try:
|
|
56
|
+
insert_at = args_list.index(cmd)
|
|
57
|
+
break
|
|
58
|
+
except ValueError:
|
|
59
|
+
continue
|
|
60
|
+
if insert_at is None:
|
|
61
|
+
if args_list and not args_list[0].startswith("-"):
|
|
62
|
+
return [args_list[0], flag, value] + args_list[1:]
|
|
63
|
+
return [flag, value] + args_list
|
|
64
|
+
return args_list[:insert_at] + [flag, value] + args_list[insert_at:]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def apply_codex_options(
|
|
68
|
+
args: Iterable[str],
|
|
69
|
+
*,
|
|
70
|
+
model: Optional[str] = None,
|
|
71
|
+
reasoning: Optional[str] = None,
|
|
72
|
+
supports_reasoning: Optional[bool] = None,
|
|
73
|
+
) -> list[str]:
|
|
74
|
+
with_model = inject_flag(args, "--model", model)
|
|
75
|
+
if reasoning and supports_reasoning is False:
|
|
76
|
+
return with_model
|
|
77
|
+
return inject_flag(with_model, "--reasoning", reasoning)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _read_help_text(binary: str) -> str:
|
|
81
|
+
try:
|
|
82
|
+
result = subprocess.run(
|
|
83
|
+
[binary, "--help"],
|
|
84
|
+
capture_output=True,
|
|
85
|
+
text=True,
|
|
86
|
+
check=False,
|
|
87
|
+
)
|
|
88
|
+
except FileNotFoundError:
|
|
89
|
+
return ""
|
|
90
|
+
return "\n".join(filter(None, [result.stdout, result.stderr]))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@lru_cache(maxsize=8)
|
|
94
|
+
def supports_flag(binary: str, flag: str) -> bool:
|
|
95
|
+
return flag in _read_help_text(binary)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def supports_reasoning(binary: str) -> bool:
|
|
99
|
+
return supports_flag(binary, "--reasoning")
|
|
21
100
|
|
|
22
101
|
|
|
23
102
|
class RepoNotFoundError(Exception):
|
|
@@ -68,12 +147,21 @@ def is_within(root: Path, target: Path) -> bool:
|
|
|
68
147
|
return False
|
|
69
148
|
|
|
70
149
|
|
|
71
|
-
def atomic_write(path: Path, content: str) -> None:
|
|
150
|
+
def atomic_write(path: Path, content: str, durable: bool = False) -> None:
|
|
72
151
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
73
152
|
tmp_path = path.with_suffix(path.suffix + ".tmp")
|
|
74
153
|
with tmp_path.open("w", encoding="utf-8") as f:
|
|
75
154
|
f.write(content)
|
|
155
|
+
if durable:
|
|
156
|
+
f.flush()
|
|
157
|
+
os.fsync(f.fileno())
|
|
76
158
|
tmp_path.replace(path)
|
|
159
|
+
if durable:
|
|
160
|
+
dir_fd = os.open(path.parent, os.O_RDONLY)
|
|
161
|
+
try:
|
|
162
|
+
os.fsync(dir_fd)
|
|
163
|
+
finally:
|
|
164
|
+
os.close(dir_fd)
|
|
77
165
|
|
|
78
166
|
|
|
79
167
|
def read_json(path: Path) -> Optional[dict]:
|
|
@@ -96,7 +184,28 @@ def _default_path_prefixes() -> list[str]:
|
|
|
96
184
|
str(home / ".opencode" / "bin"), # OpenCode default install
|
|
97
185
|
str(home / ".local" / "bin"), # Common user-local installs
|
|
98
186
|
]
|
|
99
|
-
|
|
187
|
+
repo_candidates: list[str] = []
|
|
188
|
+
repo_root = get_repo_root_context()
|
|
189
|
+
if repo_root is None:
|
|
190
|
+
try:
|
|
191
|
+
repo_root = find_repo_root()
|
|
192
|
+
except RepoNotFoundError:
|
|
193
|
+
repo_root = None
|
|
194
|
+
if repo_root is not None:
|
|
195
|
+
bin_dir = "Scripts" if os.name == "nt" else "bin"
|
|
196
|
+
repo_candidates = [
|
|
197
|
+
str(repo_root / ".venv" / bin_dir),
|
|
198
|
+
str(repo_root / ".codex-autorunner" / "bin"),
|
|
199
|
+
str(repo_root / "bin"),
|
|
200
|
+
]
|
|
201
|
+
car_shim = repo_root / "car"
|
|
202
|
+
if car_shim.exists():
|
|
203
|
+
repo_candidates.append(str(repo_root))
|
|
204
|
+
return [
|
|
205
|
+
p
|
|
206
|
+
for p in (repo_candidates + candidates)
|
|
207
|
+
if (os.path.isdir(p) or os.path.isfile(p))
|
|
208
|
+
]
|
|
100
209
|
|
|
101
210
|
|
|
102
211
|
def augmented_path(path: Optional[str] = None) -> str:
|
|
@@ -148,12 +257,8 @@ def resolve_executable(
|
|
|
148
257
|
return resolved
|
|
149
258
|
|
|
150
259
|
|
|
151
|
-
def
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def default_editor() -> str:
|
|
156
|
-
return os.environ.get("EDITOR") or "vi"
|
|
260
|
+
def default_editor(*, fallback: str = "vi") -> str:
|
|
261
|
+
return os.environ.get("EDITOR") or fallback
|
|
157
262
|
|
|
158
263
|
|
|
159
264
|
def resolve_opencode_binary(raw_command: Optional[str] = None) -> Optional[str]:
|
|
@@ -216,9 +321,10 @@ def build_opencode_supervisor(
|
|
|
216
321
|
max_handles: Optional[int] = None,
|
|
217
322
|
idle_ttl_seconds: Optional[float] = None,
|
|
218
323
|
session_stall_timeout_seconds: Optional[float] = None,
|
|
324
|
+
max_text_chars: Optional[int] = None,
|
|
219
325
|
base_env: Optional[MutableMapping[str, str]] = None,
|
|
220
326
|
subagent_models: Optional[Mapping[str, str]] = None,
|
|
221
|
-
) -> Optional[
|
|
327
|
+
) -> Optional[Any]:
|
|
222
328
|
"""
|
|
223
329
|
Unified factory for building OpenCodeSupervisor instances.
|
|
224
330
|
|
|
@@ -262,20 +368,24 @@ def build_opencode_supervisor(
|
|
|
262
368
|
if password and not username:
|
|
263
369
|
username = "opencode"
|
|
264
370
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
371
|
+
supervisor_module = importlib.import_module(
|
|
372
|
+
"codex_autorunner.agents.opencode.supervisor"
|
|
373
|
+
)
|
|
374
|
+
supervisor_cls = supervisor_module.OpenCodeSupervisor
|
|
375
|
+
supervisor = supervisor_cls(
|
|
268
376
|
command,
|
|
269
377
|
logger=logger,
|
|
270
378
|
request_timeout=request_timeout,
|
|
271
379
|
max_handles=max_handles,
|
|
272
380
|
idle_ttl_seconds=idle_ttl_seconds,
|
|
273
381
|
session_stall_timeout_seconds=session_stall_timeout_seconds,
|
|
382
|
+
max_text_chars=max_text_chars,
|
|
274
383
|
username=username if password else None,
|
|
275
384
|
password=password if password else None,
|
|
276
385
|
base_env=base_env,
|
|
277
386
|
subagent_models=subagent_models,
|
|
278
387
|
)
|
|
388
|
+
return cast(Any, supervisor)
|
|
279
389
|
|
|
280
390
|
|
|
281
391
|
def _command_available(
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .service import (
|
|
2
|
+
REVIEW_PROMPT,
|
|
3
|
+
REVIEW_PROMPT_SPEC_PROGRESS,
|
|
4
|
+
ReviewBusyError,
|
|
5
|
+
ReviewConflictError,
|
|
6
|
+
ReviewError,
|
|
7
|
+
ReviewService,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"REVIEW_PROMPT",
|
|
12
|
+
"REVIEW_PROMPT_SPEC_PROGRESS",
|
|
13
|
+
"ReviewBusyError",
|
|
14
|
+
"ReviewConflictError",
|
|
15
|
+
"ReviewError",
|
|
16
|
+
"ReviewService",
|
|
17
|
+
]
|