codex-autorunner 1.1.0__py3-none-any.whl → 1.2.1__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 (134) 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 +124 -11
  7. codex_autorunner/core/app_server_threads.py +6 -0
  8. codex_autorunner/core/config.py +238 -3
  9. codex_autorunner/core/context_awareness.py +39 -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 +683 -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/adapter.py +1 -1
  58. codex_autorunner/integrations/telegram/config.py +1 -1
  59. codex_autorunner/integrations/telegram/doctor.py +228 -6
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
  63. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  64. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
  65. codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
  66. codex_autorunner/integrations/telegram/handlers/messages.py +34 -3
  67. codex_autorunner/integrations/telegram/helpers.py +1 -3
  68. codex_autorunner/integrations/telegram/runtime.py +9 -4
  69. codex_autorunner/integrations/telegram/service.py +30 -0
  70. codex_autorunner/integrations/telegram/state.py +38 -0
  71. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
  72. codex_autorunner/integrations/telegram/transport.py +10 -3
  73. codex_autorunner/integrations/templates/__init__.py +27 -0
  74. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  75. codex_autorunner/server.py +2 -2
  76. codex_autorunner/static/agentControls.js +21 -5
  77. codex_autorunner/static/app.js +115 -11
  78. codex_autorunner/static/archive.js +274 -81
  79. codex_autorunner/static/archiveApi.js +21 -0
  80. codex_autorunner/static/chatUploads.js +137 -0
  81. codex_autorunner/static/constants.js +1 -1
  82. codex_autorunner/static/docChatCore.js +185 -13
  83. codex_autorunner/static/fileChat.js +68 -40
  84. codex_autorunner/static/fileboxUi.js +159 -0
  85. codex_autorunner/static/hub.js +46 -81
  86. codex_autorunner/static/index.html +303 -24
  87. codex_autorunner/static/messages.js +82 -4
  88. codex_autorunner/static/notifications.js +288 -0
  89. codex_autorunner/static/pma.js +1167 -0
  90. codex_autorunner/static/settings.js +3 -0
  91. codex_autorunner/static/streamUtils.js +57 -0
  92. codex_autorunner/static/styles.css +9141 -6742
  93. codex_autorunner/static/templateReposSettings.js +225 -0
  94. codex_autorunner/static/terminalManager.js +22 -3
  95. codex_autorunner/static/ticketChatActions.js +165 -3
  96. codex_autorunner/static/ticketChatStream.js +17 -119
  97. codex_autorunner/static/ticketEditor.js +41 -13
  98. codex_autorunner/static/ticketTemplates.js +798 -0
  99. codex_autorunner/static/tickets.js +69 -19
  100. codex_autorunner/static/turnEvents.js +27 -0
  101. codex_autorunner/static/turnResume.js +33 -0
  102. codex_autorunner/static/utils.js +28 -0
  103. codex_autorunner/static/workspace.js +258 -44
  104. codex_autorunner/static/workspaceFileBrowser.js +6 -4
  105. codex_autorunner/surfaces/cli/cli.py +1465 -155
  106. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  107. codex_autorunner/surfaces/web/app.py +253 -49
  108. codex_autorunner/surfaces/web/routes/__init__.py +4 -0
  109. codex_autorunner/surfaces/web/routes/analytics.py +29 -22
  110. codex_autorunner/surfaces/web/routes/archive.py +197 -0
  111. codex_autorunner/surfaces/web/routes/file_chat.py +297 -36
  112. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  113. codex_autorunner/surfaces/web/routes/flows.py +219 -29
  114. codex_autorunner/surfaces/web/routes/messages.py +70 -39
  115. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  116. codex_autorunner/surfaces/web/routes/repos.py +1 -1
  117. codex_autorunner/surfaces/web/routes/shared.py +0 -3
  118. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  119. codex_autorunner/surfaces/web/runner_manager.py +2 -2
  120. codex_autorunner/surfaces/web/schemas.py +81 -18
  121. codex_autorunner/tickets/agent_pool.py +27 -0
  122. codex_autorunner/tickets/files.py +33 -16
  123. codex_autorunner/tickets/lint.py +50 -0
  124. codex_autorunner/tickets/models.py +3 -0
  125. codex_autorunner/tickets/outbox.py +41 -5
  126. codex_autorunner/tickets/runner.py +350 -69
  127. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/METADATA +15 -19
  128. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/RECORD +132 -101
  129. codex_autorunner/core/adapter_utils.py +0 -21
  130. codex_autorunner/core/engine.py +0 -3302
  131. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/WHEEL +0 -0
  132. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/entry_points.txt +0 -0
  133. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/licenses/LICENSE +0 -0
  134. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,6 @@ from ...agents.opencode.run_prompt import OpenCodeRunConfig, run_opencode_prompt
14
14
  from ...agents.opencode.supervisor import OpenCodeSupervisor
15
15
  from ...agents.registry import has_capability, validate_agent_id
16
16
  from ...core.config import RepoConfig
17
- from ...core.engine import Engine
18
17
  from ...core.locks import (
19
18
  FileLock,
20
19
  FileLockBusy,
@@ -22,6 +21,7 @@ from ...core.locks import (
22
21
  process_alive,
23
22
  read_lock_info,
24
23
  )
24
+ from ...core.runtime import RuntimeContext
25
25
  from ...core.state import now_iso
26
26
  from ...core.utils import atomic_write, read_json
27
27
 
@@ -252,7 +252,7 @@ Stop digging deeper when:
252
252
  """
253
253
  REVIEW_PROMPT_SPEC_PROGRESS = """# Autorunner Final Review (Spec + Progress Focus)
254
254
 
255
- You are coordinating a multi-agent review immediately after an autorunner loop completes. The goal is to validate work against SPEC.md and PROGRESS.md, not to suggest generic code style fixes.
255
+ You are coordinating a multi-agent review immediately after an autorunner loop completes. The goal is to validate work against spec.md, not to suggest generic code style fixes.
256
256
 
257
257
  ## Required Scratchpad + Output
258
258
 
@@ -269,16 +269,16 @@ You are coordinating a multi-agent review immediately after an autorunner loop c
269
269
 
270
270
  ## Source of Truth + Focus
271
271
 
272
- * Read AUTORUNNER_CONTEXT.md first. It contains the exit reason, SPEC.md, PROGRESS.md, optional TODO/SUMMARY, and the last-run artifacts (output/diff/plan excerpts).
273
- * Treat SPEC.md + PROGRESS.md as contracts: extract explicit requirements, promised validation steps, and open questions.
272
+ * Read AUTORUNNER_CONTEXT.md first. It contains the exit reason, spec.md, and last-run artifacts (output/diff/plan excerpts).
273
+ * Treat spec.md as the contract: extract explicit requirements, promised validation steps, and open questions.
274
274
  * Derive must-hold invariants directly from SPEC (not generic guesses).
275
275
  * Buckets should be anchored to spec sections mapped to implementation areas, plus any recently changed/critical modules from AUTORUNNER_CONTEXT.md.
276
276
 
277
277
  ## North Star
278
278
 
279
- * Spec compliance and claim verification over style.
280
- * High-signal risks and regressions that would violate SPEC/PROGRESS commitments.
281
- * Trackable output that can be turned into TODO items.
279
+ * Spec compliance over style.
280
+ * High-signal risks and regressions that would violate spec commitments.
281
+ * Trackable output that can be turned into tickets.
282
282
 
283
283
  ## Phase 0: Setup (Coordinator)
284
284
 
@@ -288,8 +288,7 @@ Prepare scratchpad files under {{scratchpad_dir}} (see list above).
288
288
 
289
289
  1) Read AUTORUNNER_CONTEXT.md and summarize:
290
290
  * Project shape and runtime assumptions
291
- * SPEC requirements + invariants
292
- * PROGRESS claims + validation evidence
291
+ * Spec requirements + invariants
293
292
  * Open questions/gaps
294
293
  2) Define buckets in BUCKETS.md:
295
294
  * Buckets by review dimension + code areas (spec sections → code paths)
@@ -299,7 +298,6 @@ Prepare scratchpad files under {{scratchpad_dir}} (see list above).
299
298
 
300
299
  Launch subagents by review dimension:
301
300
  * Spec compliance agent: requirement → evidence mapping.
302
- * Progress verification agent: PROGRESS claims → repo/tests evidence.
303
301
  * Risk & regression agent: likely failure modes introduced by recent changes.
304
302
  * Optional: Test adequacy agent if tests are in-scope.
305
303
 
@@ -320,8 +318,8 @@ Create the final report at {{final_output_path}} with this structure:
320
318
  ## Spec-to-Implementation Matrix
321
319
  | Spec item | Status (met/partial/missing) | Evidence | Notes |
322
320
 
323
- ## Progress Claim Verification
324
- - Claim: ... (cite PROGRESS.md section)
321
+ ## Spec Verification
322
+ - Spec requirement: ... (cite spec section)
325
323
  - Verified by: ...
326
324
  - Not verified because: ...
327
325
 
@@ -393,19 +391,19 @@ def _default_state() -> dict[str, Any]:
393
391
  class ReviewService:
394
392
  def __init__(
395
393
  self,
396
- engine: Engine,
394
+ ctx: RuntimeContext,
397
395
  *,
398
396
  opencode_supervisor: Optional[OpenCodeSupervisor] = None,
399
397
  app_server_supervisor: Optional[Any] = None,
400
398
  logger: Optional[logging.Logger] = None,
401
399
  ) -> None:
402
- self.engine = engine
400
+ self.ctx = ctx
403
401
  self._opencode_supervisor = opencode_supervisor
404
402
  self._app_server_supervisor = app_server_supervisor
405
403
  self._logger = logger or logging.getLogger("codex_autorunner.review")
406
- self._state_path = _workflow_root(engine.repo_root) / "state.json"
404
+ self._state_path = _workflow_root(self.ctx.repo_root) / "state.json"
407
405
  self._lock_path = (
408
- engine.repo_root / ".codex-autorunner" / "locks" / "review.lock"
406
+ self.ctx.repo_root / ".codex-autorunner" / "locks" / "review.lock"
409
407
  )
410
408
  self._thread: Optional[threading.Thread] = None
411
409
  self._thread_lock = threading.Lock()
@@ -413,9 +411,9 @@ class ReviewService:
413
411
  self._lock_handle: Optional[FileLock] = None
414
412
 
415
413
  def _repo_config(self) -> RepoConfig:
416
- if not isinstance(self.engine.config, RepoConfig):
414
+ if not isinstance(self.ctx.config, RepoConfig):
417
415
  raise ReviewError("Review requires a repo workspace config")
418
- return self.engine.config
416
+ return self.ctx.config
419
417
 
420
418
  def status(self) -> dict[str, Any]:
421
419
  state = self._load_state()
@@ -438,7 +436,7 @@ class ReviewService:
438
436
  raise ReviewBusyError("Review already running", status_code=409)
439
437
  if self._thread and self._thread.is_alive():
440
438
  raise ReviewBusyError("Review already running", status_code=409)
441
- busy_reason = self.engine.repo_busy_reason()
439
+ busy_reason = self.ctx.repo_busy_reason()
442
440
  if busy_reason:
443
441
  raise ReviewConflictError(
444
442
  f"Cannot start review: {busy_reason}", status_code=409
@@ -475,7 +473,7 @@ class ReviewService:
475
473
  state = self.status()
476
474
  if state.get("status") in ("running", "stopping"):
477
475
  raise ReviewBusyError("Review already running", status_code=409)
478
- busy_reason = self.engine.repo_busy_reason()
476
+ busy_reason = self.ctx.repo_busy_reason()
479
477
  if busy_reason and not ignore_repo_busy:
480
478
  raise ReviewConflictError(
481
479
  f"Cannot start review: {busy_reason}", status_code=409
@@ -680,7 +678,7 @@ class ReviewService:
680
678
  )
681
679
 
682
680
  run_id = state["id"]
683
- runs_dir = _workflow_root(self.engine.repo_root) / "runs"
681
+ runs_dir = _workflow_root(self.ctx.repo_root) / "runs"
684
682
  run_dir = runs_dir / run_id
685
683
  run_dir.mkdir(parents=True, exist_ok=True)
686
684
 
@@ -735,7 +733,7 @@ class ReviewService:
735
733
  if agent_id == "codex":
736
734
  if self._app_server_supervisor is None:
737
735
  raise ReviewError("Codex backend is not configured")
738
- client = await self._app_server_supervisor.get_client(self.engine.repo_root)
736
+ client = await self._app_server_supervisor.get_client(self.ctx.repo_root)
739
737
  thread_id = uuid.uuid4().hex
740
738
  review_kwargs: dict[str, Any] = {}
741
739
  if state.get("model"):
@@ -746,7 +744,7 @@ class ReviewService:
746
744
  thread_id=thread_id,
747
745
  target={"type": "custom", "instructions": prompt},
748
746
  delivery="inline",
749
- cwd=str(self.engine.repo_root),
747
+ cwd=str(self.ctx.repo_root),
750
748
  **review_kwargs,
751
749
  )
752
750
 
@@ -802,7 +800,7 @@ class ReviewService:
802
800
  subagent_model = review_cfg.get("subagent_model")
803
801
  if subagent_agent_id:
804
802
  await self._opencode_supervisor.ensure_subagent_config(
805
- workspace_root=self.engine.repo_root,
803
+ workspace_root=self.ctx.repo_root,
806
804
  agent_id=subagent_agent_id,
807
805
  model=subagent_model,
808
806
  )
@@ -812,7 +810,7 @@ class ReviewService:
812
810
  model=state["model"],
813
811
  reasoning=state.get("reasoning"),
814
812
  prompt=prompt,
815
- workspace_root=str(self.engine.repo_root),
813
+ workspace_root=str(self.ctx.repo_root),
816
814
  timeout_seconds=timeout_seconds,
817
815
  interrupt_grace_seconds=REVIEW_INTERRUPT_GRACE_SECONDS,
818
816
  permission_policy="allow",
@@ -6,6 +6,7 @@ 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 ...manifest import ManifestError, load_manifest
9
10
  from ...tickets import (
10
11
  DEFAULT_MAX_TOTAL_TURNS,
11
12
  AgentPool,
@@ -35,18 +36,36 @@ def build_ticket_flow_definition(*, agent_pool: AgentPool) -> FlowDefinition:
35
36
  engine_state = dict(engine_state) if isinstance(engine_state, dict) else {}
36
37
 
37
38
  repo_root = find_repo_root()
38
- workspace_root = Path(input_data.get("workspace_root") or repo_root)
39
+ raw_workspace = input_data.get("workspace_root") or repo_root
40
+ workspace_root = Path(raw_workspace)
41
+ if not workspace_root.is_absolute():
42
+ workspace_root = (Path(repo_root) / workspace_root).resolve()
43
+ else:
44
+ workspace_root = workspace_root.resolve()
45
+
39
46
  ticket_dir = Path(input_data.get("ticket_dir") or ".codex-autorunner/tickets")
47
+ if not ticket_dir.is_absolute():
48
+ ticket_dir = (workspace_root / ticket_dir).resolve()
49
+
40
50
  runs_dir = Path(input_data.get("runs_dir") or ".codex-autorunner/runs")
51
+ if not runs_dir.is_absolute():
52
+ runs_dir = (workspace_root / runs_dir).resolve()
41
53
  max_total_turns = int(
42
54
  input_data.get("max_total_turns") or DEFAULT_MAX_TOTAL_TURNS
43
55
  )
44
56
  max_lint_retries = int(input_data.get("max_lint_retries") or 3)
45
57
  max_commit_retries = int(input_data.get("max_commit_retries") or 2)
58
+ max_network_retries = int(input_data.get("max_network_retries") or 5)
46
59
  auto_commit = bool(
47
60
  input_data.get("auto_commit") if "auto_commit" in input_data else True
48
61
  )
62
+ include_previous_ticket_context = bool(
63
+ input_data.get("include_previous_ticket_context")
64
+ if "include_previous_ticket_context" in input_data
65
+ else False
66
+ )
49
67
 
68
+ repo_id = _resolve_ticket_flow_repo_id(workspace_root)
50
69
  runner = TicketRunner(
51
70
  workspace_root=workspace_root,
52
71
  run_id=str(record.id),
@@ -56,9 +75,12 @@ def build_ticket_flow_definition(*, agent_pool: AgentPool) -> FlowDefinition:
56
75
  max_total_turns=max_total_turns,
57
76
  max_lint_retries=max_lint_retries,
58
77
  max_commit_retries=max_commit_retries,
78
+ max_network_retries=max_network_retries,
59
79
  auto_commit=auto_commit,
80
+ include_previous_ticket_context=include_previous_ticket_context,
60
81
  ),
61
82
  agent_pool=agent_pool,
83
+ repo_id=repo_id,
62
84
  )
63
85
 
64
86
  if emit_event is not None:
@@ -91,8 +113,28 @@ def build_ticket_flow_definition(*, agent_pool: AgentPool) -> FlowDefinition:
91
113
  "max_total_turns": {"type": "integer"},
92
114
  "max_lint_retries": {"type": "integer"},
93
115
  "max_commit_retries": {"type": "integer"},
116
+ "max_network_retries": {"type": "integer"},
94
117
  "auto_commit": {"type": "boolean"},
118
+ "include_previous_ticket_context": {"type": "boolean"},
95
119
  },
96
120
  },
97
121
  steps={"ticket_turn": _ticket_turn_step},
98
122
  )
123
+
124
+
125
+ def _resolve_ticket_flow_repo_id(workspace_root: Path) -> str:
126
+ current = workspace_root
127
+ for _ in range(5):
128
+ manifest_path = current / ".codex-autorunner" / "manifest.yml"
129
+ if manifest_path.exists():
130
+ try:
131
+ manifest = load_manifest(manifest_path, current)
132
+ except ManifestError:
133
+ return ""
134
+ entry = manifest.get_by_path(current, workspace_root)
135
+ return entry.id if entry else ""
136
+ parent = current.parent
137
+ if parent == current:
138
+ break
139
+ current = parent
140
+ return ""
@@ -1,3 +1,4 @@
1
+ from .backend_orchestrator import build_backend_orchestrator
1
2
  from .codex_adapter import CodexAdapterOrchestrator
2
3
  from .codex_backend import CodexAppServerBackend
3
4
  from .opencode_adapter import OpenCodeAdapterOrchestrator
@@ -14,4 +15,5 @@ __all__ = [
14
15
  "OpenCodeBackend",
15
16
  "build_agent_backend_factory",
16
17
  "build_app_server_supervisor_factory",
18
+ "build_backend_orchestrator",
17
19
  ]
@@ -20,6 +20,9 @@ from ...core.app_server_threads import (
20
20
  )
21
21
  from ...core.config import RepoConfig
22
22
  from ...core.ports.agent_backend import AgentBackend
23
+ from ...core.ports.backend_orchestrator import (
24
+ BackendOrchestrator as BackendOrchestratorProtocol,
25
+ )
23
26
  from ...core.ports.run_event import RunEvent
24
27
  from ...core.state import RunnerState
25
28
  from .codex_backend import CodexAppServerBackend
@@ -278,7 +281,22 @@ class BackendOrchestrator:
278
281
  return factory_fn(event_prefix, notification_handler)
279
282
 
280
283
 
284
+ def build_backend_orchestrator(
285
+ repo_root: Path, config: RepoConfig
286
+ ) -> BackendOrchestratorProtocol:
287
+ """
288
+ Build a BackendOrchestrator for protocol-agnostic backend management.
289
+ """
290
+ return BackendOrchestrator(
291
+ repo_root=repo_root,
292
+ config=config,
293
+ notification_handler=None,
294
+ logger=logging.getLogger("codex_autorunner.backend"),
295
+ )
296
+
297
+
281
298
  __all__ = [
282
299
  "BackendOrchestrator",
283
300
  "BackendContext",
301
+ "build_backend_orchestrator",
284
302
  ]
@@ -1,9 +1,11 @@
1
1
  import asyncio
2
+ import hashlib
2
3
  import logging
3
4
  from pathlib import Path
4
5
  from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, Optional, Union
5
6
 
6
7
  from ...core.circuit_breaker import CircuitBreaker
8
+ from ...core.logging_utils import log_event
7
9
  from ...core.ports.agent_backend import AgentBackend, AgentEvent, now_iso
8
10
  from ...core.ports.run_event import (
9
11
  ApprovalRequested,
@@ -172,10 +174,14 @@ class CodexAppServerBackend(AgentBackend):
172
174
  if not self._thread_id:
173
175
  await self.start_session(target={}, context={})
174
176
 
175
- _logger.info(
176
- "Running turn on thread %s with message: %s",
177
- self._thread_id or "unknown",
178
- message[:100],
177
+ message_hash = hashlib.sha256(message.encode()).hexdigest()[:16]
178
+ log_event(
179
+ self._logger,
180
+ logging.INFO,
181
+ "agent.turn_started",
182
+ thread_id=self._thread_id,
183
+ message_length=len(message),
184
+ message_hash=message_hash,
179
185
  )
180
186
 
181
187
  turn_kwargs: Dict[str, Any] = {}
@@ -220,10 +226,15 @@ class CodexAppServerBackend(AgentBackend):
220
226
  else:
221
227
  actual_session_id = self._thread_id
222
228
 
223
- _logger.info(
224
- "Running turn events on thread %s with message: %s",
225
- actual_session_id or "unknown",
226
- message[:100],
229
+ message_hash = hashlib.sha256(message.encode()).hexdigest()[:16]
230
+ log_event(
231
+ self._logger,
232
+ logging.INFO,
233
+ "agent.turn_events_started",
234
+ thread_id=actual_session_id,
235
+ turn_id=self._turn_id,
236
+ message_length=len(message),
237
+ message_hash=message_hash,
227
238
  )
228
239
 
229
240
  yield Started(
@@ -9,6 +9,7 @@ from ...core.ports.run_event import (
9
9
  OutputDelta,
10
10
  RunEvent,
11
11
  Started,
12
+ now_iso,
12
13
  )
13
14
 
14
15
  _logger = logging.getLogger(__name__)
@@ -36,7 +37,7 @@ async def run_turn_with_backend(
36
37
  session_id = await backend.start_session(target={}, context={})
37
38
 
38
39
  if event_callback:
39
- event_callback(Started(timestamp=timestamp(), session_id=session_id))
40
+ event_callback(Started(timestamp=now_iso(), session_id=session_id))
40
41
 
41
42
  if log_handler:
42
43
  log_handler(message)
@@ -64,7 +65,7 @@ async def run_turn_with_backend(
64
65
  except Exception as exc:
65
66
  _logger.error("Turn execution failed: %s", exc)
66
67
  if event_callback:
67
- event_callback(Failed(timestamp=timestamp(), error_message=str(exc)))
68
+ event_callback(Failed(timestamp=now_iso(), error_message=str(exc)))
68
69
  return 1
69
70
 
70
71
 
@@ -83,9 +84,3 @@ async def stream_turn_events(
83
84
  yield event
84
85
  else:
85
86
  yield AgentEvent.stream_delta(content="", delta_type="noop")
86
-
87
-
88
- def timestamp() -> str:
89
- from datetime import datetime, timezone
90
-
91
- return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -192,6 +192,7 @@ class AgentBackendFactory:
192
192
  max_handles=self._config.app_server.max_handles,
193
193
  idle_ttl_seconds=self._config.app_server.idle_ttl_seconds,
194
194
  session_stall_timeout_seconds=self._config.opencode.session_stall_timeout_seconds,
195
+ max_text_chars=self._config.opencode.max_text_chars,
195
196
  base_env=None,
196
197
  subagent_models=subagent_models,
197
198
  )
@@ -269,3 +270,10 @@ def build_app_server_supervisor_factory(
269
270
  )
270
271
 
271
272
  return factory
273
+
274
+
275
+ __all__ = [
276
+ "AgentBackendFactory",
277
+ "build_agent_backend_factory",
278
+ "build_app_server_supervisor_factory",
279
+ ]
@@ -1370,7 +1370,7 @@ class TelegramBotClient:
1370
1370
  return bool(result) if isinstance(result, bool) else False
1371
1371
 
1372
1372
  async def download_file(
1373
- self, file_path: str, max_size_bytes: int = 50 * 1024 * 1024
1373
+ self, file_path: str, max_size_bytes: int = 100 * 1024 * 1024
1374
1374
  ) -> bytes:
1375
1375
  safe_path = file_path.lstrip("/")
1376
1376
  url = f"{self._file_base_url}/{safe_path}"
@@ -44,7 +44,7 @@ DEFAULT_APP_SERVER_TURN_TIMEOUT_SECONDS = 28800
44
44
  DEFAULT_APPROVAL_TIMEOUT_SECONDS = 300.0
45
45
  DEFAULT_MEDIA_MAX_IMAGE_BYTES = 10 * 1024 * 1024
46
46
  DEFAULT_MEDIA_MAX_VOICE_BYTES = 10 * 1024 * 1024
47
- DEFAULT_MEDIA_MAX_FILE_BYTES = 10 * 1024 * 1024
47
+ DEFAULT_MEDIA_MAX_FILE_BYTES = 100 * 1024 * 1024
48
48
  DEFAULT_MEDIA_IMAGE_PROMPT = (
49
49
  "The user sent an image with no caption. Use it to continue the "
50
50
  "conversation; if no clear task, describe the image and ask what they want."