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.
Files changed (170) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/agents/codex/harness.py +1 -1
  3. codex_autorunner/agents/opencode/constants.py +3 -0
  4. codex_autorunner/agents/opencode/harness.py +6 -1
  5. codex_autorunner/agents/opencode/runtime.py +59 -18
  6. codex_autorunner/agents/registry.py +22 -3
  7. codex_autorunner/bootstrap.py +7 -3
  8. codex_autorunner/cli.py +5 -1174
  9. codex_autorunner/codex_cli.py +20 -84
  10. codex_autorunner/core/__init__.py +4 -0
  11. codex_autorunner/core/about_car.py +6 -1
  12. codex_autorunner/core/app_server_ids.py +59 -0
  13. codex_autorunner/core/app_server_threads.py +11 -2
  14. codex_autorunner/core/app_server_utils.py +165 -0
  15. codex_autorunner/core/archive.py +349 -0
  16. codex_autorunner/core/codex_runner.py +6 -2
  17. codex_autorunner/core/config.py +197 -3
  18. codex_autorunner/core/drafts.py +58 -4
  19. codex_autorunner/core/engine.py +1329 -680
  20. codex_autorunner/core/exceptions.py +4 -0
  21. codex_autorunner/core/flows/controller.py +25 -1
  22. codex_autorunner/core/flows/models.py +13 -0
  23. codex_autorunner/core/flows/reasons.py +52 -0
  24. codex_autorunner/core/flows/reconciler.py +131 -0
  25. codex_autorunner/core/flows/runtime.py +35 -4
  26. codex_autorunner/core/flows/store.py +83 -0
  27. codex_autorunner/core/flows/transition.py +5 -0
  28. codex_autorunner/core/flows/ux_helpers.py +257 -0
  29. codex_autorunner/core/git_utils.py +62 -0
  30. codex_autorunner/core/hub.py +121 -7
  31. codex_autorunner/core/notifications.py +14 -2
  32. codex_autorunner/core/ports/__init__.py +28 -0
  33. codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +11 -3
  34. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  35. codex_autorunner/{integrations/agents → core/ports}/run_event.py +22 -2
  36. codex_autorunner/core/state_roots.py +57 -0
  37. codex_autorunner/core/supervisor_protocol.py +15 -0
  38. codex_autorunner/core/text_delta_coalescer.py +54 -0
  39. codex_autorunner/core/ticket_linter_cli.py +201 -0
  40. codex_autorunner/core/ticket_manager_cli.py +432 -0
  41. codex_autorunner/core/update.py +4 -5
  42. codex_autorunner/core/update_paths.py +28 -0
  43. codex_autorunner/core/usage.py +164 -12
  44. codex_autorunner/core/utils.py +91 -9
  45. codex_autorunner/flows/review/__init__.py +17 -0
  46. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  47. codex_autorunner/flows/ticket_flow/definition.py +9 -2
  48. codex_autorunner/integrations/agents/__init__.py +9 -19
  49. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  50. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  51. codex_autorunner/integrations/agents/codex_backend.py +158 -17
  52. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  53. codex_autorunner/integrations/agents/opencode_backend.py +305 -32
  54. codex_autorunner/integrations/agents/runner.py +91 -0
  55. codex_autorunner/integrations/agents/wiring.py +271 -0
  56. codex_autorunner/integrations/app_server/client.py +7 -60
  57. codex_autorunner/integrations/app_server/env.py +2 -107
  58. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  59. codex_autorunner/integrations/telegram/adapter.py +65 -0
  60. codex_autorunner/integrations/telegram/config.py +46 -0
  61. codex_autorunner/integrations/telegram/constants.py +1 -1
  62. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
  63. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1203 -66
  64. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +4 -3
  65. codex_autorunner/integrations/telegram/handlers/commands_spec.py +8 -2
  66. codex_autorunner/integrations/telegram/handlers/messages.py +1 -0
  67. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  68. codex_autorunner/integrations/telegram/helpers.py +24 -1
  69. codex_autorunner/integrations/telegram/service.py +15 -10
  70. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +329 -40
  71. codex_autorunner/integrations/telegram/transport.py +3 -1
  72. codex_autorunner/routes/__init__.py +37 -76
  73. codex_autorunner/routes/agents.py +2 -137
  74. codex_autorunner/routes/analytics.py +2 -238
  75. codex_autorunner/routes/app_server.py +2 -131
  76. codex_autorunner/routes/base.py +2 -596
  77. codex_autorunner/routes/file_chat.py +4 -833
  78. codex_autorunner/routes/flows.py +4 -977
  79. codex_autorunner/routes/messages.py +4 -456
  80. codex_autorunner/routes/repos.py +2 -196
  81. codex_autorunner/routes/review.py +2 -147
  82. codex_autorunner/routes/sessions.py +2 -175
  83. codex_autorunner/routes/settings.py +2 -168
  84. codex_autorunner/routes/shared.py +2 -275
  85. codex_autorunner/routes/system.py +4 -193
  86. codex_autorunner/routes/usage.py +2 -86
  87. codex_autorunner/routes/voice.py +2 -119
  88. codex_autorunner/routes/workspace.py +2 -270
  89. codex_autorunner/server.py +2 -2
  90. codex_autorunner/static/agentControls.js +40 -11
  91. codex_autorunner/static/app.js +11 -3
  92. codex_autorunner/static/archive.js +826 -0
  93. codex_autorunner/static/archiveApi.js +37 -0
  94. codex_autorunner/static/autoRefresh.js +7 -7
  95. codex_autorunner/static/dashboard.js +224 -171
  96. codex_autorunner/static/hub.js +112 -94
  97. codex_autorunner/static/index.html +80 -33
  98. codex_autorunner/static/messages.js +486 -83
  99. codex_autorunner/static/preserve.js +17 -0
  100. codex_autorunner/static/settings.js +125 -6
  101. codex_autorunner/static/smartRefresh.js +52 -0
  102. codex_autorunner/static/styles.css +1373 -101
  103. codex_autorunner/static/tabs.js +152 -11
  104. codex_autorunner/static/terminal.js +18 -0
  105. codex_autorunner/static/ticketEditor.js +99 -5
  106. codex_autorunner/static/tickets.js +760 -87
  107. codex_autorunner/static/utils.js +11 -0
  108. codex_autorunner/static/workspace.js +133 -40
  109. codex_autorunner/static/workspaceFileBrowser.js +9 -9
  110. codex_autorunner/surfaces/__init__.py +5 -0
  111. codex_autorunner/surfaces/cli/__init__.py +6 -0
  112. codex_autorunner/surfaces/cli/cli.py +1224 -0
  113. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  114. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  115. codex_autorunner/surfaces/web/__init__.py +1 -0
  116. codex_autorunner/surfaces/web/app.py +2019 -0
  117. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  118. codex_autorunner/surfaces/web/middleware.py +587 -0
  119. codex_autorunner/surfaces/web/pty_session.py +370 -0
  120. codex_autorunner/surfaces/web/review.py +6 -0
  121. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  122. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  123. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  124. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  125. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  126. codex_autorunner/surfaces/web/routes/base.py +615 -0
  127. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  128. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  129. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  130. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  131. codex_autorunner/surfaces/web/routes/review.py +148 -0
  132. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  133. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  134. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  135. codex_autorunner/surfaces/web/routes/system.py +196 -0
  136. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  137. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  138. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  139. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  140. codex_autorunner/surfaces/web/schemas.py +417 -0
  141. codex_autorunner/surfaces/web/static_assets.py +490 -0
  142. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  143. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  144. codex_autorunner/tickets/__init__.py +8 -1
  145. codex_autorunner/tickets/agent_pool.py +26 -4
  146. codex_autorunner/tickets/files.py +6 -2
  147. codex_autorunner/tickets/models.py +3 -1
  148. codex_autorunner/tickets/outbox.py +12 -0
  149. codex_autorunner/tickets/runner.py +63 -5
  150. codex_autorunner/web/__init__.py +5 -1
  151. codex_autorunner/web/app.py +2 -1949
  152. codex_autorunner/web/hub_jobs.py +2 -191
  153. codex_autorunner/web/middleware.py +2 -586
  154. codex_autorunner/web/pty_session.py +2 -369
  155. codex_autorunner/web/runner_manager.py +2 -24
  156. codex_autorunner/web/schemas.py +2 -376
  157. codex_autorunner/web/static_assets.py +4 -441
  158. codex_autorunner/web/static_refresh.py +2 -85
  159. codex_autorunner/web/terminal_sessions.py +2 -77
  160. codex_autorunner/workspace/paths.py +49 -33
  161. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  162. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  163. codex_autorunner/core/static_assets.py +0 -55
  164. codex_autorunner-1.0.0.dist-info/METADATA +0 -246
  165. codex_autorunner-1.0.0.dist-info/RECORD +0 -251
  166. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  167. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +0 -0
  168. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  169. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  170. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -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 get_usage_series_cache(codex_home: Path) -> UsageSeriesCache:
1785
- cache_path = _default_usage_series_cache_path(codex_home)
1786
- key = str(cache_path)
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 = (codex_home or default_codex_home()).expanduser()
1804
- cache = get_usage_series_cache(codex_root)
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 = (codex_home or default_codex_home()).expanduser()
1836
- cache = get_usage_series_cache(codex_root)
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 = (codex_home or default_codex_home()).expanduser()
1861
- cache = get_usage_series_cache(codex_root)
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 = (codex_home or default_codex_home()).expanduser()
1893
- cache = get_usage_series_cache(codex_root)
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
  )
@@ -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
- TYPE_CHECKING,
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
- if TYPE_CHECKING:
20
- from ..agents.opencode.supervisor import OpenCodeSupervisor
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 "vi"
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["OpenCodeSupervisor"]:
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
- from ..agents.opencode.supervisor import OpenCodeSupervisor
266
-
267
- return OpenCodeSupervisor(
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 ..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 ..integrations.app_server.supervisor import WorkspaceAppServerSupervisor
17
- from .config import RepoConfig
18
- from .engine import Engine
19
- from .locks import FileLock, FileLockBusy, FileLockError, process_alive, read_lock_info
20
- from .state import now_iso
21
- from .utils import atomic_write, read_json
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[WorkspaceAppServerSupervisor] = None,
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 AgentPool, TicketRunConfig, TicketRunner
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(input_data.get("max_total_turns") or 25)
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 .agent_backend import AgentBackend, AgentEvent, AgentEventType
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 .run_event import (
5
- ApprovalRequested,
6
- Completed,
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
- "AgentBackend",
16
- "AgentEvent",
17
- "AgentEventType",
11
+ "CodexAdapterOrchestrator",
18
12
  "CodexAppServerBackend",
13
+ "OpenCodeAdapterOrchestrator",
19
14
  "OpenCodeBackend",
20
- "RunEvent",
21
- "Started",
22
- "OutputDelta",
23
- "ToolCall",
24
- "ApprovalRequested",
25
- "Completed",
26
- "Failed",
15
+ "build_agent_backend_factory",
16
+ "build_app_server_supervisor_factory",
27
17
  ]