codex-autorunner 1.1.0__py3-none-any.whl → 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. codex_autorunner/agents/opencode/client.py +113 -4
  2. codex_autorunner/agents/opencode/supervisor.py +4 -0
  3. codex_autorunner/agents/registry.py +17 -7
  4. codex_autorunner/bootstrap.py +219 -1
  5. codex_autorunner/core/__init__.py +17 -1
  6. codex_autorunner/core/about_car.py +114 -1
  7. codex_autorunner/core/app_server_threads.py +6 -0
  8. codex_autorunner/core/config.py +236 -1
  9. codex_autorunner/core/context_awareness.py +38 -0
  10. codex_autorunner/core/docs.py +0 -122
  11. codex_autorunner/core/filebox.py +265 -0
  12. codex_autorunner/core/flows/controller.py +71 -1
  13. codex_autorunner/core/flows/reconciler.py +4 -1
  14. codex_autorunner/core/flows/runtime.py +22 -0
  15. codex_autorunner/core/flows/store.py +61 -9
  16. codex_autorunner/core/flows/transition.py +23 -16
  17. codex_autorunner/core/flows/ux_helpers.py +18 -3
  18. codex_autorunner/core/flows/worker_process.py +32 -6
  19. codex_autorunner/core/hub.py +198 -41
  20. codex_autorunner/core/lifecycle_events.py +253 -0
  21. codex_autorunner/core/path_utils.py +2 -1
  22. codex_autorunner/core/pma_audit.py +224 -0
  23. codex_autorunner/core/pma_context.py +496 -0
  24. codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
  25. codex_autorunner/core/pma_lifecycle.py +527 -0
  26. codex_autorunner/core/pma_queue.py +367 -0
  27. codex_autorunner/core/pma_safety.py +221 -0
  28. codex_autorunner/core/pma_state.py +115 -0
  29. codex_autorunner/core/ports/agent_backend.py +2 -5
  30. codex_autorunner/core/ports/run_event.py +1 -4
  31. codex_autorunner/core/prompt.py +0 -80
  32. codex_autorunner/core/prompts.py +56 -172
  33. codex_autorunner/core/redaction.py +0 -4
  34. codex_autorunner/core/review_context.py +11 -9
  35. codex_autorunner/core/runner_controller.py +35 -33
  36. codex_autorunner/core/runner_state.py +147 -0
  37. codex_autorunner/core/runtime.py +829 -0
  38. codex_autorunner/core/sqlite_utils.py +13 -4
  39. codex_autorunner/core/state.py +7 -10
  40. codex_autorunner/core/state_roots.py +5 -0
  41. codex_autorunner/core/templates/__init__.py +39 -0
  42. codex_autorunner/core/templates/git_mirror.py +234 -0
  43. codex_autorunner/core/templates/provenance.py +56 -0
  44. codex_autorunner/core/templates/scan_cache.py +120 -0
  45. codex_autorunner/core/ticket_linter_cli.py +17 -0
  46. codex_autorunner/core/ticket_manager_cli.py +154 -92
  47. codex_autorunner/core/time_utils.py +11 -0
  48. codex_autorunner/core/types.py +18 -0
  49. codex_autorunner/core/utils.py +34 -6
  50. codex_autorunner/flows/review/service.py +23 -25
  51. codex_autorunner/flows/ticket_flow/definition.py +43 -1
  52. codex_autorunner/integrations/agents/__init__.py +2 -0
  53. codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
  54. codex_autorunner/integrations/agents/codex_backend.py +19 -8
  55. codex_autorunner/integrations/agents/runner.py +3 -8
  56. codex_autorunner/integrations/agents/wiring.py +8 -0
  57. codex_autorunner/integrations/telegram/doctor.py +228 -6
  58. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  59. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  60. codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
  61. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  62. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
  63. codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
  64. codex_autorunner/integrations/telegram/handlers/messages.py +26 -1
  65. codex_autorunner/integrations/telegram/helpers.py +1 -3
  66. codex_autorunner/integrations/telegram/runtime.py +9 -4
  67. codex_autorunner/integrations/telegram/service.py +30 -0
  68. codex_autorunner/integrations/telegram/state.py +38 -0
  69. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
  70. codex_autorunner/integrations/telegram/transport.py +10 -3
  71. codex_autorunner/integrations/templates/__init__.py +27 -0
  72. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  73. codex_autorunner/server.py +2 -2
  74. codex_autorunner/static/agentControls.js +21 -5
  75. codex_autorunner/static/app.js +115 -11
  76. codex_autorunner/static/chatUploads.js +137 -0
  77. codex_autorunner/static/docChatCore.js +185 -13
  78. codex_autorunner/static/fileChat.js +68 -40
  79. codex_autorunner/static/fileboxUi.js +159 -0
  80. codex_autorunner/static/hub.js +46 -81
  81. codex_autorunner/static/index.html +303 -24
  82. codex_autorunner/static/messages.js +82 -4
  83. codex_autorunner/static/notifications.js +255 -0
  84. codex_autorunner/static/pma.js +1167 -0
  85. codex_autorunner/static/settings.js +3 -0
  86. codex_autorunner/static/streamUtils.js +57 -0
  87. codex_autorunner/static/styles.css +9125 -6742
  88. codex_autorunner/static/templateReposSettings.js +225 -0
  89. codex_autorunner/static/ticketChatActions.js +165 -3
  90. codex_autorunner/static/ticketChatStream.js +17 -119
  91. codex_autorunner/static/ticketEditor.js +41 -13
  92. codex_autorunner/static/ticketTemplates.js +798 -0
  93. codex_autorunner/static/tickets.js +69 -19
  94. codex_autorunner/static/turnEvents.js +27 -0
  95. codex_autorunner/static/turnResume.js +33 -0
  96. codex_autorunner/static/utils.js +28 -0
  97. codex_autorunner/static/workspace.js +258 -44
  98. codex_autorunner/static/workspaceFileBrowser.js +6 -4
  99. codex_autorunner/surfaces/cli/cli.py +1465 -155
  100. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  101. codex_autorunner/surfaces/web/app.py +253 -49
  102. codex_autorunner/surfaces/web/routes/__init__.py +4 -0
  103. codex_autorunner/surfaces/web/routes/analytics.py +29 -22
  104. codex_autorunner/surfaces/web/routes/file_chat.py +317 -36
  105. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  106. codex_autorunner/surfaces/web/routes/flows.py +219 -29
  107. codex_autorunner/surfaces/web/routes/messages.py +70 -39
  108. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  109. codex_autorunner/surfaces/web/routes/repos.py +1 -1
  110. codex_autorunner/surfaces/web/routes/shared.py +0 -3
  111. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  112. codex_autorunner/surfaces/web/runner_manager.py +2 -2
  113. codex_autorunner/surfaces/web/schemas.py +70 -18
  114. codex_autorunner/tickets/agent_pool.py +27 -0
  115. codex_autorunner/tickets/files.py +33 -16
  116. codex_autorunner/tickets/lint.py +50 -0
  117. codex_autorunner/tickets/models.py +3 -0
  118. codex_autorunner/tickets/outbox.py +41 -5
  119. codex_autorunner/tickets/runner.py +350 -69
  120. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/METADATA +15 -19
  121. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/RECORD +125 -94
  122. codex_autorunner/core/adapter_utils.py +0 -21
  123. codex_autorunner/core/engine.py +0 -3302
  124. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
  125. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
  126. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
  127. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
@@ -27,6 +27,7 @@ from urllib.parse import quote
27
27
  import yaml
28
28
  from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile
29
29
 
30
+ from ....core.filebox import ensure_structure, save_file
30
31
  from ....core.flows.models import FlowRunRecord, FlowRunStatus
31
32
  from ....core.flows.store import FlowStore
32
33
  from ....core.utils import find_repo_root
@@ -47,15 +48,29 @@ def _flows_db_path(repo_root: Path) -> Path:
47
48
  return repo_root / ".codex-autorunner" / "flows.db"
48
49
 
49
50
 
50
- def _load_store_or_404(db_path: Path) -> FlowStore:
51
- store = FlowStore(db_path)
52
- try:
53
- store.initialize()
54
- return store
55
- except Exception as exc:
56
- raise HTTPException(
57
- status_code=404, detail="Flows database unavailable"
58
- ) from exc
51
+ def _resolve_workspace_and_runs(
52
+ record_input: dict[str, Any], repo_root: Path
53
+ ) -> tuple[Path, Path]:
54
+ """
55
+ Normalize workspace_root/runs_dir with sensible fallbacks.
56
+
57
+ - workspace_root defaults to the current repo_root.
58
+ - runs_dir defaults to .codex-autorunner/runs.
59
+ - If runs_dir is absolute, keep it as-is; otherwise join to workspace_root.
60
+ """
61
+
62
+ raw_workspace = record_input.get("workspace_root")
63
+ workspace_root = Path(raw_workspace) if raw_workspace else repo_root
64
+ if not workspace_root.is_absolute():
65
+ workspace_root = (repo_root / workspace_root).resolve()
66
+ else:
67
+ workspace_root = workspace_root.resolve()
68
+
69
+ runs_dir_raw = record_input.get("runs_dir") or ".codex-autorunner/runs"
70
+ runs_dir_path = Path(runs_dir_raw)
71
+ if not runs_dir_path.is_absolute():
72
+ runs_dir_path = (workspace_root / runs_dir_path).resolve()
73
+ return workspace_root, runs_dir_path
59
74
 
60
75
 
61
76
  def _timestamp(path: Path) -> Optional[str]:
@@ -103,8 +118,7 @@ def _collect_dispatch_history(
103
118
  *, repo_root: Path, run_id: str, record_input: dict[str, Any]
104
119
  ) -> list[dict[str, Any]]:
105
120
  """Collect all dispatches from the dispatch history directory."""
106
- workspace_root = Path(record_input.get("workspace_root") or repo_root)
107
- runs_dir = Path(record_input.get("runs_dir") or ".codex-autorunner/runs")
121
+ workspace_root, runs_dir = _resolve_workspace_and_runs(record_input, repo_root)
108
122
  outbox_paths = resolve_outbox_paths(
109
123
  workspace_root=workspace_root, runs_dir=runs_dir, run_id=run_id
110
124
  )
@@ -161,8 +175,7 @@ def _collect_dispatch_history(
161
175
  def _collect_reply_history(
162
176
  *, repo_root: Path, run_id: str, record_input: dict[str, Any]
163
177
  ):
164
- workspace_root = Path(record_input.get("workspace_root") or repo_root)
165
- runs_dir = Path(record_input.get("runs_dir") or ".codex-autorunner/runs")
178
+ workspace_root, runs_dir = _resolve_workspace_and_runs(record_input, repo_root)
166
179
  reply_paths = resolve_reply_paths(
167
180
  workspace_root=workspace_root, runs_dir=runs_dir, run_id=run_id
168
181
  )
@@ -236,20 +249,22 @@ def build_messages_routes() -> APIRouter:
236
249
 
237
250
  @router.get("/api/messages/active")
238
251
  def get_active_message(request: Request):
252
+ from ....core.config import load_repo_config
253
+
239
254
  repo_root = find_repo_root()
240
255
  db_path = _flows_db_path(repo_root)
241
256
  if not db_path.exists():
242
257
  return {"active": False}
243
- store = FlowStore(db_path)
244
258
  try:
245
- store.initialize()
259
+ with FlowStore(
260
+ db_path, durable=load_repo_config(repo_root).durable_writes
261
+ ) as store:
262
+ paused = store.list_flow_runs(
263
+ flow_type="ticket_flow", status=FlowRunStatus.PAUSED
264
+ )
246
265
  except Exception:
247
266
  # Corrupt flows db should not 500 the UI.
248
267
  return {"active": False}
249
-
250
- paused = store.list_flow_runs(
251
- flow_type="ticket_flow", status=FlowRunStatus.PAUSED
252
- )
253
268
  if not paused:
254
269
  return {"active": False}
255
270
 
@@ -281,16 +296,20 @@ def build_messages_routes() -> APIRouter:
281
296
 
282
297
  @router.get("/api/messages/threads")
283
298
  def list_threads():
299
+ from ....core.config import load_repo_config
300
+
284
301
  repo_root = find_repo_root()
285
302
  db_path = _flows_db_path(repo_root)
286
303
  if not db_path.exists():
287
304
  return {"conversations": []}
288
- store = FlowStore(db_path)
289
305
  try:
290
- store.initialize()
306
+ with FlowStore(
307
+ db_path, durable=load_repo_config(repo_root).durable_writes
308
+ ) as store:
309
+ runs = store.list_flow_runs(flow_type="ticket_flow")
291
310
  except Exception:
292
311
  return {"conversations": []}
293
- runs = store.list_flow_runs(flow_type="ticket_flow")
312
+
294
313
  conversations: list[dict[str, Any]] = []
295
314
  for record in runs:
296
315
  record_input = dict(record.input_data or {})
@@ -327,6 +346,8 @@ def build_messages_routes() -> APIRouter:
327
346
 
328
347
  @router.get("/api/messages/threads/{run_id}")
329
348
  def get_thread(run_id: str):
349
+ from ....core.config import load_repo_config
350
+
330
351
  repo_root = find_repo_root()
331
352
  db_path = _flows_db_path(repo_root)
332
353
  empty_response = {
@@ -337,14 +358,15 @@ def build_messages_routes() -> APIRouter:
337
358
  }
338
359
  if not db_path.exists():
339
360
  return empty_response
340
- store = _load_store_or_404(db_path)
341
361
  try:
342
- record = store.get_flow_run(run_id)
343
- finally:
344
- try:
345
- store.close()
346
- except Exception:
347
- pass
362
+ with FlowStore(
363
+ db_path, durable=load_repo_config(repo_root).durable_writes
364
+ ) as store:
365
+ record = store.get_flow_run(run_id)
366
+ except Exception:
367
+ raise HTTPException(
368
+ status_code=404, detail="Flows database unavailable"
369
+ ) from None
348
370
  if not record:
349
371
  return empty_response
350
372
  input_data = dict(record.input_data or {})
@@ -378,29 +400,31 @@ def build_messages_routes() -> APIRouter:
378
400
  body: str = Form(""),
379
401
  title: Optional[str] = Form(None),
380
402
  # NOTE: FastAPI/starlette will supply either a single UploadFile or a list
381
- # depending on how the multipart form is encoded. Declaring this as a
403
+ # depending on how is multipart form is encoded. Declaring this as a
382
404
  # concrete list avoids a common 422 where a single file upload is treated
383
405
  # as a non-list value.
384
406
  files: list[UploadFile] = File(default=[]), # noqa: B006,B008
385
407
  ):
408
+ from ....core.config import load_repo_config
409
+
386
410
  repo_root = find_repo_root()
387
411
  db_path = _flows_db_path(repo_root)
388
412
  if not db_path.exists():
389
413
  raise HTTPException(status_code=404, detail="No flows database")
390
- store = _load_store_or_404(db_path)
391
414
  try:
392
- record = store.get_flow_run(run_id)
393
- finally:
394
- try:
395
- store.close()
396
- except Exception:
397
- pass
415
+ with FlowStore(
416
+ db_path, durable=load_repo_config(repo_root).durable_writes
417
+ ) as store:
418
+ record = store.get_flow_run(run_id)
419
+ except Exception:
420
+ raise HTTPException(
421
+ status_code=404, detail="Flows database unavailable"
422
+ ) from None
398
423
  if not record:
399
424
  raise HTTPException(status_code=404, detail="Run not found")
400
425
 
401
426
  input_data = dict(record.input_data or {})
402
- workspace_root = Path(input_data.get("workspace_root") or repo_root)
403
- runs_dir = Path(input_data.get("runs_dir") or ".codex-autorunner/runs")
427
+ workspace_root, runs_dir = _resolve_workspace_and_runs(input_data, repo_root)
404
428
  reply_paths = resolve_reply_paths(
405
429
  workspace_root=workspace_root, runs_dir=runs_dir, run_id=run_id
406
430
  )
@@ -436,6 +460,13 @@ def build_messages_routes() -> APIRouter:
436
460
  data = await upload.read()
437
461
  try:
438
462
  dest.write_bytes(data)
463
+ try:
464
+ ensure_structure(repo_root)
465
+ save_file(repo_root, "inbox", filename, data)
466
+ except Exception:
467
+ _logger.debug(
468
+ "Failed to mirror attachment into FileBox", exc_info=True
469
+ )
439
470
  except OSError as exc:
440
471
  raise HTTPException(
441
472
  status_code=500, detail=f"Failed to write attachment: {exc}"