codex-telegram 0.4.7__tar.gz → 0.4.9__tar.gz
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-telegram-0.4.7 → codex-telegram-0.4.9}/PKG-INFO +1 -1
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/client.py +24 -8
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram/__init__.py +1 -1
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram.egg-info/PKG-INFO +1 -1
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_codex_client.py +41 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/workspace.py +1 -1
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/LICENSE +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/MANIFEST.in +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/README.md +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/TELEGRAM.md +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/WEB.md +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/app_runtime/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/app_runtime/bootstrap.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/app_runtime/telegram_app.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/app_runtime/web_server.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/callbacks.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/features_ui.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/handlers.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/keyboard.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/projects_ui.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/skills_ui.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/bot/thread_ui.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/approval_flow.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/approval_guardian.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/collaboration_mode.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/common.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/context.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/contracts.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/core.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/projects.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/review.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/system.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/command_router/threads.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/commands.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/event_forwarding.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/events.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex/protocol.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram/cli.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram.egg-info/SOURCES.txt +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram.egg-info/dependency_links.txt +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram.egg-info/entry_points.txt +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram.egg-info/requires.txt +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/codex_telegram.egg-info/top_level.txt +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/conf.toml.example +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/main.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/models/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/models/state.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/models/thread.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/models/user.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/pyproject.toml +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/setup.cfg +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_approval_guardian.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_approval_policy.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_callbacks_results.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_cli_entrypoint.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_command_router.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_guardian_config.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_handlers_results.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_keyboard.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_main_approval_flow.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_normalize.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_pypi_check.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_single_instance.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_telegram_bot_config.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_thread_history.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_web_runtime.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/tests/test_web_server_local_command.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/approval_policy.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/config.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/local_command.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/logger.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/normalize.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/pypi_check.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/single_instance.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/utils/web_config.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/__init__.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/dependencies.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/routes.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/runtime.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/server.py +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/static/dist/assets/index-B6IQl7AZ.js +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/static/dist/assets/index-Czx9yv3Y.css +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/static/dist/icons-tab-add.svg +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/static/dist/icons-tab-close.svg +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/static/dist/index.html +0 -0
- {codex-telegram-0.4.7 → codex-telegram-0.4.9}/web/thread_history.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import inspect
|
|
3
|
+
from dataclasses import dataclass
|
|
3
4
|
from typing import Any, Callable
|
|
4
5
|
import logging
|
|
5
6
|
|
|
@@ -10,6 +11,12 @@ from utils.config import get
|
|
|
10
11
|
logger = logging.getLogger("codex-telegram.codex")
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class _ApprovalWaiter:
|
|
16
|
+
future: asyncio.Future[str]
|
|
17
|
+
loop: asyncio.AbstractEventLoop
|
|
18
|
+
|
|
19
|
+
|
|
13
20
|
class CodexClient:
|
|
14
21
|
def __init__(self):
|
|
15
22
|
self.protocol = Protocol()
|
|
@@ -17,7 +24,7 @@ class CodexClient:
|
|
|
17
24
|
self._reader_task: asyncio.Task | None = None
|
|
18
25
|
self._stderr_task: asyncio.Task | None = None
|
|
19
26
|
self._pending: dict[int, asyncio.Future[JSONRPCResponse]] = {}
|
|
20
|
-
self._pending_approvals: dict[int,
|
|
27
|
+
self._pending_approvals: dict[int, _ApprovalWaiter] = {}
|
|
21
28
|
self._event_handlers: dict[str, list[Callable]] = {}
|
|
22
29
|
self._any_event_handlers: list[Callable] = []
|
|
23
30
|
self._approval_handlers: list[Callable] = []
|
|
@@ -114,10 +121,18 @@ class CodexClient:
|
|
|
114
121
|
self._approval_handlers.append(handler)
|
|
115
122
|
|
|
116
123
|
def submit_approval_decision(self, request_id: int, decision: str) -> bool:
|
|
117
|
-
|
|
118
|
-
if
|
|
124
|
+
waiter = self._pending_approvals.get(request_id)
|
|
125
|
+
if waiter is None or waiter.future.done():
|
|
119
126
|
return False
|
|
120
|
-
|
|
127
|
+
|
|
128
|
+
def _resolve() -> None:
|
|
129
|
+
if not waiter.future.done():
|
|
130
|
+
waiter.future.set_result(decision)
|
|
131
|
+
|
|
132
|
+
if waiter.loop.is_running():
|
|
133
|
+
waiter.loop.call_soon_threadsafe(_resolve)
|
|
134
|
+
else:
|
|
135
|
+
_resolve()
|
|
121
136
|
return True
|
|
122
137
|
|
|
123
138
|
def _write(self, msg: JSONRPCRequest | JSONRPCNotification | JSONRPCResponse):
|
|
@@ -132,8 +147,8 @@ class CodexClient:
|
|
|
132
147
|
future.set_exception(exc)
|
|
133
148
|
self._pending.clear()
|
|
134
149
|
for future in self._pending_approvals.values():
|
|
135
|
-
if not future.done():
|
|
136
|
-
future.set_result("deny")
|
|
150
|
+
if not future.future.done():
|
|
151
|
+
future.future.set_result("deny")
|
|
137
152
|
self._pending_approvals.clear()
|
|
138
153
|
|
|
139
154
|
async def _read_stdout_stream(self):
|
|
@@ -499,8 +514,9 @@ class CodexClient:
|
|
|
499
514
|
)
|
|
500
515
|
return
|
|
501
516
|
|
|
502
|
-
|
|
503
|
-
|
|
517
|
+
loop = asyncio.get_running_loop()
|
|
518
|
+
future: asyncio.Future[str] = loop.create_future()
|
|
519
|
+
self._pending_approvals[req_id] = _ApprovalWaiter(future=future, loop=loop)
|
|
504
520
|
await self._emit_approval_request(method, req_id, params)
|
|
505
521
|
|
|
506
522
|
decision_choice = self._default_choice(auto_mode)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import unittest
|
|
3
|
+
import threading
|
|
2
4
|
from unittest.mock import patch
|
|
3
5
|
from types import SimpleNamespace
|
|
4
6
|
|
|
@@ -89,6 +91,45 @@ class CodexClientServerRequestTests(unittest.IsolatedAsyncioTestCase):
|
|
|
89
91
|
answers = ((response.get("result") or {}).get("answers") or {})
|
|
90
92
|
self.assertEqual({"answers": ["Approve this Session"]}, answers.get("q2"))
|
|
91
93
|
|
|
94
|
+
async def test_submit_approval_decision_is_thread_safe(self):
|
|
95
|
+
client = CodexClient()
|
|
96
|
+
written = []
|
|
97
|
+
client._write = lambda msg: written.append(msg) # type: ignore[method-assign]
|
|
98
|
+
req = JSONRPCRequest(
|
|
99
|
+
method="item/commandExecution/requestApproval",
|
|
100
|
+
id=42,
|
|
101
|
+
params={"threadId": "thread-1"},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
with patch(
|
|
105
|
+
"codex.client.get",
|
|
106
|
+
side_effect=lambda key, default=None: "interactive" if key == "approval.mode" else "approve" if key == "approval.auto_response" else default,
|
|
107
|
+
):
|
|
108
|
+
task = asyncio.create_task(client._handle_server_request(req))
|
|
109
|
+
for _ in range(100):
|
|
110
|
+
if 42 in client._pending_approvals:
|
|
111
|
+
break
|
|
112
|
+
await asyncio.sleep(0.01)
|
|
113
|
+
else:
|
|
114
|
+
self.fail("approval request was not registered")
|
|
115
|
+
|
|
116
|
+
accepted = {}
|
|
117
|
+
|
|
118
|
+
def approve_from_thread():
|
|
119
|
+
accepted["value"] = client.submit_approval_decision(42, "approve")
|
|
120
|
+
|
|
121
|
+
thread = threading.Thread(target=approve_from_thread)
|
|
122
|
+
thread.start()
|
|
123
|
+
thread.join()
|
|
124
|
+
|
|
125
|
+
await asyncio.wait_for(task, timeout=1.0)
|
|
126
|
+
|
|
127
|
+
self.assertTrue(accepted["value"])
|
|
128
|
+
self.assertEqual(1, len(written))
|
|
129
|
+
response = written[0].to_dict()
|
|
130
|
+
self.assertEqual(42, response.get("id"))
|
|
131
|
+
self.assertEqual({"decision": "accept"}, response.get("result"))
|
|
132
|
+
|
|
92
133
|
async def test_tool_request_user_input_prefers_option_value_when_present(self):
|
|
93
134
|
client = CodexClient()
|
|
94
135
|
written = []
|
|
@@ -62,7 +62,7 @@ def workspace_suggestions(workspace: str, prefix: str, limit: int) -> list[str]:
|
|
|
62
62
|
partial_lower = partial.lower()
|
|
63
63
|
recursive_mode = not base_rel and "/" not in normalized_prefix and bool(partial_lower)
|
|
64
64
|
|
|
65
|
-
if recursive_mode
|
|
65
|
+
if recursive_mode:
|
|
66
66
|
max_scan = 4000
|
|
67
67
|
scanned = 0
|
|
68
68
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|