codex-autorunner 0.1.2__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +15 -9
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -196
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BaseTelegramSchema",
|
|
9
|
+
"TelegramPhotoSizeSchema",
|
|
10
|
+
"TelegramDocumentSchema",
|
|
11
|
+
"TelegramAudioSchema",
|
|
12
|
+
"TelegramVoiceSchema",
|
|
13
|
+
"TelegramMessageEntitySchema",
|
|
14
|
+
"TelegramMessageSchema",
|
|
15
|
+
"TelegramCallbackQuerySchema",
|
|
16
|
+
"TelegramUpdateSchema",
|
|
17
|
+
"parse_update_payload",
|
|
18
|
+
"parse_message_payload",
|
|
19
|
+
"parse_callback_query_payload",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseTelegramSchema(BaseModel):
|
|
24
|
+
model_config = {"extra": "ignore", "validate_assignment": False}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TelegramPhotoSizeSchema(BaseTelegramSchema):
|
|
28
|
+
file_id: str
|
|
29
|
+
file_unique_id: Optional[str] = None
|
|
30
|
+
width: int
|
|
31
|
+
height: int
|
|
32
|
+
file_size: Optional[int] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TelegramDocumentSchema(BaseTelegramSchema):
|
|
36
|
+
file_id: str
|
|
37
|
+
file_unique_id: Optional[str] = None
|
|
38
|
+
file_name: Optional[str] = None
|
|
39
|
+
mime_type: Optional[str] = None
|
|
40
|
+
file_size: Optional[int] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TelegramAudioSchema(BaseTelegramSchema):
|
|
44
|
+
file_id: str
|
|
45
|
+
file_unique_id: Optional[str] = None
|
|
46
|
+
duration: Optional[int] = None
|
|
47
|
+
file_name: Optional[str] = None
|
|
48
|
+
mime_type: Optional[str] = None
|
|
49
|
+
file_size: Optional[int] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TelegramVoiceSchema(BaseTelegramSchema):
|
|
53
|
+
file_id: str
|
|
54
|
+
file_unique_id: Optional[str] = None
|
|
55
|
+
duration: Optional[int] = None
|
|
56
|
+
mime_type: Optional[str] = None
|
|
57
|
+
file_size: Optional[int] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TelegramMessageEntitySchema(BaseTelegramSchema):
|
|
61
|
+
type: str
|
|
62
|
+
offset: int
|
|
63
|
+
length: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TelegramMessageSchema(BaseTelegramSchema):
|
|
67
|
+
message_id: int
|
|
68
|
+
chat: dict[str, Any] = Field(default_factory=dict)
|
|
69
|
+
from_user: Optional[dict[str, Any]] = Field(default=None, alias="from")
|
|
70
|
+
message_thread_id: Optional[int] = None
|
|
71
|
+
date: Optional[int] = None
|
|
72
|
+
text: Optional[str] = None
|
|
73
|
+
caption: Optional[str] = None
|
|
74
|
+
entities: Optional[list[dict[str, Any]]] = None
|
|
75
|
+
caption_entities: Optional[list[dict[str, Any]]] = None
|
|
76
|
+
photo: Optional[list[dict[str, Any]]] = None
|
|
77
|
+
document: Optional[dict[str, Any]] = None
|
|
78
|
+
audio: Optional[dict[str, Any]] = None
|
|
79
|
+
voice: Optional[dict[str, Any]] = None
|
|
80
|
+
media_group_id: Optional[str] = None
|
|
81
|
+
is_topic_message: bool = False
|
|
82
|
+
reply_to_message: Optional[dict[str, Any]] = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TelegramCallbackQuerySchema(BaseTelegramSchema):
|
|
86
|
+
id: str
|
|
87
|
+
from_user: dict[str, Any] = Field(alias="from")
|
|
88
|
+
data: Optional[str] = None
|
|
89
|
+
message: Optional[dict[str, Any]] = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TelegramUpdateSchema(BaseTelegramSchema):
|
|
93
|
+
update_id: int
|
|
94
|
+
message: Optional[dict[str, Any]] = None
|
|
95
|
+
edited_message: Optional[dict[str, Any]] = Field(
|
|
96
|
+
default=None, alias="edited_message"
|
|
97
|
+
)
|
|
98
|
+
callback_query: Optional[dict[str, Any]] = Field(
|
|
99
|
+
default=None, alias="callback_query"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_update_payload(payload: dict[str, Any]) -> TelegramUpdateSchema:
|
|
104
|
+
return TelegramUpdateSchema.model_validate(payload)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_message_payload(payload: dict[str, Any]) -> Optional[TelegramMessageSchema]:
|
|
108
|
+
try:
|
|
109
|
+
return TelegramMessageSchema.model_validate(payload)
|
|
110
|
+
except Exception:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def parse_callback_query_payload(
|
|
115
|
+
payload: dict[str, Any],
|
|
116
|
+
) -> Optional[TelegramCallbackQuerySchema]:
|
|
117
|
+
try:
|
|
118
|
+
return TelegramCallbackQuerySchema.model_validate(payload)
|
|
119
|
+
except Exception:
|
|
120
|
+
return None
|
|
@@ -8,6 +8,21 @@ from pathlib import Path
|
|
|
8
8
|
from typing import Any, Iterable, Optional
|
|
9
9
|
|
|
10
10
|
from .adapter import TelegramAllowlist
|
|
11
|
+
from .constants import (
|
|
12
|
+
CACHE_CLEANUP_INTERVAL_SECONDS,
|
|
13
|
+
COALESCE_BUFFER_TTL_SECONDS,
|
|
14
|
+
DEFAULT_AGENT_TURN_TIMEOUT_SECONDS,
|
|
15
|
+
MEDIA_BATCH_BUFFER_TTL_SECONDS,
|
|
16
|
+
MODEL_PENDING_TTL_SECONDS,
|
|
17
|
+
OVERSIZE_WARNING_TTL_SECONDS,
|
|
18
|
+
PENDING_APPROVAL_TTL_SECONDS,
|
|
19
|
+
PENDING_QUESTION_TTL_SECONDS,
|
|
20
|
+
PROGRESS_STREAM_TTL_SECONDS,
|
|
21
|
+
REASONING_BUFFER_TTL_SECONDS,
|
|
22
|
+
SELECTION_STATE_TTL_SECONDS,
|
|
23
|
+
TURN_PREVIEW_TTL_SECONDS,
|
|
24
|
+
UPDATE_ID_PERSIST_INTERVAL_SECONDS,
|
|
25
|
+
)
|
|
11
26
|
from .state import APPROVAL_MODE_YOLO, normalize_approval_mode
|
|
12
27
|
|
|
13
28
|
DEFAULT_ALLOWED_UPDATES = ("message", "edited_message", "callback_query")
|
|
@@ -17,6 +32,8 @@ DEFAULT_SAFE_APPROVAL_POLICY = "on-request"
|
|
|
17
32
|
DEFAULT_YOLO_APPROVAL_POLICY = "never"
|
|
18
33
|
DEFAULT_YOLO_SANDBOX_POLICY = "dangerFullAccess"
|
|
19
34
|
DEFAULT_PARSE_MODE = "HTML"
|
|
35
|
+
DEFAULT_TRIGGER_MODE = "all"
|
|
36
|
+
TRIGGER_MODE_OPTIONS = {"all", "mentions"}
|
|
20
37
|
DEFAULT_STATE_FILE = ".codex-autorunner/telegram_state.sqlite3"
|
|
21
38
|
DEFAULT_APP_SERVER_COMMAND = ["codex", "app-server"]
|
|
22
39
|
DEFAULT_APP_SERVER_MAX_HANDLES = 20
|
|
@@ -107,6 +124,22 @@ class TelegramBotShellConfig:
|
|
|
107
124
|
max_output_chars: int
|
|
108
125
|
|
|
109
126
|
|
|
127
|
+
@dataclass(frozen=True)
|
|
128
|
+
class TelegramBotCacheConfig:
|
|
129
|
+
cleanup_interval_seconds: float
|
|
130
|
+
coalesce_buffer_ttl_seconds: float
|
|
131
|
+
media_batch_buffer_ttl_seconds: float
|
|
132
|
+
model_pending_ttl_seconds: float
|
|
133
|
+
pending_approval_ttl_seconds: float
|
|
134
|
+
pending_question_ttl_seconds: float
|
|
135
|
+
reasoning_buffer_ttl_seconds: float
|
|
136
|
+
selection_state_ttl_seconds: float
|
|
137
|
+
turn_preview_ttl_seconds: float
|
|
138
|
+
progress_stream_ttl_seconds: float
|
|
139
|
+
oversize_warning_ttl_seconds: float
|
|
140
|
+
update_id_persist_interval_seconds: float
|
|
141
|
+
|
|
142
|
+
|
|
110
143
|
@dataclass(frozen=True)
|
|
111
144
|
class TelegramBotCommandScope:
|
|
112
145
|
scope: dict[str, Any]
|
|
@@ -150,12 +183,15 @@ class TelegramBotConfig:
|
|
|
150
183
|
allowed_chat_ids: set[int]
|
|
151
184
|
allowed_user_ids: set[int]
|
|
152
185
|
require_topics: bool
|
|
186
|
+
trigger_mode: str
|
|
153
187
|
defaults: TelegramBotDefaults
|
|
154
188
|
concurrency: TelegramBotConcurrency
|
|
155
189
|
media: TelegramBotMediaConfig
|
|
156
190
|
shell: TelegramBotShellConfig
|
|
191
|
+
cache: TelegramBotCacheConfig
|
|
157
192
|
progress_stream: TelegramBotProgressStreamConfig
|
|
158
193
|
command_registration: TelegramBotCommandRegistration
|
|
194
|
+
opencode_command: list[str]
|
|
159
195
|
state_file: Path
|
|
160
196
|
app_server_command_env: str
|
|
161
197
|
app_server_command: list[str]
|
|
@@ -164,6 +200,7 @@ class TelegramBotConfig:
|
|
|
164
200
|
app_server_start_timeout_seconds: float
|
|
165
201
|
app_server_start_max_attempts: Optional[int]
|
|
166
202
|
app_server_turn_timeout_seconds: Optional[float]
|
|
203
|
+
agent_turn_timeout_seconds: dict[str, Optional[float]]
|
|
167
204
|
poll_timeout_seconds: int
|
|
168
205
|
poll_request_timeout_seconds: Optional[float]
|
|
169
206
|
poll_allowed_updates: list[str]
|
|
@@ -171,6 +208,7 @@ class TelegramBotConfig:
|
|
|
171
208
|
metrics_mode: str
|
|
172
209
|
coalesce_window_seconds: float
|
|
173
210
|
agent_binaries: dict[str, str]
|
|
211
|
+
ticket_flow_auto_resume: bool
|
|
174
212
|
|
|
175
213
|
@classmethod
|
|
176
214
|
def from_raw(
|
|
@@ -183,6 +221,16 @@ class TelegramBotConfig:
|
|
|
183
221
|
) -> "TelegramBotConfig":
|
|
184
222
|
env = env or dict(os.environ)
|
|
185
223
|
cfg: dict[str, Any] = raw if isinstance(raw, dict) else {}
|
|
224
|
+
|
|
225
|
+
def _positive_float(value: Any, default: float) -> float:
|
|
226
|
+
try:
|
|
227
|
+
parsed = float(value)
|
|
228
|
+
except (TypeError, ValueError):
|
|
229
|
+
return default
|
|
230
|
+
if parsed <= 0:
|
|
231
|
+
return default
|
|
232
|
+
return parsed
|
|
233
|
+
|
|
186
234
|
enabled = bool(cfg.get("enabled", False))
|
|
187
235
|
mode = str(cfg.get("mode", "polling"))
|
|
188
236
|
bot_token_env = str(cfg.get("bot_token_env", "CAR_TELEGRAM_BOT_TOKEN"))
|
|
@@ -204,6 +252,10 @@ class TelegramBotConfig:
|
|
|
204
252
|
|
|
205
253
|
require_topics = bool(cfg.get("require_topics", False))
|
|
206
254
|
|
|
255
|
+
trigger_mode = (
|
|
256
|
+
str(cfg.get("trigger_mode", DEFAULT_TRIGGER_MODE)).strip().lower()
|
|
257
|
+
)
|
|
258
|
+
|
|
207
259
|
defaults_raw_value = cfg.get("defaults")
|
|
208
260
|
defaults_raw: dict[str, Any] = (
|
|
209
261
|
defaults_raw_value if isinstance(defaults_raw_value, dict) else {}
|
|
@@ -313,6 +365,81 @@ class TelegramBotConfig:
|
|
|
313
365
|
timeout_ms=shell_timeout_ms,
|
|
314
366
|
max_output_chars=shell_max_output_chars,
|
|
315
367
|
)
|
|
368
|
+
cache_raw_value = cfg.get("cache")
|
|
369
|
+
cache_raw: dict[str, Any] = (
|
|
370
|
+
cache_raw_value if isinstance(cache_raw_value, dict) else {}
|
|
371
|
+
)
|
|
372
|
+
cache = TelegramBotCacheConfig(
|
|
373
|
+
cleanup_interval_seconds=_positive_float(
|
|
374
|
+
cache_raw.get(
|
|
375
|
+
"cleanup_interval_seconds", CACHE_CLEANUP_INTERVAL_SECONDS
|
|
376
|
+
),
|
|
377
|
+
CACHE_CLEANUP_INTERVAL_SECONDS,
|
|
378
|
+
),
|
|
379
|
+
coalesce_buffer_ttl_seconds=_positive_float(
|
|
380
|
+
cache_raw.get(
|
|
381
|
+
"coalesce_buffer_ttl_seconds", COALESCE_BUFFER_TTL_SECONDS
|
|
382
|
+
),
|
|
383
|
+
COALESCE_BUFFER_TTL_SECONDS,
|
|
384
|
+
),
|
|
385
|
+
media_batch_buffer_ttl_seconds=_positive_float(
|
|
386
|
+
cache_raw.get(
|
|
387
|
+
"media_batch_buffer_ttl_seconds", MEDIA_BATCH_BUFFER_TTL_SECONDS
|
|
388
|
+
),
|
|
389
|
+
MEDIA_BATCH_BUFFER_TTL_SECONDS,
|
|
390
|
+
),
|
|
391
|
+
model_pending_ttl_seconds=_positive_float(
|
|
392
|
+
cache_raw.get("model_pending_ttl_seconds", MODEL_PENDING_TTL_SECONDS),
|
|
393
|
+
MODEL_PENDING_TTL_SECONDS,
|
|
394
|
+
),
|
|
395
|
+
pending_approval_ttl_seconds=_positive_float(
|
|
396
|
+
cache_raw.get(
|
|
397
|
+
"pending_approval_ttl_seconds", PENDING_APPROVAL_TTL_SECONDS
|
|
398
|
+
),
|
|
399
|
+
PENDING_APPROVAL_TTL_SECONDS,
|
|
400
|
+
),
|
|
401
|
+
pending_question_ttl_seconds=_positive_float(
|
|
402
|
+
cache_raw.get(
|
|
403
|
+
"pending_question_ttl_seconds", PENDING_QUESTION_TTL_SECONDS
|
|
404
|
+
),
|
|
405
|
+
PENDING_QUESTION_TTL_SECONDS,
|
|
406
|
+
),
|
|
407
|
+
reasoning_buffer_ttl_seconds=_positive_float(
|
|
408
|
+
cache_raw.get(
|
|
409
|
+
"reasoning_buffer_ttl_seconds", REASONING_BUFFER_TTL_SECONDS
|
|
410
|
+
),
|
|
411
|
+
REASONING_BUFFER_TTL_SECONDS,
|
|
412
|
+
),
|
|
413
|
+
selection_state_ttl_seconds=_positive_float(
|
|
414
|
+
cache_raw.get(
|
|
415
|
+
"selection_state_ttl_seconds", SELECTION_STATE_TTL_SECONDS
|
|
416
|
+
),
|
|
417
|
+
SELECTION_STATE_TTL_SECONDS,
|
|
418
|
+
),
|
|
419
|
+
turn_preview_ttl_seconds=_positive_float(
|
|
420
|
+
cache_raw.get("turn_preview_ttl_seconds", TURN_PREVIEW_TTL_SECONDS),
|
|
421
|
+
TURN_PREVIEW_TTL_SECONDS,
|
|
422
|
+
),
|
|
423
|
+
progress_stream_ttl_seconds=_positive_float(
|
|
424
|
+
cache_raw.get(
|
|
425
|
+
"progress_stream_ttl_seconds", PROGRESS_STREAM_TTL_SECONDS
|
|
426
|
+
),
|
|
427
|
+
PROGRESS_STREAM_TTL_SECONDS,
|
|
428
|
+
),
|
|
429
|
+
oversize_warning_ttl_seconds=_positive_float(
|
|
430
|
+
cache_raw.get(
|
|
431
|
+
"oversize_warning_ttl_seconds", OVERSIZE_WARNING_TTL_SECONDS
|
|
432
|
+
),
|
|
433
|
+
OVERSIZE_WARNING_TTL_SECONDS,
|
|
434
|
+
),
|
|
435
|
+
update_id_persist_interval_seconds=_positive_float(
|
|
436
|
+
cache_raw.get(
|
|
437
|
+
"update_id_persist_interval_seconds",
|
|
438
|
+
UPDATE_ID_PERSIST_INTERVAL_SECONDS,
|
|
439
|
+
),
|
|
440
|
+
UPDATE_ID_PERSIST_INTERVAL_SECONDS,
|
|
441
|
+
),
|
|
442
|
+
)
|
|
316
443
|
|
|
317
444
|
progress_raw_value = cfg.get("progress_stream")
|
|
318
445
|
progress_raw: dict[str, Any] = (
|
|
@@ -374,6 +501,11 @@ class TelegramBotConfig:
|
|
|
374
501
|
if coalesce_window_seconds <= 0:
|
|
375
502
|
coalesce_window_seconds = DEFAULT_COALESCE_WINDOW_SECONDS
|
|
376
503
|
|
|
504
|
+
ticket_flow_raw = (
|
|
505
|
+
cfg.get("ticket_flow") if isinstance(cfg.get("ticket_flow"), dict) else {}
|
|
506
|
+
)
|
|
507
|
+
ticket_flow_auto_resume = bool(ticket_flow_raw.get("auto_resume", False))
|
|
508
|
+
|
|
377
509
|
agent_binaries = dict(agent_binaries or {})
|
|
378
510
|
command_reg_raw_value = cfg.get("command_registration")
|
|
379
511
|
command_reg_raw: dict[str, Any] = (
|
|
@@ -385,9 +517,21 @@ class TelegramBotConfig:
|
|
|
385
517
|
enabled=command_reg_enabled, scopes=scopes
|
|
386
518
|
)
|
|
387
519
|
|
|
520
|
+
opencode_command = []
|
|
521
|
+
opencode_env_command = env.get("CAR_OPENCODE_COMMAND")
|
|
522
|
+
if opencode_env_command:
|
|
523
|
+
opencode_command = _parse_command(opencode_env_command)
|
|
524
|
+
if not opencode_command:
|
|
525
|
+
opencode_command = _parse_command(cfg.get("opencode_command"))
|
|
526
|
+
|
|
388
527
|
state_file = Path(cfg.get("state_file", DEFAULT_STATE_FILE))
|
|
389
528
|
if not state_file.is_absolute():
|
|
390
529
|
state_file = (root / state_file).resolve()
|
|
530
|
+
if state_file.suffix == ".json":
|
|
531
|
+
raise TelegramBotConfigError(
|
|
532
|
+
"telegram_bot.state_file must point to a SQLite database "
|
|
533
|
+
"(.sqlite3). Update your config to .codex-autorunner/telegram_state.sqlite3"
|
|
534
|
+
)
|
|
391
535
|
|
|
392
536
|
app_server_command_env = str(
|
|
393
537
|
cfg.get("app_server_command_env", "CAR_TELEGRAM_APP_SERVER_COMMAND")
|
|
@@ -440,6 +584,30 @@ class TelegramBotConfig:
|
|
|
440
584
|
if app_server_turn_timeout_seconds <= 0:
|
|
441
585
|
app_server_turn_timeout_seconds = None
|
|
442
586
|
|
|
587
|
+
agent_timeouts_raw = cfg.get("agent_timeouts")
|
|
588
|
+
has_explicit_codex_timeout = False
|
|
589
|
+
agent_timeouts: dict[str, Optional[float]] = dict(
|
|
590
|
+
DEFAULT_AGENT_TURN_TIMEOUT_SECONDS
|
|
591
|
+
)
|
|
592
|
+
if isinstance(agent_timeouts_raw, dict):
|
|
593
|
+
for key, value in agent_timeouts_raw.items():
|
|
594
|
+
if str(key) == "codex":
|
|
595
|
+
has_explicit_codex_timeout = True
|
|
596
|
+
if value is None:
|
|
597
|
+
agent_timeouts[str(key)] = None
|
|
598
|
+
continue
|
|
599
|
+
try:
|
|
600
|
+
timeout_value = float(value)
|
|
601
|
+
except (TypeError, ValueError):
|
|
602
|
+
continue
|
|
603
|
+
if timeout_value <= 0:
|
|
604
|
+
agent_timeouts[str(key)] = None
|
|
605
|
+
else:
|
|
606
|
+
agent_timeouts[str(key)] = timeout_value
|
|
607
|
+
|
|
608
|
+
if not has_explicit_codex_timeout:
|
|
609
|
+
agent_timeouts["codex"] = app_server_turn_timeout_seconds
|
|
610
|
+
|
|
443
611
|
polling_raw_value = cfg.get("polling")
|
|
444
612
|
polling_raw: dict[str, Any] = (
|
|
445
613
|
polling_raw_value if isinstance(polling_raw_value, dict) else {}
|
|
@@ -472,12 +640,15 @@ class TelegramBotConfig:
|
|
|
472
640
|
allowed_chat_ids=allowed_chat_ids,
|
|
473
641
|
allowed_user_ids=allowed_user_ids,
|
|
474
642
|
require_topics=require_topics,
|
|
643
|
+
trigger_mode=trigger_mode,
|
|
475
644
|
defaults=defaults,
|
|
476
645
|
concurrency=concurrency,
|
|
477
646
|
media=media,
|
|
478
647
|
shell=shell,
|
|
648
|
+
cache=cache,
|
|
479
649
|
progress_stream=progress_stream,
|
|
480
650
|
command_registration=command_registration,
|
|
651
|
+
opencode_command=opencode_command,
|
|
481
652
|
state_file=state_file,
|
|
482
653
|
app_server_command_env=app_server_command_env,
|
|
483
654
|
app_server_command=app_server_command,
|
|
@@ -486,6 +657,7 @@ class TelegramBotConfig:
|
|
|
486
657
|
app_server_start_timeout_seconds=app_server_start_timeout_seconds,
|
|
487
658
|
app_server_start_max_attempts=app_server_start_max_attempts,
|
|
488
659
|
app_server_turn_timeout_seconds=app_server_turn_timeout_seconds,
|
|
660
|
+
agent_turn_timeout_seconds=agent_timeouts,
|
|
489
661
|
poll_timeout_seconds=poll_timeout_seconds,
|
|
490
662
|
poll_request_timeout_seconds=poll_request_timeout_seconds,
|
|
491
663
|
poll_allowed_updates=poll_allowed_updates,
|
|
@@ -493,6 +665,7 @@ class TelegramBotConfig:
|
|
|
493
665
|
metrics_mode=metrics_mode,
|
|
494
666
|
coalesce_window_seconds=coalesce_window_seconds,
|
|
495
667
|
agent_binaries=agent_binaries,
|
|
668
|
+
ticket_flow_auto_resume=ticket_flow_auto_resume,
|
|
496
669
|
)
|
|
497
670
|
|
|
498
671
|
def validate(self) -> None:
|
|
@@ -516,6 +689,8 @@ class TelegramBotConfig:
|
|
|
516
689
|
issues.append(
|
|
517
690
|
"poll_request_timeout_seconds must be greater than poll_timeout_seconds"
|
|
518
691
|
)
|
|
692
|
+
if self.trigger_mode not in TRIGGER_MODE_OPTIONS:
|
|
693
|
+
issues.append(f"trigger_mode must be one of {sorted(TRIGGER_MODE_OPTIONS)}")
|
|
519
694
|
if issues:
|
|
520
695
|
raise TelegramBotConfigError("; ".join(issues))
|
|
521
696
|
|
|
@@ -18,7 +18,12 @@ RESUME_REFRESH_LIMIT = 10
|
|
|
18
18
|
TOKEN_USAGE_CACHE_LIMIT = 256
|
|
19
19
|
TOKEN_USAGE_TURN_CACHE_LIMIT = 512
|
|
20
20
|
DEFAULT_INTERRUPT_TIMEOUT_SECONDS = 30.0
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEFAULT_AGENT_TURN_TIMEOUT_SECONDS = {
|
|
24
|
+
"codex": 28800.0,
|
|
25
|
+
"opencode": 28800.0,
|
|
26
|
+
}
|
|
22
27
|
DEFAULT_WORKSPACE_STATE_ROOT = "~/.codex-autorunner/workspaces"
|
|
23
28
|
DEFAULT_AGENT = "codex"
|
|
24
29
|
APP_SERVER_START_BACKOFF_INITIAL_SECONDS = 1.0
|
|
@@ -100,6 +105,16 @@ TURN_PROGRESS_MAX_LEN = 160
|
|
|
100
105
|
TURN_PROGRESS_MIN_EDIT_INTERVAL_SECONDS = 1.0
|
|
101
106
|
TURN_PROGRESS_TTL_SECONDS = 900.0
|
|
102
107
|
PROGRESS_HEARTBEAT_INTERVAL_SECONDS = 5.0
|
|
108
|
+
COMPACT_MAX_ACTIONS = 10
|
|
109
|
+
COMPACT_MAX_TEXT_LENGTH = 80
|
|
110
|
+
STATUS_ICONS = {
|
|
111
|
+
"done": "✓",
|
|
112
|
+
"fail": "✗",
|
|
113
|
+
"warn": "⚠",
|
|
114
|
+
"running": "▸",
|
|
115
|
+
"update": "↻",
|
|
116
|
+
"thinking": "🧠",
|
|
117
|
+
}
|
|
103
118
|
COMMAND_DISABLED_TEMPLATE = "'/{name}' is disabled while a task is in progress."
|
|
104
119
|
MAX_MENTION_BYTES = 200_000
|
|
105
120
|
VALID_REASONING_EFFORTS = {"none", "minimal", "low", "medium", "high", "xhigh"}
|
|
@@ -72,14 +72,26 @@ def _log_denied(handlers: Any, update: TelegramUpdate) -> None:
|
|
|
72
72
|
chat_id = None
|
|
73
73
|
user_id = None
|
|
74
74
|
thread_id = None
|
|
75
|
+
message_id = None
|
|
76
|
+
update_id = None
|
|
77
|
+
conversation_id = None
|
|
75
78
|
if update.message:
|
|
76
79
|
chat_id = update.message.chat_id
|
|
77
80
|
user_id = update.message.from_user_id
|
|
78
81
|
thread_id = update.message.thread_id
|
|
82
|
+
message_id = update.message.message_id
|
|
83
|
+
update_id = update.message.update_id
|
|
79
84
|
elif update.callback:
|
|
80
85
|
chat_id = update.callback.chat_id
|
|
81
86
|
user_id = update.callback.from_user_id
|
|
82
87
|
thread_id = update.callback.thread_id
|
|
88
|
+
message_id = update.callback.message_id
|
|
89
|
+
update_id = update.callback.update_id
|
|
90
|
+
if chat_id is not None:
|
|
91
|
+
try:
|
|
92
|
+
conversation_id = topic_key(chat_id, thread_id)
|
|
93
|
+
except Exception:
|
|
94
|
+
conversation_id = None
|
|
83
95
|
log_event(
|
|
84
96
|
handlers._logger,
|
|
85
97
|
logging.INFO,
|
|
@@ -87,6 +99,9 @@ def _log_denied(handlers: Any, update: TelegramUpdate) -> None:
|
|
|
87
99
|
chat_id=chat_id,
|
|
88
100
|
user_id=user_id,
|
|
89
101
|
thread_id=thread_id,
|
|
102
|
+
message_id=message_id,
|
|
103
|
+
update_id=update_id,
|
|
104
|
+
conversation_id=conversation_id,
|
|
90
105
|
)
|
|
91
106
|
|
|
92
107
|
|
|
@@ -172,6 +187,7 @@ async def dispatch_update(handlers: Any, update: TelegramUpdate) -> None:
|
|
|
172
187
|
has_message=bool(update.message),
|
|
173
188
|
has_callback=bool(update.callback),
|
|
174
189
|
update_received_at=now_iso(),
|
|
190
|
+
conversation_id=conversation_id,
|
|
175
191
|
)
|
|
176
192
|
if (
|
|
177
193
|
update.update_id is not None
|
|
@@ -188,6 +204,7 @@ async def dispatch_update(handlers: Any, update: TelegramUpdate) -> None:
|
|
|
188
204
|
chat_id=context.chat_id,
|
|
189
205
|
thread_id=context.thread_id,
|
|
190
206
|
message_id=context.message_id,
|
|
207
|
+
conversation_id=conversation_id,
|
|
191
208
|
)
|
|
192
209
|
return
|
|
193
210
|
if not allowlist_allows(update, handlers._allowlist):
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Telegram integration doctor checks."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Union
|
|
4
|
+
|
|
5
|
+
from ...core.config import HubConfig, RepoConfig
|
|
6
|
+
from ...core.engine import DoctorCheck
|
|
7
|
+
from ...core.optional_dependencies import missing_optional_dependencies
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def telegram_doctor_checks(
|
|
11
|
+
config: Union[HubConfig, RepoConfig, Dict[str, Any]],
|
|
12
|
+
) -> list[DoctorCheck]:
|
|
13
|
+
"""Run Telegram-specific doctor checks.
|
|
14
|
+
|
|
15
|
+
Returns a list of DoctorCheck objects for Telegram integration.
|
|
16
|
+
Works with HubConfig, RepoConfig, or raw dict.
|
|
17
|
+
"""
|
|
18
|
+
checks: list[DoctorCheck] = []
|
|
19
|
+
telegram_cfg = None
|
|
20
|
+
|
|
21
|
+
if isinstance(config, dict):
|
|
22
|
+
telegram_cfg = config.get("telegram_bot")
|
|
23
|
+
elif isinstance(config.raw, dict):
|
|
24
|
+
telegram_cfg = config.raw.get("telegram_bot")
|
|
25
|
+
|
|
26
|
+
if isinstance(telegram_cfg, dict) and telegram_cfg.get("enabled") is True:
|
|
27
|
+
missing_telegram = missing_optional_dependencies((("httpx", "httpx"),))
|
|
28
|
+
if missing_telegram:
|
|
29
|
+
deps_list = ", ".join(missing_telegram)
|
|
30
|
+
checks.append(
|
|
31
|
+
DoctorCheck(
|
|
32
|
+
check_id="telegram.dependencies",
|
|
33
|
+
status="error",
|
|
34
|
+
message=f"Telegram is enabled but missing optional deps: {deps_list}",
|
|
35
|
+
fix="Install with `pip install codex-autorunner[telegram]`.",
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
checks.append(
|
|
40
|
+
DoctorCheck(
|
|
41
|
+
check_id="telegram.dependencies",
|
|
42
|
+
status="ok",
|
|
43
|
+
message="Telegram dependencies are installed.",
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return checks
|
|
@@ -11,7 +11,6 @@ from ..adapter import (
|
|
|
11
11
|
EffortCallback,
|
|
12
12
|
ModelCallback,
|
|
13
13
|
PageCallback,
|
|
14
|
-
PrFlowStartCallback,
|
|
15
14
|
QuestionCancelCallback,
|
|
16
15
|
QuestionCustomCallback,
|
|
17
16
|
QuestionDoneCallback,
|
|
@@ -80,9 +79,6 @@ async def handle_callback(handlers: Any, callback: TelegramCallbackQuery) -> Non
|
|
|
80
79
|
elif isinstance(parsed, ReviewCommitCallback):
|
|
81
80
|
if key:
|
|
82
81
|
await handlers._handle_review_commit_callback(key, callback, parsed)
|
|
83
|
-
elif isinstance(parsed, PrFlowStartCallback):
|
|
84
|
-
if key:
|
|
85
|
-
await handlers._handle_pr_flow_start_callback(key, callback, parsed)
|
|
86
82
|
elif isinstance(parsed, CancelCallback):
|
|
87
83
|
if key:
|
|
88
84
|
if parsed.kind == "interrupt":
|
|
@@ -7,6 +7,7 @@ from ..commands_spec import CommandSpec, build_command_specs
|
|
|
7
7
|
from .approvals import ApprovalsCommands
|
|
8
8
|
from .execution import ExecutionCommands
|
|
9
9
|
from .files import FilesCommands
|
|
10
|
+
from .flows import FlowCommands
|
|
10
11
|
from .formatting import FormattingHelpers
|
|
11
12
|
from .github import GitHubCommands
|
|
12
13
|
from .shared import SharedHelpers
|
|
@@ -19,6 +20,7 @@ __all__ = [
|
|
|
19
20
|
"GitHubCommands",
|
|
20
21
|
"FilesCommands",
|
|
21
22
|
"VoiceCommands",
|
|
23
|
+
"FlowCommands",
|
|
22
24
|
"ExecutionCommands",
|
|
23
25
|
"ApprovalsCommands",
|
|
24
26
|
"FormattingHelpers",
|