codex-autorunner 0.1.2__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -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 +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -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/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- 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/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -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 +1364 -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 +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- 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 +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- 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 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- 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 +344 -325
- 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 +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -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 +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -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 +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -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 +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -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 +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -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 +417 -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 +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.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/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -9,7 +9,6 @@ import re
|
|
|
9
9
|
import time
|
|
10
10
|
from contextlib import suppress
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
from os import getenv
|
|
13
12
|
from pathlib import Path
|
|
14
13
|
from typing import TYPE_CHECKING, Any, Optional
|
|
15
14
|
|
|
@@ -26,20 +25,14 @@ from .....agents.opencode.runtime import (
|
|
|
26
25
|
opencode_missing_env,
|
|
27
26
|
split_model_id,
|
|
28
27
|
)
|
|
29
|
-
from .....core.config import load_hub_config, load_repo_config
|
|
30
28
|
from .....core.logging_utils import log_event
|
|
31
29
|
from .....core.state import now_iso
|
|
32
30
|
from .....core.text_delta_coalescer import TextDeltaCoalescer
|
|
33
|
-
from .....core.utils import canonicalize_path
|
|
34
|
-
from .....integrations.github.service import GitHubError, GitHubService
|
|
35
|
-
from .....manifest import load_manifest
|
|
36
31
|
from ....app_server.client import (
|
|
37
32
|
CodexAppServerDisconnected,
|
|
38
33
|
)
|
|
39
34
|
from ...adapter import (
|
|
40
35
|
InlineButton,
|
|
41
|
-
PrFlowStartCallback,
|
|
42
|
-
TelegramCallbackQuery,
|
|
43
36
|
TelegramMessage,
|
|
44
37
|
build_inline_keyboard,
|
|
45
38
|
encode_cancel_callback,
|
|
@@ -47,7 +40,6 @@ from ...adapter import (
|
|
|
47
40
|
from ...config import AppServerUnavailableError
|
|
48
41
|
from ...constants import (
|
|
49
42
|
MAX_TOPIC_THREAD_HISTORY,
|
|
50
|
-
OPENCODE_TURN_TIMEOUT_SECONDS,
|
|
51
43
|
PLACEHOLDER_TEXT,
|
|
52
44
|
QUEUED_PLACEHOLDER_TEXT,
|
|
53
45
|
RESUME_PREVIEW_ASSISTANT_LIMIT,
|
|
@@ -411,7 +403,7 @@ class GitHubCommands(SharedHelpers):
|
|
|
411
403
|
result = await self._wait_for_turn_result(
|
|
412
404
|
setup.client,
|
|
413
405
|
turn_context.turn_handle,
|
|
414
|
-
timeout_seconds=self._config.
|
|
406
|
+
timeout_seconds=self._config.agent_turn_timeout_seconds.get("codex"),
|
|
415
407
|
topic_key=topic_key,
|
|
416
408
|
chat_id=message.chat_id,
|
|
417
409
|
thread_id=message.thread_id,
|
|
@@ -1212,6 +1204,7 @@ class GitHubCommands(SharedHelpers):
|
|
|
1212
1204
|
setup.client,
|
|
1213
1205
|
session_id=setup.review_session_id,
|
|
1214
1206
|
workspace_path=str(setup.workspace_root),
|
|
1207
|
+
model_payload=model_payload,
|
|
1215
1208
|
progress_session_ids=watched_session_ids,
|
|
1216
1209
|
permission_policy=setup.permission_policy,
|
|
1217
1210
|
permission_handler=(
|
|
@@ -1226,9 +1219,12 @@ class GitHubCommands(SharedHelpers):
|
|
|
1226
1219
|
)
|
|
1227
1220
|
with suppress(asyncio.TimeoutError):
|
|
1228
1221
|
await asyncio.wait_for(ready_event.wait(), timeout=2.0)
|
|
1229
|
-
|
|
1230
|
-
|
|
1222
|
+
timeout_seconds = self._config.agent_turn_timeout_seconds.get(
|
|
1223
|
+
"opencode"
|
|
1231
1224
|
)
|
|
1225
|
+
timeout_task: Optional[asyncio.Task] = None
|
|
1226
|
+
if timeout_seconds is not None and timeout_seconds > 0:
|
|
1227
|
+
timeout_task = asyncio.create_task(asyncio.sleep(timeout_seconds))
|
|
1232
1228
|
command_task = asyncio.create_task(
|
|
1233
1229
|
setup.client.send_command(
|
|
1234
1230
|
setup.review_session_id,
|
|
@@ -1240,45 +1236,47 @@ class GitHubCommands(SharedHelpers):
|
|
|
1240
1236
|
try:
|
|
1241
1237
|
await command_task
|
|
1242
1238
|
except Exception as exc:
|
|
1243
|
-
timeout_task
|
|
1244
|
-
|
|
1245
|
-
|
|
1239
|
+
if timeout_task is not None:
|
|
1240
|
+
timeout_task.cancel()
|
|
1241
|
+
with suppress(asyncio.CancelledError):
|
|
1242
|
+
await timeout_task
|
|
1246
1243
|
output_task.cancel()
|
|
1247
1244
|
with suppress(asyncio.CancelledError):
|
|
1248
1245
|
await output_task
|
|
1249
1246
|
raise exc
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1247
|
+
if timeout_task is not None:
|
|
1248
|
+
done, _pending = await asyncio.wait(
|
|
1249
|
+
{output_task, timeout_task},
|
|
1250
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
1251
|
+
)
|
|
1252
|
+
if timeout_task in done:
|
|
1253
|
+
runtime.interrupt_requested = True
|
|
1254
|
+
await _abort_opencode()
|
|
1255
|
+
output_task.cancel()
|
|
1256
|
+
with suppress(asyncio.CancelledError):
|
|
1257
|
+
await output_task
|
|
1258
|
+
timeout_task.cancel()
|
|
1259
|
+
with suppress(asyncio.CancelledError):
|
|
1260
|
+
await timeout_task
|
|
1261
|
+
turn_context.turn_elapsed_seconds = (
|
|
1262
|
+
time.monotonic() - turn_started_at
|
|
1263
|
+
if turn_started_at is not None
|
|
1264
|
+
else None
|
|
1265
|
+
)
|
|
1266
|
+
failure_message = "OpenCode review timed out."
|
|
1267
|
+
response_sent = await self._deliver_turn_response(
|
|
1268
|
+
chat_id=message.chat_id,
|
|
1269
|
+
thread_id=message.thread_id,
|
|
1270
|
+
reply_to=message.message_id,
|
|
1271
|
+
placeholder_id=placeholder_id,
|
|
1272
|
+
response=failure_message,
|
|
1273
|
+
)
|
|
1274
|
+
if response_sent:
|
|
1275
|
+
await self._delete_message(message.chat_id, placeholder_id)
|
|
1276
|
+
return turn_context, None
|
|
1260
1277
|
timeout_task.cancel()
|
|
1261
1278
|
with suppress(asyncio.CancelledError):
|
|
1262
1279
|
await timeout_task
|
|
1263
|
-
turn_context.turn_elapsed_seconds = (
|
|
1264
|
-
time.monotonic() - turn_started_at
|
|
1265
|
-
if turn_started_at is not None
|
|
1266
|
-
else None
|
|
1267
|
-
)
|
|
1268
|
-
failure_message = "OpenCode review timed out."
|
|
1269
|
-
response_sent = await self._deliver_turn_response(
|
|
1270
|
-
chat_id=message.chat_id,
|
|
1271
|
-
thread_id=message.thread_id,
|
|
1272
|
-
reply_to=message.message_id,
|
|
1273
|
-
placeholder_id=placeholder_id,
|
|
1274
|
-
response=failure_message,
|
|
1275
|
-
)
|
|
1276
|
-
if response_sent:
|
|
1277
|
-
await self._delete_message(message.chat_id, placeholder_id)
|
|
1278
|
-
return turn_context, None
|
|
1279
|
-
timeout_task.cancel()
|
|
1280
|
-
with suppress(asyncio.CancelledError):
|
|
1281
|
-
await timeout_task
|
|
1282
1280
|
output_result = await output_task
|
|
1283
1281
|
elapsed = (
|
|
1284
1282
|
time.monotonic() - turn_started_at
|
|
@@ -1563,520 +1561,6 @@ class GitHubCommands(SharedHelpers):
|
|
|
1563
1561
|
delivery=delivery,
|
|
1564
1562
|
)
|
|
1565
1563
|
|
|
1566
|
-
def _resolve_pr_flow_repo_id(self, record: "TelegramTopicRecord") -> Optional[str]:
|
|
1567
|
-
if record.repo_id:
|
|
1568
|
-
return record.repo_id
|
|
1569
|
-
if not self._hub_root or not self._manifest_path or not record.workspace_path:
|
|
1570
|
-
return None
|
|
1571
|
-
try:
|
|
1572
|
-
manifest = load_manifest(self._manifest_path, self._hub_root)
|
|
1573
|
-
except Exception:
|
|
1574
|
-
return None
|
|
1575
|
-
try:
|
|
1576
|
-
workspace_path = canonicalize_path(Path(record.workspace_path))
|
|
1577
|
-
except Exception:
|
|
1578
|
-
return None
|
|
1579
|
-
for repo in manifest.repos:
|
|
1580
|
-
repo_path = canonicalize_path(self._hub_root / repo.path)
|
|
1581
|
-
if repo_path == workspace_path:
|
|
1582
|
-
return repo.id
|
|
1583
|
-
return None
|
|
1584
|
-
|
|
1585
|
-
def _pr_flow_api_base(
|
|
1586
|
-
self, record: "TelegramTopicRecord"
|
|
1587
|
-
) -> tuple[Optional[str], dict[str, str]]:
|
|
1588
|
-
headers: dict[str, str] = {}
|
|
1589
|
-
if self._hub_root is not None:
|
|
1590
|
-
try:
|
|
1591
|
-
hub_config = load_hub_config(self._hub_root)
|
|
1592
|
-
except Exception:
|
|
1593
|
-
return None, headers
|
|
1594
|
-
host = hub_config.server_host
|
|
1595
|
-
port = hub_config.server_port
|
|
1596
|
-
base_path = hub_config.server_base_path
|
|
1597
|
-
auth_env = hub_config.server_auth_token_env
|
|
1598
|
-
repo_id = self._resolve_pr_flow_repo_id(record)
|
|
1599
|
-
if not repo_id:
|
|
1600
|
-
return None, headers
|
|
1601
|
-
repo_prefix = f"/repos/{repo_id}"
|
|
1602
|
-
else:
|
|
1603
|
-
if not record.workspace_path:
|
|
1604
|
-
return None, headers
|
|
1605
|
-
try:
|
|
1606
|
-
repo_config = load_repo_config(
|
|
1607
|
-
Path(record.workspace_path), hub_path=None
|
|
1608
|
-
)
|
|
1609
|
-
except Exception:
|
|
1610
|
-
return None, headers
|
|
1611
|
-
host = repo_config.server_host
|
|
1612
|
-
port = repo_config.server_port
|
|
1613
|
-
base_path = repo_config.server_base_path
|
|
1614
|
-
auth_env = repo_config.server_auth_token_env
|
|
1615
|
-
repo_prefix = ""
|
|
1616
|
-
if isinstance(auth_env, str) and auth_env:
|
|
1617
|
-
token = getenv(auth_env)
|
|
1618
|
-
if token:
|
|
1619
|
-
headers["Authorization"] = f"Bearer {token}"
|
|
1620
|
-
if not host:
|
|
1621
|
-
return None, headers
|
|
1622
|
-
if host.startswith("http://") or host.startswith("https://"):
|
|
1623
|
-
base = host.rstrip("/")
|
|
1624
|
-
else:
|
|
1625
|
-
base = f"http://{host}:{int(port)}"
|
|
1626
|
-
base_path = (base_path or "").strip("/")
|
|
1627
|
-
if base_path:
|
|
1628
|
-
base = f"{base}/{base_path}"
|
|
1629
|
-
return f"{base}{repo_prefix}", headers
|
|
1630
|
-
|
|
1631
|
-
async def _pr_flow_request(
|
|
1632
|
-
self,
|
|
1633
|
-
record: "TelegramTopicRecord",
|
|
1634
|
-
*,
|
|
1635
|
-
method: str,
|
|
1636
|
-
path: str,
|
|
1637
|
-
payload: Optional[dict[str, Any]] = None,
|
|
1638
|
-
) -> dict[str, Any]:
|
|
1639
|
-
base, headers = self._pr_flow_api_base(record)
|
|
1640
|
-
if not base:
|
|
1641
|
-
raise RuntimeError(
|
|
1642
|
-
"PR flow cannot start: repo server base URL could not be resolved for this chat/topic."
|
|
1643
|
-
)
|
|
1644
|
-
url = f"{base}{path}"
|
|
1645
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
1646
|
-
res = await client.request(method, url, json=payload, headers=headers)
|
|
1647
|
-
res.raise_for_status()
|
|
1648
|
-
data = res.json()
|
|
1649
|
-
if isinstance(data, dict):
|
|
1650
|
-
return data
|
|
1651
|
-
return {"status": "ok", "flow": data}
|
|
1652
|
-
|
|
1653
|
-
def _parse_pr_flags(self, argv: list[str]) -> tuple[Optional[str], dict[str, Any]]:
|
|
1654
|
-
ref: Optional[str] = None
|
|
1655
|
-
flags: dict[str, Any] = {}
|
|
1656
|
-
idx = 0
|
|
1657
|
-
while idx < len(argv):
|
|
1658
|
-
token = argv[idx]
|
|
1659
|
-
if token.startswith("--"):
|
|
1660
|
-
if token == "--draft":
|
|
1661
|
-
flags["draft"] = True
|
|
1662
|
-
idx += 1
|
|
1663
|
-
continue
|
|
1664
|
-
if token == "--ready":
|
|
1665
|
-
flags["draft"] = False
|
|
1666
|
-
idx += 1
|
|
1667
|
-
continue
|
|
1668
|
-
if token == "--base" and idx + 1 < len(argv):
|
|
1669
|
-
flags["base_branch"] = argv[idx + 1]
|
|
1670
|
-
idx += 2
|
|
1671
|
-
continue
|
|
1672
|
-
if token == "--until" and idx + 1 < len(argv):
|
|
1673
|
-
until = argv[idx + 1].strip().lower()
|
|
1674
|
-
if until in ("minor", "minor_only"):
|
|
1675
|
-
flags["stop_condition"] = "minor_only"
|
|
1676
|
-
elif until in ("clean", "no_issues"):
|
|
1677
|
-
flags["stop_condition"] = "no_issues"
|
|
1678
|
-
idx += 2
|
|
1679
|
-
continue
|
|
1680
|
-
if token in ("--max-cycles", "--max_cycles") and idx + 1 < len(argv):
|
|
1681
|
-
try:
|
|
1682
|
-
flags["max_cycles"] = int(argv[idx + 1])
|
|
1683
|
-
except ValueError:
|
|
1684
|
-
pass
|
|
1685
|
-
idx += 2
|
|
1686
|
-
continue
|
|
1687
|
-
if token in ("--max-runs", "--max_runs") and idx + 1 < len(argv):
|
|
1688
|
-
try:
|
|
1689
|
-
flags["max_implementation_runs"] = int(argv[idx + 1])
|
|
1690
|
-
except ValueError:
|
|
1691
|
-
pass
|
|
1692
|
-
idx += 2
|
|
1693
|
-
continue
|
|
1694
|
-
if token in ("--timeout", "--timeout-seconds") and idx + 1 < len(argv):
|
|
1695
|
-
try:
|
|
1696
|
-
flags["max_wallclock_seconds"] = int(argv[idx + 1])
|
|
1697
|
-
except ValueError:
|
|
1698
|
-
pass
|
|
1699
|
-
idx += 2
|
|
1700
|
-
continue
|
|
1701
|
-
idx += 1
|
|
1702
|
-
continue
|
|
1703
|
-
if ref is None:
|
|
1704
|
-
ref = token
|
|
1705
|
-
idx += 1
|
|
1706
|
-
return ref, flags
|
|
1707
|
-
|
|
1708
|
-
def _format_pr_flow_status(self, flow: dict[str, Any]) -> str:
|
|
1709
|
-
status = flow.get("status") or "unknown"
|
|
1710
|
-
step = flow.get("step") or "unknown"
|
|
1711
|
-
cycle = flow.get("cycle") or 0
|
|
1712
|
-
pr_url = flow.get("pr_url") or ""
|
|
1713
|
-
lines = [f"PR flow: {status} (step: {step}, cycle: {cycle})"]
|
|
1714
|
-
if pr_url:
|
|
1715
|
-
lines.append(f"PR: {pr_url}")
|
|
1716
|
-
return "\n".join(lines)
|
|
1717
|
-
|
|
1718
|
-
async def _handle_github_issue_url(
|
|
1719
|
-
self,
|
|
1720
|
-
message: TelegramMessage,
|
|
1721
|
-
key: str,
|
|
1722
|
-
slug: str,
|
|
1723
|
-
number: int,
|
|
1724
|
-
) -> None:
|
|
1725
|
-
if key is None:
|
|
1726
|
-
return
|
|
1727
|
-
|
|
1728
|
-
record = await self._router.get_topic(key)
|
|
1729
|
-
if record is None or not record.workspace_path:
|
|
1730
|
-
await self._send_message(
|
|
1731
|
-
message.chat_id,
|
|
1732
|
-
self._with_conversation_id(
|
|
1733
|
-
"Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
1734
|
-
chat_id=message.chat_id,
|
|
1735
|
-
thread_id=message.thread_id,
|
|
1736
|
-
),
|
|
1737
|
-
thread_id=message.thread_id,
|
|
1738
|
-
reply_to=message.message_id,
|
|
1739
|
-
)
|
|
1740
|
-
return
|
|
1741
|
-
|
|
1742
|
-
try:
|
|
1743
|
-
from pathlib import Path
|
|
1744
|
-
|
|
1745
|
-
service = GitHubService(Path(record.workspace_path), self._raw_config)
|
|
1746
|
-
issue_ref = f"{slug}#{number}"
|
|
1747
|
-
service.validate_issue_same_repo(issue_ref)
|
|
1748
|
-
except GitHubError as exc:
|
|
1749
|
-
await self._send_message(
|
|
1750
|
-
message.chat_id,
|
|
1751
|
-
str(exc),
|
|
1752
|
-
thread_id=message.thread_id,
|
|
1753
|
-
reply_to=message.message_id,
|
|
1754
|
-
)
|
|
1755
|
-
return
|
|
1756
|
-
|
|
1757
|
-
await self._offer_pr_flow_start(message, record, slug, number)
|
|
1758
|
-
|
|
1759
|
-
async def _offer_pr_flow_start(
|
|
1760
|
-
self,
|
|
1761
|
-
message: TelegramMessage,
|
|
1762
|
-
record: "TelegramTopicRecord",
|
|
1763
|
-
slug: str,
|
|
1764
|
-
number: int,
|
|
1765
|
-
) -> None:
|
|
1766
|
-
from ...adapter import (
|
|
1767
|
-
InlineButton,
|
|
1768
|
-
build_inline_keyboard,
|
|
1769
|
-
encode_cancel_callback,
|
|
1770
|
-
encode_pr_flow_start_callback,
|
|
1771
|
-
)
|
|
1772
|
-
|
|
1773
|
-
keyboard = build_inline_keyboard(
|
|
1774
|
-
[
|
|
1775
|
-
[
|
|
1776
|
-
InlineButton(
|
|
1777
|
-
f"Create PR for #{number}",
|
|
1778
|
-
encode_pr_flow_start_callback(slug, number),
|
|
1779
|
-
),
|
|
1780
|
-
InlineButton(
|
|
1781
|
-
"Cancel",
|
|
1782
|
-
encode_cancel_callback("pr_flow_offer"),
|
|
1783
|
-
),
|
|
1784
|
-
]
|
|
1785
|
-
]
|
|
1786
|
-
)
|
|
1787
|
-
await self._send_message(
|
|
1788
|
-
message.chat_id,
|
|
1789
|
-
f"Detected GitHub issue: {slug}#{number}\nStart PR flow to create a PR?",
|
|
1790
|
-
thread_id=message.thread_id,
|
|
1791
|
-
reply_to=message.message_id,
|
|
1792
|
-
reply_markup=keyboard,
|
|
1793
|
-
)
|
|
1794
|
-
|
|
1795
|
-
async def _handle_pr_flow_start_callback(
|
|
1796
|
-
self,
|
|
1797
|
-
key: str,
|
|
1798
|
-
callback: TelegramCallbackQuery,
|
|
1799
|
-
parsed: PrFlowStartCallback,
|
|
1800
|
-
) -> None:
|
|
1801
|
-
from ...adapter import TelegramMessage
|
|
1802
|
-
|
|
1803
|
-
await self._answer_callback(callback)
|
|
1804
|
-
record = await self._router.get_topic(key)
|
|
1805
|
-
if record is None or not record.workspace_path:
|
|
1806
|
-
return
|
|
1807
|
-
|
|
1808
|
-
issue_ref = f"{parsed.slug}#{parsed.number}"
|
|
1809
|
-
payload = {"mode": "issue", "issue": issue_ref}
|
|
1810
|
-
payload["source"] = "telegram"
|
|
1811
|
-
source_meta: dict[str, Any] = {}
|
|
1812
|
-
if callback.chat_id is not None:
|
|
1813
|
-
source_meta["chat_id"] = callback.chat_id
|
|
1814
|
-
if callback.thread_id is not None:
|
|
1815
|
-
source_meta["thread_id"] = callback.thread_id
|
|
1816
|
-
if source_meta:
|
|
1817
|
-
payload["source_meta"] = source_meta
|
|
1818
|
-
|
|
1819
|
-
message = TelegramMessage(
|
|
1820
|
-
update_id=callback.update_id,
|
|
1821
|
-
message_id=callback.message_id or 0,
|
|
1822
|
-
chat_id=callback.chat_id or 0,
|
|
1823
|
-
thread_id=callback.thread_id,
|
|
1824
|
-
from_user_id=callback.from_user_id,
|
|
1825
|
-
text="",
|
|
1826
|
-
date=None,
|
|
1827
|
-
is_topic_message=False,
|
|
1828
|
-
)
|
|
1829
|
-
|
|
1830
|
-
try:
|
|
1831
|
-
data = await self._pr_flow_request(
|
|
1832
|
-
record,
|
|
1833
|
-
method="POST",
|
|
1834
|
-
path="/api/github/pr_flow/start",
|
|
1835
|
-
payload=payload,
|
|
1836
|
-
)
|
|
1837
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1838
|
-
except Exception as exc:
|
|
1839
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1840
|
-
await self._send_message(
|
|
1841
|
-
message.chat_id,
|
|
1842
|
-
f"PR flow error: {detail}",
|
|
1843
|
-
thread_id=message.thread_id,
|
|
1844
|
-
reply_to=callback.message_id,
|
|
1845
|
-
)
|
|
1846
|
-
return
|
|
1847
|
-
await self._send_message(
|
|
1848
|
-
message.chat_id,
|
|
1849
|
-
self._format_pr_flow_status(flow),
|
|
1850
|
-
thread_id=message.thread_id,
|
|
1851
|
-
reply_to=callback.message_id,
|
|
1852
|
-
)
|
|
1853
|
-
|
|
1854
|
-
async def _handle_pr(
|
|
1855
|
-
self, message: TelegramMessage, args: str, runtime: Any
|
|
1856
|
-
) -> None:
|
|
1857
|
-
record = await self._require_bound_record(message)
|
|
1858
|
-
if not record:
|
|
1859
|
-
return
|
|
1860
|
-
argv = self._parse_command_args(args)
|
|
1861
|
-
if not argv:
|
|
1862
|
-
await self._send_message(
|
|
1863
|
-
message.chat_id,
|
|
1864
|
-
"Usage: /pr start <issueRef> | /pr fix <prRef> | /pr status | /pr stop | /pr resume | /pr collect",
|
|
1865
|
-
thread_id=message.thread_id,
|
|
1866
|
-
reply_to=message.message_id,
|
|
1867
|
-
)
|
|
1868
|
-
return
|
|
1869
|
-
command = argv[0].lower()
|
|
1870
|
-
if command == "status":
|
|
1871
|
-
try:
|
|
1872
|
-
data = await self._pr_flow_request(
|
|
1873
|
-
record, method="GET", path="/api/github/pr_flow/status"
|
|
1874
|
-
)
|
|
1875
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1876
|
-
except Exception as exc:
|
|
1877
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1878
|
-
await self._send_message(
|
|
1879
|
-
message.chat_id,
|
|
1880
|
-
f"PR flow error: {detail}",
|
|
1881
|
-
thread_id=message.thread_id,
|
|
1882
|
-
reply_to=message.message_id,
|
|
1883
|
-
)
|
|
1884
|
-
return
|
|
1885
|
-
await self._send_message(
|
|
1886
|
-
message.chat_id,
|
|
1887
|
-
self._format_pr_flow_status(flow),
|
|
1888
|
-
thread_id=message.thread_id,
|
|
1889
|
-
reply_to=message.message_id,
|
|
1890
|
-
)
|
|
1891
|
-
return
|
|
1892
|
-
if command == "stop":
|
|
1893
|
-
try:
|
|
1894
|
-
data = await self._pr_flow_request(
|
|
1895
|
-
record, method="POST", path="/api/github/pr_flow/stop", payload={}
|
|
1896
|
-
)
|
|
1897
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1898
|
-
except Exception as exc:
|
|
1899
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1900
|
-
await self._send_message(
|
|
1901
|
-
message.chat_id,
|
|
1902
|
-
f"PR flow error: {detail}",
|
|
1903
|
-
thread_id=message.thread_id,
|
|
1904
|
-
reply_to=message.message_id,
|
|
1905
|
-
)
|
|
1906
|
-
return
|
|
1907
|
-
await self._send_message(
|
|
1908
|
-
message.chat_id,
|
|
1909
|
-
self._format_pr_flow_status(flow),
|
|
1910
|
-
thread_id=message.thread_id,
|
|
1911
|
-
reply_to=message.message_id,
|
|
1912
|
-
)
|
|
1913
|
-
return
|
|
1914
|
-
if command == "resume":
|
|
1915
|
-
try:
|
|
1916
|
-
data = await self._pr_flow_request(
|
|
1917
|
-
record, method="POST", path="/api/github/pr_flow/resume", payload={}
|
|
1918
|
-
)
|
|
1919
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1920
|
-
except Exception as exc:
|
|
1921
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1922
|
-
await self._send_message(
|
|
1923
|
-
message.chat_id,
|
|
1924
|
-
f"PR flow error: {detail}",
|
|
1925
|
-
thread_id=message.thread_id,
|
|
1926
|
-
reply_to=message.message_id,
|
|
1927
|
-
)
|
|
1928
|
-
return
|
|
1929
|
-
await self._send_message(
|
|
1930
|
-
message.chat_id,
|
|
1931
|
-
self._format_pr_flow_status(flow),
|
|
1932
|
-
thread_id=message.thread_id,
|
|
1933
|
-
reply_to=message.message_id,
|
|
1934
|
-
)
|
|
1935
|
-
return
|
|
1936
|
-
if command == "collect":
|
|
1937
|
-
try:
|
|
1938
|
-
data = await self._pr_flow_request(
|
|
1939
|
-
record,
|
|
1940
|
-
method="POST",
|
|
1941
|
-
path="/api/github/pr_flow/collect",
|
|
1942
|
-
payload={},
|
|
1943
|
-
)
|
|
1944
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1945
|
-
except Exception as exc:
|
|
1946
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1947
|
-
await self._send_message(
|
|
1948
|
-
message.chat_id,
|
|
1949
|
-
f"PR flow error: {detail}",
|
|
1950
|
-
thread_id=message.thread_id,
|
|
1951
|
-
reply_to=message.message_id,
|
|
1952
|
-
)
|
|
1953
|
-
return
|
|
1954
|
-
await self._send_message(
|
|
1955
|
-
message.chat_id,
|
|
1956
|
-
self._format_pr_flow_status(flow),
|
|
1957
|
-
thread_id=message.thread_id,
|
|
1958
|
-
reply_to=message.message_id,
|
|
1959
|
-
)
|
|
1960
|
-
return
|
|
1961
|
-
if command in ("start", "implement"):
|
|
1962
|
-
ref, flags = self._parse_pr_flags(argv[1:])
|
|
1963
|
-
if not ref:
|
|
1964
|
-
gh = GitHubService(Path(record.workspace_path))
|
|
1965
|
-
issues = await asyncio.to_thread(gh.list_open_issues, limit=5)
|
|
1966
|
-
if issues:
|
|
1967
|
-
lines = ["Open issues:"]
|
|
1968
|
-
for issue in issues:
|
|
1969
|
-
num = issue.get("number")
|
|
1970
|
-
title = issue.get("title") or ""
|
|
1971
|
-
lines.append(f"- #{num} {title}".strip())
|
|
1972
|
-
lines.append("Use /pr start <issueRef> to begin.")
|
|
1973
|
-
await self._send_message(
|
|
1974
|
-
message.chat_id,
|
|
1975
|
-
"\n".join(lines),
|
|
1976
|
-
thread_id=message.thread_id,
|
|
1977
|
-
reply_to=message.message_id,
|
|
1978
|
-
)
|
|
1979
|
-
return
|
|
1980
|
-
await self._send_message(
|
|
1981
|
-
message.chat_id,
|
|
1982
|
-
"Usage: /pr start <issueRef>",
|
|
1983
|
-
thread_id=message.thread_id,
|
|
1984
|
-
reply_to=message.message_id,
|
|
1985
|
-
)
|
|
1986
|
-
return
|
|
1987
|
-
payload = {"mode": "issue", "issue": ref, **flags}
|
|
1988
|
-
payload["source"] = "telegram"
|
|
1989
|
-
payload["source_meta"] = {
|
|
1990
|
-
"chat_id": message.chat_id,
|
|
1991
|
-
"thread_id": message.thread_id,
|
|
1992
|
-
}
|
|
1993
|
-
try:
|
|
1994
|
-
data = await self._pr_flow_request(
|
|
1995
|
-
record,
|
|
1996
|
-
method="POST",
|
|
1997
|
-
path="/api/github/pr_flow/start",
|
|
1998
|
-
payload=payload,
|
|
1999
|
-
)
|
|
2000
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
2001
|
-
except Exception as exc:
|
|
2002
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
2003
|
-
await self._send_message(
|
|
2004
|
-
message.chat_id,
|
|
2005
|
-
f"PR flow error: {detail}",
|
|
2006
|
-
thread_id=message.thread_id,
|
|
2007
|
-
reply_to=message.message_id,
|
|
2008
|
-
)
|
|
2009
|
-
return
|
|
2010
|
-
await self._send_message(
|
|
2011
|
-
message.chat_id,
|
|
2012
|
-
self._format_pr_flow_status(flow),
|
|
2013
|
-
thread_id=message.thread_id,
|
|
2014
|
-
reply_to=message.message_id,
|
|
2015
|
-
)
|
|
2016
|
-
return
|
|
2017
|
-
if command in ("fix", "pr"):
|
|
2018
|
-
ref, flags = self._parse_pr_flags(argv[1:])
|
|
2019
|
-
if not ref:
|
|
2020
|
-
gh = GitHubService(Path(record.workspace_path))
|
|
2021
|
-
prs = await asyncio.to_thread(gh.list_open_prs, limit=5)
|
|
2022
|
-
if prs:
|
|
2023
|
-
lines = ["Open PRs:"]
|
|
2024
|
-
for pr in prs:
|
|
2025
|
-
num = pr.get("number")
|
|
2026
|
-
title = pr.get("title") or ""
|
|
2027
|
-
lines.append(f"- #{num} {title}".strip())
|
|
2028
|
-
lines.append("Use /pr fix <prRef> to begin.")
|
|
2029
|
-
await self._send_message(
|
|
2030
|
-
message.chat_id,
|
|
2031
|
-
"\n".join(lines),
|
|
2032
|
-
thread_id=message.thread_id,
|
|
2033
|
-
reply_to=message.message_id,
|
|
2034
|
-
)
|
|
2035
|
-
return
|
|
2036
|
-
await self._send_message(
|
|
2037
|
-
message.chat_id,
|
|
2038
|
-
"Usage: /pr fix <prRef>",
|
|
2039
|
-
thread_id=message.thread_id,
|
|
2040
|
-
reply_to=message.message_id,
|
|
2041
|
-
)
|
|
2042
|
-
return
|
|
2043
|
-
payload = {"mode": "pr", "pr": ref, **flags}
|
|
2044
|
-
payload["source"] = "telegram"
|
|
2045
|
-
payload["source_meta"] = {
|
|
2046
|
-
"chat_id": message.chat_id,
|
|
2047
|
-
"thread_id": message.thread_id,
|
|
2048
|
-
}
|
|
2049
|
-
try:
|
|
2050
|
-
data = await self._pr_flow_request(
|
|
2051
|
-
record,
|
|
2052
|
-
method="POST",
|
|
2053
|
-
path="/api/github/pr_flow/start",
|
|
2054
|
-
payload=payload,
|
|
2055
|
-
)
|
|
2056
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
2057
|
-
except Exception as exc:
|
|
2058
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
2059
|
-
await self._send_message(
|
|
2060
|
-
message.chat_id,
|
|
2061
|
-
f"PR flow error: {detail}",
|
|
2062
|
-
thread_id=message.thread_id,
|
|
2063
|
-
reply_to=message.message_id,
|
|
2064
|
-
)
|
|
2065
|
-
return
|
|
2066
|
-
await self._send_message(
|
|
2067
|
-
message.chat_id,
|
|
2068
|
-
self._format_pr_flow_status(flow),
|
|
2069
|
-
thread_id=message.thread_id,
|
|
2070
|
-
reply_to=message.message_id,
|
|
2071
|
-
)
|
|
2072
|
-
return
|
|
2073
|
-
await self._send_message(
|
|
2074
|
-
message.chat_id,
|
|
2075
|
-
"Unknown /pr command. Use /pr start|fix|status|stop|resume|collect.",
|
|
2076
|
-
thread_id=message.thread_id,
|
|
2077
|
-
reply_to=message.message_id,
|
|
2078
|
-
)
|
|
2079
|
-
|
|
2080
1564
|
async def _prompt_review_commit_picker(
|
|
2081
1565
|
self,
|
|
2082
1566
|
message: TelegramMessage,
|
|
@@ -2202,28 +1686,3 @@ def _extract_opencode_error_detail(payload: Any) -> Optional[str]:
|
|
|
2202
1686
|
if isinstance(value, str) and value:
|
|
2203
1687
|
return value
|
|
2204
1688
|
return None
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
def _format_httpx_exception(exc: Exception) -> Optional[str]:
|
|
2208
|
-
"""Format httpx exceptions for user-friendly error messages."""
|
|
2209
|
-
if isinstance(exc, httpx.HTTPStatusError):
|
|
2210
|
-
try:
|
|
2211
|
-
payload = exc.response.json()
|
|
2212
|
-
except Exception:
|
|
2213
|
-
payload = None
|
|
2214
|
-
if isinstance(payload, dict):
|
|
2215
|
-
detail = (
|
|
2216
|
-
payload.get("detail") or payload.get("message") or payload.get("error")
|
|
2217
|
-
)
|
|
2218
|
-
if isinstance(detail, str) and detail:
|
|
2219
|
-
return detail
|
|
2220
|
-
response_text = exc.response.text.strip()
|
|
2221
|
-
if response_text:
|
|
2222
|
-
return response_text
|
|
2223
|
-
return f"Request failed (HTTP {exc.response.status_code})."
|
|
2224
|
-
if isinstance(exc, httpx.RequestError):
|
|
2225
|
-
detail = str(exc).strip()
|
|
2226
|
-
if detail:
|
|
2227
|
-
return detail
|
|
2228
|
-
return "Request failed."
|
|
2229
|
-
return None
|