codex-autorunner 0.1.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/__init__.py +20 -0
- codex_autorunner/agents/base.py +2 -2
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/__init__.py +4 -0
- codex_autorunner/agents/opencode/agent_config.py +104 -0
- codex_autorunner/agents/opencode/client.py +305 -28
- codex_autorunner/agents/opencode/harness.py +71 -20
- codex_autorunner/agents/opencode/logging.py +225 -0
- codex_autorunner/agents/opencode/run_prompt.py +261 -0
- codex_autorunner/agents/opencode/runtime.py +1202 -132
- codex_autorunner/agents/opencode/supervisor.py +194 -68
- codex_autorunner/agents/registry.py +258 -0
- codex_autorunner/agents/types.py +2 -2
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +19 -40
- codex_autorunner/cli.py +234 -151
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_events.py +15 -6
- codex_autorunner/core/app_server_logging.py +55 -15
- codex_autorunner/core/app_server_prompts.py +28 -259
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/circuit_breaker.py +183 -0
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +555 -133
- codex_autorunner/core/docs.py +54 -9
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +828 -274
- codex_autorunner/core/exceptions.py +60 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +21 -13
- codex_autorunner/core/locks.py +118 -1
- codex_autorunner/core/logging_utils.py +9 -6
- codex_autorunner/core/path_utils.py +123 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/retry.py +61 -0
- codex_autorunner/core/review.py +888 -0
- codex_autorunner/core/review_context.py +161 -0
- codex_autorunner/core/run_index.py +223 -0
- codex_autorunner/core/runner_controller.py +44 -1
- codex_autorunner/core/runner_process.py +30 -1
- codex_autorunner/core/sqlite_utils.py +32 -0
- codex_autorunner/core/state.py +273 -44
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +43 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +107 -75
- codex_autorunner/core/utils.py +167 -3
- codex_autorunner/discovery.py +3 -3
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +708 -153
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +474 -185
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +239 -1
- codex_autorunner/integrations/telegram/constants.py +19 -1
- codex_autorunner/integrations/telegram/dispatch.py +44 -8
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/approvals.py +12 -10
- codex_autorunner/integrations/telegram/handlers/callbacks.py +15 -1
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +29 -0
- codex_autorunner/integrations/telegram/handlers/commands/approvals.py +173 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +2595 -0
- codex_autorunner/integrations/telegram/handlers/commands/files.py +1408 -0
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +81 -0
- codex_autorunner/integrations/telegram/handlers/commands/github.py +1688 -0
- codex_autorunner/integrations/telegram/handlers/commands/shared.py +190 -0
- codex_autorunner/integrations/telegram/handlers/commands/voice.py +112 -0
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +2043 -0
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +954 -5689
- codex_autorunner/integrations/telegram/handlers/{commands.py → commands_spec.py} +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +374 -49
- codex_autorunner/integrations/telegram/handlers/questions.py +389 -0
- codex_autorunner/integrations/telegram/handlers/selections.py +6 -4
- codex_autorunner/integrations/telegram/handlers/utils.py +171 -0
- codex_autorunner/integrations/telegram/helpers.py +90 -18
- codex_autorunner/integrations/telegram/notifications.py +126 -35
- codex_autorunner/integrations/telegram/outbox.py +214 -43
- codex_autorunner/integrations/telegram/progress_stream.py +42 -19
- codex_autorunner/integrations/telegram/runtime.py +24 -13
- codex_autorunner/integrations/telegram/service.py +500 -129
- codex_autorunner/integrations/telegram/state.py +1278 -330
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +37 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/integrations/telegram/types.py +22 -2
- codex_autorunner/integrations/telegram/voice.py +14 -15
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +25 -14
- codex_autorunner/routes/agents.py +18 -78
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +142 -113
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/repos.py +17 -0
- codex_autorunner/routes/review.py +148 -0
- codex_autorunner/routes/sessions.py +16 -8
- codex_autorunner/routes/settings.py +22 -0
- codex_autorunner/routes/shared.py +33 -3
- codex_autorunner/routes/system.py +22 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/voice.py +5 -13
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +9 -1
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +27 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -150
- 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 +67 -126
- codex_autorunner/static/index.html +788 -807
- codex_autorunner/static/liveUpdates.js +59 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -205
- codex_autorunner/static/styles.css +7577 -3758
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +53 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +21 -7
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/voice/capture.py +7 -7
- codex_autorunner/voice/service.py +51 -9
- codex_autorunner/web/app.py +419 -199
- codex_autorunner/web/hub_jobs.py +13 -2
- codex_autorunner/web/middleware.py +47 -13
- codex_autorunner/web/pty_session.py +26 -13
- codex_autorunner/web/schemas.py +114 -109
- codex_autorunner/web/static_assets.py +55 -42
- codex_autorunner/web/static_refresh.py +86 -0
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +20 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/core/doc_chat.py +0 -1415
- 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 -118
- codex_autorunner/spec_ingest.py +0 -788
- codex_autorunner/static/docChatActions.js +0 -279
- 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 -274
- 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 -442
- codex_autorunner/static/logs.js +0 -640
- codex_autorunner/static/runs.js +0 -409
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -86
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.1.dist-info/RECORD +0 -191
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Telegram integration doctor checks."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Union
|
|
4
|
+
|
|
5
|
+
from ...core.config import HubConfig, RepoConfig
|
|
6
|
+
from ...core.engine import DoctorCheck
|
|
7
|
+
from ...core.optional_dependencies import missing_optional_dependencies
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def telegram_doctor_checks(
|
|
11
|
+
config: Union[HubConfig, RepoConfig, Dict[str, Any]],
|
|
12
|
+
) -> list[DoctorCheck]:
|
|
13
|
+
"""Run Telegram-specific doctor checks.
|
|
14
|
+
|
|
15
|
+
Returns a list of DoctorCheck objects for Telegram integration.
|
|
16
|
+
Works with HubConfig, RepoConfig, or raw dict.
|
|
17
|
+
"""
|
|
18
|
+
checks: list[DoctorCheck] = []
|
|
19
|
+
telegram_cfg = None
|
|
20
|
+
|
|
21
|
+
if isinstance(config, dict):
|
|
22
|
+
telegram_cfg = config.get("telegram_bot")
|
|
23
|
+
elif isinstance(config.raw, dict):
|
|
24
|
+
telegram_cfg = config.raw.get("telegram_bot")
|
|
25
|
+
|
|
26
|
+
if isinstance(telegram_cfg, dict) and telegram_cfg.get("enabled") is True:
|
|
27
|
+
missing_telegram = missing_optional_dependencies((("httpx", "httpx"),))
|
|
28
|
+
if missing_telegram:
|
|
29
|
+
deps_list = ", ".join(missing_telegram)
|
|
30
|
+
checks.append(
|
|
31
|
+
DoctorCheck(
|
|
32
|
+
check_id="telegram.dependencies",
|
|
33
|
+
status="error",
|
|
34
|
+
message=f"Telegram is enabled but missing optional deps: {deps_list}",
|
|
35
|
+
fix="Install with `pip install codex-autorunner[telegram]`.",
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
checks.append(
|
|
40
|
+
DoctorCheck(
|
|
41
|
+
check_id="telegram.dependencies",
|
|
42
|
+
status="ok",
|
|
43
|
+
message="Telegram dependencies are installed.",
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return checks
|
|
@@ -22,7 +22,7 @@ from ..types import PendingApproval
|
|
|
22
22
|
|
|
23
23
|
class TelegramApprovalHandlers:
|
|
24
24
|
async def _restore_pending_approvals(self) -> None:
|
|
25
|
-
state = self._store.load()
|
|
25
|
+
state = await self._store.load()
|
|
26
26
|
if not state.pending_approvals:
|
|
27
27
|
return
|
|
28
28
|
grouped: dict[tuple[int, int | None], list[PendingApprovalRecord]] = {}
|
|
@@ -35,7 +35,7 @@ class TelegramApprovalHandlers:
|
|
|
35
35
|
age = _approval_age_seconds(record.created_at)
|
|
36
36
|
age_label = f"{age}s" if isinstance(age, int) else "unknown age"
|
|
37
37
|
items.append(f"{record.request_id} ({age_label})")
|
|
38
|
-
self._store.clear_pending_approval(record.request_id)
|
|
38
|
+
await self._store.clear_pending_approval(record.request_id)
|
|
39
39
|
message = (
|
|
40
40
|
"Cleared stale approval requests from a previous session. "
|
|
41
41
|
"Re-run the request or use /interrupt if the turn is still active.\n"
|
|
@@ -83,7 +83,7 @@ class TelegramApprovalHandlers:
|
|
|
83
83
|
created_at=created_at,
|
|
84
84
|
topic_key=ctx.topic_key,
|
|
85
85
|
)
|
|
86
|
-
self._store.upsert_pending_approval(approval_record)
|
|
86
|
+
await self._store.upsert_pending_approval(approval_record)
|
|
87
87
|
log_event(
|
|
88
88
|
self._logger,
|
|
89
89
|
logging.INFO,
|
|
@@ -102,7 +102,7 @@ class TelegramApprovalHandlers:
|
|
|
102
102
|
"telegram.approval.callback_too_long",
|
|
103
103
|
request_id=request_id,
|
|
104
104
|
)
|
|
105
|
-
self._store.clear_pending_approval(request_id)
|
|
105
|
+
await self._store.clear_pending_approval(request_id)
|
|
106
106
|
return "cancel"
|
|
107
107
|
payload_text, parse_mode = self._prepare_outgoing_text(
|
|
108
108
|
prompt,
|
|
@@ -132,7 +132,7 @@ class TelegramApprovalHandlers:
|
|
|
132
132
|
thread_id=ctx.thread_id,
|
|
133
133
|
exc=exc,
|
|
134
134
|
)
|
|
135
|
-
self._store.clear_pending_approval(request_id)
|
|
135
|
+
await self._store.clear_pending_approval(request_id)
|
|
136
136
|
try:
|
|
137
137
|
await self._send_message(
|
|
138
138
|
ctx.chat_id,
|
|
@@ -147,7 +147,7 @@ class TelegramApprovalHandlers:
|
|
|
147
147
|
message_id = response.get("message_id") if isinstance(response, dict) else None
|
|
148
148
|
if isinstance(message_id, int):
|
|
149
149
|
approval_record.message_id = message_id
|
|
150
|
-
self._store.upsert_pending_approval(approval_record)
|
|
150
|
+
await self._store.upsert_pending_approval(approval_record)
|
|
151
151
|
loop = asyncio.get_running_loop()
|
|
152
152
|
future: asyncio.Future[ApprovalDecision] = loop.create_future()
|
|
153
153
|
pending = PendingApproval(
|
|
@@ -171,7 +171,7 @@ class TelegramApprovalHandlers:
|
|
|
171
171
|
)
|
|
172
172
|
except asyncio.TimeoutError:
|
|
173
173
|
self._pending_approvals.pop(request_id, None)
|
|
174
|
-
self._store.clear_pending_approval(request_id)
|
|
174
|
+
await self._store.clear_pending_approval(request_id)
|
|
175
175
|
runtime.pending_request_id = None
|
|
176
176
|
log_event(
|
|
177
177
|
self._logger,
|
|
@@ -193,14 +193,14 @@ class TelegramApprovalHandlers:
|
|
|
193
193
|
return "cancel"
|
|
194
194
|
except asyncio.CancelledError:
|
|
195
195
|
self._pending_approvals.pop(request_id, None)
|
|
196
|
-
self._store.clear_pending_approval(request_id)
|
|
196
|
+
await self._store.clear_pending_approval(request_id)
|
|
197
197
|
runtime.pending_request_id = None
|
|
198
198
|
raise
|
|
199
199
|
|
|
200
200
|
async def _handle_approval_callback(
|
|
201
201
|
self, callback: TelegramCallbackQuery, parsed: ApprovalCallback
|
|
202
202
|
) -> None:
|
|
203
|
-
self._store.clear_pending_approval(parsed.request_id)
|
|
203
|
+
await self._store.clear_pending_approval(parsed.request_id)
|
|
204
204
|
pending = self._pending_approvals.pop(parsed.request_id, None)
|
|
205
205
|
if pending is None:
|
|
206
206
|
await self._answer_callback(callback, "Approval already handled")
|
|
@@ -215,7 +215,9 @@ class TelegramApprovalHandlers:
|
|
|
215
215
|
elif pending.topic_key:
|
|
216
216
|
runtime_key = pending.topic_key
|
|
217
217
|
else:
|
|
218
|
-
runtime_key = self._resolve_topic_key(
|
|
218
|
+
runtime_key = await self._resolve_topic_key(
|
|
219
|
+
pending.chat_id, pending.thread_id
|
|
220
|
+
)
|
|
219
221
|
runtime = self._router.runtime_for(runtime_key)
|
|
220
222
|
runtime.pending_request_id = None
|
|
221
223
|
log_event(
|
|
@@ -11,6 +11,10 @@ from ..adapter import (
|
|
|
11
11
|
EffortCallback,
|
|
12
12
|
ModelCallback,
|
|
13
13
|
PageCallback,
|
|
14
|
+
QuestionCancelCallback,
|
|
15
|
+
QuestionCustomCallback,
|
|
16
|
+
QuestionDoneCallback,
|
|
17
|
+
QuestionOptionCallback,
|
|
14
18
|
ResumeCallback,
|
|
15
19
|
ReviewCommitCallback,
|
|
16
20
|
TelegramCallbackQuery,
|
|
@@ -30,9 +34,19 @@ async def handle_callback(handlers: Any, callback: TelegramCallbackQuery) -> Non
|
|
|
30
34
|
return
|
|
31
35
|
key = None
|
|
32
36
|
if callback.chat_id is not None:
|
|
33
|
-
key = handlers._resolve_topic_key(callback.chat_id, callback.thread_id)
|
|
37
|
+
key = await handlers._resolve_topic_key(callback.chat_id, callback.thread_id)
|
|
34
38
|
if isinstance(parsed, ApprovalCallback):
|
|
35
39
|
await handlers._handle_approval_callback(callback, parsed)
|
|
40
|
+
elif isinstance(
|
|
41
|
+
parsed,
|
|
42
|
+
(
|
|
43
|
+
QuestionOptionCallback,
|
|
44
|
+
QuestionCancelCallback,
|
|
45
|
+
QuestionCustomCallback,
|
|
46
|
+
QuestionDoneCallback,
|
|
47
|
+
),
|
|
48
|
+
):
|
|
49
|
+
await handlers._handle_question_callback(callback, parsed)
|
|
36
50
|
elif isinstance(parsed, ResumeCallback):
|
|
37
51
|
if key:
|
|
38
52
|
state = handlers._resume_options.get(key)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Command handler modules for Telegram integration.
|
|
2
|
+
|
|
3
|
+
This package contains focused modules for handling different categories of Telegram commands.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ..commands_spec import CommandSpec, build_command_specs
|
|
7
|
+
from .approvals import ApprovalsCommands
|
|
8
|
+
from .execution import ExecutionCommands
|
|
9
|
+
from .files import FilesCommands
|
|
10
|
+
from .flows import FlowCommands
|
|
11
|
+
from .formatting import FormattingHelpers
|
|
12
|
+
from .github import GitHubCommands
|
|
13
|
+
from .shared import SharedHelpers
|
|
14
|
+
from .voice import VoiceCommands
|
|
15
|
+
from .workspace import WorkspaceCommands
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"SharedHelpers",
|
|
19
|
+
"WorkspaceCommands",
|
|
20
|
+
"GitHubCommands",
|
|
21
|
+
"FilesCommands",
|
|
22
|
+
"VoiceCommands",
|
|
23
|
+
"FlowCommands",
|
|
24
|
+
"ExecutionCommands",
|
|
25
|
+
"ApprovalsCommands",
|
|
26
|
+
"FormattingHelpers",
|
|
27
|
+
"CommandSpec",
|
|
28
|
+
"build_command_specs",
|
|
29
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from ....app_server.client import CodexAppServerError
|
|
5
|
+
from ...adapter import TelegramMessage
|
|
6
|
+
from ...config import AppServerUnavailableError
|
|
7
|
+
from ...constants import APPROVAL_POLICY_VALUES, APPROVAL_PRESETS
|
|
8
|
+
from ...helpers import (
|
|
9
|
+
_clear_policy_overrides,
|
|
10
|
+
_extract_rate_limits,
|
|
11
|
+
_format_persist_note,
|
|
12
|
+
_format_sandbox_policy,
|
|
13
|
+
_normalize_approval_preset,
|
|
14
|
+
_set_policy_overrides,
|
|
15
|
+
)
|
|
16
|
+
from .shared import SharedHelpers
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ApprovalsCommands(SharedHelpers):
|
|
20
|
+
async def _read_rate_limits(
|
|
21
|
+
self, workspace_path: Optional[str], *, agent: str
|
|
22
|
+
) -> Optional[dict[str, Any]]:
|
|
23
|
+
if self._agent_rate_limit_source(agent) != "app_server":
|
|
24
|
+
return None
|
|
25
|
+
try:
|
|
26
|
+
client = await self._client_for_workspace(workspace_path)
|
|
27
|
+
except AppServerUnavailableError:
|
|
28
|
+
return None
|
|
29
|
+
if client is None:
|
|
30
|
+
return None
|
|
31
|
+
for method in ("account/rateLimits/read", "account/read"):
|
|
32
|
+
try:
|
|
33
|
+
result = await client.request(method, params=None, timeout=5.0)
|
|
34
|
+
except (CodexAppServerError, asyncio.TimeoutError):
|
|
35
|
+
continue
|
|
36
|
+
rate_limits = _extract_rate_limits(result)
|
|
37
|
+
if rate_limits:
|
|
38
|
+
return rate_limits
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
async def _handle_approvals(
|
|
42
|
+
self, message: TelegramMessage, args: str, _runtime: Optional[Any] = None
|
|
43
|
+
) -> None:
|
|
44
|
+
argv = self._parse_command_args(args)
|
|
45
|
+
record = await self._router.ensure_topic(message.chat_id, message.thread_id)
|
|
46
|
+
argv, persist = self._extract_persist_flag(argv)
|
|
47
|
+
if not argv:
|
|
48
|
+
await self._send_approval_status(message, record)
|
|
49
|
+
return
|
|
50
|
+
mode = argv[0].lower()
|
|
51
|
+
if mode in ("yolo", "off", "disable", "disabled"):
|
|
52
|
+
await self._set_approval_mode(message, "yolo", persist=persist)
|
|
53
|
+
return
|
|
54
|
+
if mode in ("safe", "on", "enable", "enabled"):
|
|
55
|
+
await self._set_approval_mode(message, "safe", persist=persist)
|
|
56
|
+
return
|
|
57
|
+
preset = _normalize_approval_preset(mode)
|
|
58
|
+
if mode == "preset" and len(argv) > 1:
|
|
59
|
+
preset = _normalize_approval_preset(argv[1])
|
|
60
|
+
if preset:
|
|
61
|
+
await self._apply_preset_policy(message, preset, persist=persist)
|
|
62
|
+
return
|
|
63
|
+
approval_policy = argv[0] if argv[0] in APPROVAL_POLICY_VALUES else None
|
|
64
|
+
if approval_policy:
|
|
65
|
+
sandbox_policy = argv[1] if len(argv) > 1 else None
|
|
66
|
+
await self._apply_direct_policy(
|
|
67
|
+
message, approval_policy, sandbox_policy, persist=persist
|
|
68
|
+
)
|
|
69
|
+
return
|
|
70
|
+
await self._send_approval_usage(message)
|
|
71
|
+
|
|
72
|
+
def _extract_persist_flag(self, argv: list[str]) -> tuple[list[str], bool]:
|
|
73
|
+
"""Return argv without the persist flag and whether the flag was provided."""
|
|
74
|
+
if "--persist" not in argv:
|
|
75
|
+
return argv, False
|
|
76
|
+
return [arg for arg in argv if arg != "--persist"], True
|
|
77
|
+
|
|
78
|
+
async def _send_approval_status(
|
|
79
|
+
self, message: TelegramMessage, record: Any
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Send the current approval mode and policy to the user."""
|
|
82
|
+
approval_policy, sandbox_policy = self._effective_policies(record)
|
|
83
|
+
await self._send_message(
|
|
84
|
+
message.chat_id,
|
|
85
|
+
"\n".join(
|
|
86
|
+
[
|
|
87
|
+
f"Approval mode: {record.approval_mode}",
|
|
88
|
+
f"Approval policy: {approval_policy or 'default'}",
|
|
89
|
+
f"Sandbox policy: {_format_sandbox_policy(sandbox_policy)}",
|
|
90
|
+
"Usage: /approvals yolo|safe|read-only|auto|full-access",
|
|
91
|
+
]
|
|
92
|
+
),
|
|
93
|
+
thread_id=message.thread_id,
|
|
94
|
+
reply_to=message.message_id,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async def _send_approval_usage(self, message: TelegramMessage) -> None:
|
|
98
|
+
"""Send the usage hint for the /approvals command."""
|
|
99
|
+
await self._send_message(
|
|
100
|
+
message.chat_id,
|
|
101
|
+
"Usage: /approvals yolo|safe|read-only|auto|full-access",
|
|
102
|
+
thread_id=message.thread_id,
|
|
103
|
+
reply_to=message.message_id,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def _set_approval_mode(
|
|
107
|
+
self, message: TelegramMessage, mode: str, *, persist: bool
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Switch between safe and yolo modes and clear overrides."""
|
|
110
|
+
await self._router.set_approval_mode(message.chat_id, message.thread_id, mode)
|
|
111
|
+
await self._router.update_topic(
|
|
112
|
+
message.chat_id,
|
|
113
|
+
message.thread_id,
|
|
114
|
+
lambda record: _clear_policy_overrides(record),
|
|
115
|
+
)
|
|
116
|
+
await self._send_message(
|
|
117
|
+
message.chat_id,
|
|
118
|
+
_format_persist_note(f"Approval mode set to {mode}.", persist=persist),
|
|
119
|
+
thread_id=message.thread_id,
|
|
120
|
+
reply_to=message.message_id,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
async def _apply_preset_policy(
|
|
124
|
+
self, message: TelegramMessage, preset: str, *, persist: bool
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Apply an approval preset and persist if requested."""
|
|
127
|
+
approval_policy, sandbox_policy = APPROVAL_PRESETS[preset]
|
|
128
|
+
await self._router.update_topic(
|
|
129
|
+
message.chat_id,
|
|
130
|
+
message.thread_id,
|
|
131
|
+
lambda record: _set_policy_overrides(
|
|
132
|
+
record,
|
|
133
|
+
approval_policy=approval_policy,
|
|
134
|
+
sandbox_policy=sandbox_policy,
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
await self._send_message(
|
|
138
|
+
message.chat_id,
|
|
139
|
+
_format_persist_note(
|
|
140
|
+
f"Approval policy set to {approval_policy} with sandbox {sandbox_policy}.",
|
|
141
|
+
persist=persist,
|
|
142
|
+
),
|
|
143
|
+
thread_id=message.thread_id,
|
|
144
|
+
reply_to=message.message_id,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
async def _apply_direct_policy(
|
|
148
|
+
self,
|
|
149
|
+
message: TelegramMessage,
|
|
150
|
+
approval_policy: str,
|
|
151
|
+
sandbox_policy: Optional[str],
|
|
152
|
+
*,
|
|
153
|
+
persist: bool,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Set explicit approval and sandbox policies."""
|
|
156
|
+
await self._router.update_topic(
|
|
157
|
+
message.chat_id,
|
|
158
|
+
message.thread_id,
|
|
159
|
+
lambda record: _set_policy_overrides(
|
|
160
|
+
record,
|
|
161
|
+
approval_policy=approval_policy,
|
|
162
|
+
sandbox_policy=sandbox_policy,
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
await self._send_message(
|
|
166
|
+
message.chat_id,
|
|
167
|
+
_format_persist_note(
|
|
168
|
+
f"Approval policy set to {approval_policy} with sandbox {sandbox_policy or 'default'}.",
|
|
169
|
+
persist=persist,
|
|
170
|
+
),
|
|
171
|
+
thread_id=message.thread_id,
|
|
172
|
+
reply_to=message.message_id,
|
|
173
|
+
)
|