nonebot-plugin-codex 0.1.3__tar.gz → 0.1.4__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.
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/PKG-INFO +8 -1
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/README.md +7 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/pyproject.toml +1 -1
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/__init__.py +22 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/service.py +215 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/telegram.py +132 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/telegram_commands.py +10 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_release_notes.py +2 -1
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_service.py +50 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_telegram_commands.py +7 -2
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_telegram_handlers.py +186 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/LICENSE +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/config.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/native_client.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/runtime.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/telegram_rendering.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/__init__.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/conftest.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_config.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_native_client.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_plugin_entry.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_plugin_meta.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_runtime.py +0 -0
- {nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/tests/test_telegram_rendering.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nonebot-plugin-codex
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Telegram bridge plugin for driving Codex from NoneBot
|
|
5
5
|
Author-Email: ttiee <469784630@qq.com>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -122,6 +122,7 @@ codex_workdir = "/home/yourname"
|
|
|
122
122
|
|
|
123
123
|
```text
|
|
124
124
|
/codex
|
|
125
|
+
/panel
|
|
125
126
|
/cd /home/yourname/projects/demo
|
|
126
127
|
/mode resume
|
|
127
128
|
然后继续直接发送普通文本消息续聊
|
|
@@ -129,6 +130,8 @@ codex_workdir = "/home/yourname"
|
|
|
129
130
|
|
|
130
131
|
`/codex` 不带参数时会打开一个 Telegram 内的使用引导面板,方便你直接查看当前模式、工作目录、设置摘要,并进入目录浏览、设置面板或历史会话。
|
|
131
132
|
|
|
133
|
+
`/panel` 和 `/status` 会打开统一的“当前工作台”面板,把模式、模型、推理强度、权限、工作目录、当前会话状态和最近历史摘要放在同一屏里,并提供进入设置、目录、历史、新会话和停止会话的快捷操作。
|
|
134
|
+
|
|
132
135
|
你也可以直接把首条任务跟在 `/codex` 后面:
|
|
133
136
|
|
|
134
137
|
```text
|
|
@@ -147,6 +150,7 @@ codex_workdir = "/home/yourname"
|
|
|
147
150
|
```text
|
|
148
151
|
/help
|
|
149
152
|
/start
|
|
153
|
+
/panel
|
|
150
154
|
```
|
|
151
155
|
|
|
152
156
|
## 配置说明
|
|
@@ -196,6 +200,8 @@ codex_stream_read_limit = 1048576
|
|
|
196
200
|
| `/codex [prompt]` | 打开引导面板,或直接附带首条任务连接 Codex |
|
|
197
201
|
| `/help` | 打开使用引导面板 |
|
|
198
202
|
| `/start` | 打开使用引导面板 |
|
|
203
|
+
| `/panel` | 打开统一工作台面板 |
|
|
204
|
+
| `/status` | 打开统一工作台面板 |
|
|
199
205
|
| `/mode [resume\|exec]` | 查看或切换默认模式 |
|
|
200
206
|
| `/exec <prompt>` | 以一次性 `exec` 模式执行任务 |
|
|
201
207
|
| `/new` | 新建当前聊天会话 |
|
|
@@ -229,6 +235,7 @@ codex_stream_read_limit = 1048576
|
|
|
229
235
|
|
|
230
236
|
## 目录与历史会话
|
|
231
237
|
|
|
238
|
+
- `/panel` 或 `/status` 会打开统一工作台,一屏查看当前设置、工作目录、会话状态和最近历史,并跳转到常用控制面板。
|
|
232
239
|
- `/cd` 可打开目录浏览器,逐级进入目录、切换 Home、显示隐藏目录,并把当前浏览目录设置为工作目录。
|
|
233
240
|
- `/sessions` 会列出 native 与 exec 历史会话,便于恢复此前任务。
|
|
234
241
|
- 历史会话恢复时会尝试切回原始工作目录;如果原目录不存在,会保留当前目录并给出提示。
|
|
@@ -109,6 +109,7 @@ codex_workdir = "/home/yourname"
|
|
|
109
109
|
|
|
110
110
|
```text
|
|
111
111
|
/codex
|
|
112
|
+
/panel
|
|
112
113
|
/cd /home/yourname/projects/demo
|
|
113
114
|
/mode resume
|
|
114
115
|
然后继续直接发送普通文本消息续聊
|
|
@@ -116,6 +117,8 @@ codex_workdir = "/home/yourname"
|
|
|
116
117
|
|
|
117
118
|
`/codex` 不带参数时会打开一个 Telegram 内的使用引导面板,方便你直接查看当前模式、工作目录、设置摘要,并进入目录浏览、设置面板或历史会话。
|
|
118
119
|
|
|
120
|
+
`/panel` 和 `/status` 会打开统一的“当前工作台”面板,把模式、模型、推理强度、权限、工作目录、当前会话状态和最近历史摘要放在同一屏里,并提供进入设置、目录、历史、新会话和停止会话的快捷操作。
|
|
121
|
+
|
|
119
122
|
你也可以直接把首条任务跟在 `/codex` 后面:
|
|
120
123
|
|
|
121
124
|
```text
|
|
@@ -134,6 +137,7 @@ codex_workdir = "/home/yourname"
|
|
|
134
137
|
```text
|
|
135
138
|
/help
|
|
136
139
|
/start
|
|
140
|
+
/panel
|
|
137
141
|
```
|
|
138
142
|
|
|
139
143
|
## 配置说明
|
|
@@ -183,6 +187,8 @@ codex_stream_read_limit = 1048576
|
|
|
183
187
|
| `/codex [prompt]` | 打开引导面板,或直接附带首条任务连接 Codex |
|
|
184
188
|
| `/help` | 打开使用引导面板 |
|
|
185
189
|
| `/start` | 打开使用引导面板 |
|
|
190
|
+
| `/panel` | 打开统一工作台面板 |
|
|
191
|
+
| `/status` | 打开统一工作台面板 |
|
|
186
192
|
| `/mode [resume\|exec]` | 查看或切换默认模式 |
|
|
187
193
|
| `/exec <prompt>` | 以一次性 `exec` 模式执行任务 |
|
|
188
194
|
| `/new` | 新建当前聊天会话 |
|
|
@@ -216,6 +222,7 @@ codex_stream_read_limit = 1048576
|
|
|
216
222
|
|
|
217
223
|
## 目录与历史会话
|
|
218
224
|
|
|
225
|
+
- `/panel` 或 `/status` 会打开统一工作台,一屏查看当前设置、工作目录、会话状态和最近历史,并跳转到常用控制面板。
|
|
219
226
|
- `/cd` 可打开目录浏览器,逐级进入目录、切换 Home、显示隐藏目录,并把当前浏览目录设置为工作目录。
|
|
220
227
|
- `/sessions` 会列出 native 与 exec 历史会话,便于恢复此前任务。
|
|
221
228
|
- 历史会话恢复时会尝试切回原始工作目录;如果原目录不存在,会保留当前目录并给出提示。
|
{nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/__init__.py
RENAMED
|
@@ -88,6 +88,8 @@ if _runtime_ready:
|
|
|
88
88
|
codex_cmd = on_command("codex", priority=10, block=True)
|
|
89
89
|
help_cmd = on_command("help", priority=10, block=True)
|
|
90
90
|
start_cmd = on_command("start", priority=10, block=True)
|
|
91
|
+
panel_cmd = on_command("panel", priority=10, block=True)
|
|
92
|
+
status_cmd = on_command("status", priority=10, block=True)
|
|
91
93
|
mode_cmd = on_command("mode", priority=10, block=True)
|
|
92
94
|
exec_cmd = on_command("exec", priority=10, block=True)
|
|
93
95
|
new_cmd = on_command("new", priority=10, block=True)
|
|
@@ -125,6 +127,12 @@ if _runtime_ready:
|
|
|
125
127
|
block=True,
|
|
126
128
|
rule=handlers.is_onboarding_callback,
|
|
127
129
|
)
|
|
130
|
+
workspace_callback = on_type(
|
|
131
|
+
CallbackQueryEvent,
|
|
132
|
+
priority=10,
|
|
133
|
+
block=True,
|
|
134
|
+
rule=handlers.is_workspace_callback,
|
|
135
|
+
)
|
|
128
136
|
|
|
129
137
|
@codex_cmd.handle()
|
|
130
138
|
async def _handle_codex(
|
|
@@ -140,6 +148,14 @@ if _runtime_ready:
|
|
|
140
148
|
async def _handle_start(bot: Bot, event: MessageEvent) -> None:
|
|
141
149
|
await handlers.handle_start(bot, event)
|
|
142
150
|
|
|
151
|
+
@panel_cmd.handle()
|
|
152
|
+
async def _handle_panel(bot: Bot, event: MessageEvent) -> None:
|
|
153
|
+
await handlers.handle_panel(bot, event)
|
|
154
|
+
|
|
155
|
+
@status_cmd.handle()
|
|
156
|
+
async def _handle_status(bot: Bot, event: MessageEvent) -> None:
|
|
157
|
+
await handlers.handle_status(bot, event)
|
|
158
|
+
|
|
143
159
|
@mode_cmd.handle()
|
|
144
160
|
async def _handle_mode(
|
|
145
161
|
bot: Bot, event: MessageEvent, args: Message = CommandArg()
|
|
@@ -220,6 +236,12 @@ if _runtime_ready:
|
|
|
220
236
|
) -> None:
|
|
221
237
|
await handlers.handle_onboarding_callback(bot, event)
|
|
222
238
|
|
|
239
|
+
@workspace_callback.handle()
|
|
240
|
+
async def _handle_workspace_callback(
|
|
241
|
+
bot: Bot, event: CallbackQueryEvent
|
|
242
|
+
) -> None:
|
|
243
|
+
await handlers.handle_workspace_callback(bot, event)
|
|
244
|
+
|
|
223
245
|
@follow_up.handle()
|
|
224
246
|
async def _handle_follow_up(bot: Bot, event: MessageEvent) -> None:
|
|
225
247
|
await handlers.handle_follow_up(bot, event)
|
{nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/service.py
RENAMED
|
@@ -40,6 +40,8 @@ SETTING_STALE_MESSAGE = "设置面板已失效,请重新执行对应命令"
|
|
|
40
40
|
SUPPORTED_SETTING_PANELS = {"mode", "model", "effort", "permission"}
|
|
41
41
|
ONBOARDING_CALLBACK_PREFIX = "cop"
|
|
42
42
|
ONBOARDING_STALE_MESSAGE = "引导面板已失效,请重新执行 /codex"
|
|
43
|
+
WORKSPACE_CALLBACK_PREFIX = "cwp"
|
|
44
|
+
WORKSPACE_STALE_MESSAGE = "工作台面板已失效,请重新执行 /panel"
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
@dataclass(slots=True)
|
|
@@ -205,6 +207,14 @@ class OnboardingPanelState:
|
|
|
205
207
|
message_id: int | None = None
|
|
206
208
|
|
|
207
209
|
|
|
210
|
+
@dataclass(slots=True)
|
|
211
|
+
class WorkspacePanelState:
|
|
212
|
+
chat_key: str
|
|
213
|
+
token: str
|
|
214
|
+
version: int
|
|
215
|
+
message_id: int | None = None
|
|
216
|
+
|
|
217
|
+
|
|
208
218
|
def build_chat_key(chat_type: str, chat_id: int) -> str:
|
|
209
219
|
if chat_type == "private":
|
|
210
220
|
return f"private_{chat_id}"
|
|
@@ -356,6 +366,22 @@ def decode_onboarding_callback(payload: str) -> tuple[str, int, str]:
|
|
|
356
366
|
return token, version, parts[3]
|
|
357
367
|
|
|
358
368
|
|
|
369
|
+
def encode_workspace_callback(token: str, version: int, action: str) -> str:
|
|
370
|
+
return f"{WORKSPACE_CALLBACK_PREFIX}:{token}:{version}:{action}"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def decode_workspace_callback(payload: str) -> tuple[str, int, str]:
|
|
374
|
+
parts = payload.split(":")
|
|
375
|
+
if len(parts) != 4 or parts[0] != WORKSPACE_CALLBACK_PREFIX:
|
|
376
|
+
raise ValueError("无效的工作台回调。")
|
|
377
|
+
token = parts[1]
|
|
378
|
+
try:
|
|
379
|
+
version = int(parts[2])
|
|
380
|
+
except ValueError as exc:
|
|
381
|
+
raise ValueError("无效的工作台回调。") from exc
|
|
382
|
+
return token, version, parts[3]
|
|
383
|
+
|
|
384
|
+
|
|
359
385
|
def parse_event_line(line: str) -> dict[str, Any] | None:
|
|
360
386
|
try:
|
|
361
387
|
payload = json.loads(line)
|
|
@@ -528,6 +554,7 @@ class CodexBridgeService:
|
|
|
528
554
|
self.history_browsers: dict[str, HistoryBrowserState] = {}
|
|
529
555
|
self.setting_panels: dict[str, SettingPanelState] = {}
|
|
530
556
|
self.onboarding_panels: dict[str, OnboardingPanelState] = {}
|
|
557
|
+
self.workspace_panels: dict[str, WorkspacePanelState] = {}
|
|
531
558
|
self._native_history_entries: list[HistoricalSessionSummary] = []
|
|
532
559
|
self._native_history_loaded = False
|
|
533
560
|
self._history_log_cache: dict[str, HistoryLogCacheEntry] = {}
|
|
@@ -1715,6 +1742,194 @@ class CodexBridgeService:
|
|
|
1715
1742
|
self.get_onboarding_panel(chat_key, token=token, version=version)
|
|
1716
1743
|
self.onboarding_panels.pop(chat_key, None)
|
|
1717
1744
|
|
|
1745
|
+
def _replace_workspace_panel_state(
|
|
1746
|
+
self,
|
|
1747
|
+
chat_key: str,
|
|
1748
|
+
*,
|
|
1749
|
+
previous: WorkspacePanelState | None = None,
|
|
1750
|
+
) -> WorkspacePanelState:
|
|
1751
|
+
state = WorkspacePanelState(
|
|
1752
|
+
chat_key=chat_key,
|
|
1753
|
+
token=previous.token if previous else self._make_browser_token(),
|
|
1754
|
+
version=(previous.version + 1) if previous else 1,
|
|
1755
|
+
message_id=previous.message_id if previous else None,
|
|
1756
|
+
)
|
|
1757
|
+
self.workspace_panels[chat_key] = state
|
|
1758
|
+
return state
|
|
1759
|
+
|
|
1760
|
+
def open_workspace_panel(self, chat_key: str) -> WorkspacePanelState:
|
|
1761
|
+
self.get_preferences(chat_key)
|
|
1762
|
+
return self._replace_workspace_panel_state(chat_key)
|
|
1763
|
+
|
|
1764
|
+
def get_workspace_panel(
|
|
1765
|
+
self,
|
|
1766
|
+
chat_key: str,
|
|
1767
|
+
token: str | None = None,
|
|
1768
|
+
version: int | None = None,
|
|
1769
|
+
) -> WorkspacePanelState:
|
|
1770
|
+
state = self.workspace_panels.get(chat_key)
|
|
1771
|
+
if state is None:
|
|
1772
|
+
raise ValueError(WORKSPACE_STALE_MESSAGE)
|
|
1773
|
+
if token is not None and state.token != token:
|
|
1774
|
+
raise ValueError(WORKSPACE_STALE_MESSAGE)
|
|
1775
|
+
if version is not None and state.version != version:
|
|
1776
|
+
raise ValueError(WORKSPACE_STALE_MESSAGE)
|
|
1777
|
+
return state
|
|
1778
|
+
|
|
1779
|
+
def remember_workspace_panel_message(
|
|
1780
|
+
self,
|
|
1781
|
+
chat_key: str,
|
|
1782
|
+
token: str,
|
|
1783
|
+
message_id: int | None,
|
|
1784
|
+
) -> None:
|
|
1785
|
+
if message_id is None:
|
|
1786
|
+
return
|
|
1787
|
+
panel = self.get_workspace_panel(chat_key, token=token)
|
|
1788
|
+
panel.message_id = message_id
|
|
1789
|
+
|
|
1790
|
+
def close_workspace_panel(self, chat_key: str, token: str, version: int) -> None:
|
|
1791
|
+
self.get_workspace_panel(chat_key, token=token, version=version)
|
|
1792
|
+
self.workspace_panels.pop(chat_key, None)
|
|
1793
|
+
|
|
1794
|
+
def _workspace_active_mode(self, chat_key: str, preferences: ChatPreferences) -> str:
|
|
1795
|
+
session = self.sessions.get(chat_key)
|
|
1796
|
+
if session is not None and session.active_mode in {"resume", "exec"}:
|
|
1797
|
+
return session.active_mode
|
|
1798
|
+
return preferences.default_mode
|
|
1799
|
+
|
|
1800
|
+
def _workspace_session_summary(self, chat_key: str) -> str:
|
|
1801
|
+
session = self.sessions.get(chat_key)
|
|
1802
|
+
if session is None:
|
|
1803
|
+
return "未开始"
|
|
1804
|
+
active_mode = (
|
|
1805
|
+
session.active_mode
|
|
1806
|
+
if session.active_mode in {"resume", "exec"}
|
|
1807
|
+
else "resume"
|
|
1808
|
+
)
|
|
1809
|
+
thread_id = (
|
|
1810
|
+
self._current_exec_thread_id(session)
|
|
1811
|
+
if active_mode == "exec"
|
|
1812
|
+
else session.native_thread_id or session.thread_id
|
|
1813
|
+
)
|
|
1814
|
+
if not thread_id and not session.active:
|
|
1815
|
+
return "未开始"
|
|
1816
|
+
if not thread_id:
|
|
1817
|
+
return f"{active_mode} | 未绑定"
|
|
1818
|
+
return f"{active_mode} | {thread_id}"
|
|
1819
|
+
|
|
1820
|
+
def _workspace_recent_history_lines(self) -> list[str]:
|
|
1821
|
+
try:
|
|
1822
|
+
entries = self.list_history_sessions()[:2]
|
|
1823
|
+
except ValueError:
|
|
1824
|
+
return ["最近历史:不可用"]
|
|
1825
|
+
if not entries:
|
|
1826
|
+
return ["最近历史:无"]
|
|
1827
|
+
lines = ["最近历史:"]
|
|
1828
|
+
for entry in entries:
|
|
1829
|
+
lines.append(
|
|
1830
|
+
"- "
|
|
1831
|
+
f"{entry.thread_name} | "
|
|
1832
|
+
f"{self._format_history_relative_time(entry.updated_at)}"
|
|
1833
|
+
)
|
|
1834
|
+
return lines
|
|
1835
|
+
|
|
1836
|
+
def navigate_workspace_panel(
|
|
1837
|
+
self,
|
|
1838
|
+
chat_key: str,
|
|
1839
|
+
token: str,
|
|
1840
|
+
version: int,
|
|
1841
|
+
action: str,
|
|
1842
|
+
) -> WorkspacePanelState:
|
|
1843
|
+
panel = self.get_workspace_panel(chat_key, token=token, version=version)
|
|
1844
|
+
if action != "refresh":
|
|
1845
|
+
raise ValueError("未知工作台操作。")
|
|
1846
|
+
return self._replace_workspace_panel_state(chat_key, previous=panel)
|
|
1847
|
+
|
|
1848
|
+
def render_workspace_panel(
|
|
1849
|
+
self, chat_key: str
|
|
1850
|
+
) -> tuple[str, InlineKeyboardMarkup]:
|
|
1851
|
+
panel = self.get_workspace_panel(chat_key)
|
|
1852
|
+
preferences = self.get_preferences(chat_key)
|
|
1853
|
+
lines = [
|
|
1854
|
+
"当前工作台",
|
|
1855
|
+
f"当前模式:{self._workspace_active_mode(chat_key, preferences)}",
|
|
1856
|
+
f"当前设置:{format_preferences_summary(preferences)}",
|
|
1857
|
+
f"当前工作目录:{preferences.workdir}",
|
|
1858
|
+
f"当前会话:{self._workspace_session_summary(chat_key)}",
|
|
1859
|
+
*self._workspace_recent_history_lines(),
|
|
1860
|
+
]
|
|
1861
|
+
keyboard = [
|
|
1862
|
+
[
|
|
1863
|
+
InlineKeyboardButton(
|
|
1864
|
+
text="模式",
|
|
1865
|
+
callback_data=encode_workspace_callback(
|
|
1866
|
+
panel.token, panel.version, "mode"
|
|
1867
|
+
),
|
|
1868
|
+
),
|
|
1869
|
+
InlineKeyboardButton(
|
|
1870
|
+
text="模型",
|
|
1871
|
+
callback_data=encode_workspace_callback(
|
|
1872
|
+
panel.token, panel.version, "model"
|
|
1873
|
+
),
|
|
1874
|
+
),
|
|
1875
|
+
InlineKeyboardButton(
|
|
1876
|
+
text="强度",
|
|
1877
|
+
callback_data=encode_workspace_callback(
|
|
1878
|
+
panel.token, panel.version, "effort"
|
|
1879
|
+
),
|
|
1880
|
+
),
|
|
1881
|
+
InlineKeyboardButton(
|
|
1882
|
+
text="权限",
|
|
1883
|
+
callback_data=encode_workspace_callback(
|
|
1884
|
+
panel.token, panel.version, "permission"
|
|
1885
|
+
),
|
|
1886
|
+
),
|
|
1887
|
+
],
|
|
1888
|
+
[
|
|
1889
|
+
InlineKeyboardButton(
|
|
1890
|
+
text="目录",
|
|
1891
|
+
callback_data=encode_workspace_callback(
|
|
1892
|
+
panel.token, panel.version, "browse"
|
|
1893
|
+
),
|
|
1894
|
+
),
|
|
1895
|
+
InlineKeyboardButton(
|
|
1896
|
+
text="历史",
|
|
1897
|
+
callback_data=encode_workspace_callback(
|
|
1898
|
+
panel.token, panel.version, "history"
|
|
1899
|
+
),
|
|
1900
|
+
),
|
|
1901
|
+
],
|
|
1902
|
+
[
|
|
1903
|
+
InlineKeyboardButton(
|
|
1904
|
+
text="新会话",
|
|
1905
|
+
callback_data=encode_workspace_callback(
|
|
1906
|
+
panel.token, panel.version, "new"
|
|
1907
|
+
),
|
|
1908
|
+
),
|
|
1909
|
+
InlineKeyboardButton(
|
|
1910
|
+
text="停止",
|
|
1911
|
+
callback_data=encode_workspace_callback(
|
|
1912
|
+
panel.token, panel.version, "stop"
|
|
1913
|
+
),
|
|
1914
|
+
),
|
|
1915
|
+
],
|
|
1916
|
+
[
|
|
1917
|
+
InlineKeyboardButton(
|
|
1918
|
+
text="刷新",
|
|
1919
|
+
callback_data=encode_workspace_callback(
|
|
1920
|
+
panel.token, panel.version, "refresh"
|
|
1921
|
+
),
|
|
1922
|
+
),
|
|
1923
|
+
InlineKeyboardButton(
|
|
1924
|
+
text="关闭",
|
|
1925
|
+
callback_data=encode_workspace_callback(
|
|
1926
|
+
panel.token, panel.version, "close"
|
|
1927
|
+
),
|
|
1928
|
+
),
|
|
1929
|
+
],
|
|
1930
|
+
]
|
|
1931
|
+
return "\n".join(lines), InlineKeyboardMarkup(inline_keyboard=keyboard)
|
|
1932
|
+
|
|
1718
1933
|
def render_onboarding_panel(
|
|
1719
1934
|
self, chat_key: str
|
|
1720
1935
|
) -> tuple[str, InlineKeyboardMarkup]:
|
{nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/telegram.py
RENAMED
|
@@ -19,10 +19,13 @@ from .service import (
|
|
|
19
19
|
ONBOARDING_CALLBACK_PREFIX,
|
|
20
20
|
SETTING_STALE_MESSAGE,
|
|
21
21
|
SETTING_CALLBACK_PREFIX,
|
|
22
|
+
WORKSPACE_STALE_MESSAGE,
|
|
23
|
+
WORKSPACE_CALLBACK_PREFIX,
|
|
22
24
|
CodexBridgeService,
|
|
23
25
|
chunk_text,
|
|
24
26
|
build_chat_key,
|
|
25
27
|
decode_onboarding_callback,
|
|
28
|
+
decode_workspace_callback,
|
|
26
29
|
format_result_text,
|
|
27
30
|
decode_browser_callback,
|
|
28
31
|
decode_history_callback,
|
|
@@ -354,6 +357,11 @@ class TelegramHandlers:
|
|
|
354
357
|
f"{ONBOARDING_CALLBACK_PREFIX}:"
|
|
355
358
|
)
|
|
356
359
|
|
|
360
|
+
async def is_workspace_callback(self, event: CallbackQueryEvent) -> bool:
|
|
361
|
+
return isinstance(event.data, str) and event.data.startswith(
|
|
362
|
+
f"{WORKSPACE_CALLBACK_PREFIX}:"
|
|
363
|
+
)
|
|
364
|
+
|
|
357
365
|
def callback_message_id(self, event: CallbackQueryEvent) -> int | None:
|
|
358
366
|
message = getattr(event, "message", None)
|
|
359
367
|
return getattr(message, "message_id", None)
|
|
@@ -446,6 +454,18 @@ class TelegramHandlers:
|
|
|
446
454
|
getattr(message, "message_id", None),
|
|
447
455
|
)
|
|
448
456
|
|
|
457
|
+
async def send_workspace_panel(
|
|
458
|
+
self, bot: Bot, event: MessageEvent, chat_key: str
|
|
459
|
+
) -> None:
|
|
460
|
+
panel = self.service.open_workspace_panel(chat_key)
|
|
461
|
+
text, markup = self.service.render_workspace_panel(chat_key)
|
|
462
|
+
message = await self.send_event_message(bot, event, text, reply_markup=markup)
|
|
463
|
+
self.service.remember_workspace_panel_message(
|
|
464
|
+
chat_key,
|
|
465
|
+
panel.token,
|
|
466
|
+
getattr(message, "message_id", None),
|
|
467
|
+
)
|
|
468
|
+
|
|
449
469
|
async def edit_or_resend_browser(
|
|
450
470
|
self,
|
|
451
471
|
bot: Bot,
|
|
@@ -547,6 +567,39 @@ class TelegramHandlers:
|
|
|
547
567
|
getattr(message, "message_id", None),
|
|
548
568
|
)
|
|
549
569
|
|
|
570
|
+
async def edit_or_resend_workspace_panel(
|
|
571
|
+
self,
|
|
572
|
+
bot: Bot,
|
|
573
|
+
event: CallbackQueryEvent,
|
|
574
|
+
chat_key: str,
|
|
575
|
+
) -> None:
|
|
576
|
+
panel = self.service.get_workspace_panel(chat_key)
|
|
577
|
+
text, markup = self.service.render_workspace_panel(chat_key)
|
|
578
|
+
message_id = self.callback_message_id(event) or panel.message_id
|
|
579
|
+
chat_id = self.event_chat(event).id
|
|
580
|
+
try:
|
|
581
|
+
if message_id is None:
|
|
582
|
+
raise ValueError("missing message id")
|
|
583
|
+
await self.edit_message(
|
|
584
|
+
bot,
|
|
585
|
+
chat_id=chat_id,
|
|
586
|
+
message_id=message_id,
|
|
587
|
+
text=text,
|
|
588
|
+
reply_markup=markup,
|
|
589
|
+
)
|
|
590
|
+
self.service.remember_workspace_panel_message(
|
|
591
|
+
chat_key, panel.token, message_id
|
|
592
|
+
)
|
|
593
|
+
except Exception:
|
|
594
|
+
message = await self.send_chat_message(
|
|
595
|
+
bot, chat_id, text, reply_markup=markup
|
|
596
|
+
)
|
|
597
|
+
self.service.remember_workspace_panel_message(
|
|
598
|
+
chat_key,
|
|
599
|
+
panel.token,
|
|
600
|
+
getattr(message, "message_id", None),
|
|
601
|
+
)
|
|
602
|
+
|
|
550
603
|
async def handle_codex(self, bot: Bot, event: MessageEvent, args: Message) -> None:
|
|
551
604
|
chat_key = self.chat_key(event)
|
|
552
605
|
session = self.service.activate_chat(chat_key)
|
|
@@ -569,6 +622,12 @@ class TelegramHandlers:
|
|
|
569
622
|
async def handle_start(self, bot: Bot, event: MessageEvent) -> None:
|
|
570
623
|
await self.send_onboarding_panel(bot, event, self.chat_key(event))
|
|
571
624
|
|
|
625
|
+
async def handle_panel(self, bot: Bot, event: MessageEvent) -> None:
|
|
626
|
+
await self.send_workspace_panel(bot, event, self.chat_key(event))
|
|
627
|
+
|
|
628
|
+
async def handle_status(self, bot: Bot, event: MessageEvent) -> None:
|
|
629
|
+
await self.send_workspace_panel(bot, event, self.chat_key(event))
|
|
630
|
+
|
|
572
631
|
async def handle_mode(self, bot: Bot, event: MessageEvent, args: Message) -> None:
|
|
573
632
|
chat_key = self.chat_key(event)
|
|
574
633
|
mode = args.extract_plain_text().strip()
|
|
@@ -890,6 +949,79 @@ class TelegramHandlers:
|
|
|
890
949
|
event.id, text=self.error_text(exc), show_alert=True
|
|
891
950
|
)
|
|
892
951
|
|
|
952
|
+
async def handle_workspace_callback(
|
|
953
|
+
self, bot: Bot, event: CallbackQueryEvent
|
|
954
|
+
) -> None:
|
|
955
|
+
if not isinstance(event.data, str):
|
|
956
|
+
await bot.answer_callback_query(
|
|
957
|
+
event.id, text=WORKSPACE_STALE_MESSAGE, show_alert=True
|
|
958
|
+
)
|
|
959
|
+
return
|
|
960
|
+
|
|
961
|
+
try:
|
|
962
|
+
chat_key = self.chat_key(event)
|
|
963
|
+
chat_id = self.event_chat(event).id
|
|
964
|
+
token, version, action = decode_workspace_callback(event.data)
|
|
965
|
+
self.service.get_workspace_panel(chat_key, token=token, version=version)
|
|
966
|
+
if action in {"mode", "model", "effort", "permission"}:
|
|
967
|
+
await self.send_setting_panel_to_chat(bot, chat_id, chat_key, action)
|
|
968
|
+
await bot.answer_callback_query(event.id)
|
|
969
|
+
return
|
|
970
|
+
if action == "browse":
|
|
971
|
+
await self.send_browser_to_chat(bot, chat_id, chat_key)
|
|
972
|
+
await bot.answer_callback_query(event.id)
|
|
973
|
+
return
|
|
974
|
+
if action == "history":
|
|
975
|
+
await self.send_history_browser_to_chat(bot, chat_id, chat_key)
|
|
976
|
+
await bot.answer_callback_query(event.id)
|
|
977
|
+
return
|
|
978
|
+
if action == "new":
|
|
979
|
+
await self.service.reset_chat(chat_key, keep_active=True)
|
|
980
|
+
await self.send_chat_message(
|
|
981
|
+
bot,
|
|
982
|
+
chat_id,
|
|
983
|
+
(
|
|
984
|
+
"已清空当前 Codex 会话。下一条普通消息会按以下设置新开会话:\n"
|
|
985
|
+
f"{self.current_summary(chat_key)}"
|
|
986
|
+
),
|
|
987
|
+
)
|
|
988
|
+
await bot.answer_callback_query(event.id, text="已新开会话。")
|
|
989
|
+
return
|
|
990
|
+
if action == "stop":
|
|
991
|
+
await self.service.reset_chat(chat_key, keep_active=False)
|
|
992
|
+
await self.send_chat_message(
|
|
993
|
+
bot, chat_id, "已断开当前聊天窗口的 Codex 会话。"
|
|
994
|
+
)
|
|
995
|
+
await bot.answer_callback_query(event.id, text="已停止。")
|
|
996
|
+
return
|
|
997
|
+
if action == "close":
|
|
998
|
+
self.service.close_workspace_panel(chat_key, token, version)
|
|
999
|
+
message_id = self.callback_message_id(event)
|
|
1000
|
+
if message_id is not None:
|
|
1001
|
+
await self.edit_message(
|
|
1002
|
+
bot,
|
|
1003
|
+
chat_id=chat_id,
|
|
1004
|
+
message_id=message_id,
|
|
1005
|
+
text="工作台已关闭。",
|
|
1006
|
+
reply_markup=None,
|
|
1007
|
+
)
|
|
1008
|
+
await bot.answer_callback_query(event.id, text="已关闭。")
|
|
1009
|
+
return
|
|
1010
|
+
self.service.navigate_workspace_panel(chat_key, token, version, action)
|
|
1011
|
+
await self.edit_or_resend_workspace_panel(bot, event, chat_key)
|
|
1012
|
+
await bot.answer_callback_query(event.id)
|
|
1013
|
+
except ValueError as exc:
|
|
1014
|
+
text = str(exc) or WORKSPACE_STALE_MESSAGE
|
|
1015
|
+
await bot.answer_callback_query(
|
|
1016
|
+
event.id,
|
|
1017
|
+
text=text,
|
|
1018
|
+
show_alert=text == WORKSPACE_STALE_MESSAGE,
|
|
1019
|
+
)
|
|
1020
|
+
except RuntimeError as exc:
|
|
1021
|
+
await bot.answer_callback_query(
|
|
1022
|
+
event.id, text=self.error_text(exc), show_alert=True
|
|
1023
|
+
)
|
|
1024
|
+
|
|
893
1025
|
async def handle_follow_up(self, bot: Bot, event: MessageEvent) -> None:
|
|
894
1026
|
chat_key = self.chat_key(event)
|
|
895
1027
|
session = self.service.get_session(chat_key)
|
|
@@ -28,6 +28,16 @@ TELEGRAM_COMMAND_SPECS: tuple[TelegramCommandSpec, ...] = (
|
|
|
28
28
|
description="打开使用引导面板",
|
|
29
29
|
usage="/start",
|
|
30
30
|
),
|
|
31
|
+
TelegramCommandSpec(
|
|
32
|
+
name="panel",
|
|
33
|
+
description="打开当前工作台",
|
|
34
|
+
usage="/panel",
|
|
35
|
+
),
|
|
36
|
+
TelegramCommandSpec(
|
|
37
|
+
name="status",
|
|
38
|
+
description="打开当前工作台",
|
|
39
|
+
usage="/status",
|
|
40
|
+
),
|
|
31
41
|
TelegramCommandSpec(
|
|
32
42
|
name="mode",
|
|
33
43
|
description="查看或切换默认模式",
|
|
@@ -20,7 +20,8 @@ def test_render_release_notes_groups_conventional_commits() -> None:
|
|
|
20
20
|
],
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
-
assert
|
|
23
|
+
assert notes.startswith("Released on ")
|
|
24
|
+
assert "# v0.1.2" not in notes
|
|
24
25
|
assert (
|
|
25
26
|
"Compare: https://github.com/ttiee/nonebot-plugin-codex/compare/"
|
|
26
27
|
"v0.1.1...v0.1.2"
|
|
@@ -451,6 +451,56 @@ def test_render_setting_panels_show_expected_headings(
|
|
|
451
451
|
assert markup.inline_keyboard
|
|
452
452
|
|
|
453
453
|
|
|
454
|
+
def test_render_workspace_panel_shows_current_state_and_recent_history(
|
|
455
|
+
tmp_path: Path,
|
|
456
|
+
model_cache_file: Path,
|
|
457
|
+
) -> None:
|
|
458
|
+
service = make_service(tmp_path, model_cache_file)
|
|
459
|
+
workdir = tmp_path / "workspace"
|
|
460
|
+
workdir.mkdir()
|
|
461
|
+
service.preference_overrides["private_1"] = service._default_preferences() # noqa: SLF001
|
|
462
|
+
service.preference_overrides["private_1"].workdir = str(workdir.resolve())
|
|
463
|
+
session = service.activate_chat("private_1")
|
|
464
|
+
session.active_mode = "exec"
|
|
465
|
+
session.exec_thread_id = "exec-1"
|
|
466
|
+
session.thread_id = "exec-1"
|
|
467
|
+
write_history_session(
|
|
468
|
+
tmp_path,
|
|
469
|
+
session_id="exec-1",
|
|
470
|
+
thread_name="Recent Session",
|
|
471
|
+
assistant_text="assistant world",
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
service.open_workspace_panel("private_1")
|
|
475
|
+
text, markup = service.render_workspace_panel("private_1")
|
|
476
|
+
|
|
477
|
+
assert "当前工作台" in text
|
|
478
|
+
assert "当前模式:exec" in text
|
|
479
|
+
assert "模型: gpt-5 | 推理: xhigh | 权限: safe" in text
|
|
480
|
+
assert f"当前工作目录:{workdir.resolve()}" in text
|
|
481
|
+
assert "当前会话:exec | exec-1" in text
|
|
482
|
+
assert "Recent Session" in text
|
|
483
|
+
assert markup.inline_keyboard
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def test_navigate_workspace_panel_refresh_reuses_token_and_bumps_version(
|
|
487
|
+
tmp_path: Path,
|
|
488
|
+
model_cache_file: Path,
|
|
489
|
+
) -> None:
|
|
490
|
+
service = make_service(tmp_path, model_cache_file)
|
|
491
|
+
|
|
492
|
+
panel = service.open_workspace_panel("private_1")
|
|
493
|
+
refreshed = service.navigate_workspace_panel(
|
|
494
|
+
"private_1",
|
|
495
|
+
panel.token,
|
|
496
|
+
panel.version,
|
|
497
|
+
"refresh",
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
assert refreshed.token == panel.token
|
|
501
|
+
assert refreshed.version == panel.version + 1
|
|
502
|
+
|
|
503
|
+
|
|
454
504
|
@pytest.mark.asyncio
|
|
455
505
|
async def test_apply_permission_setting_panel_updates_preference(
|
|
456
506
|
tmp_path: Path, model_cache_file: Path
|
|
@@ -12,6 +12,8 @@ def test_build_telegram_commands_uses_expected_order_and_chinese_descriptions()
|
|
|
12
12
|
"codex",
|
|
13
13
|
"help",
|
|
14
14
|
"start",
|
|
15
|
+
"panel",
|
|
16
|
+
"status",
|
|
15
17
|
"mode",
|
|
16
18
|
"exec",
|
|
17
19
|
"new",
|
|
@@ -30,6 +32,8 @@ def test_build_telegram_commands_uses_expected_order_and_chinese_descriptions()
|
|
|
30
32
|
{"command": "codex", "description": "连接 Codex 并可附带首条任务"},
|
|
31
33
|
{"command": "help", "description": "打开使用引导面板"},
|
|
32
34
|
{"command": "start", "description": "打开使用引导面板"},
|
|
35
|
+
{"command": "panel", "description": "打开当前工作台"},
|
|
36
|
+
{"command": "status", "description": "打开当前工作台"},
|
|
33
37
|
{"command": "mode", "description": "查看或切换默认模式"},
|
|
34
38
|
{"command": "exec", "description": "以一次性 exec 模式执行任务"},
|
|
35
39
|
{"command": "new", "description": "新建当前聊天会话"},
|
|
@@ -47,6 +51,7 @@ def test_build_telegram_commands_uses_expected_order_and_chinese_descriptions()
|
|
|
47
51
|
|
|
48
52
|
def test_build_plugin_usage_lists_all_commands() -> None:
|
|
49
53
|
assert build_plugin_usage() == (
|
|
50
|
-
"/codex [prompt], /help, /start, /
|
|
51
|
-
"/model, /effort, /permission, /pwd, /cd, /home,
|
|
54
|
+
"/codex [prompt], /help, /start, /panel, /status, /mode, /exec, /new, "
|
|
55
|
+
"/stop, /models, /model, /effort, /permission, /pwd, /cd, /home, "
|
|
56
|
+
"/sessions"
|
|
52
57
|
)
|
|
@@ -16,6 +16,7 @@ from nonebot_plugin_codex.service import (
|
|
|
16
16
|
encode_browser_callback,
|
|
17
17
|
encode_history_callback,
|
|
18
18
|
encode_setting_callback,
|
|
19
|
+
encode_workspace_callback,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
|
|
@@ -146,6 +147,8 @@ class FakeService:
|
|
|
146
147
|
self.setting_text = "模式设置"
|
|
147
148
|
self.onboarding_text = "开始使用 Codex"
|
|
148
149
|
self.onboarding_markup = SimpleNamespace(name="onboarding")
|
|
150
|
+
self.workspace_text = "当前工作台"
|
|
151
|
+
self.workspace_markup = SimpleNamespace(name="workspace")
|
|
149
152
|
self.default_mode = "resume"
|
|
150
153
|
self.execute_calls: list[tuple[str, str | None]] = []
|
|
151
154
|
self.browser_token = "token"
|
|
@@ -162,6 +165,9 @@ class FakeService:
|
|
|
162
165
|
self.onboarding_token = "onboarding"
|
|
163
166
|
self.onboarding_version = 1
|
|
164
167
|
self.onboarding_closed = False
|
|
168
|
+
self.workspace_token = "workspace"
|
|
169
|
+
self.workspace_version = 1
|
|
170
|
+
self.workspace_closed = False
|
|
165
171
|
|
|
166
172
|
def get_session(self, chat_key: str) -> ChatSession:
|
|
167
173
|
return self.session
|
|
@@ -362,6 +368,49 @@ class FakeService:
|
|
|
362
368
|
def close_onboarding_panel(self, chat_key: str, token: str, version: int) -> None:
|
|
363
369
|
self.onboarding_closed = True
|
|
364
370
|
|
|
371
|
+
def open_workspace_panel(self, chat_key: str) -> SimpleNamespace:
|
|
372
|
+
return SimpleNamespace(token=self.workspace_token)
|
|
373
|
+
|
|
374
|
+
def render_workspace_panel(self, chat_key: str) -> tuple[str, Any]:
|
|
375
|
+
return self.workspace_text, self.workspace_markup
|
|
376
|
+
|
|
377
|
+
def remember_workspace_panel_message(
|
|
378
|
+
self, chat_key: str, token: str, message_id: int | None
|
|
379
|
+
) -> None:
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
def get_workspace_panel(
|
|
383
|
+
self,
|
|
384
|
+
chat_key: str,
|
|
385
|
+
token: str | None = None,
|
|
386
|
+
version: int | None = None,
|
|
387
|
+
) -> SimpleNamespace:
|
|
388
|
+
if token is not None and token != self.workspace_token:
|
|
389
|
+
raise ValueError("工作台面板已失效,请重新执行 /panel")
|
|
390
|
+
if version is not None and version != self.workspace_version:
|
|
391
|
+
raise ValueError("工作台面板已失效,请重新执行 /panel")
|
|
392
|
+
return SimpleNamespace(
|
|
393
|
+
token=self.workspace_token,
|
|
394
|
+
version=self.workspace_version,
|
|
395
|
+
message_id=1,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def close_workspace_panel(self, chat_key: str, token: str, version: int) -> None:
|
|
399
|
+
self.workspace_closed = True
|
|
400
|
+
|
|
401
|
+
def navigate_workspace_panel(
|
|
402
|
+
self,
|
|
403
|
+
chat_key: str,
|
|
404
|
+
token: str,
|
|
405
|
+
version: int,
|
|
406
|
+
action: str,
|
|
407
|
+
) -> SimpleNamespace:
|
|
408
|
+
return SimpleNamespace(
|
|
409
|
+
token=self.workspace_token,
|
|
410
|
+
version=self.workspace_version,
|
|
411
|
+
message_id=1,
|
|
412
|
+
)
|
|
413
|
+
|
|
365
414
|
|
|
366
415
|
def make_real_service(
|
|
367
416
|
tmp_path: Path,
|
|
@@ -527,6 +576,143 @@ async def test_handle_sessions_opens_history_browser() -> None:
|
|
|
527
576
|
assert bot.sent[0]["text"] == "Codex 历史会话"
|
|
528
577
|
|
|
529
578
|
|
|
579
|
+
@pytest.mark.asyncio
|
|
580
|
+
@pytest.mark.parametrize("handler_name", ["handle_panel", "handle_status"])
|
|
581
|
+
async def test_panel_and_status_open_workspace_panel(handler_name: str) -> None:
|
|
582
|
+
service = FakeService()
|
|
583
|
+
handlers = TelegramHandlers(service)
|
|
584
|
+
bot = FakeBot()
|
|
585
|
+
|
|
586
|
+
await getattr(handlers, handler_name)(bot, FakeEvent(""))
|
|
587
|
+
|
|
588
|
+
assert bot.sent[0]["text"] == "当前工作台"
|
|
589
|
+
assert bot.sent[0]["reply_markup"] is service.workspace_markup
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@pytest.mark.asyncio
|
|
593
|
+
@pytest.mark.parametrize(
|
|
594
|
+
("action", "expected_text"),
|
|
595
|
+
[
|
|
596
|
+
("mode", "模式设置"),
|
|
597
|
+
("model", "模型设置"),
|
|
598
|
+
("effort", "推理强度设置"),
|
|
599
|
+
("permission", "权限模式设置"),
|
|
600
|
+
("browse", "目录浏览"),
|
|
601
|
+
("history", "Codex 历史会话"),
|
|
602
|
+
],
|
|
603
|
+
)
|
|
604
|
+
async def test_handle_workspace_callback_opens_existing_panels(
|
|
605
|
+
action: str,
|
|
606
|
+
expected_text: str,
|
|
607
|
+
) -> None:
|
|
608
|
+
service = FakeService()
|
|
609
|
+
handlers = TelegramHandlers(service)
|
|
610
|
+
bot = FakeBot()
|
|
611
|
+
event = FakeCallbackEvent(
|
|
612
|
+
encode_workspace_callback(
|
|
613
|
+
service.workspace_token,
|
|
614
|
+
service.workspace_version,
|
|
615
|
+
action,
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
await handlers.handle_workspace_callback(bot, event)
|
|
620
|
+
|
|
621
|
+
assert bot.sent[0]["text"] == expected_text
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
@pytest.mark.asyncio
|
|
625
|
+
async def test_handle_workspace_callback_new_resets_chat() -> None:
|
|
626
|
+
service = FakeService()
|
|
627
|
+
service.session.thread_id = "thread-1"
|
|
628
|
+
handlers = TelegramHandlers(service)
|
|
629
|
+
bot = FakeBot()
|
|
630
|
+
event = FakeCallbackEvent(
|
|
631
|
+
encode_workspace_callback(
|
|
632
|
+
service.workspace_token,
|
|
633
|
+
service.workspace_version,
|
|
634
|
+
"new",
|
|
635
|
+
)
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
await handlers.handle_workspace_callback(bot, event)
|
|
639
|
+
|
|
640
|
+
assert "已清空当前 Codex 会话" in bot.sent[0]["text"]
|
|
641
|
+
assert bot.answered[0]["text"] == "已新开会话。"
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
@pytest.mark.asyncio
|
|
645
|
+
async def test_handle_workspace_callback_stop_disconnects_chat() -> None:
|
|
646
|
+
service = FakeService()
|
|
647
|
+
service.session.active = True
|
|
648
|
+
service.session.thread_id = "thread-1"
|
|
649
|
+
handlers = TelegramHandlers(service)
|
|
650
|
+
bot = FakeBot()
|
|
651
|
+
event = FakeCallbackEvent(
|
|
652
|
+
encode_workspace_callback(
|
|
653
|
+
service.workspace_token,
|
|
654
|
+
service.workspace_version,
|
|
655
|
+
"stop",
|
|
656
|
+
)
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
await handlers.handle_workspace_callback(bot, event)
|
|
660
|
+
|
|
661
|
+
assert bot.sent[0]["text"] == "已断开当前聊天窗口的 Codex 会话。"
|
|
662
|
+
assert bot.answered[0]["text"] == "已停止。"
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@pytest.mark.asyncio
|
|
666
|
+
async def test_handle_workspace_callback_refresh_rerenders_panel() -> None:
|
|
667
|
+
service = FakeService()
|
|
668
|
+
handlers = TelegramHandlers(service)
|
|
669
|
+
bot = FakeBot()
|
|
670
|
+
event = FakeCallbackEvent(
|
|
671
|
+
encode_workspace_callback(
|
|
672
|
+
service.workspace_token,
|
|
673
|
+
service.workspace_version,
|
|
674
|
+
"refresh",
|
|
675
|
+
)
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
await handlers.handle_workspace_callback(bot, event)
|
|
679
|
+
|
|
680
|
+
assert bot.edited[0]["text"] == "当前工作台"
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
@pytest.mark.asyncio
|
|
684
|
+
async def test_handle_workspace_callback_close_closes_panel() -> None:
|
|
685
|
+
service = FakeService()
|
|
686
|
+
handlers = TelegramHandlers(service)
|
|
687
|
+
bot = FakeBot()
|
|
688
|
+
event = FakeCallbackEvent(
|
|
689
|
+
encode_workspace_callback(
|
|
690
|
+
service.workspace_token,
|
|
691
|
+
service.workspace_version,
|
|
692
|
+
"close",
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
await handlers.handle_workspace_callback(bot, event)
|
|
697
|
+
|
|
698
|
+
assert service.workspace_closed is True
|
|
699
|
+
assert bot.edited[0]["text"] == "工作台已关闭。"
|
|
700
|
+
assert bot.answered[0]["text"] == "已关闭。"
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
@pytest.mark.asyncio
|
|
704
|
+
async def test_handle_workspace_callback_rejects_stale_payload() -> None:
|
|
705
|
+
handlers = TelegramHandlers(FakeService())
|
|
706
|
+
bot = FakeBot()
|
|
707
|
+
|
|
708
|
+
await handlers.handle_workspace_callback(
|
|
709
|
+
bot, FakeCallbackEvent("cwp:stale:1:browse")
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
assert bot.answered[0]["text"] == "工作台面板已失效,请重新执行 /panel"
|
|
713
|
+
assert bot.answered[0]["show_alert"] is True
|
|
714
|
+
|
|
715
|
+
|
|
530
716
|
@pytest.mark.asyncio
|
|
531
717
|
@pytest.mark.parametrize(
|
|
532
718
|
("payload", "expected_text"),
|
|
File without changes
|
{nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/config.py
RENAMED
|
File without changes
|
{nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/native_client.py
RENAMED
|
File without changes
|
{nonebot_plugin_codex-0.1.3 → nonebot_plugin_codex-0.1.4}/src/nonebot_plugin_codex/runtime.py
RENAMED
|
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
|