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
|
@@ -20,13 +20,12 @@ 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
|
-
from ....integrations.github.service import GitHubError, GitHubService
|
|
25
25
|
from ...app_server.client import _normalize_sandbox_policy
|
|
26
26
|
from ..adapter import (
|
|
27
27
|
CompactCallback,
|
|
28
28
|
InlineButton,
|
|
29
|
-
PrFlowStartCallback,
|
|
30
29
|
TelegramCallbackQuery,
|
|
31
30
|
TelegramCommand,
|
|
32
31
|
TelegramMessage,
|
|
@@ -56,6 +55,7 @@ from ..constants import (
|
|
|
56
55
|
TurnKey,
|
|
57
56
|
)
|
|
58
57
|
from ..helpers import (
|
|
58
|
+
CodexFeatureRow,
|
|
59
59
|
_coerce_model_options,
|
|
60
60
|
_compact_preview,
|
|
61
61
|
_extract_command_result,
|
|
@@ -79,6 +79,9 @@ from ..helpers import (
|
|
|
79
79
|
_set_rollout_path,
|
|
80
80
|
_thread_summary_preview,
|
|
81
81
|
_with_conversation_id,
|
|
82
|
+
derive_codex_features_command,
|
|
83
|
+
format_codex_features,
|
|
84
|
+
parse_codex_features_list,
|
|
82
85
|
)
|
|
83
86
|
from ..state import (
|
|
84
87
|
parse_topic_key,
|
|
@@ -98,6 +101,7 @@ from .commands import (
|
|
|
98
101
|
ApprovalsCommands,
|
|
99
102
|
ExecutionCommands,
|
|
100
103
|
FilesCommands,
|
|
104
|
+
FlowCommands,
|
|
101
105
|
FormattingHelpers,
|
|
102
106
|
GitHubCommands,
|
|
103
107
|
VoiceCommands,
|
|
@@ -113,20 +117,7 @@ OUTBOX_CONTEXT_RE = re.compile(
|
|
|
113
117
|
"(?:\\b(?:pdf|png|jpg|jpeg|gif|webp|svg|csv|tsv|json|yaml|yml|zip|tar|gz|tgz|xlsx|xls|docx|pptx|md|txt|log|html|xml)\\b|\\.(?:pdf|png|jpg|jpeg|gif|webp|svg|csv|tsv|json|yaml|yml|zip|tar|gz|tgz|xlsx|xls|docx|pptx|md|txt|log|html|xml)\\b|\\b(?:outbox)\\b)",
|
|
114
118
|
re.IGNORECASE,
|
|
115
119
|
)
|
|
116
|
-
|
|
117
|
-
"car",
|
|
118
|
-
"codex",
|
|
119
|
-
"todo",
|
|
120
|
-
"progress",
|
|
121
|
-
"opinions",
|
|
122
|
-
"spec",
|
|
123
|
-
"summary",
|
|
124
|
-
"autorunner",
|
|
125
|
-
"work docs",
|
|
126
|
-
)
|
|
127
|
-
CAR_CONTEXT_HINT = (
|
|
128
|
-
"Context: read .codex-autorunner/ABOUT_CAR.md for repo-specific rules."
|
|
129
|
-
)
|
|
120
|
+
|
|
130
121
|
FILES_HINT_TEMPLATE = """Inbox: {inbox}
|
|
131
122
|
Outbox (pending): {outbox}
|
|
132
123
|
Topic key: {topic_key}
|
|
@@ -297,7 +288,7 @@ def _iter_exception_chain(exc: BaseException) -> list[BaseException]:
|
|
|
297
288
|
def _sanitize_error_detail(detail: str, *, limit: int = 200) -> str:
|
|
298
289
|
cleaned = " ".join(detail.split())
|
|
299
290
|
if len(cleaned) > limit:
|
|
300
|
-
return f"{cleaned[:limit - 3]}..."
|
|
291
|
+
return f"{cleaned[: limit - 3]}..."
|
|
301
292
|
return cleaned
|
|
302
293
|
|
|
303
294
|
|
|
@@ -376,13 +367,13 @@ def _format_media_batch_failure(
|
|
|
376
367
|
class TelegramCommandHandlers(
|
|
377
368
|
WorkspaceCommands,
|
|
378
369
|
GitHubCommands,
|
|
370
|
+
FlowCommands,
|
|
379
371
|
FilesCommands,
|
|
380
372
|
VoiceCommands,
|
|
381
373
|
ExecutionCommands,
|
|
382
374
|
ApprovalsCommands,
|
|
383
375
|
FormattingHelpers,
|
|
384
376
|
):
|
|
385
|
-
|
|
386
377
|
async def _handle_help(
|
|
387
378
|
self, message: TelegramMessage, _args: str, _runtime: Any
|
|
388
379
|
) -> None:
|
|
@@ -1182,451 +1173,6 @@ class TelegramCommandHandlers(
|
|
|
1182
1173
|
reply_to=message.message_id,
|
|
1183
1174
|
)
|
|
1184
1175
|
|
|
1185
|
-
async def _pr_flow_request(
|
|
1186
|
-
self,
|
|
1187
|
-
record: "TelegramTopicRecord",
|
|
1188
|
-
*,
|
|
1189
|
-
method: str,
|
|
1190
|
-
path: str,
|
|
1191
|
-
payload: Optional[dict[str, Any]] = None,
|
|
1192
|
-
) -> dict[str, Any]:
|
|
1193
|
-
base, headers = self._pr_flow_api_base(record)
|
|
1194
|
-
if not base:
|
|
1195
|
-
raise RuntimeError(
|
|
1196
|
-
"PR flow cannot start: repo server base URL could not be resolved for this chat/topic."
|
|
1197
|
-
)
|
|
1198
|
-
url = f"{base}{path}"
|
|
1199
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
1200
|
-
res = await client.request(method, url, json=payload, headers=headers)
|
|
1201
|
-
res.raise_for_status()
|
|
1202
|
-
data = res.json()
|
|
1203
|
-
if isinstance(data, dict):
|
|
1204
|
-
return data
|
|
1205
|
-
return {"status": "ok", "flow": data}
|
|
1206
|
-
|
|
1207
|
-
def _parse_pr_flags(self, argv: list[str]) -> tuple[Optional[str], dict[str, Any]]:
|
|
1208
|
-
ref: Optional[str] = None
|
|
1209
|
-
flags: dict[str, Any] = {}
|
|
1210
|
-
idx = 0
|
|
1211
|
-
while idx < len(argv):
|
|
1212
|
-
token = argv[idx]
|
|
1213
|
-
if token.startswith("--"):
|
|
1214
|
-
if token == "--draft":
|
|
1215
|
-
flags["draft"] = True
|
|
1216
|
-
idx += 1
|
|
1217
|
-
continue
|
|
1218
|
-
if token == "--ready":
|
|
1219
|
-
flags["draft"] = False
|
|
1220
|
-
idx += 1
|
|
1221
|
-
continue
|
|
1222
|
-
if token == "--base" and idx + 1 < len(argv):
|
|
1223
|
-
flags["base_branch"] = argv[idx + 1]
|
|
1224
|
-
idx += 2
|
|
1225
|
-
continue
|
|
1226
|
-
if token == "--until" and idx + 1 < len(argv):
|
|
1227
|
-
until = argv[idx + 1].strip().lower()
|
|
1228
|
-
if until in ("minor", "minor_only"):
|
|
1229
|
-
flags["stop_condition"] = "minor_only"
|
|
1230
|
-
elif until in ("clean", "no_issues"):
|
|
1231
|
-
flags["stop_condition"] = "no_issues"
|
|
1232
|
-
idx += 2
|
|
1233
|
-
continue
|
|
1234
|
-
if token in ("--max-cycles", "--max_cycles") and idx + 1 < len(argv):
|
|
1235
|
-
try:
|
|
1236
|
-
flags["max_cycles"] = int(argv[idx + 1])
|
|
1237
|
-
except ValueError:
|
|
1238
|
-
pass
|
|
1239
|
-
idx += 2
|
|
1240
|
-
continue
|
|
1241
|
-
if token in ("--max-runs", "--max_runs") and idx + 1 < len(argv):
|
|
1242
|
-
try:
|
|
1243
|
-
flags["max_implementation_runs"] = int(argv[idx + 1])
|
|
1244
|
-
except ValueError:
|
|
1245
|
-
pass
|
|
1246
|
-
idx += 2
|
|
1247
|
-
continue
|
|
1248
|
-
if token in ("--timeout", "--timeout-seconds") and idx + 1 < len(argv):
|
|
1249
|
-
try:
|
|
1250
|
-
flags["max_wallclock_seconds"] = int(argv[idx + 1])
|
|
1251
|
-
except ValueError:
|
|
1252
|
-
pass
|
|
1253
|
-
idx += 2
|
|
1254
|
-
continue
|
|
1255
|
-
idx += 1
|
|
1256
|
-
continue
|
|
1257
|
-
if ref is None:
|
|
1258
|
-
ref = token
|
|
1259
|
-
idx += 1
|
|
1260
|
-
return ref, flags
|
|
1261
|
-
|
|
1262
|
-
def _format_pr_flow_status(self, flow: dict[str, Any]) -> str:
|
|
1263
|
-
status = flow.get("status") or "unknown"
|
|
1264
|
-
step = flow.get("step") or "unknown"
|
|
1265
|
-
cycle = flow.get("cycle") or 0
|
|
1266
|
-
pr_url = flow.get("pr_url") or ""
|
|
1267
|
-
lines = [f"PR flow: {status} (step: {step}, cycle: {cycle})"]
|
|
1268
|
-
if pr_url:
|
|
1269
|
-
lines.append(f"PR: {pr_url}")
|
|
1270
|
-
return "\n".join(lines)
|
|
1271
|
-
|
|
1272
|
-
async def _handle_github_issue_url(
|
|
1273
|
-
self, message: TelegramMessage, key: str, slug: str, number: int
|
|
1274
|
-
) -> None:
|
|
1275
|
-
if key is None:
|
|
1276
|
-
return
|
|
1277
|
-
|
|
1278
|
-
record = await self._router.get_topic(key)
|
|
1279
|
-
if record is None or not record.workspace_path:
|
|
1280
|
-
await self._send_message(
|
|
1281
|
-
message.chat_id,
|
|
1282
|
-
self._with_conversation_id(
|
|
1283
|
-
"Topic not bound. Use /bind <repo_id> or /bind <path>.",
|
|
1284
|
-
chat_id=message.chat_id,
|
|
1285
|
-
thread_id=message.thread_id,
|
|
1286
|
-
),
|
|
1287
|
-
thread_id=message.thread_id,
|
|
1288
|
-
reply_to=message.message_id,
|
|
1289
|
-
)
|
|
1290
|
-
return
|
|
1291
|
-
|
|
1292
|
-
try:
|
|
1293
|
-
from pathlib import Path
|
|
1294
|
-
|
|
1295
|
-
service = GitHubService(Path(record.workspace_path), self._raw_config)
|
|
1296
|
-
issue_ref = f"{slug}#{number}"
|
|
1297
|
-
service.validate_issue_same_repo(issue_ref)
|
|
1298
|
-
except GitHubError as exc:
|
|
1299
|
-
await self._send_message(
|
|
1300
|
-
message.chat_id,
|
|
1301
|
-
str(exc),
|
|
1302
|
-
thread_id=message.thread_id,
|
|
1303
|
-
reply_to=message.message_id,
|
|
1304
|
-
)
|
|
1305
|
-
return
|
|
1306
|
-
|
|
1307
|
-
await self._offer_pr_flow_start(message, record, slug, number)
|
|
1308
|
-
|
|
1309
|
-
async def _offer_pr_flow_start(
|
|
1310
|
-
self,
|
|
1311
|
-
message: TelegramMessage,
|
|
1312
|
-
record: "TelegramTopicRecord",
|
|
1313
|
-
slug: str,
|
|
1314
|
-
number: int,
|
|
1315
|
-
) -> None:
|
|
1316
|
-
from ..adapter import (
|
|
1317
|
-
InlineButton,
|
|
1318
|
-
build_inline_keyboard,
|
|
1319
|
-
encode_cancel_callback,
|
|
1320
|
-
encode_pr_flow_start_callback,
|
|
1321
|
-
)
|
|
1322
|
-
|
|
1323
|
-
keyboard = build_inline_keyboard(
|
|
1324
|
-
[
|
|
1325
|
-
[
|
|
1326
|
-
InlineButton(
|
|
1327
|
-
f"Create PR for #{number}",
|
|
1328
|
-
encode_pr_flow_start_callback(slug, number),
|
|
1329
|
-
),
|
|
1330
|
-
InlineButton(
|
|
1331
|
-
"Cancel",
|
|
1332
|
-
encode_cancel_callback("pr_flow_offer"),
|
|
1333
|
-
),
|
|
1334
|
-
]
|
|
1335
|
-
]
|
|
1336
|
-
)
|
|
1337
|
-
await self._send_message(
|
|
1338
|
-
message.chat_id,
|
|
1339
|
-
f"Detected GitHub issue: {slug}#{number}\nStart PR flow to create a PR?",
|
|
1340
|
-
thread_id=message.thread_id,
|
|
1341
|
-
reply_to=message.message_id,
|
|
1342
|
-
reply_markup=keyboard,
|
|
1343
|
-
)
|
|
1344
|
-
|
|
1345
|
-
async def _handle_pr_flow_start_callback(
|
|
1346
|
-
self,
|
|
1347
|
-
key: str,
|
|
1348
|
-
callback: TelegramCallbackQuery,
|
|
1349
|
-
parsed: PrFlowStartCallback,
|
|
1350
|
-
) -> None:
|
|
1351
|
-
from ..adapter import TelegramMessage
|
|
1352
|
-
|
|
1353
|
-
await self._answer_callback(callback)
|
|
1354
|
-
record = await self._router.get_topic(key)
|
|
1355
|
-
if record is None or not record.workspace_path:
|
|
1356
|
-
return
|
|
1357
|
-
|
|
1358
|
-
issue_ref = f"{parsed.slug}#{parsed.number}"
|
|
1359
|
-
payload = {"mode": "issue", "issue": issue_ref}
|
|
1360
|
-
payload["source"] = "telegram"
|
|
1361
|
-
source_meta: dict[str, Any] = {}
|
|
1362
|
-
if callback.chat_id is not None:
|
|
1363
|
-
source_meta["chat_id"] = callback.chat_id
|
|
1364
|
-
if callback.thread_id is not None:
|
|
1365
|
-
source_meta["thread_id"] = callback.thread_id
|
|
1366
|
-
if source_meta:
|
|
1367
|
-
payload["source_meta"] = source_meta
|
|
1368
|
-
|
|
1369
|
-
message = TelegramMessage(
|
|
1370
|
-
update_id=callback.update_id,
|
|
1371
|
-
message_id=callback.message_id or 0,
|
|
1372
|
-
chat_id=callback.chat_id or 0,
|
|
1373
|
-
thread_id=callback.thread_id,
|
|
1374
|
-
from_user_id=callback.from_user_id,
|
|
1375
|
-
text="",
|
|
1376
|
-
date=None,
|
|
1377
|
-
is_topic_message=False,
|
|
1378
|
-
)
|
|
1379
|
-
|
|
1380
|
-
try:
|
|
1381
|
-
data = await self._pr_flow_request(
|
|
1382
|
-
record,
|
|
1383
|
-
method="POST",
|
|
1384
|
-
path="/api/github/pr_flow/start",
|
|
1385
|
-
payload=payload,
|
|
1386
|
-
)
|
|
1387
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1388
|
-
except Exception as exc:
|
|
1389
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1390
|
-
await self._send_message(
|
|
1391
|
-
message.chat_id,
|
|
1392
|
-
f"PR flow error: {detail}",
|
|
1393
|
-
thread_id=message.thread_id,
|
|
1394
|
-
reply_to=callback.message_id,
|
|
1395
|
-
)
|
|
1396
|
-
return
|
|
1397
|
-
await self._send_message(
|
|
1398
|
-
message.chat_id,
|
|
1399
|
-
self._format_pr_flow_status(flow),
|
|
1400
|
-
thread_id=message.thread_id,
|
|
1401
|
-
reply_to=callback.message_id,
|
|
1402
|
-
)
|
|
1403
|
-
|
|
1404
|
-
async def _handle_pr(
|
|
1405
|
-
self, message: TelegramMessage, args: str, runtime: Any
|
|
1406
|
-
) -> None:
|
|
1407
|
-
record = await self._require_bound_record(message)
|
|
1408
|
-
if not record:
|
|
1409
|
-
return
|
|
1410
|
-
argv = self._parse_command_args(args)
|
|
1411
|
-
if not argv:
|
|
1412
|
-
await self._send_message(
|
|
1413
|
-
message.chat_id,
|
|
1414
|
-
"Usage: /pr start <issueRef> | /pr fix <prRef> | /pr status | /pr stop | /pr resume | /pr collect",
|
|
1415
|
-
thread_id=message.thread_id,
|
|
1416
|
-
reply_to=message.message_id,
|
|
1417
|
-
)
|
|
1418
|
-
return
|
|
1419
|
-
command = argv[0].lower()
|
|
1420
|
-
if command == "status":
|
|
1421
|
-
try:
|
|
1422
|
-
data = await self._pr_flow_request(
|
|
1423
|
-
record, method="GET", path="/api/github/pr_flow/status"
|
|
1424
|
-
)
|
|
1425
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1426
|
-
except Exception as exc:
|
|
1427
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1428
|
-
await self._send_message(
|
|
1429
|
-
message.chat_id,
|
|
1430
|
-
f"PR flow error: {detail}",
|
|
1431
|
-
thread_id=message.thread_id,
|
|
1432
|
-
reply_to=message.message_id,
|
|
1433
|
-
)
|
|
1434
|
-
return
|
|
1435
|
-
await self._send_message(
|
|
1436
|
-
message.chat_id,
|
|
1437
|
-
self._format_pr_flow_status(flow),
|
|
1438
|
-
thread_id=message.thread_id,
|
|
1439
|
-
reply_to=message.message_id,
|
|
1440
|
-
)
|
|
1441
|
-
return
|
|
1442
|
-
if command == "stop":
|
|
1443
|
-
try:
|
|
1444
|
-
data = await self._pr_flow_request(
|
|
1445
|
-
record, method="POST", path="/api/github/pr_flow/stop", payload={}
|
|
1446
|
-
)
|
|
1447
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1448
|
-
except Exception as exc:
|
|
1449
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1450
|
-
await self._send_message(
|
|
1451
|
-
message.chat_id,
|
|
1452
|
-
f"PR flow error: {detail}",
|
|
1453
|
-
thread_id=message.thread_id,
|
|
1454
|
-
reply_to=message.message_id,
|
|
1455
|
-
)
|
|
1456
|
-
return
|
|
1457
|
-
await self._send_message(
|
|
1458
|
-
message.chat_id,
|
|
1459
|
-
self._format_pr_flow_status(flow),
|
|
1460
|
-
thread_id=message.thread_id,
|
|
1461
|
-
reply_to=message.message_id,
|
|
1462
|
-
)
|
|
1463
|
-
return
|
|
1464
|
-
if command == "resume":
|
|
1465
|
-
try:
|
|
1466
|
-
data = await self._pr_flow_request(
|
|
1467
|
-
record, method="POST", path="/api/github/pr_flow/resume", payload={}
|
|
1468
|
-
)
|
|
1469
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1470
|
-
except Exception as exc:
|
|
1471
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1472
|
-
await self._send_message(
|
|
1473
|
-
message.chat_id,
|
|
1474
|
-
f"PR flow error: {detail}",
|
|
1475
|
-
thread_id=message.thread_id,
|
|
1476
|
-
reply_to=message.message_id,
|
|
1477
|
-
)
|
|
1478
|
-
return
|
|
1479
|
-
await self._send_message(
|
|
1480
|
-
message.chat_id,
|
|
1481
|
-
self._format_pr_flow_status(flow),
|
|
1482
|
-
thread_id=message.thread_id,
|
|
1483
|
-
reply_to=message.message_id,
|
|
1484
|
-
)
|
|
1485
|
-
return
|
|
1486
|
-
if command == "collect":
|
|
1487
|
-
try:
|
|
1488
|
-
data = await self._pr_flow_request(
|
|
1489
|
-
record,
|
|
1490
|
-
method="POST",
|
|
1491
|
-
path="/api/github/pr_flow/collect",
|
|
1492
|
-
payload={},
|
|
1493
|
-
)
|
|
1494
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1495
|
-
except Exception as exc:
|
|
1496
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1497
|
-
await self._send_message(
|
|
1498
|
-
message.chat_id,
|
|
1499
|
-
f"PR flow error: {detail}",
|
|
1500
|
-
thread_id=message.thread_id,
|
|
1501
|
-
reply_to=message.message_id,
|
|
1502
|
-
)
|
|
1503
|
-
return
|
|
1504
|
-
await self._send_message(
|
|
1505
|
-
message.chat_id,
|
|
1506
|
-
self._format_pr_flow_status(flow),
|
|
1507
|
-
thread_id=message.thread_id,
|
|
1508
|
-
reply_to=message.message_id,
|
|
1509
|
-
)
|
|
1510
|
-
return
|
|
1511
|
-
if command in ("start", "implement"):
|
|
1512
|
-
ref, flags = self._parse_pr_flags(argv[1:])
|
|
1513
|
-
if not ref:
|
|
1514
|
-
gh = GitHubService(Path(record.workspace_path))
|
|
1515
|
-
issues = await asyncio.to_thread(gh.list_open_issues, limit=5)
|
|
1516
|
-
if issues:
|
|
1517
|
-
lines = ["Open issues:"]
|
|
1518
|
-
for issue in issues:
|
|
1519
|
-
num = issue.get("number")
|
|
1520
|
-
title = issue.get("title") or ""
|
|
1521
|
-
lines.append(f"- #{num} {title}".strip())
|
|
1522
|
-
lines.append("Use /pr start <issueRef> to begin.")
|
|
1523
|
-
await self._send_message(
|
|
1524
|
-
message.chat_id,
|
|
1525
|
-
"\n".join(lines),
|
|
1526
|
-
thread_id=message.thread_id,
|
|
1527
|
-
reply_to=message.message_id,
|
|
1528
|
-
)
|
|
1529
|
-
return
|
|
1530
|
-
await self._send_message(
|
|
1531
|
-
message.chat_id,
|
|
1532
|
-
"Usage: /pr start <issueRef>",
|
|
1533
|
-
thread_id=message.thread_id,
|
|
1534
|
-
reply_to=message.message_id,
|
|
1535
|
-
)
|
|
1536
|
-
return
|
|
1537
|
-
payload = {"mode": "issue", "issue": ref, **flags}
|
|
1538
|
-
payload["source"] = "telegram"
|
|
1539
|
-
payload["source_meta"] = {
|
|
1540
|
-
"chat_id": message.chat_id,
|
|
1541
|
-
"thread_id": message.thread_id,
|
|
1542
|
-
}
|
|
1543
|
-
try:
|
|
1544
|
-
data = await self._pr_flow_request(
|
|
1545
|
-
record,
|
|
1546
|
-
method="POST",
|
|
1547
|
-
path="/api/github/pr_flow/start",
|
|
1548
|
-
payload=payload,
|
|
1549
|
-
)
|
|
1550
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1551
|
-
except Exception as exc:
|
|
1552
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1553
|
-
await self._send_message(
|
|
1554
|
-
message.chat_id,
|
|
1555
|
-
f"PR flow error: {detail}",
|
|
1556
|
-
thread_id=message.thread_id,
|
|
1557
|
-
reply_to=message.message_id,
|
|
1558
|
-
)
|
|
1559
|
-
return
|
|
1560
|
-
await self._send_message(
|
|
1561
|
-
message.chat_id,
|
|
1562
|
-
self._format_pr_flow_status(flow),
|
|
1563
|
-
thread_id=message.thread_id,
|
|
1564
|
-
reply_to=message.message_id,
|
|
1565
|
-
)
|
|
1566
|
-
return
|
|
1567
|
-
if command in ("fix", "pr"):
|
|
1568
|
-
ref, flags = self._parse_pr_flags(argv[1:])
|
|
1569
|
-
if not ref:
|
|
1570
|
-
gh = GitHubService(Path(record.workspace_path))
|
|
1571
|
-
prs = await asyncio.to_thread(gh.list_open_prs, limit=5)
|
|
1572
|
-
if prs:
|
|
1573
|
-
lines = ["Open PRs:"]
|
|
1574
|
-
for pr in prs:
|
|
1575
|
-
num = pr.get("number")
|
|
1576
|
-
title = pr.get("title") or ""
|
|
1577
|
-
lines.append(f"- #{num} {title}".strip())
|
|
1578
|
-
lines.append("Use /pr fix <prRef> to begin.")
|
|
1579
|
-
await self._send_message(
|
|
1580
|
-
message.chat_id,
|
|
1581
|
-
"\n".join(lines),
|
|
1582
|
-
thread_id=message.thread_id,
|
|
1583
|
-
reply_to=message.message_id,
|
|
1584
|
-
)
|
|
1585
|
-
return
|
|
1586
|
-
await self._send_message(
|
|
1587
|
-
message.chat_id,
|
|
1588
|
-
"Usage: /pr fix <prRef>",
|
|
1589
|
-
thread_id=message.thread_id,
|
|
1590
|
-
reply_to=message.message_id,
|
|
1591
|
-
)
|
|
1592
|
-
return
|
|
1593
|
-
payload = {"mode": "pr", "pr": ref, **flags}
|
|
1594
|
-
payload["source"] = "telegram"
|
|
1595
|
-
payload["source_meta"] = {
|
|
1596
|
-
"chat_id": message.chat_id,
|
|
1597
|
-
"thread_id": message.thread_id,
|
|
1598
|
-
}
|
|
1599
|
-
try:
|
|
1600
|
-
data = await self._pr_flow_request(
|
|
1601
|
-
record,
|
|
1602
|
-
method="POST",
|
|
1603
|
-
path="/api/github/pr_flow/start",
|
|
1604
|
-
payload=payload,
|
|
1605
|
-
)
|
|
1606
|
-
flow = data.get("flow") if isinstance(data, dict) else data
|
|
1607
|
-
except Exception as exc:
|
|
1608
|
-
detail = _format_httpx_exception(exc) or str(exc)
|
|
1609
|
-
await self._send_message(
|
|
1610
|
-
message.chat_id,
|
|
1611
|
-
f"PR flow error: {detail}",
|
|
1612
|
-
thread_id=message.thread_id,
|
|
1613
|
-
reply_to=message.message_id,
|
|
1614
|
-
)
|
|
1615
|
-
return
|
|
1616
|
-
await self._send_message(
|
|
1617
|
-
message.chat_id,
|
|
1618
|
-
self._format_pr_flow_status(flow),
|
|
1619
|
-
thread_id=message.thread_id,
|
|
1620
|
-
reply_to=message.message_id,
|
|
1621
|
-
)
|
|
1622
|
-
return
|
|
1623
|
-
await self._send_message(
|
|
1624
|
-
message.chat_id,
|
|
1625
|
-
"Unknown /pr command. Use /pr start|fix|status|stop|resume|collect.",
|
|
1626
|
-
thread_id=message.thread_id,
|
|
1627
|
-
reply_to=message.message_id,
|
|
1628
|
-
)
|
|
1629
|
-
|
|
1630
1176
|
async def _list_recent_commits(
|
|
1631
1177
|
self, record: TelegramTopicRecord
|
|
1632
1178
|
) -> list[tuple[str, str]]:
|
|
@@ -2068,36 +1614,84 @@ class TelegramCommandHandlers(
|
|
|
2068
1614
|
)
|
|
2069
1615
|
return
|
|
2070
1616
|
argv = self._parse_command_args(args)
|
|
2071
|
-
|
|
1617
|
+
|
|
1618
|
+
async def _read_explicit_config_features() -> Optional[str]:
|
|
2072
1619
|
try:
|
|
2073
1620
|
result = await client.request("config/read", {"includeLayers": False})
|
|
1621
|
+
except Exception:
|
|
1622
|
+
return None
|
|
1623
|
+
return _format_feature_flags(result)
|
|
1624
|
+
|
|
1625
|
+
async def _fetch_codex_features() -> (
|
|
1626
|
+
tuple[list[CodexFeatureRow], Optional[str]]
|
|
1627
|
+
):
|
|
1628
|
+
features_command = derive_codex_features_command(
|
|
1629
|
+
self._config.app_server_command
|
|
1630
|
+
)
|
|
1631
|
+
try:
|
|
1632
|
+
result = await client.request(
|
|
1633
|
+
"command/exec",
|
|
1634
|
+
{
|
|
1635
|
+
"cwd": record.workspace_path,
|
|
1636
|
+
"command": features_command,
|
|
1637
|
+
"timeoutMs": 10000,
|
|
1638
|
+
},
|
|
1639
|
+
)
|
|
2074
1640
|
except Exception as exc:
|
|
2075
1641
|
log_event(
|
|
2076
1642
|
self._logger,
|
|
2077
1643
|
logging.WARNING,
|
|
2078
|
-
"telegram.experimental.
|
|
1644
|
+
"telegram.experimental.exec_failed",
|
|
2079
1645
|
chat_id=message.chat_id,
|
|
2080
1646
|
thread_id=message.thread_id,
|
|
2081
1647
|
exc=exc,
|
|
2082
1648
|
)
|
|
1649
|
+
return (
|
|
1650
|
+
[],
|
|
1651
|
+
"Failed to run `codex features list`; check Codex install/PATH.",
|
|
1652
|
+
)
|
|
1653
|
+
stdout, stderr, exit_code = _extract_command_result(result)
|
|
1654
|
+
if exit_code not in (None, 0):
|
|
1655
|
+
detail = stderr.strip() if isinstance(stderr, str) else ""
|
|
1656
|
+
msg = f"`{' '.join(features_command)}` failed (exit {exit_code})."
|
|
1657
|
+
if detail:
|
|
1658
|
+
msg = f"{msg} stderr: {detail}"
|
|
1659
|
+
return [], msg
|
|
1660
|
+
rows = parse_codex_features_list(stdout)
|
|
1661
|
+
if not rows:
|
|
1662
|
+
return (
|
|
1663
|
+
[],
|
|
1664
|
+
f"No feature rows returned by `{' '.join(features_command)}`.",
|
|
1665
|
+
)
|
|
1666
|
+
return rows, None
|
|
1667
|
+
|
|
1668
|
+
list_all = bool(argv and argv[0].lower() == "all")
|
|
1669
|
+
is_list_request = not argv or list_all or argv[0].lower() in ("list", "ls")
|
|
1670
|
+
if is_list_request:
|
|
1671
|
+
stage_filter = None if list_all else "beta"
|
|
1672
|
+
rows, error = await _fetch_codex_features()
|
|
1673
|
+
if error:
|
|
1674
|
+
fallback = await _read_explicit_config_features()
|
|
1675
|
+
message_lines = [error]
|
|
1676
|
+
if fallback and fallback.strip() != "No feature flags found.":
|
|
1677
|
+
message_lines.append("")
|
|
1678
|
+
message_lines.append("Explicit config entries (may be incomplete):")
|
|
1679
|
+
message_lines.append(fallback)
|
|
2083
1680
|
await self._send_message(
|
|
2084
1681
|
message.chat_id,
|
|
2085
|
-
|
|
2086
|
-
"Failed to read config; check logs for details.",
|
|
2087
|
-
chat_id=message.chat_id,
|
|
2088
|
-
thread_id=message.thread_id,
|
|
2089
|
-
),
|
|
1682
|
+
"\n".join(message_lines),
|
|
2090
1683
|
thread_id=message.thread_id,
|
|
2091
1684
|
reply_to=message.message_id,
|
|
2092
1685
|
)
|
|
2093
1686
|
return
|
|
2094
1687
|
await self._send_message(
|
|
2095
1688
|
message.chat_id,
|
|
2096
|
-
|
|
1689
|
+
format_codex_features(rows, stage_filter=stage_filter),
|
|
2097
1690
|
thread_id=message.thread_id,
|
|
2098
1691
|
reply_to=message.message_id,
|
|
2099
1692
|
)
|
|
2100
1693
|
return
|
|
1694
|
+
|
|
2101
1695
|
if len(argv) < 2:
|
|
2102
1696
|
await self._send_message(
|
|
2103
1697
|
message.chat_id,
|
|
@@ -2128,9 +1722,35 @@ class TelegramCommandHandlers(
|
|
|
2128
1722
|
reply_to=message.message_id,
|
|
2129
1723
|
)
|
|
2130
1724
|
return
|
|
1725
|
+
|
|
1726
|
+
rows, error = await _fetch_codex_features()
|
|
1727
|
+
if error:
|
|
1728
|
+
await self._send_message(
|
|
1729
|
+
message.chat_id,
|
|
1730
|
+
error,
|
|
1731
|
+
thread_id=message.thread_id,
|
|
1732
|
+
reply_to=message.message_id,
|
|
1733
|
+
)
|
|
1734
|
+
return
|
|
1735
|
+
|
|
1736
|
+
normalized_feature = feature
|
|
1737
|
+
if feature.startswith("features."):
|
|
1738
|
+
normalized_feature = feature[len("features.") :]
|
|
1739
|
+
target_row = next((row for row in rows if row.key == normalized_feature), None)
|
|
1740
|
+
if target_row is None:
|
|
1741
|
+
available = ", ".join(sorted(row.key for row in rows))
|
|
1742
|
+
await self._send_message(
|
|
1743
|
+
message.chat_id,
|
|
1744
|
+
f"Unknown feature '{feature}'. Known features: {available}\n"
|
|
1745
|
+
"Use /experimental all to list all stages.",
|
|
1746
|
+
thread_id=message.thread_id,
|
|
1747
|
+
reply_to=message.message_id,
|
|
1748
|
+
)
|
|
1749
|
+
return
|
|
1750
|
+
|
|
2131
1751
|
key_path = feature if feature.startswith("features.") else f"features.{feature}"
|
|
2132
1752
|
try:
|
|
2133
|
-
await client.request(
|
|
1753
|
+
write_result = await client.request(
|
|
2134
1754
|
"config/value/write",
|
|
2135
1755
|
{"keyPath": key_path, "value": value, "mergeStrategy": "replace"},
|
|
2136
1756
|
)
|
|
@@ -2154,9 +1774,49 @@ class TelegramCommandHandlers(
|
|
|
2154
1774
|
reply_to=message.message_id,
|
|
2155
1775
|
)
|
|
2156
1776
|
return
|
|
1777
|
+
|
|
1778
|
+
post_rows, post_error = await _fetch_codex_features()
|
|
1779
|
+
effective_row = None
|
|
1780
|
+
if not post_error:
|
|
1781
|
+
effective_row = next(
|
|
1782
|
+
(row for row in post_rows if row.key == normalized_feature), None
|
|
1783
|
+
)
|
|
1784
|
+
|
|
1785
|
+
lines = [f"Feature {key_path} set to {value}."]
|
|
1786
|
+
if effective_row:
|
|
1787
|
+
lines.append(
|
|
1788
|
+
f"Stage: {effective_row.stage}; effective state: {effective_row.enabled}."
|
|
1789
|
+
)
|
|
1790
|
+
elif post_error:
|
|
1791
|
+
lines.append(f"(Could not verify effective state: {post_error})")
|
|
1792
|
+
|
|
1793
|
+
if isinstance(write_result, dict):
|
|
1794
|
+
status = write_result.get("status")
|
|
1795
|
+
overridden = write_result.get("overriddenMetadata")
|
|
1796
|
+
if status == "okOverridden" and isinstance(overridden, dict):
|
|
1797
|
+
message_txt = overridden.get("message")
|
|
1798
|
+
effective_value = overridden.get("effectiveValue")
|
|
1799
|
+
layer = overridden.get("overridingLayer") or {}
|
|
1800
|
+
layer_name = layer.get("name") if isinstance(layer, dict) else None
|
|
1801
|
+
layer_version = (
|
|
1802
|
+
layer.get("version") if isinstance(layer, dict) else None
|
|
1803
|
+
)
|
|
1804
|
+
lines.append("Write was overridden by another config layer.")
|
|
1805
|
+
if layer_name:
|
|
1806
|
+
layer_desc = (
|
|
1807
|
+
f"{layer_name} (version {layer_version})"
|
|
1808
|
+
if layer_version
|
|
1809
|
+
else layer_name
|
|
1810
|
+
)
|
|
1811
|
+
lines.append(f"- Overriding layer: {layer_desc}")
|
|
1812
|
+
if effective_value is not None:
|
|
1813
|
+
lines.append(f"- Effective value: {effective_value}")
|
|
1814
|
+
if isinstance(message_txt, str) and message_txt:
|
|
1815
|
+
lines.append(f"- Note: {message_txt}")
|
|
1816
|
+
|
|
2157
1817
|
await self._send_message(
|
|
2158
1818
|
message.chat_id,
|
|
2159
|
-
|
|
1819
|
+
"\n".join(lines),
|
|
2160
1820
|
thread_id=message.thread_id,
|
|
2161
1821
|
reply_to=message.message_id,
|
|
2162
1822
|
)
|
|
@@ -2352,7 +2012,6 @@ class TelegramCommandHandlers(
|
|
|
2352
2012
|
async def _handle_compact_callback(
|
|
2353
2013
|
self, key: str, callback: TelegramCallbackQuery, parsed: CompactCallback
|
|
2354
2014
|
) -> None:
|
|
2355
|
-
|
|
2356
2015
|
async def _send_compact_status(text: str) -> bool:
|
|
2357
2016
|
try:
|
|
2358
2017
|
await self._send_message(
|
|
@@ -2619,7 +2278,7 @@ Summary applied.""",
|
|
|
2619
2278
|
repo_ref = (self._update_repo_ref or DEFAULT_UPDATE_REPO_REF).strip()
|
|
2620
2279
|
if not repo_ref:
|
|
2621
2280
|
repo_ref = DEFAULT_UPDATE_REPO_REF
|
|
2622
|
-
update_dir =
|
|
2281
|
+
update_dir = resolve_update_paths().cache_dir
|
|
2623
2282
|
notify_reply_to = reply_to
|
|
2624
2283
|
if notify_reply_to is None and callback is not None:
|
|
2625
2284
|
notify_reply_to = callback.message_id
|
|
@@ -2630,6 +2289,7 @@ Summary applied.""",
|
|
|
2630
2289
|
update_dir=update_dir,
|
|
2631
2290
|
logger=self._logger,
|
|
2632
2291
|
update_target=update_target,
|
|
2292
|
+
skip_checks=bool(getattr(self, "_update_skip_checks", False)),
|
|
2633
2293
|
notify_chat_id=chat_id,
|
|
2634
2294
|
notify_thread_id=thread_id,
|
|
2635
2295
|
notify_reply_to=notify_reply_to,
|
|
@@ -2724,7 +2384,7 @@ Summary applied.""",
|
|
|
2724
2384
|
)
|
|
2725
2385
|
|
|
2726
2386
|
def _update_status_path(self) -> Path:
|
|
2727
|
-
return
|
|
2387
|
+
return resolve_update_paths().status_path
|
|
2728
2388
|
|
|
2729
2389
|
def _read_update_status(self) -> Optional[dict[str, Any]]:
|
|
2730
2390
|
path = self._update_status_path()
|
|
@@ -2773,7 +2433,6 @@ Summary applied.""",
|
|
|
2773
2433
|
timeout_seconds: float = 300.0,
|
|
2774
2434
|
interval_seconds: float = 2.0,
|
|
2775
2435
|
) -> None:
|
|
2776
|
-
|
|
2777
2436
|
async def _watch() -> None:
|
|
2778
2437
|
deadline = time.monotonic() + timeout_seconds
|
|
2779
2438
|
while time.monotonic() < deadline:
|
|
@@ -2809,7 +2468,7 @@ Summary applied.""",
|
|
|
2809
2468
|
)
|
|
2810
2469
|
|
|
2811
2470
|
def _compact_status_path(self) -> Path:
|
|
2812
|
-
return
|
|
2471
|
+
return resolve_update_paths().compact_status_path
|
|
2813
2472
|
|
|
2814
2473
|
def _read_compact_status(self) -> Optional[dict[str, Any]]:
|
|
2815
2474
|
path = self._compact_status_path()
|