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
|
@@ -20,6 +20,7 @@ from ....agents.opencode.supervisor import OpenCodeSupervisorError
|
|
|
20
20
|
from ....core.logging_utils import log_event
|
|
21
21
|
from ....core.state import now_iso
|
|
22
22
|
from ....core.update import _normalize_update_target, _spawn_update_process
|
|
23
|
+
from ....core.update_paths import resolve_update_paths
|
|
23
24
|
from ....core.utils import canonicalize_path
|
|
24
25
|
from ...app_server.client import _normalize_sandbox_policy
|
|
25
26
|
from ..adapter import (
|
|
@@ -869,7 +870,7 @@ class TelegramCommandHandlers(
|
|
|
869
870
|
key = await self._resolve_topic_key(message.chat_id, message.thread_id)
|
|
870
871
|
self._model_options.pop(key, None)
|
|
871
872
|
self._model_pending.pop(key, None)
|
|
872
|
-
record = await self._router.
|
|
873
|
+
record = await self._router.ensure_topic(message.chat_id, message.thread_id)
|
|
873
874
|
agent = self._effective_agent(record)
|
|
874
875
|
supports_effort = self._agent_supports_effort(agent)
|
|
875
876
|
list_params = {
|
|
@@ -877,10 +878,17 @@ class TelegramCommandHandlers(
|
|
|
877
878
|
"limit": DEFAULT_MODEL_LIST_LIMIT,
|
|
878
879
|
"agent": agent,
|
|
879
880
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
881
|
+
workspace_path, error = self._resolve_workspace_path(record, allow_pma=True)
|
|
882
|
+
if workspace_path is None:
|
|
883
|
+
await self._send_message(
|
|
884
|
+
message.chat_id,
|
|
885
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
886
|
+
thread_id=message.thread_id,
|
|
887
|
+
reply_to=message.message_id,
|
|
883
888
|
)
|
|
889
|
+
return
|
|
890
|
+
try:
|
|
891
|
+
client = await self._client_for_workspace(workspace_path)
|
|
884
892
|
except AppServerUnavailableError as exc:
|
|
885
893
|
log_event(
|
|
886
894
|
self._logger,
|
|
@@ -900,16 +908,20 @@ class TelegramCommandHandlers(
|
|
|
900
908
|
if client is None:
|
|
901
909
|
await self._send_message(
|
|
902
910
|
message.chat_id,
|
|
903
|
-
"Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
911
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
904
912
|
thread_id=message.thread_id,
|
|
905
913
|
reply_to=message.message_id,
|
|
906
914
|
)
|
|
907
915
|
return
|
|
908
916
|
argv = self._parse_command_args(args)
|
|
909
917
|
if not argv:
|
|
918
|
+
record_for_models = self._record_with_workspace_path(record, workspace_path)
|
|
910
919
|
try:
|
|
911
920
|
result = await self._fetch_model_list(
|
|
912
|
-
|
|
921
|
+
record_for_models,
|
|
922
|
+
agent=agent,
|
|
923
|
+
client=client,
|
|
924
|
+
list_params=list_params,
|
|
913
925
|
)
|
|
914
926
|
except OpenCodeSupervisorError as exc:
|
|
915
927
|
log_event(
|
|
@@ -992,9 +1004,13 @@ class TelegramCommandHandlers(
|
|
|
992
1004
|
)
|
|
993
1005
|
return
|
|
994
1006
|
if argv[0].lower() in ("list", "ls"):
|
|
1007
|
+
record_for_models = self._record_with_workspace_path(record, workspace_path)
|
|
995
1008
|
try:
|
|
996
1009
|
result = await self._fetch_model_list(
|
|
997
|
-
|
|
1010
|
+
record_for_models,
|
|
1011
|
+
agent=agent,
|
|
1012
|
+
client=client,
|
|
1013
|
+
list_params=list_params,
|
|
998
1014
|
)
|
|
999
1015
|
except OpenCodeSupervisorError as exc:
|
|
1000
1016
|
log_event(
|
|
@@ -1107,6 +1123,101 @@ class TelegramCommandHandlers(
|
|
|
1107
1123
|
reply_to=message.message_id,
|
|
1108
1124
|
)
|
|
1109
1125
|
|
|
1126
|
+
async def _handle_pma(
|
|
1127
|
+
self, message: TelegramMessage, args: str, _runtime: Any
|
|
1128
|
+
) -> None:
|
|
1129
|
+
if not self._hub_root:
|
|
1130
|
+
await self._send_message(
|
|
1131
|
+
message.chat_id,
|
|
1132
|
+
"PMA unavailable; hub root not configured.",
|
|
1133
|
+
thread_id=message.thread_id,
|
|
1134
|
+
reply_to=message.message_id,
|
|
1135
|
+
)
|
|
1136
|
+
return
|
|
1137
|
+
|
|
1138
|
+
supervisor = getattr(self, "_hub_supervisor", None)
|
|
1139
|
+
if supervisor and hasattr(supervisor, "hub_config"):
|
|
1140
|
+
pma_config = supervisor.hub_config.pma
|
|
1141
|
+
if not pma_config.enabled:
|
|
1142
|
+
await self._send_message(
|
|
1143
|
+
message.chat_id,
|
|
1144
|
+
"PMA is disabled in hub config. Set pma.enabled: true to enable.",
|
|
1145
|
+
thread_id=message.thread_id,
|
|
1146
|
+
reply_to=message.message_id,
|
|
1147
|
+
)
|
|
1148
|
+
return
|
|
1149
|
+
argv = self._parse_command_args(args)
|
|
1150
|
+
action = argv[0].lower() if argv else ""
|
|
1151
|
+
key = await self._resolve_topic_key(message.chat_id, message.thread_id)
|
|
1152
|
+
record = await self._router.get_topic(key)
|
|
1153
|
+
current = bool(record and record.pma_enabled)
|
|
1154
|
+
if action in ("status", "show"):
|
|
1155
|
+
enabled = current
|
|
1156
|
+
elif action in ("on", "enable", "true"):
|
|
1157
|
+
enabled = True
|
|
1158
|
+
elif action in ("off", "disable", "false"):
|
|
1159
|
+
enabled = False
|
|
1160
|
+
elif action:
|
|
1161
|
+
await self._send_message(
|
|
1162
|
+
message.chat_id,
|
|
1163
|
+
"Usage: /pma [on|off|status]",
|
|
1164
|
+
thread_id=message.thread_id,
|
|
1165
|
+
reply_to=message.message_id,
|
|
1166
|
+
)
|
|
1167
|
+
return
|
|
1168
|
+
else:
|
|
1169
|
+
enabled = not current
|
|
1170
|
+
|
|
1171
|
+
if record is None:
|
|
1172
|
+
await self._router.ensure_topic(message.chat_id, message.thread_id)
|
|
1173
|
+
|
|
1174
|
+
def apply_pma(record: TelegramTopicRecord) -> None:
|
|
1175
|
+
record.pma_enabled = enabled
|
|
1176
|
+
if enabled:
|
|
1177
|
+
# Save previous binding before entering PMA mode.
|
|
1178
|
+
record.pma_prev_repo_id = record.repo_id
|
|
1179
|
+
record.pma_prev_workspace_path = record.workspace_path
|
|
1180
|
+
record.pma_prev_workspace_id = record.workspace_id
|
|
1181
|
+
record.pma_prev_active_thread_id = record.active_thread_id
|
|
1182
|
+
# Mutual exclusion: PMA mode implies Hub context, so unbind specific repo.
|
|
1183
|
+
record.workspace_path = None
|
|
1184
|
+
record.repo_id = None
|
|
1185
|
+
record.workspace_id = None
|
|
1186
|
+
record.active_thread_id = None
|
|
1187
|
+
else:
|
|
1188
|
+
# Restore previous binding when exiting PMA mode.
|
|
1189
|
+
if record.pma_prev_repo_id or record.pma_prev_workspace_path:
|
|
1190
|
+
record.repo_id = record.pma_prev_repo_id
|
|
1191
|
+
record.workspace_path = record.pma_prev_workspace_path
|
|
1192
|
+
record.workspace_id = record.pma_prev_workspace_id
|
|
1193
|
+
record.active_thread_id = record.pma_prev_active_thread_id
|
|
1194
|
+
# Clear saved previous binding after restore.
|
|
1195
|
+
record.pma_prev_repo_id = None
|
|
1196
|
+
record.pma_prev_workspace_path = None
|
|
1197
|
+
record.pma_prev_workspace_id = None
|
|
1198
|
+
record.pma_prev_active_thread_id = None
|
|
1199
|
+
|
|
1200
|
+
await self._router.update_topic(
|
|
1201
|
+
message.chat_id,
|
|
1202
|
+
message.thread_id,
|
|
1203
|
+
apply_pma,
|
|
1204
|
+
)
|
|
1205
|
+
status = "enabled" if enabled else "disabled"
|
|
1206
|
+
if enabled:
|
|
1207
|
+
hint = "Use /pma off to exit. Previous repo binding saved."
|
|
1208
|
+
else:
|
|
1209
|
+
previous = (record and record.pma_prev_workspace_path) or None
|
|
1210
|
+
if previous:
|
|
1211
|
+
hint = f"Back to repo mode. Restored {previous}."
|
|
1212
|
+
else:
|
|
1213
|
+
hint = "Back to repo mode."
|
|
1214
|
+
await self._send_message(
|
|
1215
|
+
message.chat_id,
|
|
1216
|
+
f"PMA mode {status}. {hint}",
|
|
1217
|
+
thread_id=message.thread_id,
|
|
1218
|
+
reply_to=message.message_id,
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1110
1221
|
async def _opencode_review_arguments(target: dict[str, Any]) -> str:
|
|
1111
1222
|
target_type = target.get("type")
|
|
1112
1223
|
if target_type == "uncommittedChanges":
|
|
@@ -1833,10 +1944,14 @@ class TelegramCommandHandlers(
|
|
|
1833
1944
|
async def _apply_compact_summary(
|
|
1834
1945
|
self, message: TelegramMessage, record: "TelegramTopicRecord", summary_text: str
|
|
1835
1946
|
) -> tuple[bool, str | None]:
|
|
1836
|
-
|
|
1837
|
-
|
|
1947
|
+
workspace_path, error = self._resolve_workspace_path(record, allow_pma=True)
|
|
1948
|
+
if not workspace_path:
|
|
1949
|
+
return (
|
|
1950
|
+
False,
|
|
1951
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
1952
|
+
)
|
|
1838
1953
|
try:
|
|
1839
|
-
client = await self._client_for_workspace(
|
|
1954
|
+
client = await self._client_for_workspace(workspace_path)
|
|
1840
1955
|
except AppServerUnavailableError as exc:
|
|
1841
1956
|
log_event(
|
|
1842
1957
|
self._logger,
|
|
@@ -1848,7 +1963,10 @@ class TelegramCommandHandlers(
|
|
|
1848
1963
|
)
|
|
1849
1964
|
return False, "App server unavailable; try again or check logs."
|
|
1850
1965
|
if client is None:
|
|
1851
|
-
return (
|
|
1966
|
+
return (
|
|
1967
|
+
False,
|
|
1968
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
1969
|
+
)
|
|
1852
1970
|
log_event(
|
|
1853
1971
|
self._logger,
|
|
1854
1972
|
logging.INFO,
|
|
@@ -1856,11 +1974,11 @@ class TelegramCommandHandlers(
|
|
|
1856
1974
|
chat_id=message.chat_id,
|
|
1857
1975
|
thread_id=message.thread_id,
|
|
1858
1976
|
summary_len=len(summary_text),
|
|
1859
|
-
workspace_path=
|
|
1977
|
+
workspace_path=workspace_path,
|
|
1860
1978
|
)
|
|
1861
1979
|
try:
|
|
1862
1980
|
agent = self._effective_agent(record)
|
|
1863
|
-
thread = await client.thread_start(
|
|
1981
|
+
thread = await client.thread_start(workspace_path, agent=agent)
|
|
1864
1982
|
except Exception as exc:
|
|
1865
1983
|
log_event(
|
|
1866
1984
|
self._logger,
|
|
@@ -1872,7 +1990,7 @@ class TelegramCommandHandlers(
|
|
|
1872
1990
|
)
|
|
1873
1991
|
return False, "Failed to start a new thread."
|
|
1874
1992
|
if not await self._require_thread_workspace(
|
|
1875
|
-
message,
|
|
1993
|
+
message, workspace_path, thread, action="thread_start"
|
|
1876
1994
|
):
|
|
1877
1995
|
return False, "Failed to start a new thread."
|
|
1878
1996
|
new_thread_id = _extract_thread_id(thread)
|
|
@@ -1910,7 +2028,7 @@ class TelegramCommandHandlers(
|
|
|
1910
2028
|
) -> None:
|
|
1911
2029
|
argv = self._parse_command_args(args)
|
|
1912
2030
|
if argv and argv[0].lower() in ("soft", "summary", "summarize"):
|
|
1913
|
-
record = await self._require_bound_record(message)
|
|
2031
|
+
record = await self._require_bound_record(message, allow_pma=True)
|
|
1914
2032
|
if not record:
|
|
1915
2033
|
return
|
|
1916
2034
|
await self._handle_normal_message(
|
|
@@ -1918,33 +2036,51 @@ class TelegramCommandHandlers(
|
|
|
1918
2036
|
)
|
|
1919
2037
|
return
|
|
1920
2038
|
auto_apply = bool(argv and argv[0].lower() == "apply")
|
|
1921
|
-
record = await self._require_bound_record(message)
|
|
2039
|
+
record = await self._require_bound_record(message, allow_pma=True)
|
|
1922
2040
|
if not record:
|
|
1923
2041
|
return
|
|
1924
2042
|
key = await self._resolve_topic_key(message.chat_id, message.thread_id)
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2043
|
+
pma_enabled = bool(record.pma_enabled)
|
|
2044
|
+
if pma_enabled:
|
|
2045
|
+
registry = getattr(self, "_hub_thread_registry", None)
|
|
2046
|
+
pma_key = self._pma_registry_key(record, message)
|
|
2047
|
+
pma_thread_id = (
|
|
2048
|
+
registry.get_thread_id(pma_key)
|
|
2049
|
+
if registry is not None and pma_key
|
|
2050
|
+
else None
|
|
2051
|
+
)
|
|
2052
|
+
if not pma_thread_id:
|
|
2053
|
+
await self._send_message(
|
|
2054
|
+
message.chat_id,
|
|
2055
|
+
"No active PMA thread to compact. Send a message or use /new to start one.",
|
|
2056
|
+
thread_id=message.thread_id,
|
|
2057
|
+
reply_to=message.message_id,
|
|
2058
|
+
)
|
|
2059
|
+
return
|
|
2060
|
+
else:
|
|
2061
|
+
if not record.active_thread_id:
|
|
2062
|
+
await self._send_message(
|
|
2063
|
+
message.chat_id,
|
|
2064
|
+
"No active thread to compact. Use /new to start one.",
|
|
2065
|
+
thread_id=message.thread_id,
|
|
2066
|
+
reply_to=message.message_id,
|
|
2067
|
+
)
|
|
2068
|
+
return
|
|
2069
|
+
conflict_key = await self._find_thread_conflict(
|
|
2070
|
+
record.active_thread_id, key=key
|
|
1942
2071
|
)
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2072
|
+
if conflict_key:
|
|
2073
|
+
await self._router.set_active_thread(
|
|
2074
|
+
message.chat_id, message.thread_id, None
|
|
2075
|
+
)
|
|
2076
|
+
await self._handle_thread_conflict(
|
|
2077
|
+
message, record.active_thread_id, conflict_key
|
|
2078
|
+
)
|
|
2079
|
+
return
|
|
2080
|
+
verified = await self._verify_active_thread(message, record)
|
|
2081
|
+
if not verified:
|
|
2082
|
+
return
|
|
2083
|
+
record = verified
|
|
1948
2084
|
outcome = await self._run_turn_and_collect_result(
|
|
1949
2085
|
message,
|
|
1950
2086
|
runtime,
|
|
@@ -2066,9 +2202,13 @@ Compact canceled.""",
|
|
|
2066
2202
|
)
|
|
2067
2203
|
self._compact_pending.pop(key, None)
|
|
2068
2204
|
record = await self._router.get_topic(key)
|
|
2069
|
-
if record is None
|
|
2205
|
+
if record is None:
|
|
2070
2206
|
await self._answer_callback(callback, "Selection expired")
|
|
2071
2207
|
return
|
|
2208
|
+
workspace_path, error = self._resolve_workspace_path(record, allow_pma=True)
|
|
2209
|
+
if workspace_path is None:
|
|
2210
|
+
await self._answer_callback(callback, error or "Selection expired")
|
|
2211
|
+
return
|
|
2072
2212
|
if callback.chat_id is None:
|
|
2073
2213
|
return
|
|
2074
2214
|
await self._answer_callback(callback, "Applying summary...")
|
|
@@ -2277,7 +2417,7 @@ Summary applied.""",
|
|
|
2277
2417
|
repo_ref = (self._update_repo_ref or DEFAULT_UPDATE_REPO_REF).strip()
|
|
2278
2418
|
if not repo_ref:
|
|
2279
2419
|
repo_ref = DEFAULT_UPDATE_REPO_REF
|
|
2280
|
-
update_dir =
|
|
2420
|
+
update_dir = resolve_update_paths().cache_dir
|
|
2281
2421
|
notify_reply_to = reply_to
|
|
2282
2422
|
if notify_reply_to is None and callback is not None:
|
|
2283
2423
|
notify_reply_to = callback.message_id
|
|
@@ -2383,7 +2523,7 @@ Summary applied.""",
|
|
|
2383
2523
|
)
|
|
2384
2524
|
|
|
2385
2525
|
def _update_status_path(self) -> Path:
|
|
2386
|
-
return
|
|
2526
|
+
return resolve_update_paths().status_path
|
|
2387
2527
|
|
|
2388
2528
|
def _read_update_status(self) -> Optional[dict[str, Any]]:
|
|
2389
2529
|
path = self._update_status_path()
|
|
@@ -2467,7 +2607,7 @@ Summary applied.""",
|
|
|
2467
2607
|
)
|
|
2468
2608
|
|
|
2469
2609
|
def _compact_status_path(self) -> Path:
|
|
2470
|
-
return
|
|
2610
|
+
return resolve_update_paths().compact_status_path
|
|
2471
2611
|
|
|
2472
2612
|
def _read_compact_status(self) -> Optional[dict[str, Any]]:
|
|
2473
2613
|
path = self._compact_status_path()
|
|
@@ -2633,14 +2773,23 @@ Summary applied.""",
|
|
|
2633
2773
|
async def _handle_logout(
|
|
2634
2774
|
self, message: TelegramMessage, _args: str, _runtime: Any
|
|
2635
2775
|
) -> None:
|
|
2636
|
-
record = await self._require_bound_record(message)
|
|
2776
|
+
record = await self._require_bound_record(message, allow_pma=True)
|
|
2637
2777
|
if not record:
|
|
2638
2778
|
return
|
|
2639
|
-
|
|
2779
|
+
workspace_path, error = self._resolve_workspace_path(record, allow_pma=True)
|
|
2780
|
+
if workspace_path is None:
|
|
2781
|
+
await self._send_message(
|
|
2782
|
+
message.chat_id,
|
|
2783
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
2784
|
+
thread_id=message.thread_id,
|
|
2785
|
+
reply_to=message.message_id,
|
|
2786
|
+
)
|
|
2787
|
+
return
|
|
2788
|
+
client = await self._client_for_workspace(workspace_path)
|
|
2640
2789
|
if client is None:
|
|
2641
2790
|
await self._send_message(
|
|
2642
2791
|
message.chat_id,
|
|
2643
|
-
"Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
2792
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
2644
2793
|
thread_id=message.thread_id,
|
|
2645
2794
|
reply_to=message.message_id,
|
|
2646
2795
|
)
|
|
@@ -2686,14 +2835,23 @@ Summary applied.""",
|
|
|
2686
2835
|
reply_to=message.message_id,
|
|
2687
2836
|
)
|
|
2688
2837
|
return
|
|
2689
|
-
record = await self._require_bound_record(message)
|
|
2838
|
+
record = await self._require_bound_record(message, allow_pma=True)
|
|
2690
2839
|
if not record:
|
|
2691
2840
|
return
|
|
2692
|
-
|
|
2841
|
+
workspace_path, error = self._resolve_workspace_path(record, allow_pma=True)
|
|
2842
|
+
if workspace_path is None:
|
|
2843
|
+
await self._send_message(
|
|
2844
|
+
message.chat_id,
|
|
2845
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
2846
|
+
thread_id=message.thread_id,
|
|
2847
|
+
reply_to=message.message_id,
|
|
2848
|
+
)
|
|
2849
|
+
return
|
|
2850
|
+
client = await self._client_for_workspace(workspace_path)
|
|
2693
2851
|
if client is None:
|
|
2694
2852
|
await self._send_message(
|
|
2695
2853
|
message.chat_id,
|
|
2696
|
-
"Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
2854
|
+
error or "Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
2697
2855
|
thread_id=message.thread_id,
|
|
2698
2856
|
reply_to=message.message_id,
|
|
2699
2857
|
)
|
|
@@ -16,6 +16,12 @@ class CommandSpec:
|
|
|
16
16
|
|
|
17
17
|
def build_command_specs(handlers: Any) -> dict[str, CommandSpec]:
|
|
18
18
|
return {
|
|
19
|
+
"repos": CommandSpec(
|
|
20
|
+
"repos",
|
|
21
|
+
"list available repositories in the hub",
|
|
22
|
+
handlers._handle_repos,
|
|
23
|
+
allow_during_turn=True,
|
|
24
|
+
),
|
|
19
25
|
"bind": CommandSpec(
|
|
20
26
|
"bind",
|
|
21
27
|
"bind this topic to a workspace",
|
|
@@ -23,9 +29,14 @@ def build_command_specs(handlers: Any) -> dict[str, CommandSpec]:
|
|
|
23
29
|
),
|
|
24
30
|
"new": CommandSpec(
|
|
25
31
|
"new",
|
|
26
|
-
"start a new session",
|
|
32
|
+
"start a new PMA session",
|
|
27
33
|
lambda message, _args, _runtime: handlers._handle_new(message),
|
|
28
34
|
),
|
|
35
|
+
"reset": CommandSpec(
|
|
36
|
+
"reset",
|
|
37
|
+
"reset PMA thread state (clear volatile state)",
|
|
38
|
+
lambda message, _args, _runtime: handlers._handle_reset(message),
|
|
39
|
+
),
|
|
29
40
|
"resume": CommandSpec(
|
|
30
41
|
"resume",
|
|
31
42
|
"list or resume a previous session",
|
|
@@ -38,13 +49,13 @@ def build_command_specs(handlers: Any) -> dict[str, CommandSpec]:
|
|
|
38
49
|
),
|
|
39
50
|
"flow": CommandSpec(
|
|
40
51
|
"flow",
|
|
41
|
-
"
|
|
52
|
+
"ticket flow controls (status, runs, bootstrap, resume, stop, archive, reply)",
|
|
42
53
|
lambda message, args, _runtime: handlers._handle_flow(message, args),
|
|
43
54
|
allow_during_turn=True,
|
|
44
55
|
),
|
|
45
56
|
"reply": CommandSpec(
|
|
46
57
|
"reply",
|
|
47
|
-
"reply to a paused ticket flow dispatch",
|
|
58
|
+
"reply to a paused ticket flow dispatch (prefer /flow reply)",
|
|
48
59
|
lambda message, args, _runtime: handlers._handle_reply(message, args),
|
|
49
60
|
allow_during_turn=True,
|
|
50
61
|
),
|
|
@@ -63,6 +74,12 @@ def build_command_specs(handlers: Any) -> dict[str, CommandSpec]:
|
|
|
63
74
|
"set approval and sandbox policy",
|
|
64
75
|
handlers._handle_approvals,
|
|
65
76
|
),
|
|
77
|
+
"pma": CommandSpec(
|
|
78
|
+
"pma",
|
|
79
|
+
"toggle PMA mode for this topic",
|
|
80
|
+
handlers._handle_pma,
|
|
81
|
+
allow_during_turn=True,
|
|
82
|
+
),
|
|
66
83
|
"status": CommandSpec(
|
|
67
84
|
"status",
|
|
68
85
|
"show current binding and thread status",
|
|
@@ -75,6 +75,21 @@ def _message_text_candidate(message: TelegramMessage) -> tuple[str, str, Any]:
|
|
|
75
75
|
return raw_text, text_candidate, entities
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def _record_with_media_workspace(
|
|
79
|
+
handlers: Any, record: Any
|
|
80
|
+
) -> tuple[Any, Optional[str]]:
|
|
81
|
+
"""Ensure media handlers have a workspace path, including PMA topics."""
|
|
82
|
+
if record is None:
|
|
83
|
+
return None, None
|
|
84
|
+
pma_enabled = bool(getattr(record, "pma_enabled", False))
|
|
85
|
+
if not pma_enabled:
|
|
86
|
+
return record, None
|
|
87
|
+
hub_root = getattr(handlers, "_hub_root", None)
|
|
88
|
+
if hub_root is None:
|
|
89
|
+
return None, "PMA unavailable; hub root not configured."
|
|
90
|
+
return dataclasses.replace(record, workspace_path=str(hub_root)), None
|
|
91
|
+
|
|
92
|
+
|
|
78
93
|
async def _clear_pending_options(
|
|
79
94
|
handlers: Any, key: str, message: TelegramMessage
|
|
80
95
|
) -> None:
|
|
@@ -263,6 +278,7 @@ async def handle_message_inner(
|
|
|
263
278
|
if text and text.startswith("!") and not has_media:
|
|
264
279
|
handlers._resume_options.pop(key, None)
|
|
265
280
|
handlers._bind_options.pop(key, None)
|
|
281
|
+
handlers._flow_run_options.pop(key, None)
|
|
266
282
|
handlers._agent_options.pop(key, None)
|
|
267
283
|
handlers._model_options.pop(key, None)
|
|
268
284
|
handlers._model_pending.pop(key, None)
|
|
@@ -351,7 +367,8 @@ async def handle_message_inner(
|
|
|
351
367
|
record = await handlers._router.get_topic(key)
|
|
352
368
|
paused = None
|
|
353
369
|
workspace_root: Optional[Path] = None
|
|
354
|
-
|
|
370
|
+
pma_enabled = bool(record and getattr(record, "pma_enabled", False))
|
|
371
|
+
if not pma_enabled and record and record.workspace_path:
|
|
355
372
|
workspace_root = canonicalize_path(Path(record.workspace_path))
|
|
356
373
|
preferred_run_id = handlers._ticket_flow_pause_targets.get(
|
|
357
374
|
str(workspace_root), None
|
|
@@ -800,6 +817,15 @@ async def handle_media_message(
|
|
|
800
817
|
return
|
|
801
818
|
key = await handlers._resolve_topic_key(message.chat_id, message.thread_id)
|
|
802
819
|
record = await handlers._router.get_topic(key)
|
|
820
|
+
record, pma_error = _record_with_media_workspace(handlers, record)
|
|
821
|
+
if pma_error:
|
|
822
|
+
await handlers._send_message(
|
|
823
|
+
message.chat_id,
|
|
824
|
+
pma_error,
|
|
825
|
+
thread_id=message.thread_id,
|
|
826
|
+
reply_to=message.message_id,
|
|
827
|
+
)
|
|
828
|
+
return
|
|
803
829
|
if record is None or not record.workspace_path:
|
|
804
830
|
await handlers._send_message(
|
|
805
831
|
message.chat_id,
|
|
@@ -7,6 +7,8 @@ from ..adapter import (
|
|
|
7
7
|
AgentCallback,
|
|
8
8
|
CancelCallback,
|
|
9
9
|
EffortCallback,
|
|
10
|
+
FlowCallback,
|
|
11
|
+
FlowRunCallback,
|
|
10
12
|
ModelCallback,
|
|
11
13
|
PageCallback,
|
|
12
14
|
ReviewCommitCallback,
|
|
@@ -18,6 +20,7 @@ from ..adapter import (
|
|
|
18
20
|
build_agent_keyboard,
|
|
19
21
|
build_bind_keyboard,
|
|
20
22
|
build_effort_keyboard,
|
|
23
|
+
build_flow_runs_keyboard,
|
|
21
24
|
build_model_keyboard,
|
|
22
25
|
build_resume_keyboard,
|
|
23
26
|
build_review_commit_keyboard,
|
|
@@ -29,6 +32,7 @@ from ..constants import (
|
|
|
29
32
|
BIND_PICKER_PROMPT,
|
|
30
33
|
DEFAULT_PAGE_SIZE,
|
|
31
34
|
EFFORT_PICKER_PROMPT,
|
|
35
|
+
FLOW_RUNS_PICKER_PROMPT,
|
|
32
36
|
MODEL_PICKER_PROMPT,
|
|
33
37
|
RESUME_BUTTON_PREVIEW_LIMIT,
|
|
34
38
|
RESUME_PICKER_PROMPT,
|
|
@@ -393,6 +397,21 @@ class TelegramSelectionHandlers:
|
|
|
393
397
|
delivery=state.delivery,
|
|
394
398
|
)
|
|
395
399
|
|
|
400
|
+
async def _handle_flow_run_callback(
|
|
401
|
+
self,
|
|
402
|
+
key: str,
|
|
403
|
+
callback: TelegramCallbackQuery,
|
|
404
|
+
parsed: FlowRunCallback,
|
|
405
|
+
) -> None:
|
|
406
|
+
state = self._flow_run_options.get(key)
|
|
407
|
+
if not state or not _selection_contains(state.items, parsed.run_id):
|
|
408
|
+
await self._answer_callback(callback, "Selection expired")
|
|
409
|
+
return
|
|
410
|
+
self._flow_run_options.pop(key, None)
|
|
411
|
+
await self._handle_flow_callback(
|
|
412
|
+
callback, FlowCallback(action="status", run_id=parsed.run_id)
|
|
413
|
+
)
|
|
414
|
+
|
|
396
415
|
def _selection_prompt(self, base: str, state: SelectionState) -> str:
|
|
397
416
|
total_pages = _page_count(len(state.items), DEFAULT_PAGE_SIZE)
|
|
398
417
|
return _format_selection_prompt(base, state.page, total_pages)
|
|
@@ -486,6 +505,37 @@ class TelegramSelectionHandlers:
|
|
|
486
505
|
include_cancel=True,
|
|
487
506
|
)
|
|
488
507
|
|
|
508
|
+
def _build_flow_runs_keyboard(self, state: SelectionState) -> dict[str, Any]:
|
|
509
|
+
page_items = _page_slice(state.items, state.page, DEFAULT_PAGE_SIZE)
|
|
510
|
+
options = []
|
|
511
|
+
for idx, (item_id, label) in enumerate(page_items, 1):
|
|
512
|
+
button_label = label
|
|
513
|
+
if state.button_labels:
|
|
514
|
+
button_label = state.button_labels.get(item_id, label)
|
|
515
|
+
options.append(
|
|
516
|
+
(
|
|
517
|
+
item_id,
|
|
518
|
+
f"{idx}) {_compact_preview(button_label, RESUME_BUTTON_PREVIEW_LIMIT)}",
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
return build_flow_runs_keyboard(
|
|
522
|
+
options,
|
|
523
|
+
page_button=self._page_button("flow-runs", state),
|
|
524
|
+
include_cancel=True,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
def _flow_runs_prompt(self, state: SelectionState) -> str:
|
|
528
|
+
total_pages = _page_count(len(state.items), DEFAULT_PAGE_SIZE)
|
|
529
|
+
page_items = _page_slice(state.items, state.page, DEFAULT_PAGE_SIZE)
|
|
530
|
+
lines = [FLOW_RUNS_PICKER_PROMPT]
|
|
531
|
+
for run_id, label in page_items:
|
|
532
|
+
if label:
|
|
533
|
+
lines.append(f"- {run_id} — {label}")
|
|
534
|
+
else:
|
|
535
|
+
lines.append(f"- {run_id}")
|
|
536
|
+
base = "\n".join(lines)
|
|
537
|
+
return _format_selection_prompt(base, state.page, total_pages)
|
|
538
|
+
|
|
489
539
|
def _build_effort_keyboard(self, option: ModelOption) -> dict[str, Any]:
|
|
490
540
|
options = []
|
|
491
541
|
for effort in option.efforts:
|
|
@@ -566,6 +616,9 @@ class TelegramSelectionHandlers:
|
|
|
566
616
|
elif parsed.kind == "review-custom":
|
|
567
617
|
self._pending_review_custom.pop(key, None)
|
|
568
618
|
text = "Custom review cancelled."
|
|
619
|
+
elif parsed.kind == "flow-runs":
|
|
620
|
+
self._flow_run_options.pop(key, None)
|
|
621
|
+
text = "Flow run selection cancelled."
|
|
569
622
|
else:
|
|
570
623
|
await self._answer_callback(callback, "Selection expired")
|
|
571
624
|
return
|
|
@@ -605,6 +658,10 @@ class TelegramSelectionHandlers:
|
|
|
605
658
|
Callable[[SelectionState], dict[str, Any]],
|
|
606
659
|
self._build_review_commit_keyboard,
|
|
607
660
|
)
|
|
661
|
+
elif parsed.kind == "flow-runs":
|
|
662
|
+
state = self._flow_run_options.get(key)
|
|
663
|
+
prompt_base = ""
|
|
664
|
+
build_keyboard = self._build_flow_runs_keyboard
|
|
608
665
|
else:
|
|
609
666
|
await self._answer_callback(callback, "Selection expired")
|
|
610
667
|
return
|
|
@@ -617,7 +674,10 @@ class TelegramSelectionHandlers:
|
|
|
617
674
|
return
|
|
618
675
|
page = parsed.page % total_pages
|
|
619
676
|
state.page = page
|
|
620
|
-
|
|
677
|
+
if parsed.kind == "flow-runs":
|
|
678
|
+
prompt = self._flow_runs_prompt(state)
|
|
679
|
+
else:
|
|
680
|
+
prompt = _format_selection_prompt(prompt_base, page, total_pages)
|
|
621
681
|
keyboard = build_keyboard(state)
|
|
622
682
|
await self._update_selection_message(key, callback, prompt, keyboard)
|
|
623
683
|
await self._answer_callback(callback, f"Page {page + 1}/{total_pages}")
|