feat(telegram): make /ctx work everywhere (#159)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user