codex-autorunner 0.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 (147) hide show
  1. codex_autorunner/__init__.py +3 -0
  2. codex_autorunner/bootstrap.py +151 -0
  3. codex_autorunner/cli.py +886 -0
  4. codex_autorunner/codex_cli.py +79 -0
  5. codex_autorunner/codex_runner.py +17 -0
  6. codex_autorunner/core/__init__.py +1 -0
  7. codex_autorunner/core/about_car.py +125 -0
  8. codex_autorunner/core/codex_runner.py +100 -0
  9. codex_autorunner/core/config.py +1465 -0
  10. codex_autorunner/core/doc_chat.py +547 -0
  11. codex_autorunner/core/docs.py +37 -0
  12. codex_autorunner/core/engine.py +720 -0
  13. codex_autorunner/core/git_utils.py +206 -0
  14. codex_autorunner/core/hub.py +756 -0
  15. codex_autorunner/core/injected_context.py +9 -0
  16. codex_autorunner/core/locks.py +57 -0
  17. codex_autorunner/core/logging_utils.py +158 -0
  18. codex_autorunner/core/notifications.py +465 -0
  19. codex_autorunner/core/optional_dependencies.py +41 -0
  20. codex_autorunner/core/prompt.py +107 -0
  21. codex_autorunner/core/prompts.py +275 -0
  22. codex_autorunner/core/request_context.py +21 -0
  23. codex_autorunner/core/runner_controller.py +116 -0
  24. codex_autorunner/core/runner_process.py +29 -0
  25. codex_autorunner/core/snapshot.py +576 -0
  26. codex_autorunner/core/state.py +156 -0
  27. codex_autorunner/core/update.py +567 -0
  28. codex_autorunner/core/update_runner.py +44 -0
  29. codex_autorunner/core/usage.py +1221 -0
  30. codex_autorunner/core/utils.py +108 -0
  31. codex_autorunner/discovery.py +102 -0
  32. codex_autorunner/housekeeping.py +423 -0
  33. codex_autorunner/integrations/__init__.py +1 -0
  34. codex_autorunner/integrations/app_server/__init__.py +6 -0
  35. codex_autorunner/integrations/app_server/client.py +1386 -0
  36. codex_autorunner/integrations/app_server/supervisor.py +206 -0
  37. codex_autorunner/integrations/github/__init__.py +10 -0
  38. codex_autorunner/integrations/github/service.py +889 -0
  39. codex_autorunner/integrations/telegram/__init__.py +1 -0
  40. codex_autorunner/integrations/telegram/adapter.py +1401 -0
  41. codex_autorunner/integrations/telegram/commands_registry.py +104 -0
  42. codex_autorunner/integrations/telegram/config.py +450 -0
  43. codex_autorunner/integrations/telegram/constants.py +154 -0
  44. codex_autorunner/integrations/telegram/dispatch.py +162 -0
  45. codex_autorunner/integrations/telegram/handlers/__init__.py +0 -0
  46. codex_autorunner/integrations/telegram/handlers/approvals.py +241 -0
  47. codex_autorunner/integrations/telegram/handlers/callbacks.py +72 -0
  48. codex_autorunner/integrations/telegram/handlers/commands.py +160 -0
  49. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +5262 -0
  50. codex_autorunner/integrations/telegram/handlers/messages.py +477 -0
  51. codex_autorunner/integrations/telegram/handlers/selections.py +545 -0
  52. codex_autorunner/integrations/telegram/helpers.py +2084 -0
  53. codex_autorunner/integrations/telegram/notifications.py +164 -0
  54. codex_autorunner/integrations/telegram/outbox.py +174 -0
  55. codex_autorunner/integrations/telegram/rendering.py +102 -0
  56. codex_autorunner/integrations/telegram/retry.py +37 -0
  57. codex_autorunner/integrations/telegram/runtime.py +270 -0
  58. codex_autorunner/integrations/telegram/service.py +921 -0
  59. codex_autorunner/integrations/telegram/state.py +1223 -0
  60. codex_autorunner/integrations/telegram/transport.py +318 -0
  61. codex_autorunner/integrations/telegram/types.py +57 -0
  62. codex_autorunner/integrations/telegram/voice.py +413 -0
  63. codex_autorunner/manifest.py +150 -0
  64. codex_autorunner/routes/__init__.py +53 -0
  65. codex_autorunner/routes/base.py +470 -0
  66. codex_autorunner/routes/docs.py +275 -0
  67. codex_autorunner/routes/github.py +197 -0
  68. codex_autorunner/routes/repos.py +121 -0
  69. codex_autorunner/routes/sessions.py +137 -0
  70. codex_autorunner/routes/shared.py +137 -0
  71. codex_autorunner/routes/system.py +175 -0
  72. codex_autorunner/routes/terminal_images.py +107 -0
  73. codex_autorunner/routes/voice.py +128 -0
  74. codex_autorunner/server.py +23 -0
  75. codex_autorunner/spec_ingest.py +113 -0
  76. codex_autorunner/static/app.js +95 -0
  77. codex_autorunner/static/autoRefresh.js +209 -0
  78. codex_autorunner/static/bootstrap.js +105 -0
  79. codex_autorunner/static/bus.js +23 -0
  80. codex_autorunner/static/cache.js +52 -0
  81. codex_autorunner/static/constants.js +48 -0
  82. codex_autorunner/static/dashboard.js +795 -0
  83. codex_autorunner/static/docs.js +1514 -0
  84. codex_autorunner/static/env.js +99 -0
  85. codex_autorunner/static/github.js +168 -0
  86. codex_autorunner/static/hub.js +1511 -0
  87. codex_autorunner/static/index.html +622 -0
  88. codex_autorunner/static/loader.js +28 -0
  89. codex_autorunner/static/logs.js +690 -0
  90. codex_autorunner/static/mobileCompact.js +300 -0
  91. codex_autorunner/static/snapshot.js +116 -0
  92. codex_autorunner/static/state.js +87 -0
  93. codex_autorunner/static/styles.css +4966 -0
  94. codex_autorunner/static/tabs.js +50 -0
  95. codex_autorunner/static/terminal.js +21 -0
  96. codex_autorunner/static/terminalManager.js +3535 -0
  97. codex_autorunner/static/todoPreview.js +25 -0
  98. codex_autorunner/static/types.d.ts +8 -0
  99. codex_autorunner/static/utils.js +597 -0
  100. codex_autorunner/static/vendor/LICENSE.xterm +24 -0
  101. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-cyrillic-ext.woff2 +0 -0
  102. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-cyrillic.woff2 +0 -0
  103. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-greek.woff2 +0 -0
  104. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-latin-ext.woff2 +0 -0
  105. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-latin.woff2 +0 -0
  106. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-vietnamese.woff2 +0 -0
  107. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-cyrillic-ext.woff2 +0 -0
  108. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-cyrillic.woff2 +0 -0
  109. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-greek.woff2 +0 -0
  110. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-latin-ext.woff2 +0 -0
  111. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-latin.woff2 +0 -0
  112. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-vietnamese.woff2 +0 -0
  113. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-cyrillic-ext.woff2 +0 -0
  114. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-cyrillic.woff2 +0 -0
  115. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-greek.woff2 +0 -0
  116. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-latin-ext.woff2 +0 -0
  117. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-latin.woff2 +0 -0
  118. codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-vietnamese.woff2 +0 -0
  119. codex_autorunner/static/vendor/fonts/jetbrains-mono/OFL.txt +93 -0
  120. codex_autorunner/static/vendor/xterm-addon-fit.js +2 -0
  121. codex_autorunner/static/vendor/xterm.css +209 -0
  122. codex_autorunner/static/vendor/xterm.js +2 -0
  123. codex_autorunner/static/voice.js +591 -0
  124. codex_autorunner/voice/__init__.py +39 -0
  125. codex_autorunner/voice/capture.py +349 -0
  126. codex_autorunner/voice/config.py +167 -0
  127. codex_autorunner/voice/provider.py +66 -0
  128. codex_autorunner/voice/providers/__init__.py +7 -0
  129. codex_autorunner/voice/providers/openai_whisper.py +345 -0
  130. codex_autorunner/voice/resolver.py +36 -0
  131. codex_autorunner/voice/service.py +210 -0
  132. codex_autorunner/web/__init__.py +1 -0
  133. codex_autorunner/web/app.py +1037 -0
  134. codex_autorunner/web/hub_jobs.py +181 -0
  135. codex_autorunner/web/middleware.py +552 -0
  136. codex_autorunner/web/pty_session.py +357 -0
  137. codex_autorunner/web/runner_manager.py +25 -0
  138. codex_autorunner/web/schemas.py +253 -0
  139. codex_autorunner/web/static_assets.py +430 -0
  140. codex_autorunner/web/terminal_sessions.py +78 -0
  141. codex_autorunner/workspace.py +16 -0
  142. codex_autorunner-0.1.0.dist-info/METADATA +240 -0
  143. codex_autorunner-0.1.0.dist-info/RECORD +147 -0
  144. codex_autorunner-0.1.0.dist-info/WHEEL +5 -0
  145. codex_autorunner-0.1.0.dist-info/entry_points.txt +3 -0
  146. codex_autorunner-0.1.0.dist-info/licenses/LICENSE +21 -0
  147. codex_autorunner-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,318 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import secrets
5
+ from typing import Any, Optional
6
+
7
+ from ...core.logging_utils import log_event
8
+ from ...core.state import now_iso
9
+ from .adapter import TelegramCallbackQuery
10
+ from .constants import PLACEHOLDER_TEXT, TELEGRAM_MAX_MESSAGE_LENGTH
11
+ from .helpers import _format_turn_metrics, _should_trace_message, _with_conversation_id
12
+ from .state import OutboxRecord
13
+
14
+
15
+ class TelegramMessageTransport:
16
+ async def _send_message_with_outbox(
17
+ self,
18
+ chat_id: int,
19
+ text: str,
20
+ *,
21
+ thread_id: Optional[int],
22
+ reply_to: Optional[int],
23
+ placeholder_id: Optional[int] = None,
24
+ ) -> bool:
25
+ record = OutboxRecord(
26
+ record_id=secrets.token_hex(8),
27
+ chat_id=chat_id,
28
+ thread_id=thread_id,
29
+ reply_to_message_id=reply_to,
30
+ placeholder_message_id=placeholder_id,
31
+ text=text,
32
+ created_at=now_iso(),
33
+ )
34
+ return await self._outbox_manager.send_message_with_outbox(record)
35
+
36
+ async def _edit_message_text(
37
+ self,
38
+ chat_id: int,
39
+ message_id: int,
40
+ text: str,
41
+ *,
42
+ reply_markup: Optional[dict[str, Any]] = None,
43
+ ) -> bool:
44
+ try:
45
+ payload_text, parse_mode = self._prepare_message(text)
46
+ await self._bot.edit_message_text(
47
+ chat_id,
48
+ message_id,
49
+ payload_text,
50
+ reply_markup=reply_markup,
51
+ parse_mode=parse_mode,
52
+ )
53
+ except Exception:
54
+ return False
55
+ return True
56
+
57
+ async def _delete_message(self, chat_id: int, message_id: Optional[int]) -> bool:
58
+ if message_id is None:
59
+ return False
60
+ try:
61
+ return bool(await self._bot.delete_message(chat_id, message_id))
62
+ except Exception:
63
+ return False
64
+
65
+ async def _edit_callback_message(
66
+ self,
67
+ callback: TelegramCallbackQuery,
68
+ text: str,
69
+ *,
70
+ reply_markup: Optional[dict[str, Any]] = None,
71
+ ) -> bool:
72
+ if callback.chat_id is None or callback.message_id is None:
73
+ return False
74
+ return await self._edit_message_text(
75
+ callback.chat_id,
76
+ callback.message_id,
77
+ text,
78
+ reply_markup=reply_markup,
79
+ )
80
+
81
+ def _format_voice_transcript_message(self, text: str, agent_status: str) -> str:
82
+ header = "User:\n"
83
+ footer = f"\n\nAgent:\n{agent_status}"
84
+ max_len = TELEGRAM_MAX_MESSAGE_LENGTH
85
+ available = max_len - len(header) - len(footer)
86
+ if available <= 0:
87
+ return f"{header}{footer.lstrip()}"
88
+ transcript = text
89
+ truncation_note = "\n\n...(truncated)"
90
+ if len(transcript) > available:
91
+ remaining = available - len(truncation_note)
92
+ if remaining < 0:
93
+ remaining = 0
94
+ transcript = transcript[:remaining].rstrip()
95
+ transcript = f"{transcript}{truncation_note}"
96
+ return f"{header}{transcript}{footer}"
97
+
98
+ async def _send_voice_transcript_message(
99
+ self,
100
+ chat_id: int,
101
+ text: str,
102
+ *,
103
+ thread_id: Optional[int],
104
+ reply_to: Optional[int],
105
+ ) -> Optional[int]:
106
+ payload_text, parse_mode = self._prepare_outgoing_text(
107
+ text,
108
+ chat_id=chat_id,
109
+ thread_id=thread_id,
110
+ reply_to=reply_to,
111
+ )
112
+ response = await self._bot.send_message(
113
+ chat_id,
114
+ payload_text,
115
+ message_thread_id=thread_id,
116
+ reply_to_message_id=reply_to,
117
+ parse_mode=parse_mode,
118
+ )
119
+ message_id = response.get("message_id") if isinstance(response, dict) else None
120
+ return message_id if isinstance(message_id, int) else None
121
+
122
+ async def _finalize_voice_transcript(
123
+ self,
124
+ chat_id: int,
125
+ message_id: Optional[int],
126
+ transcript_text: Optional[str],
127
+ ) -> None:
128
+ if message_id is None or transcript_text is None:
129
+ return
130
+ final_message = self._format_voice_transcript_message(
131
+ transcript_text,
132
+ "Reply below.",
133
+ )
134
+ await self._edit_message_text(chat_id, message_id, final_message)
135
+
136
+ async def _send_placeholder(
137
+ self,
138
+ chat_id: int,
139
+ *,
140
+ thread_id: Optional[int],
141
+ reply_to: Optional[int],
142
+ ) -> Optional[int]:
143
+ try:
144
+ payload_text, parse_mode = self._prepare_outgoing_text(
145
+ PLACEHOLDER_TEXT,
146
+ chat_id=chat_id,
147
+ thread_id=thread_id,
148
+ reply_to=reply_to,
149
+ )
150
+ response = await self._bot.send_message(
151
+ chat_id,
152
+ payload_text,
153
+ message_thread_id=thread_id,
154
+ reply_to_message_id=reply_to,
155
+ parse_mode=parse_mode,
156
+ )
157
+ except Exception as exc:
158
+ log_event(
159
+ self._logger,
160
+ logging.WARNING,
161
+ "telegram.placeholder.failed",
162
+ chat_id=chat_id,
163
+ thread_id=thread_id,
164
+ reply_to_message_id=reply_to,
165
+ exc=exc,
166
+ )
167
+ return None
168
+ message_id = response.get("message_id") if isinstance(response, dict) else None
169
+ return message_id if isinstance(message_id, int) else None
170
+
171
+ async def _deliver_turn_response(
172
+ self,
173
+ *,
174
+ chat_id: int,
175
+ thread_id: Optional[int],
176
+ reply_to: Optional[int],
177
+ placeholder_id: Optional[int],
178
+ response: str,
179
+ ) -> bool:
180
+ return await self._send_message_with_outbox(
181
+ chat_id,
182
+ response,
183
+ thread_id=thread_id,
184
+ reply_to=reply_to,
185
+ placeholder_id=placeholder_id,
186
+ )
187
+
188
+ async def _send_turn_metrics(
189
+ self,
190
+ *,
191
+ chat_id: int,
192
+ thread_id: Optional[int],
193
+ reply_to: Optional[int],
194
+ elapsed_seconds: Optional[float],
195
+ token_usage: Optional[dict[str, Any]],
196
+ ) -> bool:
197
+ metrics = _format_turn_metrics(token_usage, elapsed_seconds)
198
+ if not metrics:
199
+ return False
200
+ return await self._send_message_with_outbox(
201
+ chat_id,
202
+ metrics,
203
+ thread_id=thread_id,
204
+ reply_to=reply_to,
205
+ )
206
+
207
+ async def _send_message(
208
+ self,
209
+ chat_id: int,
210
+ text: str,
211
+ *,
212
+ thread_id: Optional[int] = None,
213
+ reply_to: Optional[int] = None,
214
+ reply_markup: Optional[dict[str, Any]] = None,
215
+ ) -> None:
216
+ if _should_trace_message(text):
217
+ text = _with_conversation_id(
218
+ text,
219
+ chat_id=chat_id,
220
+ thread_id=thread_id,
221
+ )
222
+ prefix = self._build_debug_prefix(
223
+ chat_id=chat_id,
224
+ thread_id=thread_id,
225
+ reply_to=reply_to,
226
+ )
227
+ if prefix:
228
+ text = f"{prefix}{text}"
229
+ parse_mode = self._config.parse_mode
230
+ if parse_mode:
231
+ rendered, used_mode = self._render_message(text)
232
+ if used_mode and len(rendered) > TELEGRAM_MAX_MESSAGE_LENGTH:
233
+ extension = "txt"
234
+ if used_mode in ("Markdown", "MarkdownV2"):
235
+ extension = "md"
236
+ elif used_mode == "HTML":
237
+ extension = "html"
238
+ await self._send_document(
239
+ chat_id,
240
+ text.encode("utf-8"),
241
+ filename=f"response.{extension}",
242
+ thread_id=thread_id,
243
+ reply_to=reply_to,
244
+ caption="Response too long; see attached.",
245
+ )
246
+ return
247
+ payload_text = rendered if used_mode else text
248
+ await self._bot.send_message_chunks(
249
+ chat_id,
250
+ payload_text,
251
+ message_thread_id=thread_id,
252
+ reply_to_message_id=reply_to,
253
+ reply_markup=reply_markup,
254
+ parse_mode=used_mode,
255
+ )
256
+ return
257
+ payload_text, parse_mode = self._prepare_outgoing_text(
258
+ text,
259
+ chat_id=chat_id,
260
+ thread_id=thread_id,
261
+ reply_to=reply_to,
262
+ )
263
+ await self._bot.send_message_chunks(
264
+ chat_id,
265
+ payload_text,
266
+ message_thread_id=thread_id,
267
+ reply_to_message_id=reply_to,
268
+ reply_markup=reply_markup,
269
+ parse_mode=parse_mode,
270
+ )
271
+
272
+ async def _send_document(
273
+ self,
274
+ chat_id: int,
275
+ data: bytes,
276
+ *,
277
+ filename: str,
278
+ thread_id: Optional[int] = None,
279
+ reply_to: Optional[int] = None,
280
+ caption: Optional[str] = None,
281
+ ) -> None:
282
+ try:
283
+ await self._bot.send_document(
284
+ chat_id,
285
+ data,
286
+ filename=filename,
287
+ message_thread_id=thread_id,
288
+ reply_to_message_id=reply_to,
289
+ caption=caption,
290
+ )
291
+ except Exception as exc:
292
+ log_event(
293
+ self._logger,
294
+ logging.WARNING,
295
+ "telegram.send_document.failed",
296
+ chat_id=chat_id,
297
+ thread_id=thread_id,
298
+ reply_to_message_id=reply_to,
299
+ exc=exc,
300
+ )
301
+
302
+ async def _answer_callback(
303
+ self, callback: Optional[TelegramCallbackQuery], text: str
304
+ ) -> None:
305
+ if callback is None:
306
+ return
307
+ try:
308
+ await self._bot.answer_callback_query(callback.callback_id, text=text)
309
+ except Exception as exc:
310
+ log_event(
311
+ self._logger,
312
+ logging.WARNING,
313
+ "telegram.answer_callback.failed",
314
+ chat_id=callback.chat_id,
315
+ thread_id=callback.thread_id,
316
+ callback_id=callback.callback_id,
317
+ exc=exc,
318
+ )
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import dataclasses
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from ..app_server.client import ApprovalDecision
9
+ from .helpers import ModelOption
10
+
11
+
12
+ @dataclass
13
+ class PendingApproval:
14
+ request_id: str
15
+ turn_id: str
16
+ codex_thread_id: Optional[str]
17
+ chat_id: int
18
+ thread_id: Optional[int]
19
+ topic_key: Optional[str]
20
+ message_id: Optional[int]
21
+ created_at: str
22
+ future: asyncio.Future[ApprovalDecision]
23
+
24
+
25
+ @dataclass
26
+ class TurnContext:
27
+ topic_key: str
28
+ chat_id: int
29
+ thread_id: Optional[int]
30
+ codex_thread_id: Optional[str]
31
+ reply_to_message_id: Optional[int]
32
+ placeholder_message_id: Optional[int] = None
33
+
34
+
35
+ @dataclass
36
+ class CompactState:
37
+ summary_text: str
38
+ display_text: str
39
+ message_id: int
40
+ created_at: str
41
+
42
+
43
+ @dataclass
44
+ class SelectionState:
45
+ items: list[tuple[str, str]]
46
+ page: int = 0
47
+ button_labels: Optional[dict[str, str]] = None
48
+
49
+
50
+ @dataclass
51
+ class ReviewCommitSelectionState(SelectionState):
52
+ delivery: str = "inline"
53
+
54
+
55
+ @dataclass
56
+ class ModelPickerState(SelectionState):
57
+ options: dict[str, ModelOption] = dataclasses.field(default_factory=dict)