codex-autorunner 0.1.2__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/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -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 +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- 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 +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- 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/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- 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 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +344 -325
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- 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 +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.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,12 +1,17 @@
|
|
|
1
|
+
import contextvars
|
|
2
|
+
import importlib
|
|
1
3
|
import json
|
|
2
4
|
import logging
|
|
3
5
|
import os
|
|
4
6
|
import shlex
|
|
5
7
|
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from functools import lru_cache
|
|
6
10
|
from pathlib import Path
|
|
7
11
|
from typing import (
|
|
8
|
-
|
|
12
|
+
Any,
|
|
9
13
|
Dict,
|
|
14
|
+
Iterable,
|
|
10
15
|
Mapping,
|
|
11
16
|
MutableMapping,
|
|
12
17
|
Optional,
|
|
@@ -15,16 +20,115 @@ from typing import (
|
|
|
15
20
|
cast,
|
|
16
21
|
)
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
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")
|
|
20
100
|
|
|
21
101
|
|
|
22
102
|
class RepoNotFoundError(Exception):
|
|
23
103
|
pass
|
|
24
104
|
|
|
25
105
|
|
|
26
|
-
|
|
27
|
-
|
|
106
|
+
_repo_root_ctx: contextvars.ContextVar[Optional[Path]] = contextvars.ContextVar(
|
|
107
|
+
"codex_autorunner_repo_root", default=None
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def set_repo_root_context(
|
|
112
|
+
repo_root: Optional[Path],
|
|
113
|
+
) -> contextvars.Token[Optional[Path]]:
|
|
114
|
+
"""Set the current repo root for the active context."""
|
|
115
|
+
return _repo_root_ctx.set(repo_root.resolve() if repo_root else None)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def reset_repo_root_context(token: contextvars.Token[Optional[Path]]) -> None:
|
|
119
|
+
_repo_root_ctx.reset(token)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_repo_root_context() -> Optional[Path]:
|
|
123
|
+
return _repo_root_ctx.get()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def find_repo_root(start: Optional[Path] = None) -> Path:
|
|
127
|
+
ctx_root = get_repo_root_context()
|
|
128
|
+
if ctx_root is not None and (ctx_root / ".git").exists():
|
|
129
|
+
return ctx_root
|
|
130
|
+
|
|
131
|
+
current = (start or Path.cwd()).resolve()
|
|
28
132
|
for parent in [current] + list(current.parents):
|
|
29
133
|
if (parent / ".git").exists():
|
|
30
134
|
return parent
|
|
@@ -127,8 +231,8 @@ def ensure_executable(binary: str) -> bool:
|
|
|
127
231
|
return resolve_executable(binary) is not None
|
|
128
232
|
|
|
129
233
|
|
|
130
|
-
def default_editor() -> str:
|
|
131
|
-
return os.environ.get("EDITOR") or
|
|
234
|
+
def default_editor(*, fallback: str = "vi") -> str:
|
|
235
|
+
return os.environ.get("EDITOR") or fallback
|
|
132
236
|
|
|
133
237
|
|
|
134
238
|
def resolve_opencode_binary(raw_command: Optional[str] = None) -> Optional[str]:
|
|
@@ -190,9 +294,10 @@ def build_opencode_supervisor(
|
|
|
190
294
|
request_timeout: Optional[float] = None,
|
|
191
295
|
max_handles: Optional[int] = None,
|
|
192
296
|
idle_ttl_seconds: Optional[float] = None,
|
|
297
|
+
session_stall_timeout_seconds: Optional[float] = None,
|
|
193
298
|
base_env: Optional[MutableMapping[str, str]] = None,
|
|
194
299
|
subagent_models: Optional[Mapping[str, str]] = None,
|
|
195
|
-
) -> Optional[
|
|
300
|
+
) -> Optional[Any]:
|
|
196
301
|
"""
|
|
197
302
|
Unified factory for building OpenCodeSupervisor instances.
|
|
198
303
|
|
|
@@ -236,19 +341,23 @@ def build_opencode_supervisor(
|
|
|
236
341
|
if password and not username:
|
|
237
342
|
username = "opencode"
|
|
238
343
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
344
|
+
supervisor_module = importlib.import_module(
|
|
345
|
+
"codex_autorunner.agents.opencode.supervisor"
|
|
346
|
+
)
|
|
347
|
+
supervisor_cls = supervisor_module.OpenCodeSupervisor
|
|
348
|
+
supervisor = supervisor_cls(
|
|
242
349
|
command,
|
|
243
350
|
logger=logger,
|
|
244
351
|
request_timeout=request_timeout,
|
|
245
352
|
max_handles=max_handles,
|
|
246
353
|
idle_ttl_seconds=idle_ttl_seconds,
|
|
354
|
+
session_stall_timeout_seconds=session_stall_timeout_seconds,
|
|
247
355
|
username=username if password else None,
|
|
248
356
|
password=password if password else None,
|
|
249
357
|
base_env=base_env,
|
|
250
358
|
subagent_models=subagent_models,
|
|
251
359
|
)
|
|
360
|
+
return cast(Any, supervisor)
|
|
252
361
|
|
|
253
362
|
|
|
254
363
|
def _command_available(
|
codex_autorunner/discovery.py
CHANGED
|
@@ -57,7 +57,7 @@ def discover_and_init(hub_config: HubConfig) -> Tuple[Manifest, List[DiscoveryRe
|
|
|
57
57
|
|
|
58
58
|
def _record_repo(repo_entry: ManifestRepo, *, added: bool) -> None:
|
|
59
59
|
repo_path = (hub_config.root / repo_entry.path).resolve()
|
|
60
|
-
initialized = (repo_path / ".codex-autorunner" / "
|
|
60
|
+
initialized = (repo_path / ".codex-autorunner" / "tickets").exists()
|
|
61
61
|
init_error: Optional[str] = None
|
|
62
62
|
if hub_config.auto_init_missing and repo_path.exists() and not initialized:
|
|
63
63
|
try:
|
|
@@ -176,9 +176,7 @@ def discover_and_init(hub_config: HubConfig) -> Tuple[Manifest, List[DiscoveryRe
|
|
|
176
176
|
absolute_path=repo_path,
|
|
177
177
|
added_to_manifest=False,
|
|
178
178
|
exists_on_disk=repo_path.exists(),
|
|
179
|
-
initialized=(
|
|
180
|
-
repo_path / ".codex-autorunner" / "state.sqlite3"
|
|
181
|
-
).exists(),
|
|
179
|
+
initialized=(repo_path / ".codex-autorunner" / "tickets").exists(),
|
|
182
180
|
init_error=None,
|
|
183
181
|
)
|
|
184
182
|
)
|
|
@@ -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
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from ...core.flows.definition import EmitEventFn, FlowDefinition, StepOutcome
|
|
7
|
+
from ...core.flows.models import FlowEventType, FlowRunRecord
|
|
8
|
+
from ...core.utils import find_repo_root
|
|
9
|
+
from ...tickets import (
|
|
10
|
+
DEFAULT_MAX_TOTAL_TURNS,
|
|
11
|
+
AgentPool,
|
|
12
|
+
TicketRunConfig,
|
|
13
|
+
TicketRunner,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_ticket_flow_definition(*, agent_pool: AgentPool) -> FlowDefinition:
|
|
18
|
+
"""Build the single-step ticket runner flow.
|
|
19
|
+
|
|
20
|
+
The flow is intentionally simple: each step executes at most one agent turn
|
|
21
|
+
against the current ticket, and re-schedules itself until paused or complete.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
async def _ticket_turn_step(
|
|
25
|
+
record: FlowRunRecord,
|
|
26
|
+
input_data: Dict[str, Any],
|
|
27
|
+
emit_event: Optional[EmitEventFn],
|
|
28
|
+
) -> StepOutcome:
|
|
29
|
+
# Namespace all state under `ticket_engine` to avoid collisions with other flows.
|
|
30
|
+
engine_state = (
|
|
31
|
+
record.state.get("ticket_engine")
|
|
32
|
+
if isinstance(record.state, dict)
|
|
33
|
+
else None
|
|
34
|
+
)
|
|
35
|
+
engine_state = dict(engine_state) if isinstance(engine_state, dict) else {}
|
|
36
|
+
|
|
37
|
+
repo_root = find_repo_root()
|
|
38
|
+
workspace_root = Path(input_data.get("workspace_root") or repo_root)
|
|
39
|
+
ticket_dir = Path(input_data.get("ticket_dir") or ".codex-autorunner/tickets")
|
|
40
|
+
runs_dir = Path(input_data.get("runs_dir") or ".codex-autorunner/runs")
|
|
41
|
+
max_total_turns = int(
|
|
42
|
+
input_data.get("max_total_turns") or DEFAULT_MAX_TOTAL_TURNS
|
|
43
|
+
)
|
|
44
|
+
max_lint_retries = int(input_data.get("max_lint_retries") or 3)
|
|
45
|
+
max_commit_retries = int(input_data.get("max_commit_retries") or 2)
|
|
46
|
+
auto_commit = bool(
|
|
47
|
+
input_data.get("auto_commit") if "auto_commit" in input_data else True
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
runner = TicketRunner(
|
|
51
|
+
workspace_root=workspace_root,
|
|
52
|
+
run_id=str(record.id),
|
|
53
|
+
config=TicketRunConfig(
|
|
54
|
+
ticket_dir=ticket_dir,
|
|
55
|
+
runs_dir=runs_dir,
|
|
56
|
+
max_total_turns=max_total_turns,
|
|
57
|
+
max_lint_retries=max_lint_retries,
|
|
58
|
+
max_commit_retries=max_commit_retries,
|
|
59
|
+
auto_commit=auto_commit,
|
|
60
|
+
),
|
|
61
|
+
agent_pool=agent_pool,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if emit_event is not None:
|
|
65
|
+
emit_event(FlowEventType.STEP_PROGRESS, {"message": "Running ticket turn"})
|
|
66
|
+
result = await runner.step(engine_state, emit_event=emit_event)
|
|
67
|
+
out_state = dict(record.state or {})
|
|
68
|
+
out_state["ticket_engine"] = result.state
|
|
69
|
+
|
|
70
|
+
if result.status == "completed":
|
|
71
|
+
return StepOutcome.complete(output=out_state)
|
|
72
|
+
if result.status == "paused":
|
|
73
|
+
return StepOutcome.pause(output=out_state)
|
|
74
|
+
if result.status == "failed":
|
|
75
|
+
return StepOutcome.fail(
|
|
76
|
+
error=result.reason or "Ticket engine failed", output=out_state
|
|
77
|
+
)
|
|
78
|
+
return StepOutcome.continue_to(next_steps={"ticket_turn"}, output=out_state)
|
|
79
|
+
|
|
80
|
+
return FlowDefinition(
|
|
81
|
+
flow_type="ticket_flow",
|
|
82
|
+
name="Ticket Flow",
|
|
83
|
+
description="Ticket-based agent workflow runner",
|
|
84
|
+
initial_step="ticket_turn",
|
|
85
|
+
input_schema={
|
|
86
|
+
"type": "object",
|
|
87
|
+
"properties": {
|
|
88
|
+
"workspace_root": {"type": "string"},
|
|
89
|
+
"ticket_dir": {"type": "string"},
|
|
90
|
+
"runs_dir": {"type": "string"},
|
|
91
|
+
"max_total_turns": {"type": "integer"},
|
|
92
|
+
"max_lint_retries": {"type": "integer"},
|
|
93
|
+
"max_commit_retries": {"type": "integer"},
|
|
94
|
+
"auto_commit": {"type": "boolean"},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
steps={"ticket_turn": _ticket_turn_step},
|
|
98
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .codex_adapter import CodexAdapterOrchestrator
|
|
2
|
+
from .codex_backend import CodexAppServerBackend
|
|
3
|
+
from .opencode_adapter import OpenCodeAdapterOrchestrator
|
|
4
|
+
from .opencode_backend import OpenCodeBackend
|
|
5
|
+
from .wiring import (
|
|
6
|
+
build_agent_backend_factory,
|
|
7
|
+
build_app_server_supervisor_factory,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CodexAdapterOrchestrator",
|
|
12
|
+
"CodexAppServerBackend",
|
|
13
|
+
"OpenCodeAdapterOrchestrator",
|
|
14
|
+
"OpenCodeBackend",
|
|
15
|
+
"build_agent_backend_factory",
|
|
16
|
+
"build_app_server_supervisor_factory",
|
|
17
|
+
]
|