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.
Files changed (276) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/__main__.py +4 -0
  3. codex_autorunner/agents/codex/harness.py +1 -1
  4. codex_autorunner/agents/opencode/client.py +68 -35
  5. codex_autorunner/agents/opencode/constants.py +3 -0
  6. codex_autorunner/agents/opencode/harness.py +6 -1
  7. codex_autorunner/agents/opencode/logging.py +21 -5
  8. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  9. codex_autorunner/agents/opencode/runtime.py +176 -47
  10. codex_autorunner/agents/opencode/supervisor.py +36 -48
  11. codex_autorunner/agents/registry.py +155 -8
  12. codex_autorunner/api.py +25 -0
  13. codex_autorunner/bootstrap.py +22 -37
  14. codex_autorunner/cli.py +5 -1156
  15. codex_autorunner/codex_cli.py +20 -84
  16. codex_autorunner/core/__init__.py +4 -0
  17. codex_autorunner/core/about_car.py +49 -32
  18. codex_autorunner/core/adapter_utils.py +21 -0
  19. codex_autorunner/core/app_server_ids.py +59 -0
  20. codex_autorunner/core/app_server_logging.py +7 -3
  21. codex_autorunner/core/app_server_prompts.py +27 -260
  22. codex_autorunner/core/app_server_threads.py +26 -28
  23. codex_autorunner/core/app_server_utils.py +165 -0
  24. codex_autorunner/core/archive.py +349 -0
  25. codex_autorunner/core/codex_runner.py +12 -2
  26. codex_autorunner/core/config.py +587 -103
  27. codex_autorunner/core/docs.py +10 -2
  28. codex_autorunner/core/drafts.py +136 -0
  29. codex_autorunner/core/engine.py +1531 -866
  30. codex_autorunner/core/exceptions.py +4 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +202 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +88 -0
  35. codex_autorunner/core/flows/reasons.py +52 -0
  36. codex_autorunner/core/flows/reconciler.py +131 -0
  37. codex_autorunner/core/flows/runtime.py +382 -0
  38. codex_autorunner/core/flows/store.py +568 -0
  39. codex_autorunner/core/flows/transition.py +138 -0
  40. codex_autorunner/core/flows/ux_helpers.py +257 -0
  41. codex_autorunner/core/flows/worker_process.py +242 -0
  42. codex_autorunner/core/git_utils.py +62 -0
  43. codex_autorunner/core/hub.py +136 -16
  44. codex_autorunner/core/locks.py +4 -0
  45. codex_autorunner/core/notifications.py +14 -2
  46. codex_autorunner/core/ports/__init__.py +28 -0
  47. codex_autorunner/core/ports/agent_backend.py +150 -0
  48. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  49. codex_autorunner/core/ports/run_event.py +91 -0
  50. codex_autorunner/core/prompt.py +15 -7
  51. codex_autorunner/core/redaction.py +29 -0
  52. codex_autorunner/core/review_context.py +5 -8
  53. codex_autorunner/core/run_index.py +6 -0
  54. codex_autorunner/core/runner_process.py +5 -2
  55. codex_autorunner/core/state.py +0 -88
  56. codex_autorunner/core/state_roots.py +57 -0
  57. codex_autorunner/core/supervisor_protocol.py +15 -0
  58. codex_autorunner/core/supervisor_utils.py +67 -0
  59. codex_autorunner/core/text_delta_coalescer.py +54 -0
  60. codex_autorunner/core/ticket_linter_cli.py +201 -0
  61. codex_autorunner/core/ticket_manager_cli.py +432 -0
  62. codex_autorunner/core/update.py +24 -16
  63. codex_autorunner/core/update_paths.py +28 -0
  64. codex_autorunner/core/update_runner.py +2 -0
  65. codex_autorunner/core/usage.py +164 -12
  66. codex_autorunner/core/utils.py +120 -11
  67. codex_autorunner/discovery.py +2 -4
  68. codex_autorunner/flows/review/__init__.py +17 -0
  69. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  70. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  71. codex_autorunner/flows/ticket_flow/definition.py +98 -0
  72. codex_autorunner/integrations/agents/__init__.py +17 -0
  73. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  74. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  75. codex_autorunner/integrations/agents/codex_backend.py +448 -0
  76. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  77. codex_autorunner/integrations/agents/opencode_backend.py +598 -0
  78. codex_autorunner/integrations/agents/runner.py +91 -0
  79. codex_autorunner/integrations/agents/wiring.py +271 -0
  80. codex_autorunner/integrations/app_server/client.py +583 -152
  81. codex_autorunner/integrations/app_server/env.py +2 -107
  82. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  83. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  84. codex_autorunner/integrations/telegram/adapter.py +204 -165
  85. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  86. codex_autorunner/integrations/telegram/config.py +221 -0
  87. codex_autorunner/integrations/telegram/constants.py +17 -2
  88. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  89. codex_autorunner/integrations/telegram/doctor.py +47 -0
  90. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
  91. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  92. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  93. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  94. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
  95. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  96. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  97. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  98. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
  99. codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
  100. codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
  101. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  102. codex_autorunner/integrations/telegram/helpers.py +111 -16
  103. codex_autorunner/integrations/telegram/outbox.py +208 -37
  104. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  105. codex_autorunner/integrations/telegram/service.py +221 -42
  106. codex_autorunner/integrations/telegram/state.py +100 -2
  107. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
  108. codex_autorunner/integrations/telegram/transport.py +39 -4
  109. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  110. codex_autorunner/manifest.py +2 -0
  111. codex_autorunner/plugin_api.py +22 -0
  112. codex_autorunner/routes/__init__.py +37 -67
  113. codex_autorunner/routes/agents.py +2 -137
  114. codex_autorunner/routes/analytics.py +3 -0
  115. codex_autorunner/routes/app_server.py +2 -131
  116. codex_autorunner/routes/base.py +2 -624
  117. codex_autorunner/routes/file_chat.py +7 -0
  118. codex_autorunner/routes/flows.py +7 -0
  119. codex_autorunner/routes/messages.py +7 -0
  120. codex_autorunner/routes/repos.py +2 -196
  121. codex_autorunner/routes/review.py +2 -147
  122. codex_autorunner/routes/sessions.py +2 -175
  123. codex_autorunner/routes/settings.py +2 -168
  124. codex_autorunner/routes/shared.py +2 -275
  125. codex_autorunner/routes/system.py +4 -188
  126. codex_autorunner/routes/usage.py +3 -0
  127. codex_autorunner/routes/voice.py +2 -119
  128. codex_autorunner/routes/workspace.py +3 -0
  129. codex_autorunner/server.py +3 -2
  130. codex_autorunner/static/agentControls.js +41 -11
  131. codex_autorunner/static/agentEvents.js +248 -0
  132. codex_autorunner/static/app.js +35 -24
  133. codex_autorunner/static/archive.js +826 -0
  134. codex_autorunner/static/archiveApi.js +37 -0
  135. codex_autorunner/static/autoRefresh.js +36 -8
  136. codex_autorunner/static/bootstrap.js +1 -0
  137. codex_autorunner/static/bus.js +1 -0
  138. codex_autorunner/static/cache.js +1 -0
  139. codex_autorunner/static/constants.js +20 -4
  140. codex_autorunner/static/dashboard.js +344 -325
  141. codex_autorunner/static/diffRenderer.js +37 -0
  142. codex_autorunner/static/docChatCore.js +324 -0
  143. codex_autorunner/static/docChatStorage.js +65 -0
  144. codex_autorunner/static/docChatVoice.js +65 -0
  145. codex_autorunner/static/docEditor.js +133 -0
  146. codex_autorunner/static/env.js +1 -0
  147. codex_autorunner/static/eventSummarizer.js +166 -0
  148. codex_autorunner/static/fileChat.js +182 -0
  149. codex_autorunner/static/health.js +155 -0
  150. codex_autorunner/static/hub.js +126 -185
  151. codex_autorunner/static/index.html +839 -863
  152. codex_autorunner/static/liveUpdates.js +1 -0
  153. codex_autorunner/static/loader.js +1 -0
  154. codex_autorunner/static/messages.js +873 -0
  155. codex_autorunner/static/mobileCompact.js +2 -1
  156. codex_autorunner/static/preserve.js +17 -0
  157. codex_autorunner/static/settings.js +149 -217
  158. codex_autorunner/static/smartRefresh.js +52 -0
  159. codex_autorunner/static/styles.css +8850 -3876
  160. codex_autorunner/static/tabs.js +175 -11
  161. codex_autorunner/static/terminal.js +32 -0
  162. codex_autorunner/static/terminalManager.js +34 -59
  163. codex_autorunner/static/ticketChatActions.js +333 -0
  164. codex_autorunner/static/ticketChatEvents.js +16 -0
  165. codex_autorunner/static/ticketChatStorage.js +16 -0
  166. codex_autorunner/static/ticketChatStream.js +264 -0
  167. codex_autorunner/static/ticketEditor.js +844 -0
  168. codex_autorunner/static/ticketVoice.js +9 -0
  169. codex_autorunner/static/tickets.js +1988 -0
  170. codex_autorunner/static/utils.js +43 -3
  171. codex_autorunner/static/voice.js +1 -0
  172. codex_autorunner/static/workspace.js +765 -0
  173. codex_autorunner/static/workspaceApi.js +53 -0
  174. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  175. codex_autorunner/surfaces/__init__.py +5 -0
  176. codex_autorunner/surfaces/cli/__init__.py +6 -0
  177. codex_autorunner/surfaces/cli/cli.py +1224 -0
  178. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  179. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  180. codex_autorunner/surfaces/web/__init__.py +1 -0
  181. codex_autorunner/surfaces/web/app.py +2019 -0
  182. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  183. codex_autorunner/surfaces/web/middleware.py +587 -0
  184. codex_autorunner/surfaces/web/pty_session.py +370 -0
  185. codex_autorunner/surfaces/web/review.py +6 -0
  186. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  187. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  188. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  189. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  190. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  191. codex_autorunner/surfaces/web/routes/base.py +615 -0
  192. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  193. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  194. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  195. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  196. codex_autorunner/surfaces/web/routes/review.py +148 -0
  197. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  198. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  199. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  200. codex_autorunner/surfaces/web/routes/system.py +196 -0
  201. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  202. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  203. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  204. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  205. codex_autorunner/surfaces/web/schemas.py +417 -0
  206. codex_autorunner/surfaces/web/static_assets.py +490 -0
  207. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  208. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  209. codex_autorunner/tickets/__init__.py +27 -0
  210. codex_autorunner/tickets/agent_pool.py +399 -0
  211. codex_autorunner/tickets/files.py +89 -0
  212. codex_autorunner/tickets/frontmatter.py +55 -0
  213. codex_autorunner/tickets/lint.py +102 -0
  214. codex_autorunner/tickets/models.py +97 -0
  215. codex_autorunner/tickets/outbox.py +244 -0
  216. codex_autorunner/tickets/replies.py +179 -0
  217. codex_autorunner/tickets/runner.py +881 -0
  218. codex_autorunner/tickets/spec_ingest.py +77 -0
  219. codex_autorunner/web/__init__.py +5 -1
  220. codex_autorunner/web/app.py +2 -1771
  221. codex_autorunner/web/hub_jobs.py +2 -191
  222. codex_autorunner/web/middleware.py +2 -587
  223. codex_autorunner/web/pty_session.py +2 -369
  224. codex_autorunner/web/runner_manager.py +2 -24
  225. codex_autorunner/web/schemas.py +2 -396
  226. codex_autorunner/web/static_assets.py +4 -484
  227. codex_autorunner/web/static_refresh.py +2 -85
  228. codex_autorunner/web/terminal_sessions.py +2 -77
  229. codex_autorunner/workspace/__init__.py +40 -0
  230. codex_autorunner/workspace/paths.py +335 -0
  231. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  232. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  233. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
  234. codex_autorunner/agents/execution/policy.py +0 -292
  235. codex_autorunner/agents/factory.py +0 -52
  236. codex_autorunner/agents/orchestrator.py +0 -358
  237. codex_autorunner/core/doc_chat.py +0 -1446
  238. codex_autorunner/core/snapshot.py +0 -580
  239. codex_autorunner/integrations/github/chatops.py +0 -268
  240. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  241. codex_autorunner/routes/docs.py +0 -381
  242. codex_autorunner/routes/github.py +0 -327
  243. codex_autorunner/routes/runs.py +0 -250
  244. codex_autorunner/spec_ingest.py +0 -812
  245. codex_autorunner/static/docChatActions.js +0 -287
  246. codex_autorunner/static/docChatEvents.js +0 -300
  247. codex_autorunner/static/docChatRender.js +0 -205
  248. codex_autorunner/static/docChatStream.js +0 -361
  249. codex_autorunner/static/docs.js +0 -20
  250. codex_autorunner/static/docsClipboard.js +0 -69
  251. codex_autorunner/static/docsCrud.js +0 -257
  252. codex_autorunner/static/docsDocUpdates.js +0 -62
  253. codex_autorunner/static/docsDrafts.js +0 -16
  254. codex_autorunner/static/docsElements.js +0 -69
  255. codex_autorunner/static/docsInit.js +0 -285
  256. codex_autorunner/static/docsParse.js +0 -160
  257. codex_autorunner/static/docsSnapshot.js +0 -87
  258. codex_autorunner/static/docsSpecIngest.js +0 -263
  259. codex_autorunner/static/docsState.js +0 -127
  260. codex_autorunner/static/docsThreadRegistry.js +0 -44
  261. codex_autorunner/static/docsUi.js +0 -153
  262. codex_autorunner/static/docsVoice.js +0 -56
  263. codex_autorunner/static/github.js +0 -504
  264. codex_autorunner/static/logs.js +0 -678
  265. codex_autorunner/static/review.js +0 -157
  266. codex_autorunner/static/runs.js +0 -418
  267. codex_autorunner/static/snapshot.js +0 -124
  268. codex_autorunner/static/state.js +0 -94
  269. codex_autorunner/static/todoPreview.js +0 -27
  270. codex_autorunner/workspace.py +0 -16
  271. codex_autorunner-0.1.2.dist-info/METADATA +0 -249
  272. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  273. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  274. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  275. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  276. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -41,14 +41,33 @@ class TelegramMessageTransport:
41
41
  message_id: int,
42
42
  text: str,
43
43
  *,
44
+ message_thread_id: Optional[int] = None,
44
45
  reply_markup: Optional[dict[str, Any]] = None,
45
46
  ) -> bool:
46
47
  try:
47
48
  payload_text, parse_mode = self._prepare_message(text)
49
+ if len(payload_text) > TELEGRAM_MAX_MESSAGE_LENGTH:
50
+ trimmed = trim_markdown_message(
51
+ payload_text,
52
+ max_len=TELEGRAM_MAX_MESSAGE_LENGTH,
53
+ render=(
54
+ _format_telegram_html
55
+ if parse_mode == "HTML"
56
+ else (
57
+ lambda v: (
58
+ _format_telegram_markdown(v, parse_mode)
59
+ if parse_mode in ("Markdown", "MarkdownV2")
60
+ else v
61
+ )
62
+ )
63
+ ),
64
+ )
65
+ payload_text = trimmed
48
66
  await self._bot.edit_message_text(
49
67
  chat_id,
50
68
  message_id,
51
69
  payload_text,
70
+ message_thread_id=message_thread_id,
52
71
  reply_markup=reply_markup,
53
72
  parse_mode=parse_mode,
54
73
  )
@@ -56,11 +75,17 @@ class TelegramMessageTransport:
56
75
  return False
57
76
  return True
58
77
 
59
- async def _delete_message(self, chat_id: int, message_id: Optional[int]) -> bool:
78
+ async def _delete_message(
79
+ self, chat_id: int, message_id: Optional[int], thread_id: Optional[int] = None
80
+ ) -> bool:
60
81
  if message_id is None:
61
82
  return False
62
83
  try:
63
- return bool(await self._bot.delete_message(chat_id, message_id))
84
+ return bool(
85
+ await self._bot.delete_message(
86
+ chat_id, message_id, message_thread_id=thread_id
87
+ )
88
+ )
64
89
  except Exception:
65
90
  return False
66
91
 
@@ -77,6 +102,7 @@ class TelegramMessageTransport:
77
102
  callback.chat_id,
78
103
  callback.message_id,
79
104
  text,
105
+ message_thread_id=callback.thread_id,
80
106
  reply_markup=reply_markup,
81
107
  )
82
108
 
@@ -358,7 +384,7 @@ class TelegramMessageTransport:
358
384
  thread_id: Optional[int] = None,
359
385
  reply_to: Optional[int] = None,
360
386
  caption: Optional[str] = None,
361
- ) -> None:
387
+ ) -> bool:
362
388
  try:
363
389
  await self._bot.send_document(
364
390
  chat_id,
@@ -368,6 +394,7 @@ class TelegramMessageTransport:
368
394
  reply_to_message_id=reply_to,
369
395
  caption=caption,
370
396
  )
397
+ return True
371
398
  except Exception as exc:
372
399
  log_event(
373
400
  self._logger,
@@ -378,6 +405,7 @@ class TelegramMessageTransport:
378
405
  reply_to_message_id=reply_to,
379
406
  exc=exc,
380
407
  )
408
+ return False
381
409
 
382
410
  async def _answer_callback(
383
411
  self, callback: Optional[TelegramCallbackQuery], text: str
@@ -385,7 +413,13 @@ class TelegramMessageTransport:
385
413
  if callback is None:
386
414
  return
387
415
  try:
388
- await self._bot.answer_callback_query(callback.callback_id, text=text)
416
+ await self._bot.answer_callback_query(
417
+ callback.callback_id,
418
+ chat_id=callback.chat_id,
419
+ thread_id=callback.thread_id,
420
+ message_id=callback.message_id,
421
+ text=text,
422
+ )
389
423
  except Exception as exc:
390
424
  log_event(
391
425
  self._logger,
@@ -393,6 +427,7 @@ class TelegramMessageTransport:
393
427
  "telegram.answer_callback.failed",
394
428
  chat_id=callback.chat_id,
395
429
  thread_id=callback.thread_id,
430
+ message_id=callback.message_id,
396
431
  callback_id=callback.callback_id,
397
432
  exc=exc,
398
433
  )
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Optional
4
+
5
+ from .adapter import TelegramMessage
6
+
7
+ TriggerMode = Literal["all", "mentions"]
8
+
9
+
10
+ def should_trigger_run(
11
+ message: TelegramMessage,
12
+ *,
13
+ text: str,
14
+ bot_username: Optional[str],
15
+ ) -> bool:
16
+ """Return True if this message should start a run in mentions-only mode.
17
+
18
+ This mirrors Takopi's "mentions" trigger mode semantics (subset):
19
+
20
+ - Always trigger in private chats.
21
+ - Trigger when the bot is explicitly mentioned: "@<bot_username>" anywhere in the text.
22
+ - Trigger when replying to a bot message (but ignore the common forum-topic
23
+ "implicit root reply" case where clients set reply_to_message_id == thread_id).
24
+ - Otherwise, do not trigger (commands and other explicit affordances are handled elsewhere).
25
+ """
26
+
27
+ if message.chat_type == "private":
28
+ return True
29
+
30
+ lowered = (text or "").lower()
31
+ if bot_username:
32
+ needle = f"@{bot_username}".lower()
33
+ if needle in lowered:
34
+ return True
35
+
36
+ implicit_topic_reply = (
37
+ message.thread_id is not None
38
+ and message.reply_to_message_id is not None
39
+ and message.reply_to_message_id == message.thread_id
40
+ )
41
+
42
+ if message.reply_to_is_bot and not implicit_topic_reply:
43
+ return True
44
+
45
+ if (
46
+ bot_username
47
+ and message.reply_to_username
48
+ and message.reply_to_username.lower() == bot_username.lower()
49
+ and not implicit_topic_reply
50
+ ):
51
+ return True
52
+
53
+ return False
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, cast
7
7
  import yaml
8
8
 
9
9
  MANIFEST_VERSION = 2
10
+ MANIFEST_HEADER = "# GENERATED by CAR - DO NOT EDIT\n"
10
11
  _SAFE_REPO_ID_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$")
11
12
  _SANITIZE_REPO_ID_PATTERN = re.compile(r"[^A-Za-z0-9._-]+")
12
13
 
@@ -194,4 +195,5 @@ def save_manifest(manifest_path: Path, manifest: Manifest, hub_root: Path) -> No
194
195
  "repos": [repo.to_dict(hub_root) for repo in manifest.repos],
195
196
  }
196
197
  with manifest_path.open("w", encoding="utf-8") as f:
198
+ f.write(MANIFEST_HEADER)
197
199
  yaml.safe_dump(payload, f, sort_keys=False)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ """Codex Autorunner plugin API metadata.
4
+
5
+ This module is intentionally small and stable. External plugins SHOULD depend
6
+ only on the public API in `codex_autorunner.api` + this version constant.
7
+
8
+ Notes:
9
+ - Backwards-incompatible changes to the plugin API MUST bump
10
+ `CAR_PLUGIN_API_VERSION`.
11
+ """
12
+
13
+ CAR_PLUGIN_API_VERSION = 1
14
+
15
+ # Entry point groups (Python packaging entry points).
16
+ #
17
+ # Plugins can publish new agent backends by defining an entry point:
18
+ #
19
+ # [project.entry-points."codex_autorunner.agent_backends"]
20
+ # myagent = "my_package.my_module:AGENT_BACKEND"
21
+ #
22
+ CAR_AGENT_ENTRYPOINT_GROUP = "codex_autorunner.agent_backends"
@@ -1,67 +1,37 @@
1
- """
2
- Modular API routes for the codex-autorunner server.
3
-
4
- This package splits the monolithic api_routes.py into focused modules:
5
- - base: Index, state streaming, and general endpoints
6
- - agents: Agent harness models and event streaming
7
- - app_server: App-server thread registry endpoints
8
- - docs: Document management (read/write) and chat
9
- - github: GitHub integration endpoints
10
- - repos: Run control (start/stop/resume/reset)
11
- - runs: Run telemetry and artifacts
12
- - sessions: Terminal session registry endpoints
13
- - settings: Session settings for autorunner overrides
14
- - voice: Voice transcription and config
15
- - terminal_images: Terminal image uploads
16
- """
17
-
18
- from pathlib import Path
19
-
20
- from fastapi import APIRouter
21
-
22
- from .agents import build_agents_routes
23
- from .app_server import build_app_server_routes
24
- from .base import build_base_routes
25
- from .docs import build_docs_routes
26
- from .github import build_github_routes
27
- from .repos import build_repos_routes
28
- from .review import build_review_routes
29
- from .runs import build_runs_routes
30
- from .sessions import build_sessions_routes
31
- from .settings import build_settings_routes
32
- from .system import build_system_routes
33
- from .terminal_images import build_terminal_image_routes
34
- from .voice import build_voice_routes
35
-
36
-
37
- def build_repo_router(static_dir: Path) -> APIRouter:
38
- """
39
- Build the complete API router by combining all route modules.
40
-
41
- Args:
42
- static_dir: Path to the static assets directory
43
-
44
- Returns:
45
- Combined APIRouter with all endpoints
46
- """
47
- router = APIRouter()
48
-
49
- # Include all route modules
50
- router.include_router(build_base_routes(static_dir))
51
- router.include_router(build_agents_routes())
52
- router.include_router(build_app_server_routes())
53
- router.include_router(build_docs_routes())
54
- router.include_router(build_github_routes())
55
- router.include_router(build_repos_routes())
56
- router.include_router(build_review_routes())
57
- router.include_router(build_runs_routes())
58
- router.include_router(build_sessions_routes())
59
- router.include_router(build_settings_routes())
60
- router.include_router(build_system_routes())
61
- router.include_router(build_terminal_image_routes())
62
- router.include_router(build_voice_routes())
63
-
64
- return router
65
-
66
-
67
- __all__ = ["build_repo_router"]
1
+ """Backward-compatible route exports."""
2
+
3
+ from . import ( # noqa: F401
4
+ analytics,
5
+ app_server,
6
+ base,
7
+ file_chat,
8
+ flows,
9
+ messages,
10
+ repos,
11
+ review,
12
+ sessions,
13
+ settings,
14
+ shared,
15
+ system,
16
+ usage,
17
+ voice,
18
+ workspace,
19
+ )
20
+
21
+ __all__ = [
22
+ "analytics",
23
+ "app_server",
24
+ "base",
25
+ "file_chat",
26
+ "flows",
27
+ "messages",
28
+ "repos",
29
+ "review",
30
+ "sessions",
31
+ "settings",
32
+ "shared",
33
+ "system",
34
+ "usage",
35
+ "voice",
36
+ "workspace",
37
+ ]
@@ -1,138 +1,3 @@
1
- """
2
- Agent harness support routes (models + event streaming).
3
- """
1
+ """Backward-compatible agent routes."""
4
2
 
5
- from __future__ import annotations
6
-
7
- from typing import Any, Optional
8
-
9
- from fastapi import APIRouter, HTTPException, Request
10
- from fastapi.responses import StreamingResponse
11
-
12
- from ..agents.codex.harness import CodexHarness
13
- from ..agents.opencode.harness import OpenCodeHarness
14
- from ..agents.opencode.supervisor import OpenCodeSupervisorError
15
- from ..agents.types import ModelCatalog
16
- from .shared import SSE_HEADERS
17
-
18
-
19
- def _available_agents(request: Request) -> tuple[list[dict[str, str]], str]:
20
- agents: list[dict[str, str]] = []
21
- default_agent: Optional[str] = None
22
-
23
- if getattr(request.app.state, "app_server_supervisor", None) is not None:
24
- agents.append({"id": "codex", "name": "Codex", "protocol_version": "2.0"})
25
- default_agent = "codex"
26
-
27
- if getattr(request.app.state, "opencode_supervisor", None) is not None:
28
- supervisor = getattr(request.app.state, "opencode_supervisor", None)
29
- version = None
30
- if supervisor and hasattr(supervisor, "_handles"):
31
- handles = supervisor._handles
32
- if handles:
33
- first_handle = next(iter(handles.values()), None)
34
- if first_handle:
35
- version = getattr(first_handle, "version", None)
36
- agent_data = {"id": "opencode", "name": "OpenCode"}
37
- if version:
38
- agent_data["version"] = str(version)
39
- agents.append(agent_data)
40
- if default_agent is None:
41
- default_agent = "opencode"
42
-
43
- if not agents:
44
- agents = [{"id": "codex", "name": "Codex", "protocol_version": "2.0"}]
45
- default_agent = "codex"
46
-
47
- return agents, default_agent or "codex"
48
-
49
-
50
- def _serialize_model_catalog(catalog: ModelCatalog) -> dict[str, Any]:
51
- return {
52
- "default_model": catalog.default_model,
53
- "models": [
54
- {
55
- "id": model.id,
56
- "display_name": model.display_name,
57
- "supports_reasoning": model.supports_reasoning,
58
- "reasoning_options": list(model.reasoning_options),
59
- }
60
- for model in catalog.models
61
- ],
62
- }
63
-
64
-
65
- def build_agents_routes() -> APIRouter:
66
- router = APIRouter()
67
-
68
- @router.get("/api/agents")
69
- def list_agents(request: Request) -> dict[str, Any]:
70
- agents, default_agent = _available_agents(request)
71
- return {"agents": agents, "default": default_agent}
72
-
73
- @router.get("/api/agents/{agent}/models")
74
- async def list_agent_models(agent: str, request: Request):
75
- agent_id = (agent or "").strip().lower()
76
- engine = request.app.state.engine
77
- if agent_id == "codex":
78
- supervisor = request.app.state.app_server_supervisor
79
- events = request.app.state.app_server_events
80
- if supervisor is None:
81
- raise HTTPException(status_code=404, detail="Codex harness unavailable")
82
- codex_harness = CodexHarness(supervisor, events)
83
- catalog = await codex_harness.model_catalog(engine.repo_root)
84
- return _serialize_model_catalog(catalog)
85
- if agent_id == "opencode":
86
- supervisor = getattr(request.app.state, "opencode_supervisor", None)
87
- if supervisor is None:
88
- raise HTTPException(
89
- status_code=404, detail="OpenCode harness unavailable"
90
- )
91
- try:
92
- opencode_harness = OpenCodeHarness(supervisor)
93
- catalog = await opencode_harness.model_catalog(engine.repo_root)
94
- return _serialize_model_catalog(catalog)
95
- except OpenCodeSupervisorError as exc:
96
- raise HTTPException(status_code=502, detail=str(exc)) from exc
97
- except Exception as exc:
98
- raise HTTPException(status_code=502, detail=str(exc)) from exc
99
- raise HTTPException(status_code=404, detail="Unknown agent")
100
-
101
- @router.get("/api/agents/{agent}/turns/{turn_id}/events")
102
- async def stream_agent_turn_events(
103
- agent: str, turn_id: str, request: Request, thread_id: Optional[str] = None
104
- ):
105
- agent_id = (agent or "").strip().lower()
106
- if agent_id == "codex":
107
- events = getattr(request.app.state, "app_server_events", None)
108
- if events is None:
109
- raise HTTPException(status_code=404, detail="Codex events unavailable")
110
- if not thread_id:
111
- raise HTTPException(status_code=400, detail="thread_id is required")
112
- return StreamingResponse(
113
- events.stream(thread_id, turn_id),
114
- media_type="text/event-stream",
115
- headers=SSE_HEADERS,
116
- )
117
- if agent_id == "opencode":
118
- if not thread_id:
119
- raise HTTPException(status_code=400, detail="thread_id is required")
120
- supervisor = getattr(request.app.state, "opencode_supervisor", None)
121
- if supervisor is None:
122
- raise HTTPException(
123
- status_code=404, detail="OpenCode events unavailable"
124
- )
125
- harness = OpenCodeHarness(supervisor)
126
- return StreamingResponse(
127
- harness.stream_events(
128
- request.app.state.engine.repo_root, thread_id, turn_id
129
- ),
130
- media_type="text/event-stream",
131
- headers=SSE_HEADERS,
132
- )
133
- raise HTTPException(status_code=404, detail="Unknown agent")
134
-
135
- return router
136
-
137
-
138
- __all__ = ["build_agents_routes"]
3
+ from ..surfaces.web.routes.agents import * # noqa: F401,F403
@@ -0,0 +1,3 @@
1
+ """Backward-compatible analytics routes."""
2
+
3
+ from ..surfaces.web.routes.analytics import * # noqa: F401,F403
@@ -1,132 +1,3 @@
1
- """
2
- App-server support routes (thread registry).
3
- """
1
+ """Backward-compatible app-server routes."""
4
2
 
5
- from pathlib import Path
6
-
7
- from fastapi import APIRouter, HTTPException, Request
8
- from fastapi.responses import FileResponse, StreamingResponse
9
-
10
- from ..core.app_server_threads import normalize_feature_key
11
- from ..core.utils import is_within
12
- from ..integrations.app_server.client import CodexAppServerError
13
- from ..web.schemas import (
14
- AppServerThreadArchiveRequest,
15
- AppServerThreadArchiveResponse,
16
- AppServerThreadResetAllResponse,
17
- AppServerThreadResetRequest,
18
- AppServerThreadResetResponse,
19
- AppServerThreadsResponse,
20
- )
21
- from .shared import SSE_HEADERS
22
-
23
-
24
- def build_app_server_routes() -> APIRouter:
25
- router = APIRouter()
26
-
27
- @router.get("/api/app-server/turns/{turn_id}/events")
28
- async def stream_app_server_turn_events(
29
- turn_id: str, request: Request, thread_id: str
30
- ):
31
- events = getattr(request.app.state, "app_server_events", None)
32
- if events is None:
33
- raise HTTPException(status_code=404, detail="App-server events unavailable")
34
- if not thread_id:
35
- raise HTTPException(status_code=400, detail="thread_id is required")
36
- return StreamingResponse(
37
- events.stream(thread_id, turn_id),
38
- media_type="text/event-stream",
39
- headers=SSE_HEADERS,
40
- )
41
-
42
- @router.get("/api/app-server/threads", response_model=AppServerThreadsResponse)
43
- def app_server_threads(request: Request):
44
- registry = request.app.state.app_server_threads
45
- return registry.feature_map()
46
-
47
- @router.get("/api/app-server/models")
48
- async def app_server_models(request: Request):
49
- engine = request.app.state.engine
50
- supervisor = request.app.state.app_server_supervisor
51
- try:
52
- client = await supervisor.get_client(engine.repo_root)
53
- return await client.model_list()
54
- except CodexAppServerError as exc:
55
- raise HTTPException(status_code=502, detail=str(exc)) from exc
56
-
57
- @router.post(
58
- "/api/app-server/threads/reset", response_model=AppServerThreadResetResponse
59
- )
60
- def reset_app_server_thread(request: Request, payload: AppServerThreadResetRequest):
61
- registry = request.app.state.app_server_threads
62
- try:
63
- key = normalize_feature_key(payload.key)
64
- except ValueError as exc:
65
- raise HTTPException(status_code=400, detail=str(exc)) from exc
66
- cleared = registry.reset_thread(key)
67
- return {"status": "ok", "key": key, "cleared": cleared}
68
-
69
- @router.post(
70
- "/api/app-server/threads/archive",
71
- response_model=AppServerThreadArchiveResponse,
72
- )
73
- async def archive_app_server_thread(
74
- request: Request, payload: AppServerThreadArchiveRequest
75
- ):
76
- thread_id = payload.thread_id.strip()
77
- if not thread_id:
78
- raise HTTPException(status_code=400, detail="thread_id is required")
79
- engine = request.app.state.engine
80
- supervisor = request.app.state.app_server_supervisor
81
- try:
82
- client = await supervisor.get_client(engine.repo_root)
83
- await client.thread_archive(thread_id)
84
- except CodexAppServerError as exc:
85
- raise HTTPException(status_code=502, detail=str(exc)) from exc
86
- return {"status": "ok", "thread_id": thread_id, "archived": True}
87
-
88
- @router.post(
89
- "/api/app-server/threads/reset-all",
90
- response_model=AppServerThreadResetAllResponse,
91
- )
92
- def reset_app_server_threads(request: Request):
93
- registry = request.app.state.app_server_threads
94
- registry.reset_all()
95
- return {"status": "ok", "cleared": True}
96
-
97
- @router.get("/api/app-server/threads/backup")
98
- def download_app_server_threads_backup(request: Request):
99
- registry = request.app.state.app_server_threads
100
- notice = registry.corruption_notice() or {}
101
- backup_path = notice.get("backup_path")
102
- if not isinstance(backup_path, str) or not backup_path:
103
- raise HTTPException(status_code=404, detail="No backup available")
104
- path = Path(backup_path)
105
- engine = request.app.state.engine
106
- if not is_within(engine.repo_root, path):
107
- raise HTTPException(status_code=400, detail="Invalid backup path")
108
- if not path.exists():
109
- raise HTTPException(status_code=404, detail="Backup not found")
110
- return FileResponse(path, filename=path.name)
111
-
112
- @router.get("/api/app-server/account")
113
- async def app_server_account(request: Request):
114
- engine = request.app.state.engine
115
- supervisor = request.app.state.app_server_supervisor
116
- try:
117
- client = await supervisor.get_client(engine.repo_root)
118
- return await client.account_read()
119
- except CodexAppServerError as exc:
120
- raise HTTPException(status_code=502, detail=str(exc)) from exc
121
-
122
- @router.get("/api/app-server/rate-limits")
123
- async def app_server_rate_limits(request: Request):
124
- engine = request.app.state.engine
125
- supervisor = request.app.state.app_server_supervisor
126
- try:
127
- client = await supervisor.get_client(engine.repo_root)
128
- return await client.rate_limits_read()
129
- except CodexAppServerError as exc:
130
- raise HTTPException(status_code=502, detail=str(exc)) from exc
131
-
132
- return router
3
+ from ..surfaces.web.routes.app_server import * # noqa: F401,F403