codex-autorunner 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +113 -4
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/runtime.py +59 -18
- codex_autorunner/agents/opencode/supervisor.py +4 -0
- codex_autorunner/agents/registry.py +36 -7
- codex_autorunner/bootstrap.py +226 -4
- codex_autorunner/cli.py +5 -1174
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +20 -0
- codex_autorunner/core/about_car.py +119 -1
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_threads.py +17 -2
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +6 -2
- codex_autorunner/core/config.py +433 -4
- codex_autorunner/core/context_awareness.py +38 -0
- codex_autorunner/core/docs.py +0 -122
- codex_autorunner/core/drafts.py +58 -4
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/filebox.py +265 -0
- codex_autorunner/core/flows/controller.py +96 -2
- codex_autorunner/core/flows/models.py +13 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +134 -0
- codex_autorunner/core/flows/runtime.py +57 -4
- codex_autorunner/core/flows/store.py +142 -7
- codex_autorunner/core/flows/transition.py +27 -15
- codex_autorunner/core/flows/ux_helpers.py +272 -0
- codex_autorunner/core/flows/worker_process.py +32 -6
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +291 -20
- codex_autorunner/core/lifecycle_events.py +253 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/path_utils.py +2 -1
- codex_autorunner/core/pma_audit.py +224 -0
- codex_autorunner/core/pma_context.py +496 -0
- codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
- codex_autorunner/core/pma_lifecycle.py +527 -0
- codex_autorunner/core/pma_queue.py +367 -0
- codex_autorunner/core/pma_safety.py +221 -0
- codex_autorunner/core/pma_state.py +115 -0
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +13 -8
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/{integrations/agents → core/ports}/run_event.py +23 -6
- codex_autorunner/core/prompt.py +0 -80
- codex_autorunner/core/prompts.py +56 -172
- codex_autorunner/core/redaction.py +0 -4
- codex_autorunner/core/review_context.py +11 -9
- codex_autorunner/core/runner_controller.py +35 -33
- codex_autorunner/core/runner_state.py +147 -0
- codex_autorunner/core/runtime.py +829 -0
- codex_autorunner/core/sqlite_utils.py +13 -4
- codex_autorunner/core/state.py +7 -10
- codex_autorunner/core/state_roots.py +62 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/templates/__init__.py +39 -0
- codex_autorunner/core/templates/git_mirror.py +234 -0
- codex_autorunner/core/templates/provenance.py +56 -0
- codex_autorunner/core/templates/scan_cache.py +120 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +218 -0
- codex_autorunner/core/ticket_manager_cli.py +494 -0
- codex_autorunner/core/time_utils.py +11 -0
- codex_autorunner/core/types.py +18 -0
- codex_autorunner/core/update.py +4 -5
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +125 -15
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +37 -34
- codex_autorunner/flows/ticket_flow/definition.py +52 -3
- codex_autorunner/integrations/agents/__init__.py +11 -19
- codex_autorunner/integrations/agents/backend_orchestrator.py +302 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +177 -25
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +305 -32
- codex_autorunner/integrations/agents/runner.py +86 -0
- codex_autorunner/integrations/agents/wiring.py +279 -0
- codex_autorunner/integrations/app_server/client.py +7 -60
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/telegram/adapter.py +65 -0
- codex_autorunner/integrations/telegram/config.py +46 -0
- codex_autorunner/integrations/telegram/constants.py +1 -1
- codex_autorunner/integrations/telegram/doctor.py +228 -6
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
- codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1496 -71
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +206 -48
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +20 -3
- codex_autorunner/integrations/telegram/handlers/messages.py +27 -1
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +22 -1
- codex_autorunner/integrations/telegram/runtime.py +9 -4
- codex_autorunner/integrations/telegram/service.py +45 -10
- codex_autorunner/integrations/telegram/state.py +38 -0
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +338 -43
- codex_autorunner/integrations/telegram/transport.py +13 -4
- codex_autorunner/integrations/templates/__init__.py +27 -0
- codex_autorunner/integrations/templates/scan_agent.py +312 -0
- codex_autorunner/routes/__init__.py +37 -76
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +2 -238
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -596
- codex_autorunner/routes/file_chat.py +4 -833
- codex_autorunner/routes/flows.py +4 -977
- codex_autorunner/routes/messages.py +4 -456
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -193
- codex_autorunner/routes/usage.py +2 -86
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +2 -270
- codex_autorunner/server.py +4 -4
- codex_autorunner/static/agentControls.js +61 -16
- codex_autorunner/static/app.js +126 -14
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +7 -7
- codex_autorunner/static/chatUploads.js +137 -0
- codex_autorunner/static/dashboard.js +224 -171
- codex_autorunner/static/docChatCore.js +185 -13
- codex_autorunner/static/fileChat.js +68 -40
- codex_autorunner/static/fileboxUi.js +159 -0
- codex_autorunner/static/hub.js +114 -131
- codex_autorunner/static/index.html +375 -49
- codex_autorunner/static/messages.js +568 -87
- codex_autorunner/static/notifications.js +255 -0
- codex_autorunner/static/pma.js +1167 -0
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +128 -6
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/streamUtils.js +57 -0
- codex_autorunner/static/styles.css +9798 -6143
- codex_autorunner/static/tabs.js +152 -11
- codex_autorunner/static/templateReposSettings.js +225 -0
- codex_autorunner/static/terminal.js +18 -0
- codex_autorunner/static/ticketChatActions.js +165 -3
- codex_autorunner/static/ticketChatStream.js +17 -119
- codex_autorunner/static/ticketEditor.js +137 -15
- codex_autorunner/static/ticketTemplates.js +798 -0
- codex_autorunner/static/tickets.js +821 -98
- codex_autorunner/static/turnEvents.js +27 -0
- codex_autorunner/static/turnResume.js +33 -0
- codex_autorunner/static/utils.js +39 -0
- codex_autorunner/static/workspace.js +389 -82
- codex_autorunner/static/workspaceFileBrowser.js +15 -13
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +2534 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/cli/pma_cli.py +817 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2223 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +82 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +284 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +1117 -0
- codex_autorunner/surfaces/web/routes/filebox.py +227 -0
- codex_autorunner/surfaces/web/routes/flows.py +1354 -0
- codex_autorunner/surfaces/web/routes/messages.py +490 -0
- codex_autorunner/surfaces/web/routes/pma.py +1652 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +277 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/templates.py +634 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +469 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +8 -1
- codex_autorunner/tickets/agent_pool.py +53 -4
- codex_autorunner/tickets/files.py +37 -16
- codex_autorunner/tickets/lint.py +50 -0
- codex_autorunner/tickets/models.py +6 -1
- codex_autorunner/tickets/outbox.py +50 -2
- codex_autorunner/tickets/runner.py +396 -57
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1949
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -586
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -376
- codex_autorunner/web/static_assets.py +4 -441
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/paths.py +49 -33
- codex_autorunner-1.2.0.dist-info/METADATA +150 -0
- codex_autorunner-1.2.0.dist-info/RECORD +339 -0
- codex_autorunner/core/adapter_utils.py +0 -21
- codex_autorunner/core/engine.py +0 -2653
- codex_autorunner/core/static_assets.py +0 -55
- codex_autorunner-1.0.0.dist-info/METADATA +0 -246
- codex_autorunner-1.0.0.dist-info/RECORD +0 -251
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -62,6 +62,7 @@ DEFAULT_MESSAGE_OVERFLOW = "document"
|
|
|
62
62
|
MESSAGE_OVERFLOW_OPTIONS = {"document", "split", "trim"}
|
|
63
63
|
DEFAULT_METRICS_MODE = "separate"
|
|
64
64
|
METRICS_MODE_OPTIONS = {"separate", "append_to_response", "append_to_progress"}
|
|
65
|
+
DEFAULT_PAUSE_DISPATCH_MAX_FILE_BYTES = 50 * 1024 * 1024
|
|
65
66
|
|
|
66
67
|
PARSE_MODE_ALIASES = {
|
|
67
68
|
"html": "HTML",
|
|
@@ -160,6 +161,14 @@ class TelegramBotProgressStreamConfig:
|
|
|
160
161
|
min_edit_interval_seconds: float
|
|
161
162
|
|
|
162
163
|
|
|
164
|
+
@dataclass(frozen=True)
|
|
165
|
+
class PauseDispatchNotifications:
|
|
166
|
+
enabled: bool
|
|
167
|
+
send_attachments: bool
|
|
168
|
+
max_file_size_bytes: int
|
|
169
|
+
chunk_long_messages: bool
|
|
170
|
+
|
|
171
|
+
|
|
163
172
|
@dataclass(frozen=True)
|
|
164
173
|
class TelegramMediaCandidate:
|
|
165
174
|
kind: str
|
|
@@ -209,6 +218,8 @@ class TelegramBotConfig:
|
|
|
209
218
|
coalesce_window_seconds: float
|
|
210
219
|
agent_binaries: dict[str, str]
|
|
211
220
|
ticket_flow_auto_resume: bool
|
|
221
|
+
pause_dispatch_notifications: PauseDispatchNotifications
|
|
222
|
+
default_notification_chat_id: Optional[int]
|
|
212
223
|
|
|
213
224
|
@classmethod
|
|
214
225
|
def from_raw(
|
|
@@ -506,6 +517,39 @@ class TelegramBotConfig:
|
|
|
506
517
|
)
|
|
507
518
|
ticket_flow_auto_resume = bool(ticket_flow_raw.get("auto_resume", False))
|
|
508
519
|
|
|
520
|
+
pause_raw_value = cfg.get("pause_dispatch_notifications")
|
|
521
|
+
pause_raw: dict[str, Any] = (
|
|
522
|
+
pause_raw_value if isinstance(pause_raw_value, dict) else {}
|
|
523
|
+
)
|
|
524
|
+
pause_enabled = bool(pause_raw.get("enabled", enabled))
|
|
525
|
+
pause_send_attachments = bool(pause_raw.get("send_attachments", True))
|
|
526
|
+
pause_max_file_size_bytes = int(
|
|
527
|
+
pause_raw.get("max_file_size_bytes", DEFAULT_PAUSE_DISPATCH_MAX_FILE_BYTES)
|
|
528
|
+
)
|
|
529
|
+
if pause_max_file_size_bytes <= 0:
|
|
530
|
+
pause_max_file_size_bytes = DEFAULT_PAUSE_DISPATCH_MAX_FILE_BYTES
|
|
531
|
+
pause_chunk_long_messages = bool(pause_raw.get("chunk_long_messages", True))
|
|
532
|
+
pause_dispatch_notifications = PauseDispatchNotifications(
|
|
533
|
+
enabled=pause_enabled,
|
|
534
|
+
send_attachments=pause_send_attachments,
|
|
535
|
+
max_file_size_bytes=pause_max_file_size_bytes,
|
|
536
|
+
chunk_long_messages=pause_chunk_long_messages,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
default_notification_chat_raw = cfg.get("default_notification_chat_id")
|
|
540
|
+
default_notification_chat_id: Optional[int] = None
|
|
541
|
+
env_chat_candidates = _parse_int_list(env.get(chat_id_env))
|
|
542
|
+
if default_notification_chat_raw is not None:
|
|
543
|
+
try:
|
|
544
|
+
default_notification_chat_id = int(default_notification_chat_raw)
|
|
545
|
+
except (TypeError, ValueError):
|
|
546
|
+
default_notification_chat_id = None
|
|
547
|
+
if default_notification_chat_id is None:
|
|
548
|
+
if env_chat_candidates:
|
|
549
|
+
default_notification_chat_id = env_chat_candidates[0]
|
|
550
|
+
elif allowed_chat_ids:
|
|
551
|
+
default_notification_chat_id = min(allowed_chat_ids)
|
|
552
|
+
|
|
509
553
|
agent_binaries = dict(agent_binaries or {})
|
|
510
554
|
command_reg_raw_value = cfg.get("command_registration")
|
|
511
555
|
command_reg_raw: dict[str, Any] = (
|
|
@@ -666,6 +710,8 @@ class TelegramBotConfig:
|
|
|
666
710
|
coalesce_window_seconds=coalesce_window_seconds,
|
|
667
711
|
agent_binaries=agent_binaries,
|
|
668
712
|
ticket_flow_auto_resume=ticket_flow_auto_resume,
|
|
713
|
+
pause_dispatch_notifications=pause_dispatch_notifications,
|
|
714
|
+
default_notification_chat_id=default_notification_chat_id,
|
|
669
715
|
)
|
|
670
716
|
|
|
671
717
|
def validate(self) -> None:
|
|
@@ -24,7 +24,6 @@ DEFAULT_AGENT_TURN_TIMEOUT_SECONDS = {
|
|
|
24
24
|
"codex": 28800.0,
|
|
25
25
|
"opencode": 28800.0,
|
|
26
26
|
}
|
|
27
|
-
DEFAULT_WORKSPACE_STATE_ROOT = "~/.codex-autorunner/workspaces"
|
|
28
27
|
DEFAULT_AGENT = "codex"
|
|
29
28
|
APP_SERVER_START_BACKOFF_INITIAL_SECONDS = 1.0
|
|
30
29
|
APP_SERVER_START_BACKOFF_MAX_SECONDS = 30.0
|
|
@@ -66,6 +65,7 @@ UPDATE_PICKER_PROMPT = "Select update target (buttons below)."
|
|
|
66
65
|
REVIEW_COMMIT_PICKER_PROMPT = (
|
|
67
66
|
"Select a commit to review (buttons below or reply with number)."
|
|
68
67
|
)
|
|
68
|
+
FLOW_RUNS_PICKER_PROMPT = "Select a ticket flow run (buttons below)."
|
|
69
69
|
REVIEW_COMMIT_BUTTON_LABEL_LIMIT = 80
|
|
70
70
|
UPDATE_TARGET_OPTIONS = (
|
|
71
71
|
("both", "Both (web + Telegram)"),
|
|
@@ -1,47 +1,269 @@
|
|
|
1
1
|
"""Telegram integration doctor checks."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from pathlib import Path
|
|
3
7
|
from typing import Any, Dict, Union
|
|
4
8
|
|
|
5
9
|
from ...core.config import HubConfig, RepoConfig
|
|
6
|
-
from ...core.engine import DoctorCheck
|
|
7
10
|
from ...core.optional_dependencies import missing_optional_dependencies
|
|
11
|
+
from ...core.runtime import DoctorCheck
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
STUCK_TURN_THRESHOLD_MINUTES = 30
|
|
16
|
+
STATE_FILE_CHECK = ".codex-autorunner/telegram_state.sqlite3"
|
|
8
17
|
|
|
9
18
|
|
|
10
19
|
def telegram_doctor_checks(
|
|
11
20
|
config: Union[HubConfig, RepoConfig, Dict[str, Any]],
|
|
21
|
+
repo_root: Union[Path, None] = None,
|
|
12
22
|
) -> list[DoctorCheck]:
|
|
13
23
|
"""Run Telegram-specific doctor checks.
|
|
14
24
|
|
|
15
25
|
Returns a list of DoctorCheck objects for Telegram integration.
|
|
16
26
|
Works with HubConfig, RepoConfig, or raw dict.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config: HubConfig, RepoConfig, or raw dict
|
|
30
|
+
repo_root: Optional repo root path for state file checks
|
|
17
31
|
"""
|
|
18
32
|
checks: list[DoctorCheck] = []
|
|
19
33
|
telegram_cfg = None
|
|
20
34
|
|
|
21
35
|
if isinstance(config, dict):
|
|
22
36
|
telegram_cfg = config.get("telegram_bot")
|
|
37
|
+
if not telegram_cfg:
|
|
38
|
+
telegram_cfg = config.get("notifications", {}).get("telegram", {})
|
|
23
39
|
elif isinstance(config.raw, dict):
|
|
24
40
|
telegram_cfg = config.raw.get("telegram_bot")
|
|
41
|
+
if not telegram_cfg:
|
|
42
|
+
telegram_cfg = config.raw.get("notifications", {}).get("telegram", {})
|
|
25
43
|
|
|
26
|
-
|
|
44
|
+
enabled = isinstance(telegram_cfg, dict) and telegram_cfg.get("enabled") is True
|
|
45
|
+
|
|
46
|
+
if enabled:
|
|
27
47
|
missing_telegram = missing_optional_dependencies((("httpx", "httpx"),))
|
|
28
48
|
if missing_telegram:
|
|
29
49
|
deps_list = ", ".join(missing_telegram)
|
|
30
50
|
checks.append(
|
|
31
51
|
DoctorCheck(
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
name="Telegram dependencies",
|
|
53
|
+
passed=False,
|
|
34
54
|
message=f"Telegram is enabled but missing optional deps: {deps_list}",
|
|
55
|
+
check_id="telegram.dependencies",
|
|
35
56
|
fix="Install with `pip install codex-autorunner[telegram]`.",
|
|
36
57
|
)
|
|
37
58
|
)
|
|
59
|
+
return checks
|
|
38
60
|
else:
|
|
39
61
|
checks.append(
|
|
40
62
|
DoctorCheck(
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
name="Telegram dependencies",
|
|
64
|
+
passed=True,
|
|
43
65
|
message="Telegram dependencies are installed.",
|
|
66
|
+
check_id="telegram.dependencies",
|
|
67
|
+
severity="info",
|
|
44
68
|
)
|
|
45
69
|
)
|
|
46
70
|
|
|
71
|
+
bot_token_env = telegram_cfg.get("bot_token_env", "CAR_TELEGRAM_BOT_TOKEN")
|
|
72
|
+
chat_id_env = telegram_cfg.get("chat_id_env", "CAR_TELEGRAM_CHAT_ID")
|
|
73
|
+
|
|
74
|
+
bot_token = os.environ.get(bot_token_env)
|
|
75
|
+
chat_id = os.environ.get(chat_id_env)
|
|
76
|
+
|
|
77
|
+
if not bot_token:
|
|
78
|
+
checks.append(
|
|
79
|
+
DoctorCheck(
|
|
80
|
+
name="Telegram bot token",
|
|
81
|
+
passed=False,
|
|
82
|
+
message=f"Telegram bot token not found in environment: {bot_token_env}",
|
|
83
|
+
check_id="telegram.bot_token",
|
|
84
|
+
fix=f"Set {bot_token_env} environment variable or disable Telegram.",
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
checks.append(
|
|
89
|
+
DoctorCheck(
|
|
90
|
+
name="Telegram bot token",
|
|
91
|
+
passed=True,
|
|
92
|
+
message=f"Bot token configured (env: {bot_token_env}).",
|
|
93
|
+
check_id="telegram.bot_token",
|
|
94
|
+
severity="info",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if not chat_id:
|
|
99
|
+
checks.append(
|
|
100
|
+
DoctorCheck(
|
|
101
|
+
name="Telegram chat ID",
|
|
102
|
+
passed=False,
|
|
103
|
+
message=f"Telegram chat_id not found in environment: {chat_id_env}",
|
|
104
|
+
check_id="telegram.chat_id",
|
|
105
|
+
fix=f"Set {chat_id_env} environment variable for notifications.",
|
|
106
|
+
severity="warning",
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
checks.append(
|
|
111
|
+
DoctorCheck(
|
|
112
|
+
name="Telegram chat ID",
|
|
113
|
+
passed=True,
|
|
114
|
+
message=f"Chat ID configured (env: {chat_id_env}).",
|
|
115
|
+
check_id="telegram.chat_id",
|
|
116
|
+
severity="info",
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
allowed_chats = telegram_cfg.get("allowed_chat_ids", [])
|
|
121
|
+
allowed_users = telegram_cfg.get("allowed_user_ids", [])
|
|
122
|
+
if not allowed_chats and not allowed_users:
|
|
123
|
+
checks.append(
|
|
124
|
+
DoctorCheck(
|
|
125
|
+
name="Telegram access control",
|
|
126
|
+
passed=False,
|
|
127
|
+
message="No allowed_chat_ids or allowed_user_ids configured",
|
|
128
|
+
check_id="telegram.access_control",
|
|
129
|
+
fix="Configure allowed_chat_ids or allowed_user_ids in telegram_bot config.",
|
|
130
|
+
severity="warning",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
checks.append(
|
|
135
|
+
DoctorCheck(
|
|
136
|
+
name="Telegram access control",
|
|
137
|
+
passed=True,
|
|
138
|
+
message=f"Access control configured: {len(allowed_chats)} chats, {len(allowed_users)} users.",
|
|
139
|
+
check_id="telegram.access_control",
|
|
140
|
+
severity="info",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
state_file_path = None
|
|
145
|
+
if repo_root:
|
|
146
|
+
state_file_path = repo_root / STATE_FILE_CHECK
|
|
147
|
+
if not state_file_path.exists():
|
|
148
|
+
checks.append(
|
|
149
|
+
DoctorCheck(
|
|
150
|
+
name="Telegram state file",
|
|
151
|
+
passed=False,
|
|
152
|
+
message=f"Telegram state file not found: {state_file_path}",
|
|
153
|
+
check_id="telegram.state_file",
|
|
154
|
+
severity="warning",
|
|
155
|
+
fix="Run a Telegram command to initialize the state file.",
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
checks.append(
|
|
160
|
+
DoctorCheck(
|
|
161
|
+
name="Telegram state file",
|
|
162
|
+
passed=True,
|
|
163
|
+
message=f"State file exists: {state_file_path}",
|
|
164
|
+
check_id="telegram.state_file",
|
|
165
|
+
severity="info",
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
_check_stuck_turns(checks, state_file_path)
|
|
170
|
+
|
|
171
|
+
mode = telegram_cfg.get("mode", "polling")
|
|
172
|
+
if mode not in ("polling", "webhook"):
|
|
173
|
+
checks.append(
|
|
174
|
+
DoctorCheck(
|
|
175
|
+
name="Telegram mode",
|
|
176
|
+
passed=False,
|
|
177
|
+
message=f"Invalid Telegram mode: {mode}",
|
|
178
|
+
check_id="telegram.mode",
|
|
179
|
+
fix="Set mode to 'polling' or 'webhook' in telegram_bot config.",
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
checks.append(
|
|
184
|
+
DoctorCheck(
|
|
185
|
+
name="Telegram mode",
|
|
186
|
+
passed=True,
|
|
187
|
+
message=f"Telegram mode: {mode}",
|
|
188
|
+
check_id="telegram.mode",
|
|
189
|
+
severity="info",
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
checks.append(
|
|
194
|
+
DoctorCheck(
|
|
195
|
+
name="Telegram enabled",
|
|
196
|
+
passed=True,
|
|
197
|
+
message="Telegram integration is disabled.",
|
|
198
|
+
check_id="telegram.enabled",
|
|
199
|
+
severity="info",
|
|
200
|
+
fix="Set telegram_bot.enabled=true in config to enable.",
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
47
204
|
return checks
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _check_stuck_turns(checks: list[DoctorCheck], state_file_path: Path) -> None:
|
|
208
|
+
"""Check for stuck turns in Telegram state."""
|
|
209
|
+
try:
|
|
210
|
+
from ...core.sqlite_utils import connect_sqlite
|
|
211
|
+
|
|
212
|
+
conn = connect_sqlite(state_file_path)
|
|
213
|
+
cursor = conn.cursor()
|
|
214
|
+
|
|
215
|
+
cursor.execute("PRAGMA table_info(turns)")
|
|
216
|
+
columns = {row[1] for row in cursor.fetchall()}
|
|
217
|
+
|
|
218
|
+
if "status" not in columns:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
threshold = datetime.now(timezone.utc) - timedelta(
|
|
222
|
+
minutes=STUCK_TURN_THRESHOLD_MINUTES
|
|
223
|
+
)
|
|
224
|
+
cursor.execute(
|
|
225
|
+
"""
|
|
226
|
+
SELECT topic_key, status, updated_at
|
|
227
|
+
FROM turns
|
|
228
|
+
WHERE status = 'running' AND updated_at < ?
|
|
229
|
+
ORDER BY updated_at ASC
|
|
230
|
+
LIMIT 5
|
|
231
|
+
""",
|
|
232
|
+
(threshold.isoformat(),),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
stuck_turns = cursor.fetchall()
|
|
236
|
+
conn.close()
|
|
237
|
+
|
|
238
|
+
if stuck_turns:
|
|
239
|
+
topics = ", ".join([turn[0] for turn in stuck_turns])
|
|
240
|
+
checks.append(
|
|
241
|
+
DoctorCheck(
|
|
242
|
+
name="Telegram stuck turns",
|
|
243
|
+
passed=False,
|
|
244
|
+
message=f"Found {len(stuck_turns)} stuck turns (inactive > {STUCK_TURN_THRESHOLD_MINUTES}m): {topics}",
|
|
245
|
+
check_id="telegram.stuck_turns",
|
|
246
|
+
fix="Review logs and consider restarting the bot or clearing stuck turns.",
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
checks.append(
|
|
251
|
+
DoctorCheck(
|
|
252
|
+
name="Telegram stuck turns",
|
|
253
|
+
passed=True,
|
|
254
|
+
message="No stuck turns detected.",
|
|
255
|
+
check_id="telegram.stuck_turns",
|
|
256
|
+
severity="info",
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
except Exception as exc:
|
|
260
|
+
logger.debug("Failed to check for stuck turns: %s", exc)
|
|
261
|
+
checks.append(
|
|
262
|
+
DoctorCheck(
|
|
263
|
+
name="Telegram stuck turns",
|
|
264
|
+
passed=True,
|
|
265
|
+
message=f"Could not check for stuck turns: {exc}",
|
|
266
|
+
check_id="telegram.stuck_turns",
|
|
267
|
+
severity="warning",
|
|
268
|
+
)
|
|
269
|
+
)
|
|
@@ -9,6 +9,8 @@ from ..adapter import (
|
|
|
9
9
|
CancelCallback,
|
|
10
10
|
CompactCallback,
|
|
11
11
|
EffortCallback,
|
|
12
|
+
FlowCallback,
|
|
13
|
+
FlowRunCallback,
|
|
12
14
|
ModelCallback,
|
|
13
15
|
PageCallback,
|
|
14
16
|
QuestionCancelCallback,
|
|
@@ -91,3 +93,8 @@ async def handle_callback(handlers: Any, callback: TelegramCallbackQuery) -> Non
|
|
|
91
93
|
elif isinstance(parsed, PageCallback):
|
|
92
94
|
if key:
|
|
93
95
|
await handlers._handle_selection_page(key, parsed, callback)
|
|
96
|
+
elif isinstance(parsed, FlowCallback):
|
|
97
|
+
await handlers._handle_flow_callback(callback, parsed)
|
|
98
|
+
elif isinstance(parsed, FlowRunCallback):
|
|
99
|
+
if key:
|
|
100
|
+
await handlers._handle_flow_run_callback(key, callback, parsed)
|