codex-autorunner 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/agents/codex/harness.py +1 -1
- 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/registry.py +22 -3
- codex_autorunner/bootstrap.py +7 -3
- codex_autorunner/cli.py +5 -1174
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +6 -1
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_threads.py +11 -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 +197 -3
- codex_autorunner/core/drafts.py +58 -4
- codex_autorunner/core/engine.py +1329 -680
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/controller.py +25 -1
- codex_autorunner/core/flows/models.py +13 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +35 -4
- codex_autorunner/core/flows/store.py +83 -0
- codex_autorunner/core/flows/transition.py +5 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +121 -7
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +11 -3
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/{integrations/agents → core/ports}/run_event.py +22 -2
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +4 -5
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +91 -9
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/definition.py +9 -2
- codex_autorunner/integrations/agents/__init__.py +9 -19
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +158 -17
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +305 -32
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -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/handlers/callbacks.py +7 -0
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1203 -66
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +4 -3
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +8 -2
- codex_autorunner/integrations/telegram/handlers/messages.py +1 -0
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +24 -1
- codex_autorunner/integrations/telegram/service.py +15 -10
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +329 -40
- codex_autorunner/integrations/telegram/transport.py +3 -1
- 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 +2 -2
- codex_autorunner/static/agentControls.js +40 -11
- codex_autorunner/static/app.js +11 -3
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +7 -7
- codex_autorunner/static/dashboard.js +224 -171
- codex_autorunner/static/hub.js +112 -94
- codex_autorunner/static/index.html +80 -33
- codex_autorunner/static/messages.js +486 -83
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +125 -6
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +1373 -101
- codex_autorunner/static/tabs.js +152 -11
- codex_autorunner/static/terminal.js +18 -0
- codex_autorunner/static/ticketEditor.js +99 -5
- codex_autorunner/static/tickets.js +760 -87
- codex_autorunner/static/utils.js +11 -0
- codex_autorunner/static/workspace.js +133 -40
- codex_autorunner/static/workspaceFileBrowser.js +9 -9
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +8 -1
- codex_autorunner/tickets/agent_pool.py +26 -4
- codex_autorunner/tickets/files.py +6 -2
- codex_autorunner/tickets/models.py +3 -1
- codex_autorunner/tickets/outbox.py +12 -0
- codex_autorunner/tickets/runner.py +63 -5
- 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.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- 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.1.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
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):
|
|
@@ -152,8 +231,8 @@ def ensure_executable(binary: str) -> bool:
|
|
|
152
231
|
return resolve_executable(binary) is not None
|
|
153
232
|
|
|
154
233
|
|
|
155
|
-
def default_editor() -> str:
|
|
156
|
-
return os.environ.get("EDITOR") or
|
|
234
|
+
def default_editor(*, fallback: str = "vi") -> str:
|
|
235
|
+
return os.environ.get("EDITOR") or fallback
|
|
157
236
|
|
|
158
237
|
|
|
159
238
|
def resolve_opencode_binary(raw_command: Optional[str] = None) -> Optional[str]:
|
|
@@ -218,7 +297,7 @@ def build_opencode_supervisor(
|
|
|
218
297
|
session_stall_timeout_seconds: Optional[float] = None,
|
|
219
298
|
base_env: Optional[MutableMapping[str, str]] = None,
|
|
220
299
|
subagent_models: Optional[Mapping[str, str]] = None,
|
|
221
|
-
) -> Optional[
|
|
300
|
+
) -> Optional[Any]:
|
|
222
301
|
"""
|
|
223
302
|
Unified factory for building OpenCodeSupervisor instances.
|
|
224
303
|
|
|
@@ -262,9 +341,11 @@ def build_opencode_supervisor(
|
|
|
262
341
|
if password and not username:
|
|
263
342
|
username = "opencode"
|
|
264
343
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
344
|
+
supervisor_module = importlib.import_module(
|
|
345
|
+
"codex_autorunner.agents.opencode.supervisor"
|
|
346
|
+
)
|
|
347
|
+
supervisor_cls = supervisor_module.OpenCodeSupervisor
|
|
348
|
+
supervisor = supervisor_cls(
|
|
268
349
|
command,
|
|
269
350
|
logger=logger,
|
|
270
351
|
request_timeout=request_timeout,
|
|
@@ -276,6 +357,7 @@ def build_opencode_supervisor(
|
|
|
276
357
|
base_env=base_env,
|
|
277
358
|
subagent_models=subagent_models,
|
|
278
359
|
)
|
|
360
|
+
return cast(Any, supervisor)
|
|
279
361
|
|
|
280
362
|
|
|
281
363
|
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
|
+
]
|
|
@@ -10,15 +10,20 @@ import zipfile
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, Optional
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from .
|
|
18
|
-
from .
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
from ...agents.opencode.run_prompt import OpenCodeRunConfig, run_opencode_prompt
|
|
14
|
+
from ...agents.opencode.supervisor import OpenCodeSupervisor
|
|
15
|
+
from ...agents.registry import has_capability, validate_agent_id
|
|
16
|
+
from ...core.config import RepoConfig
|
|
17
|
+
from ...core.engine import Engine
|
|
18
|
+
from ...core.locks import (
|
|
19
|
+
FileLock,
|
|
20
|
+
FileLockBusy,
|
|
21
|
+
FileLockError,
|
|
22
|
+
process_alive,
|
|
23
|
+
read_lock_info,
|
|
24
|
+
)
|
|
25
|
+
from ...core.state import now_iso
|
|
26
|
+
from ...core.utils import atomic_write, read_json
|
|
22
27
|
|
|
23
28
|
REVIEW_STATE_VERSION = 1
|
|
24
29
|
REVIEW_TIMEOUT_SECONDS = 3600
|
|
@@ -391,7 +396,7 @@ class ReviewService:
|
|
|
391
396
|
engine: Engine,
|
|
392
397
|
*,
|
|
393
398
|
opencode_supervisor: Optional[OpenCodeSupervisor] = None,
|
|
394
|
-
app_server_supervisor: Optional[
|
|
399
|
+
app_server_supervisor: Optional[Any] = None,
|
|
395
400
|
logger: Optional[logging.Logger] = None,
|
|
396
401
|
) -> None:
|
|
397
402
|
self.engine = engine
|
|
@@ -6,7 +6,12 @@ from typing import Any, Dict, Optional
|
|
|
6
6
|
from ...core.flows.definition import EmitEventFn, FlowDefinition, StepOutcome
|
|
7
7
|
from ...core.flows.models import FlowEventType, FlowRunRecord
|
|
8
8
|
from ...core.utils import find_repo_root
|
|
9
|
-
from ...tickets import
|
|
9
|
+
from ...tickets import (
|
|
10
|
+
DEFAULT_MAX_TOTAL_TURNS,
|
|
11
|
+
AgentPool,
|
|
12
|
+
TicketRunConfig,
|
|
13
|
+
TicketRunner,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
def build_ticket_flow_definition(*, agent_pool: AgentPool) -> FlowDefinition:
|
|
@@ -33,7 +38,9 @@ def build_ticket_flow_definition(*, agent_pool: AgentPool) -> FlowDefinition:
|
|
|
33
38
|
workspace_root = Path(input_data.get("workspace_root") or repo_root)
|
|
34
39
|
ticket_dir = Path(input_data.get("ticket_dir") or ".codex-autorunner/tickets")
|
|
35
40
|
runs_dir = Path(input_data.get("runs_dir") or ".codex-autorunner/runs")
|
|
36
|
-
max_total_turns = int(
|
|
41
|
+
max_total_turns = int(
|
|
42
|
+
input_data.get("max_total_turns") or DEFAULT_MAX_TOTAL_TURNS
|
|
43
|
+
)
|
|
37
44
|
max_lint_retries = int(input_data.get("max_lint_retries") or 3)
|
|
38
45
|
max_commit_retries = int(input_data.get("max_commit_retries") or 2)
|
|
39
46
|
auto_commit = bool(
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .codex_adapter import CodexAdapterOrchestrator
|
|
2
2
|
from .codex_backend import CodexAppServerBackend
|
|
3
|
+
from .opencode_adapter import OpenCodeAdapterOrchestrator
|
|
3
4
|
from .opencode_backend import OpenCodeBackend
|
|
4
|
-
from .
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Failed,
|
|
8
|
-
OutputDelta,
|
|
9
|
-
RunEvent,
|
|
10
|
-
Started,
|
|
11
|
-
ToolCall,
|
|
5
|
+
from .wiring import (
|
|
6
|
+
build_agent_backend_factory,
|
|
7
|
+
build_app_server_supervisor_factory,
|
|
12
8
|
)
|
|
13
9
|
|
|
14
10
|
__all__ = [
|
|
15
|
-
"
|
|
16
|
-
"AgentEvent",
|
|
17
|
-
"AgentEventType",
|
|
11
|
+
"CodexAdapterOrchestrator",
|
|
18
12
|
"CodexAppServerBackend",
|
|
13
|
+
"OpenCodeAdapterOrchestrator",
|
|
19
14
|
"OpenCodeBackend",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"OutputDelta",
|
|
23
|
-
"ToolCall",
|
|
24
|
-
"ApprovalRequested",
|
|
25
|
-
"Completed",
|
|
26
|
-
"Failed",
|
|
15
|
+
"build_agent_backend_factory",
|
|
16
|
+
"build_app_server_supervisor_factory",
|
|
27
17
|
]
|