feat(telegram): make /ctx work everywhere (#159)

This commit is contained in:
banteg
2026-01-17 01:17:50 +04:00
committed by GitHub
parent b215279a3c
commit 419ec5078b
13 changed files with 390 additions and 20 deletions
+125 -1
View File
@@ -135,6 +135,7 @@ def test_build_bot_commands_includes_cancel_and_engine() -> None:
assert {"command": "cancel", "description": "cancel run"} in commands
assert {"command": "file", "description": "upload or fetch files"} in commands
assert {"command": "new", "description": "start a new thread"} in commands
assert {"command": "ctx", "description": "show or update context"} in commands
assert {"command": "agent", "description": "set default agent"} in commands
assert any(cmd["command"] == "codex" for cmd in commands)
@@ -179,7 +180,7 @@ def test_build_bot_commands_includes_topics_when_enabled() -> None:
commands = build_bot_commands(runtime, include_topics=True)
assert {"command": "topic", "description": "create or bind a topic"} in commands
assert {"command": "ctx", "description": "show or update topic context"} in commands
assert {"command": "ctx", "description": "show or update context"} in commands
def test_build_bot_commands_includes_command_plugins(monkeypatch) -> None:
@@ -2515,6 +2516,129 @@ async def test_run_main_loop_hides_resume_line_when_disabled(
assert resume_value not in final_text
@pytest.mark.anyio
async def test_run_main_loop_hides_resume_line_without_context(
tmp_path: Path,
) -> None:
resume_value = "resume-ctxless"
state_path = tmp_path / "takopi.toml"
transport = FakeTransport()
bot = FakeBot()
runner = ScriptRunner(
[Return(answer="ok")],
engine=CODEX_ENGINE,
resume_value=resume_value,
)
exec_cfg = ExecBridgeConfig(
transport=transport,
presenter=MarkdownPresenter(),
final_notify=True,
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=_empty_projects(),
config_path=state_path,
)
cfg = TelegramBridgeConfig(
bot=bot,
runtime=runtime,
chat_id=123,
startup_msg="",
exec_cfg=exec_cfg,
forward_coalesce_s=FAST_FORWARD_COALESCE_S,
media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S,
session_mode="chat",
show_resume_line=False,
)
async def poller(_cfg: TelegramBridgeConfig):
yield TelegramIncomingMessage(
transport="telegram",
chat_id=123,
message_id=1,
text="hello",
reply_to_message_id=None,
reply_to_text=None,
sender_id=123,
chat_type="private",
)
await run_main_loop(cfg, poller)
assert transport.send_calls
final_text = transport.send_calls[-1]["message"].text
assert resume_value not in final_text
@pytest.mark.anyio
async def test_run_main_loop_applies_chat_bound_context(
tmp_path: Path,
) -> None:
state_path = tmp_path / "takopi.toml"
transport = FakeTransport()
bot = FakeBot()
runner = ScriptRunner([Return(answer="ok")], engine=CODEX_ENGINE)
exec_cfg = ExecBridgeConfig(
transport=transport,
presenter=MarkdownPresenter(),
final_notify=True,
)
projects = ProjectsConfig(
projects={
"alpha": ProjectConfig(
alias="Alpha",
path=tmp_path,
worktrees_dir=Path(".worktrees"),
),
"beta": ProjectConfig(
alias="Beta",
path=tmp_path / "beta",
worktrees_dir=Path(".worktrees"),
),
},
default_project="alpha",
)
(tmp_path / "beta").mkdir()
runtime = TransportRuntime(
router=_make_router(runner),
projects=projects,
config_path=state_path,
)
prefs = ChatPrefsStore(resolve_prefs_path(state_path))
await prefs.set_context(123, RunContext(project="beta"))
cfg = TelegramBridgeConfig(
bot=bot,
runtime=runtime,
chat_id=123,
startup_msg="",
exec_cfg=exec_cfg,
forward_coalesce_s=FAST_FORWARD_COALESCE_S,
media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S,
session_mode="chat",
show_resume_line=False,
)
async def poller(_cfg: TelegramBridgeConfig):
yield TelegramIncomingMessage(
transport="telegram",
chat_id=123,
message_id=1,
text="hello",
reply_to_message_id=None,
reply_to_text=None,
sender_id=123,
chat_type="private",
)
await run_main_loop(cfg, poller)
assert transport.send_calls
final_text = transport.send_calls[-1]["message"].text
assert "`ctx: Beta`" in final_text
@pytest.mark.anyio
async def test_run_main_loop_chat_sessions_isolate_group_senders(
tmp_path: Path,
+59 -1
View File
@@ -4,8 +4,12 @@ from pathlib import Path
import pytest
from takopi.settings import TelegramTopicsSettings
from takopi.config import ProjectConfig, ProjectsConfig
from takopi.runners.mock import Return, ScriptRunner
from takopi.telegram.chat_sessions import ChatSessionStore
from takopi.telegram.chat_prefs import ChatPrefsStore, resolve_prefs_path
from takopi.telegram.commands.topics import (
_handle_chat_ctx_command,
_handle_chat_new_command,
_handle_ctx_command,
_handle_new_command,
@@ -13,7 +17,13 @@ from takopi.telegram.commands.topics import (
)
from takopi.telegram.topic_state import TopicStateStore
from takopi.telegram.types import TelegramIncomingMessage
from tests.telegram_fakes import FakeTransport, make_cfg
from tests.telegram_fakes import (
DEFAULT_ENGINE_ID,
FakeTransport,
_make_router,
make_cfg,
)
from takopi.transport_runtime import TransportRuntime
def _msg(
@@ -37,6 +47,27 @@ def _msg(
)
def _runtime(tmp_path: Path) -> tuple[TransportRuntime, Path]:
runner = ScriptRunner([Return(answer="ok")], engine=DEFAULT_ENGINE_ID)
projects = ProjectsConfig(
projects={
"alpha": ProjectConfig(
alias="Alpha",
path=tmp_path,
worktrees_dir=Path(".worktrees"),
)
},
default_project="alpha",
)
state_path = tmp_path / "takopi.toml"
runtime = TransportRuntime(
router=_make_router(runner),
projects=projects,
config_path=state_path,
)
return runtime, state_path
@pytest.mark.anyio
async def test_ctx_command_requires_topic(tmp_path: Path) -> None:
transport = FakeTransport()
@@ -60,6 +91,33 @@ async def test_ctx_command_requires_topic(tmp_path: Path) -> None:
assert "only works inside a topic" in text
@pytest.mark.anyio
async def test_chat_ctx_command_sets_binding(tmp_path: Path) -> None:
transport = FakeTransport()
runtime, state_path = _runtime(tmp_path)
cfg = replace(make_cfg(transport), runtime=runtime, session_mode="chat")
store = ChatPrefsStore(resolve_prefs_path(state_path))
msg = _msg("/ctx set alpha @dev", chat_type="private")
await _handle_chat_ctx_command(
cfg,
msg,
args_text="set alpha @dev",
chat_prefs=store,
)
msg_show = _msg("/ctx", chat_type="private")
await _handle_chat_ctx_command(
cfg,
msg_show,
args_text="",
chat_prefs=store,
)
text = transport.send_calls[-1]["message"].text
assert "bound ctx: Alpha @dev" in text
@pytest.mark.anyio
async def test_new_command_requires_topic(tmp_path: Path) -> None:
transport = FakeTransport()