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
|
@@ -7,24 +7,25 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
import socket
|
|
9
9
|
import time
|
|
10
|
+
from datetime import datetime
|
|
10
11
|
from pathlib import Path
|
|
11
|
-
from typing import TYPE_CHECKING, Any, Coroutine, Optional, Sequence
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Optional, Sequence
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from .progress_stream import TurnProgressTracker
|
|
15
16
|
from .state import TelegramTopicRecord
|
|
16
17
|
|
|
17
18
|
from ...agents.opencode.supervisor import OpenCodeSupervisor
|
|
19
|
+
from ...core.flows.models import FlowRunRecord
|
|
18
20
|
from ...core.locks import process_alive
|
|
19
21
|
from ...core.logging_utils import log_event
|
|
20
22
|
from ...core.request_context import reset_conversation_id, set_conversation_id
|
|
21
23
|
from ...core.state import now_iso
|
|
22
24
|
from ...core.text_delta_coalescer import TextDeltaCoalescer
|
|
23
|
-
from ...core.utils import
|
|
24
|
-
build_opencode_supervisor,
|
|
25
|
-
)
|
|
25
|
+
from ...core.utils import build_opencode_supervisor
|
|
26
26
|
from ...housekeeping import HousekeepingConfig, run_housekeeping_for_roots
|
|
27
27
|
from ...manifest import load_manifest
|
|
28
|
+
from ...tickets.replies import dispatch_reply, ensure_reply_dirs, resolve_reply_paths
|
|
28
29
|
from ...voice import VoiceConfig, VoiceService
|
|
29
30
|
from ..app_server.supervisor import WorkspaceAppServerSupervisor
|
|
30
31
|
from .adapter import (
|
|
@@ -45,21 +46,9 @@ from .config import (
|
|
|
45
46
|
TelegramMediaCandidate,
|
|
46
47
|
)
|
|
47
48
|
from .constants import (
|
|
48
|
-
CACHE_CLEANUP_INTERVAL_SECONDS,
|
|
49
|
-
COALESCE_BUFFER_TTL_SECONDS,
|
|
50
49
|
DEFAULT_INTERRUPT_TIMEOUT_SECONDS,
|
|
51
50
|
DEFAULT_WORKSPACE_STATE_ROOT,
|
|
52
|
-
MEDIA_BATCH_BUFFER_TTL_SECONDS,
|
|
53
|
-
MODEL_PENDING_TTL_SECONDS,
|
|
54
|
-
OVERSIZE_WARNING_TTL_SECONDS,
|
|
55
|
-
PENDING_APPROVAL_TTL_SECONDS,
|
|
56
|
-
PENDING_QUESTION_TTL_SECONDS,
|
|
57
|
-
PROGRESS_STREAM_TTL_SECONDS,
|
|
58
51
|
QUEUED_PLACEHOLDER_TEXT,
|
|
59
|
-
REASONING_BUFFER_TTL_SECONDS,
|
|
60
|
-
SELECTION_STATE_TTL_SECONDS,
|
|
61
|
-
TURN_PREVIEW_TTL_SECONDS,
|
|
62
|
-
UPDATE_ID_PERSIST_INTERVAL_SECONDS,
|
|
63
52
|
TurnKey,
|
|
64
53
|
)
|
|
65
54
|
from .dispatch import dispatch_update
|
|
@@ -88,6 +77,7 @@ from .state import (
|
|
|
88
77
|
parse_topic_key,
|
|
89
78
|
topic_key,
|
|
90
79
|
)
|
|
80
|
+
from .ticket_flow_bridge import TelegramTicketFlowBridge
|
|
91
81
|
from .transport import TelegramMessageTransport
|
|
92
82
|
from .types import (
|
|
93
83
|
CompactState,
|
|
@@ -100,17 +90,19 @@ from .types import (
|
|
|
100
90
|
)
|
|
101
91
|
from .voice import TelegramVoiceManager
|
|
102
92
|
|
|
93
|
+
TICKET_FLOW_WATCH_INTERVAL_SECONDS = 20
|
|
94
|
+
|
|
103
95
|
|
|
104
96
|
def _build_opencode_supervisor(
|
|
105
97
|
config: TelegramBotConfig,
|
|
106
98
|
*,
|
|
107
99
|
logger: logging.Logger,
|
|
108
100
|
) -> Optional[OpenCodeSupervisor]:
|
|
109
|
-
|
|
101
|
+
opencode_command = config.opencode_command or None
|
|
110
102
|
opencode_binary = config.agent_binaries.get("opencode")
|
|
111
103
|
|
|
112
104
|
supervisor = build_opencode_supervisor(
|
|
113
|
-
opencode_command=
|
|
105
|
+
opencode_command=opencode_command,
|
|
114
106
|
opencode_binary=opencode_binary,
|
|
115
107
|
workspace_root=config.root,
|
|
116
108
|
logger=logger,
|
|
@@ -133,6 +125,26 @@ def _build_opencode_supervisor(
|
|
|
133
125
|
return supervisor
|
|
134
126
|
|
|
135
127
|
|
|
128
|
+
def _next_reply_seq_sync(reply_history_dir: Any) -> int:
|
|
129
|
+
from pathlib import Path
|
|
130
|
+
|
|
131
|
+
path = Path(reply_history_dir)
|
|
132
|
+
if not path.exists() or not path.is_dir():
|
|
133
|
+
return 1
|
|
134
|
+
existing: list[int] = []
|
|
135
|
+
_SEQ_RE = __import__("re").compile(r"^[0-9]{4}$")
|
|
136
|
+
for child in path.iterdir():
|
|
137
|
+
try:
|
|
138
|
+
if not child.is_dir():
|
|
139
|
+
continue
|
|
140
|
+
if not _SEQ_RE.fullmatch(child.name):
|
|
141
|
+
continue
|
|
142
|
+
existing.append(int(child.name))
|
|
143
|
+
except OSError:
|
|
144
|
+
continue
|
|
145
|
+
return (max(existing) + 1) if existing else 1
|
|
146
|
+
|
|
147
|
+
|
|
136
148
|
class TelegramBotService(
|
|
137
149
|
TelegramRuntimeHelpers,
|
|
138
150
|
TelegramMessageTransport,
|
|
@@ -154,6 +166,8 @@ class TelegramBotService(
|
|
|
154
166
|
housekeeping_config: Optional[HousekeepingConfig] = None,
|
|
155
167
|
update_repo_url: Optional[str] = None,
|
|
156
168
|
update_repo_ref: Optional[str] = None,
|
|
169
|
+
update_skip_checks: bool = False,
|
|
170
|
+
app_server_auto_restart: Optional[bool] = None,
|
|
157
171
|
) -> None:
|
|
158
172
|
self._config = config
|
|
159
173
|
self._logger = logger or logging.getLogger(__name__)
|
|
@@ -161,6 +175,8 @@ class TelegramBotService(
|
|
|
161
175
|
self._manifest_path = manifest_path
|
|
162
176
|
self._update_repo_url = update_repo_url
|
|
163
177
|
self._update_repo_ref = update_repo_ref
|
|
178
|
+
self._update_skip_checks = update_skip_checks
|
|
179
|
+
self._app_server_auto_restart = app_server_auto_restart
|
|
164
180
|
self._allowlist = config.allowlist()
|
|
165
181
|
self._store = TelegramStateStore(
|
|
166
182
|
config.state_file, default_approval_mode=config.defaults.approval_mode
|
|
@@ -174,6 +190,7 @@ class TelegramBotService(
|
|
|
174
190
|
approval_handler=self._handle_approval_request,
|
|
175
191
|
notification_handler=self._handle_app_server_notification,
|
|
176
192
|
logger=self._logger,
|
|
193
|
+
auto_restart=self._app_server_auto_restart,
|
|
177
194
|
max_handles=config.app_server_max_handles,
|
|
178
195
|
idle_ttl_seconds=config.app_server_idle_ttl_seconds,
|
|
179
196
|
)
|
|
@@ -224,6 +241,13 @@ class TelegramBotService(
|
|
|
224
241
|
self._oversize_warnings: set[TurnKey] = set()
|
|
225
242
|
self._pending_approvals: dict[str, PendingApproval] = {}
|
|
226
243
|
self._pending_questions: dict[str, PendingQuestion] = {}
|
|
244
|
+
self._ticket_flow_pause_targets: dict[str, str] = {}
|
|
245
|
+
self._ticket_flow_bridge = TelegramTicketFlowBridge(
|
|
246
|
+
logger=self._logger,
|
|
247
|
+
store=self._store,
|
|
248
|
+
pause_targets=self._ticket_flow_pause_targets,
|
|
249
|
+
send_message_with_outbox=self._send_message_with_outbox,
|
|
250
|
+
)
|
|
227
251
|
self._resume_options: dict[str, SelectionState] = {}
|
|
228
252
|
self._bind_options: dict[str, SelectionState] = {}
|
|
229
253
|
self._update_options: dict[str, SelectionState] = {}
|
|
@@ -249,6 +273,7 @@ class TelegramBotService(
|
|
|
249
273
|
)
|
|
250
274
|
self._outbox_task: Optional[asyncio.Task[None]] = None
|
|
251
275
|
self._cache_cleanup_task: Optional[asyncio.Task[None]] = None
|
|
276
|
+
self._ticket_flow_watch_task: Optional[asyncio.Task[None]] = None
|
|
252
277
|
self._cache_timestamps: dict[str, dict[object, float]] = {}
|
|
253
278
|
self._last_update_ids: dict[str, int] = {}
|
|
254
279
|
self._last_update_persisted_at: dict[str, float] = {}
|
|
@@ -540,6 +565,11 @@ class TelegramBotService(
|
|
|
540
565
|
self._voice_task = asyncio.create_task(self._voice_manager.run_loop())
|
|
541
566
|
self._housekeeping_task = asyncio.create_task(self._housekeeping_loop())
|
|
542
567
|
self._cache_cleanup_task = asyncio.create_task(self._cache_cleanup_loop())
|
|
568
|
+
self._ticket_flow_watch_task = asyncio.create_task(
|
|
569
|
+
self._ticket_flow_bridge.watch_ticket_flow_pauses(
|
|
570
|
+
TICKET_FLOW_WATCH_INTERVAL_SECONDS
|
|
571
|
+
)
|
|
572
|
+
)
|
|
543
573
|
self._spawn_task(self._prewarm_workspace_clients())
|
|
544
574
|
log_event(
|
|
545
575
|
self._logger,
|
|
@@ -557,6 +587,9 @@ class TelegramBotService(
|
|
|
557
587
|
media_images=self._config.media.images,
|
|
558
588
|
media_voice=self._config.media.voice,
|
|
559
589
|
app_server_turn_timeout_seconds=self._config.app_server_turn_timeout_seconds,
|
|
590
|
+
agent_turn_timeout_seconds=dict(
|
|
591
|
+
self._config.agent_turn_timeout_seconds
|
|
592
|
+
),
|
|
560
593
|
poller_offset=self._poller.offset,
|
|
561
594
|
)
|
|
562
595
|
try:
|
|
@@ -622,6 +655,12 @@ class TelegramBotService(
|
|
|
622
655
|
await self._cache_cleanup_task
|
|
623
656
|
except asyncio.CancelledError:
|
|
624
657
|
pass
|
|
658
|
+
if self._ticket_flow_watch_task is not None:
|
|
659
|
+
self._ticket_flow_watch_task.cancel()
|
|
660
|
+
try:
|
|
661
|
+
await self._ticket_flow_watch_task
|
|
662
|
+
except asyncio.CancelledError:
|
|
663
|
+
pass
|
|
625
664
|
if self._spawned_tasks:
|
|
626
665
|
for task in list(self._spawned_tasks):
|
|
627
666
|
task.cancel()
|
|
@@ -871,73 +910,206 @@ class TelegramBotService(
|
|
|
871
910
|
self._pending_questions.pop(key, None)
|
|
872
911
|
|
|
873
912
|
async def _cache_cleanup_loop(self) -> None:
|
|
874
|
-
interval = max(
|
|
913
|
+
interval = max(self._config.cache.cleanup_interval_seconds, 1.0)
|
|
875
914
|
while True:
|
|
876
915
|
await asyncio.sleep(interval)
|
|
877
916
|
self._evict_expired_cache_entries(
|
|
878
|
-
"reasoning_buffers",
|
|
917
|
+
"reasoning_buffers", self._config.cache.reasoning_buffer_ttl_seconds
|
|
918
|
+
)
|
|
919
|
+
self._evict_expired_cache_entries(
|
|
920
|
+
"turn_preview", self._config.cache.turn_preview_ttl_seconds
|
|
879
921
|
)
|
|
880
|
-
self._evict_expired_cache_entries("turn_preview", TURN_PREVIEW_TTL_SECONDS)
|
|
881
922
|
self._evict_expired_cache_entries(
|
|
882
|
-
"progress_trackers",
|
|
923
|
+
"progress_trackers", self._config.cache.progress_stream_ttl_seconds
|
|
883
924
|
)
|
|
884
925
|
self._evict_expired_cache_entries(
|
|
885
|
-
"oversize_warnings",
|
|
926
|
+
"oversize_warnings", self._config.cache.oversize_warning_ttl_seconds
|
|
886
927
|
)
|
|
887
928
|
self._evict_expired_cache_entries(
|
|
888
|
-
"coalesced_buffers",
|
|
929
|
+
"coalesced_buffers", self._config.cache.coalesce_buffer_ttl_seconds
|
|
889
930
|
)
|
|
890
931
|
self._evict_expired_cache_entries(
|
|
891
|
-
"media_batch_buffers",
|
|
932
|
+
"media_batch_buffers",
|
|
933
|
+
self._config.cache.media_batch_buffer_ttl_seconds,
|
|
892
934
|
)
|
|
893
935
|
self._evict_expired_cache_entries(
|
|
894
|
-
"resume_options",
|
|
936
|
+
"resume_options", self._config.cache.selection_state_ttl_seconds
|
|
895
937
|
)
|
|
896
938
|
self._evict_expired_cache_entries(
|
|
897
|
-
"bind_options",
|
|
939
|
+
"bind_options", self._config.cache.selection_state_ttl_seconds
|
|
898
940
|
)
|
|
899
941
|
self._evict_expired_cache_entries(
|
|
900
|
-
"agent_options",
|
|
942
|
+
"agent_options", self._config.cache.selection_state_ttl_seconds
|
|
901
943
|
)
|
|
902
944
|
self._evict_expired_cache_entries(
|
|
903
|
-
"update_options",
|
|
945
|
+
"update_options", self._config.cache.selection_state_ttl_seconds
|
|
904
946
|
)
|
|
905
947
|
self._evict_expired_cache_entries(
|
|
906
|
-
"update_confirm_options",
|
|
948
|
+
"update_confirm_options",
|
|
949
|
+
self._config.cache.selection_state_ttl_seconds,
|
|
907
950
|
)
|
|
908
951
|
self._evict_expired_cache_entries(
|
|
909
|
-
"review_commit_options",
|
|
952
|
+
"review_commit_options",
|
|
953
|
+
self._config.cache.selection_state_ttl_seconds,
|
|
910
954
|
)
|
|
911
955
|
self._evict_expired_cache_entries(
|
|
912
|
-
"review_commit_subjects",
|
|
956
|
+
"review_commit_subjects",
|
|
957
|
+
self._config.cache.selection_state_ttl_seconds,
|
|
913
958
|
)
|
|
914
959
|
self._evict_expired_cache_entries(
|
|
915
|
-
"pending_review_custom",
|
|
960
|
+
"pending_review_custom",
|
|
961
|
+
self._config.cache.selection_state_ttl_seconds,
|
|
916
962
|
)
|
|
917
963
|
self._evict_expired_cache_entries(
|
|
918
|
-
"compact_pending",
|
|
964
|
+
"compact_pending", self._config.cache.selection_state_ttl_seconds
|
|
919
965
|
)
|
|
920
966
|
self._evict_expired_cache_entries(
|
|
921
|
-
"model_options",
|
|
967
|
+
"model_options", self._config.cache.selection_state_ttl_seconds
|
|
922
968
|
)
|
|
923
969
|
self._evict_expired_cache_entries(
|
|
924
|
-
"model_pending",
|
|
970
|
+
"model_pending", self._config.cache.model_pending_ttl_seconds
|
|
925
971
|
)
|
|
926
972
|
self._evict_expired_cache_entries(
|
|
927
|
-
"pending_approvals",
|
|
973
|
+
"pending_approvals", self._config.cache.pending_approval_ttl_seconds
|
|
928
974
|
)
|
|
929
975
|
self._evict_expired_cache_entries(
|
|
930
|
-
"pending_questions",
|
|
976
|
+
"pending_questions", self._config.cache.pending_question_ttl_seconds
|
|
931
977
|
)
|
|
932
978
|
now = time.monotonic()
|
|
933
979
|
expired_placeholders = []
|
|
934
980
|
for key, timestamp in self._queued_placeholder_timestamps.items():
|
|
935
|
-
if (now - timestamp) >
|
|
981
|
+
if (now - timestamp) > self._config.cache.pending_approval_ttl_seconds:
|
|
936
982
|
expired_placeholders.append(key)
|
|
937
983
|
for key in expired_placeholders:
|
|
938
984
|
self._queued_placeholder_map.pop(key, None)
|
|
939
985
|
self._queued_placeholder_timestamps.pop(key, None)
|
|
940
986
|
|
|
987
|
+
@staticmethod
|
|
988
|
+
def _parse_last_active(record: "TelegramTopicRecord") -> float:
|
|
989
|
+
raw = getattr(record, "last_active_at", None)
|
|
990
|
+
if isinstance(raw, str):
|
|
991
|
+
try:
|
|
992
|
+
return datetime.strptime(raw, "%Y-%m-%dT%H:%M:%SZ").timestamp()
|
|
993
|
+
except ValueError:
|
|
994
|
+
return float("-inf")
|
|
995
|
+
return float("-inf")
|
|
996
|
+
|
|
997
|
+
def _select_ticket_flow_topic(
|
|
998
|
+
self, entries: list[tuple[str, "TelegramTopicRecord"]]
|
|
999
|
+
) -> Optional[tuple[str, "TelegramTopicRecord"]]:
|
|
1000
|
+
return self._ticket_flow_bridge._select_ticket_flow_topic(entries)
|
|
1001
|
+
|
|
1002
|
+
@staticmethod
|
|
1003
|
+
def _set_ticket_dispatch_marker(
|
|
1004
|
+
value: Optional[str],
|
|
1005
|
+
) -> "Callable[[TelegramTopicRecord], None]":
|
|
1006
|
+
def apply(topic: "TelegramTopicRecord") -> None:
|
|
1007
|
+
topic.last_ticket_dispatch_seq = value
|
|
1008
|
+
|
|
1009
|
+
return apply
|
|
1010
|
+
|
|
1011
|
+
async def _ticket_flow_watch_loop(self) -> None:
|
|
1012
|
+
await self._ticket_flow_bridge.watch_ticket_flow_pauses(
|
|
1013
|
+
TICKET_FLOW_WATCH_INTERVAL_SECONDS
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
async def _watch_ticket_flow_pauses(self) -> None:
|
|
1017
|
+
await self._ticket_flow_bridge._scan_and_notify_pauses()
|
|
1018
|
+
|
|
1019
|
+
async def _notify_ticket_flow_pause(
|
|
1020
|
+
self,
|
|
1021
|
+
workspace_root: Path,
|
|
1022
|
+
entries: list[tuple[str, "TelegramTopicRecord"]],
|
|
1023
|
+
) -> None:
|
|
1024
|
+
await self._ticket_flow_bridge._notify_ticket_flow_pause(
|
|
1025
|
+
workspace_root, entries
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
def _load_ticket_flow_pause(
|
|
1029
|
+
self, workspace_root: Path
|
|
1030
|
+
) -> Optional[tuple[str, str, str]]:
|
|
1031
|
+
return self._ticket_flow_bridge._load_ticket_flow_pause(workspace_root)
|
|
1032
|
+
|
|
1033
|
+
def _latest_dispatch_seq(self, history_dir: Path) -> Optional[str]:
|
|
1034
|
+
return self._ticket_flow_bridge._latest_dispatch_seq(history_dir)
|
|
1035
|
+
|
|
1036
|
+
def _format_ticket_flow_pause_reason(self, record: "FlowRunRecord") -> str:
|
|
1037
|
+
return self._ticket_flow_bridge._format_ticket_flow_pause_reason(record)
|
|
1038
|
+
|
|
1039
|
+
def _format_ticket_flow_pause_message(
|
|
1040
|
+
self, run_id: str, seq: str, content: str
|
|
1041
|
+
) -> str:
|
|
1042
|
+
return self._ticket_flow_bridge._format_ticket_flow_pause_message(
|
|
1043
|
+
run_id, seq, content
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
def _get_paused_ticket_flow(
|
|
1047
|
+
self, workspace_root: Path, preferred_run_id: Optional[str] = None
|
|
1048
|
+
) -> Optional[tuple[str, FlowRunRecord]]:
|
|
1049
|
+
return self._ticket_flow_bridge.get_paused_ticket_flow(
|
|
1050
|
+
workspace_root, preferred_run_id=preferred_run_id
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
async def _write_user_reply_from_telegram(
|
|
1054
|
+
self,
|
|
1055
|
+
workspace_root: Path,
|
|
1056
|
+
run_id: str,
|
|
1057
|
+
run_record: FlowRunRecord,
|
|
1058
|
+
message: TelegramMessage,
|
|
1059
|
+
text: str,
|
|
1060
|
+
files: Optional[list[tuple[str, bytes]]] = None,
|
|
1061
|
+
) -> tuple[bool, str]:
|
|
1062
|
+
try:
|
|
1063
|
+
input_data = dict(run_record.input_data or {})
|
|
1064
|
+
runs_dir_raw = input_data.get("runs_dir")
|
|
1065
|
+
runs_dir = (
|
|
1066
|
+
Path(runs_dir_raw)
|
|
1067
|
+
if isinstance(runs_dir_raw, str) and runs_dir_raw
|
|
1068
|
+
else Path(".codex-autorunner/runs")
|
|
1069
|
+
)
|
|
1070
|
+
reply_paths = resolve_reply_paths(
|
|
1071
|
+
workspace_root=workspace_root, runs_dir=runs_dir, run_id=run_id
|
|
1072
|
+
)
|
|
1073
|
+
ensure_reply_dirs(reply_paths)
|
|
1074
|
+
|
|
1075
|
+
cleaned_text = text.strip()
|
|
1076
|
+
raw = cleaned_text
|
|
1077
|
+
if raw and not raw.endswith("\n"):
|
|
1078
|
+
raw += "\n"
|
|
1079
|
+
|
|
1080
|
+
await asyncio.to_thread(
|
|
1081
|
+
reply_paths.user_reply_path.write_text, raw, encoding="utf-8"
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
if files:
|
|
1085
|
+
for filename, data in files:
|
|
1086
|
+
dest = reply_paths.reply_dir / filename
|
|
1087
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
1088
|
+
await asyncio.to_thread(dest.write_bytes, data)
|
|
1089
|
+
|
|
1090
|
+
seq = await asyncio.to_thread(
|
|
1091
|
+
lambda: _next_reply_seq_sync(reply_paths.reply_history_dir)
|
|
1092
|
+
)
|
|
1093
|
+
dispatch, errors = await asyncio.to_thread(
|
|
1094
|
+
dispatch_reply, reply_paths, next_seq=seq
|
|
1095
|
+
)
|
|
1096
|
+
if errors:
|
|
1097
|
+
return False, "\n".join(errors)
|
|
1098
|
+
if dispatch is None:
|
|
1099
|
+
return False, "Failed to archive reply"
|
|
1100
|
+
return (
|
|
1101
|
+
True,
|
|
1102
|
+
f"Reply archived (seq {dispatch.seq}). Use /flow resume to continue.",
|
|
1103
|
+
)
|
|
1104
|
+
except Exception as exc:
|
|
1105
|
+
self._logger.warning(
|
|
1106
|
+
"Failed to write USER_REPLY.md from Telegram",
|
|
1107
|
+
exc=exc,
|
|
1108
|
+
workspace_root=str(workspace_root),
|
|
1109
|
+
run_id=run_id,
|
|
1110
|
+
)
|
|
1111
|
+
return False, f"Failed to write reply: {exc}"
|
|
1112
|
+
|
|
941
1113
|
async def _interrupt_timeout_check(
|
|
942
1114
|
self, key: str, turn_id: str, message_id: int
|
|
943
1115
|
) -> None:
|
|
@@ -1205,7 +1377,9 @@ class TelegramBotService(
|
|
|
1205
1377
|
async def _maybe_persist_update_id(self, key: str, update_id: int) -> None:
|
|
1206
1378
|
now = time.monotonic()
|
|
1207
1379
|
last_persisted = self._last_update_persisted_at.get(key, 0.0)
|
|
1208
|
-
if (
|
|
1380
|
+
if (
|
|
1381
|
+
now - last_persisted
|
|
1382
|
+
) < self._config.cache.update_id_persist_interval_seconds:
|
|
1209
1383
|
return
|
|
1210
1384
|
|
|
1211
1385
|
def apply(record: "TelegramTopicRecord") -> None:
|
|
@@ -205,6 +205,7 @@ class TelegramTopicRecord:
|
|
|
205
205
|
rollout_path: Optional[str] = None
|
|
206
206
|
approval_mode: str = APPROVAL_MODE_YOLO
|
|
207
207
|
last_active_at: Optional[str] = None
|
|
208
|
+
last_ticket_dispatch_seq: Optional[str] = None
|
|
208
209
|
|
|
209
210
|
@classmethod
|
|
210
211
|
def from_dict(
|
|
@@ -291,6 +292,11 @@ class TelegramTopicRecord:
|
|
|
291
292
|
last_active_at = payload.get("last_active_at") or payload.get("lastActiveAt")
|
|
292
293
|
if not isinstance(last_active_at, str):
|
|
293
294
|
last_active_at = None
|
|
295
|
+
last_ticket_dispatch_seq = payload.get(
|
|
296
|
+
"last_ticket_dispatch_seq"
|
|
297
|
+
) or payload.get("lastTicketDispatchSeq")
|
|
298
|
+
if not isinstance(last_ticket_dispatch_seq, str):
|
|
299
|
+
last_ticket_dispatch_seq = None
|
|
294
300
|
return cls(
|
|
295
301
|
repo_id=repo_id,
|
|
296
302
|
workspace_path=workspace_path,
|
|
@@ -310,6 +316,7 @@ class TelegramTopicRecord:
|
|
|
310
316
|
rollout_path=rollout_path,
|
|
311
317
|
approval_mode=approval_mode,
|
|
312
318
|
last_active_at=last_active_at,
|
|
319
|
+
last_ticket_dispatch_seq=last_ticket_dispatch_seq,
|
|
313
320
|
)
|
|
314
321
|
|
|
315
322
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -335,6 +342,7 @@ class TelegramTopicRecord:
|
|
|
335
342
|
"rollout_path": self.rollout_path,
|
|
336
343
|
"approval_mode": self.approval_mode,
|
|
337
344
|
"last_active_at": self.last_active_at,
|
|
345
|
+
"last_ticket_dispatch_seq": self.last_ticket_dispatch_seq,
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
|
|
@@ -444,6 +452,10 @@ class OutboxRecord:
|
|
|
444
452
|
attempts: int = 0
|
|
445
453
|
last_error: Optional[str] = None
|
|
446
454
|
last_attempt_at: Optional[str] = None
|
|
455
|
+
next_attempt_at: Optional[str] = None
|
|
456
|
+
operation: Optional[str] = None
|
|
457
|
+
message_id: Optional[int] = None
|
|
458
|
+
outbox_key: Optional[str] = None
|
|
447
459
|
|
|
448
460
|
@classmethod
|
|
449
461
|
def from_dict(cls, payload: dict[str, Any]) -> Optional["OutboxRecord"]:
|
|
@@ -459,6 +471,10 @@ class OutboxRecord:
|
|
|
459
471
|
attempts = payload.get("attempts", 0)
|
|
460
472
|
last_error = payload.get("last_error")
|
|
461
473
|
last_attempt_at = payload.get("last_attempt_at")
|
|
474
|
+
next_attempt_at = payload.get("next_attempt_at")
|
|
475
|
+
operation = payload.get("operation")
|
|
476
|
+
message_id = payload.get("message_id")
|
|
477
|
+
outbox_key = payload.get("outbox_key")
|
|
462
478
|
if not isinstance(record_id, str) or not record_id:
|
|
463
479
|
return None
|
|
464
480
|
if not isinstance(chat_id, int):
|
|
@@ -481,6 +497,14 @@ class OutboxRecord:
|
|
|
481
497
|
last_error = None
|
|
482
498
|
if not isinstance(last_attempt_at, str):
|
|
483
499
|
last_attempt_at = None
|
|
500
|
+
if not isinstance(next_attempt_at, str):
|
|
501
|
+
next_attempt_at = None
|
|
502
|
+
if not isinstance(operation, str):
|
|
503
|
+
operation = None
|
|
504
|
+
if message_id is not None and not isinstance(message_id, int):
|
|
505
|
+
message_id = None
|
|
506
|
+
if not isinstance(outbox_key, str):
|
|
507
|
+
outbox_key = None
|
|
484
508
|
return cls(
|
|
485
509
|
record_id=record_id,
|
|
486
510
|
chat_id=chat_id,
|
|
@@ -492,6 +516,10 @@ class OutboxRecord:
|
|
|
492
516
|
attempts=attempts,
|
|
493
517
|
last_error=last_error,
|
|
494
518
|
last_attempt_at=last_attempt_at,
|
|
519
|
+
next_attempt_at=next_attempt_at,
|
|
520
|
+
operation=operation,
|
|
521
|
+
message_id=message_id,
|
|
522
|
+
outbox_key=outbox_key,
|
|
495
523
|
)
|
|
496
524
|
|
|
497
525
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -506,6 +534,10 @@ class OutboxRecord:
|
|
|
506
534
|
"attempts": self.attempts,
|
|
507
535
|
"last_error": self.last_error,
|
|
508
536
|
"last_attempt_at": self.last_attempt_at,
|
|
537
|
+
"next_attempt_at": self.next_attempt_at,
|
|
538
|
+
"operation": self.operation,
|
|
539
|
+
"message_id": self.message_id,
|
|
540
|
+
"outbox_key": self.outbox_key,
|
|
509
541
|
}
|
|
510
542
|
|
|
511
543
|
|
|
@@ -674,6 +706,27 @@ class TelegramStateStore:
|
|
|
674
706
|
async def get_topic(self, key: str) -> Optional[TelegramTopicRecord]:
|
|
675
707
|
return await self._run(self._get_topic_sync, key)
|
|
676
708
|
|
|
709
|
+
async def list_topics(self) -> dict[str, TelegramTopicRecord]:
|
|
710
|
+
"""Return all stored topics keyed by topic_key."""
|
|
711
|
+
return await self._run(self._list_topics_sync)
|
|
712
|
+
|
|
713
|
+
def _list_topics_sync(self) -> dict[str, TelegramTopicRecord]:
|
|
714
|
+
conn = self._ensure_connection()
|
|
715
|
+
cursor = conn.execute("SELECT topic_key, payload_json FROM telegram_topics")
|
|
716
|
+
topics: dict[str, TelegramTopicRecord] = {}
|
|
717
|
+
for key, payload_json in cursor.fetchall():
|
|
718
|
+
try:
|
|
719
|
+
payload = (
|
|
720
|
+
json.loads(payload_json) if isinstance(payload_json, str) else {}
|
|
721
|
+
)
|
|
722
|
+
except Exception:
|
|
723
|
+
payload = {}
|
|
724
|
+
record = TelegramTopicRecord.from_dict(
|
|
725
|
+
payload, default_approval_mode=self._default_approval_mode
|
|
726
|
+
)
|
|
727
|
+
topics[str(key)] = record
|
|
728
|
+
return topics
|
|
729
|
+
|
|
677
730
|
async def get_topic_scope(self, key: str) -> Optional[str]:
|
|
678
731
|
return await self._run(self._get_topic_scope_sync, key)
|
|
679
732
|
|
|
@@ -807,6 +860,11 @@ class TelegramStateStore:
|
|
|
807
860
|
loop = asyncio.get_running_loop()
|
|
808
861
|
return await loop.run_in_executor(self._executor, func, *args)
|
|
809
862
|
|
|
863
|
+
def _ensure_connection(self) -> sqlite3.Connection:
|
|
864
|
+
# Backwards-compatible helper used by older call sites.
|
|
865
|
+
# _connection_sync() remains the single source of truth for opening the DB.
|
|
866
|
+
return self._connection_sync()
|
|
867
|
+
|
|
810
868
|
def _connection_sync(self) -> sqlite3.Connection:
|
|
811
869
|
if self._connection is None:
|
|
812
870
|
conn = connect_sqlite(self._path)
|
|
@@ -927,16 +985,41 @@ class TelegramStateStore:
|
|
|
927
985
|
thread_id INTEGER,
|
|
928
986
|
created_at TEXT NOT NULL,
|
|
929
987
|
updated_at TEXT NOT NULL,
|
|
988
|
+
next_attempt_at TEXT,
|
|
989
|
+
operation TEXT,
|
|
990
|
+
message_id INTEGER,
|
|
991
|
+
outbox_key TEXT,
|
|
930
992
|
payload_json TEXT NOT NULL
|
|
931
993
|
)
|
|
932
994
|
"""
|
|
933
995
|
)
|
|
996
|
+
# Ensure legacy DBs gain the newer columns before creating indexes that
|
|
997
|
+
# reference them. The ALTERs are idempotent and cheap.
|
|
998
|
+
for col, col_type in [
|
|
999
|
+
("next_attempt_at", "TEXT"),
|
|
1000
|
+
("operation", "TEXT"),
|
|
1001
|
+
("message_id", "INTEGER"),
|
|
1002
|
+
("outbox_key", "TEXT"),
|
|
1003
|
+
]:
|
|
1004
|
+
try:
|
|
1005
|
+
conn.execute(
|
|
1006
|
+
f"ALTER TABLE telegram_outbox ADD COLUMN {col} {col_type}"
|
|
1007
|
+
)
|
|
1008
|
+
except sqlite3.OperationalError:
|
|
1009
|
+
pass
|
|
934
1010
|
conn.execute(
|
|
935
1011
|
"""
|
|
936
1012
|
CREATE INDEX IF NOT EXISTS idx_tg_outbox_created
|
|
937
1013
|
ON telegram_outbox(created_at)
|
|
938
1014
|
"""
|
|
939
1015
|
)
|
|
1016
|
+
conn.execute(
|
|
1017
|
+
"""
|
|
1018
|
+
CREATE INDEX IF NOT EXISTS idx_tg_outbox_key
|
|
1019
|
+
ON telegram_outbox(outbox_key)
|
|
1020
|
+
WHERE outbox_key IS NOT NULL
|
|
1021
|
+
"""
|
|
1022
|
+
)
|
|
940
1023
|
conn.execute(
|
|
941
1024
|
"""
|
|
942
1025
|
CREATE TABLE IF NOT EXISTS telegram_pending_voice (
|
|
@@ -1019,7 +1102,10 @@ class TelegramStateStore:
|
|
|
1019
1102
|
def _load_legacy_state_json(self, path: Path) -> Optional[TelegramState]:
|
|
1020
1103
|
try:
|
|
1021
1104
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
1022
|
-
except (OSError, json.JSONDecodeError):
|
|
1105
|
+
except (OSError, json.JSONDecodeError, UnicodeDecodeError):
|
|
1106
|
+
# The path may already be a SQLite file (e.g., state_file still ends
|
|
1107
|
+
# with .json after the migration to SQLite). In that case, ignore the
|
|
1108
|
+
# legacy load attempt and treat the DB as the source of truth.
|
|
1023
1109
|
return None
|
|
1024
1110
|
if not isinstance(payload, dict):
|
|
1025
1111
|
return None
|
|
@@ -1748,14 +1834,22 @@ class TelegramStateStore:
|
|
|
1748
1834
|
thread_id,
|
|
1749
1835
|
created_at,
|
|
1750
1836
|
updated_at,
|
|
1837
|
+
next_attempt_at,
|
|
1838
|
+
operation,
|
|
1839
|
+
message_id,
|
|
1840
|
+
outbox_key,
|
|
1751
1841
|
payload_json
|
|
1752
1842
|
)
|
|
1753
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1843
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1754
1844
|
ON CONFLICT(record_id) DO UPDATE SET
|
|
1755
1845
|
chat_id=excluded.chat_id,
|
|
1756
1846
|
thread_id=excluded.thread_id,
|
|
1757
1847
|
created_at=excluded.created_at,
|
|
1758
1848
|
updated_at=excluded.updated_at,
|
|
1849
|
+
next_attempt_at=excluded.next_attempt_at,
|
|
1850
|
+
operation=excluded.operation,
|
|
1851
|
+
message_id=excluded.message_id,
|
|
1852
|
+
outbox_key=excluded.outbox_key,
|
|
1759
1853
|
payload_json=excluded.payload_json
|
|
1760
1854
|
""",
|
|
1761
1855
|
(
|
|
@@ -1764,6 +1858,10 @@ class TelegramStateStore:
|
|
|
1764
1858
|
record.thread_id,
|
|
1765
1859
|
record.created_at,
|
|
1766
1860
|
updated_at,
|
|
1861
|
+
record.next_attempt_at,
|
|
1862
|
+
record.operation,
|
|
1863
|
+
record.message_id,
|
|
1864
|
+
record.outbox_key,
|
|
1767
1865
|
payload_json,
|
|
1768
1866
|
),
|
|
1769
1867
|
)
|