fix(sdk): correct WebSocket URL pattern for platform-agent
AgentApiWrapper._build_ws_url was building /v1/agent_ws/{chat_id}/
which does not exist in platform-agent. Fixed to /agent_ws/?thread_id={chat_id}
to match the actual endpoint and query-param isolation scheme.
Also simplify Matrix MVP settings handlers to MVP_UNAVAILABLE stubs
and add handle_unknown_command for unregistered !commands.
This commit is contained in:
parent
07c5078934
commit
fbcf44980e
5 changed files with 52 additions and 136 deletions
17
.dockerignore
Normal file
17
.dockerignore
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.venv/
|
||||||
|
.worktrees/
|
||||||
|
|
||||||
|
# Local runtime state must not be baked into the image.
|
||||||
|
lambda_matrix.db
|
||||||
|
matrix_store/
|
||||||
|
lambda_bot.db
|
||||||
|
|
||||||
|
# Local environment and editor state
|
||||||
|
.env
|
||||||
|
.idea/
|
||||||
|
|
@ -10,13 +10,13 @@ from adapter.matrix.handlers.confirm import make_handle_cancel, make_handle_conf
|
||||||
from adapter.matrix.handlers.context_commands import (
|
from adapter.matrix.handlers.context_commands import (
|
||||||
make_handle_context,
|
make_handle_context,
|
||||||
make_handle_load,
|
make_handle_load,
|
||||||
make_handle_reset,
|
|
||||||
make_handle_save,
|
make_handle_save,
|
||||||
)
|
)
|
||||||
from adapter.matrix.handlers.settings import (
|
from adapter.matrix.handlers.settings import (
|
||||||
handle_help,
|
handle_help,
|
||||||
handle_settings,
|
handle_settings,
|
||||||
handle_settings_connectors,
|
handle_settings_connectors,
|
||||||
|
handle_unknown_command,
|
||||||
handle_settings_plan,
|
handle_settings_plan,
|
||||||
handle_settings_safety,
|
handle_settings_safety,
|
||||||
handle_settings_skills,
|
handle_settings_skills,
|
||||||
|
|
@ -43,6 +43,7 @@ def register_matrix_handlers(
|
||||||
dispatcher.register(IncomingCommand, "archive", make_handle_archive(client, store))
|
dispatcher.register(IncomingCommand, "archive", make_handle_archive(client, store))
|
||||||
dispatcher.register(IncomingCommand, "help", handle_help)
|
dispatcher.register(IncomingCommand, "help", handle_help)
|
||||||
dispatcher.register(IncomingCommand, "settings", handle_settings)
|
dispatcher.register(IncomingCommand, "settings", handle_settings)
|
||||||
|
dispatcher.register(IncomingCommand, "reset", handle_settings)
|
||||||
dispatcher.register(IncomingCommand, "settings_skills", handle_settings_skills)
|
dispatcher.register(IncomingCommand, "settings_skills", handle_settings_skills)
|
||||||
dispatcher.register(IncomingCommand, "settings_connectors", handle_settings_connectors)
|
dispatcher.register(IncomingCommand, "settings_connectors", handle_settings_connectors)
|
||||||
dispatcher.register(IncomingCommand, "settings_soul", handle_settings_soul)
|
dispatcher.register(IncomingCommand, "settings_soul", handle_settings_soul)
|
||||||
|
|
@ -54,9 +55,9 @@ def register_matrix_handlers(
|
||||||
dispatcher.register(IncomingCallback, "confirm", make_handle_confirm(store))
|
dispatcher.register(IncomingCallback, "confirm", make_handle_confirm(store))
|
||||||
dispatcher.register(IncomingCallback, "cancel", make_handle_cancel(store))
|
dispatcher.register(IncomingCallback, "cancel", make_handle_cancel(store))
|
||||||
dispatcher.register(IncomingCallback, "toggle_skill", handle_toggle_skill)
|
dispatcher.register(IncomingCallback, "toggle_skill", handle_toggle_skill)
|
||||||
|
dispatcher.register(IncomingCommand, "*", handle_unknown_command)
|
||||||
|
|
||||||
if agent_api is not None and prototype_state is not None:
|
if agent_api is not None and prototype_state is not None:
|
||||||
dispatcher.register(IncomingCommand, "save", make_handle_save(agent_api, store, prototype_state))
|
dispatcher.register(IncomingCommand, "save", make_handle_save(agent_api, store, prototype_state))
|
||||||
dispatcher.register(IncomingCommand, "load", make_handle_load(store, prototype_state))
|
dispatcher.register(IncomingCommand, "load", make_handle_load(store, prototype_state))
|
||||||
dispatcher.register(IncomingCommand, "reset", make_handle_reset(store, agent_base_url))
|
|
||||||
dispatcher.register(IncomingCommand, "context", make_handle_context(store, prototype_state))
|
dispatcher.register(IncomingCommand, "context", make_handle_context(store, prototype_state))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from adapter.matrix.reactions import build_skills_text
|
from core.protocol import IncomingCommand, OutgoingMessage
|
||||||
from core.protocol import IncomingCommand, OutgoingMessage, SettingsAction
|
|
||||||
|
|
||||||
|
|
||||||
HELP_TEXT = "\n".join(
|
HELP_TEXT = "\n".join(
|
||||||
|
|
@ -12,77 +11,25 @@ HELP_TEXT = "\n".join(
|
||||||
"!chats список активных чатов",
|
"!chats список активных чатов",
|
||||||
"!rename <название> переименовать текущий чат",
|
"!rename <название> переименовать текущий чат",
|
||||||
"!archive архивировать текущий чат",
|
"!archive архивировать текущий чат",
|
||||||
"!settings общий обзор настроек",
|
"!context показать текущее состояние контекста",
|
||||||
"!skills список навыков",
|
"!save [имя] сохранить текущий контекст",
|
||||||
"!soul [поле значение] показать или изменить личность",
|
"!load показать сохранённые контексты",
|
||||||
"!safety [триггер on/off] показать или изменить безопасность",
|
"",
|
||||||
"!status краткий статус",
|
"Остальные команды и настройки скрыты в MVP, чтобы не вводить в заблуждение.",
|
||||||
"!whoami показать ваш id",
|
|
||||||
"!yes / !no подтвердить или отменить действие",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _render_mapping(title: str, data: dict | None) -> str:
|
MVP_UNAVAILABLE_TEXT = (
|
||||||
data = data or {}
|
"Эта команда скрыта в MVP и сейчас недоступна. "
|
||||||
lines = [title]
|
"Используй !help для списка поддерживаемых команд."
|
||||||
if not data:
|
)
|
||||||
lines.append("Нет данных.")
|
|
||||||
else:
|
|
||||||
for key, value in data.items():
|
|
||||||
lines.append(f"• {key}: {value}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_bool(value: str) -> bool:
|
|
||||||
return value.lower() in {"1", "true", "yes", "on", "enable", "enabled"}
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings(
|
async def handle_settings(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
settings = await settings_mgr.get(event.user_id)
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
chats = await chat_mgr.list_active(event.user_id)
|
|
||||||
|
|
||||||
skills_lines = []
|
|
||||||
for name, enabled in settings.skills.items():
|
|
||||||
state = "on" if enabled else "off"
|
|
||||||
skills_lines.append(f" {state} {name}")
|
|
||||||
skills_text = "\n".join(skills_lines) if skills_lines else " нет навыков"
|
|
||||||
|
|
||||||
soul_lines = []
|
|
||||||
for key, value in (settings.soul or {}).items():
|
|
||||||
soul_lines.append(f" {key}: {value}")
|
|
||||||
soul_text = "\n".join(soul_lines) if soul_lines else " по умолчанию"
|
|
||||||
|
|
||||||
safety_lines = []
|
|
||||||
for key, value in (settings.safety or {}).items():
|
|
||||||
state = "on" if value else "off"
|
|
||||||
safety_lines.append(f" {state} {key}")
|
|
||||||
safety_text = "\n".join(safety_lines) if safety_lines else " по умолчанию"
|
|
||||||
|
|
||||||
chat_lines = [f" {chat.display_name} ({chat.chat_id})" for chat in chats]
|
|
||||||
chats_text = "\n".join(chat_lines) if chat_lines else " нет активных чатов"
|
|
||||||
|
|
||||||
dashboard = "\n".join(
|
|
||||||
[
|
|
||||||
"Настройки",
|
|
||||||
"",
|
|
||||||
"Скиллы:",
|
|
||||||
skills_text,
|
|
||||||
"",
|
|
||||||
"Личность:",
|
|
||||||
soul_text,
|
|
||||||
"",
|
|
||||||
"Безопасность:",
|
|
||||||
safety_text,
|
|
||||||
"",
|
|
||||||
f"Активные чаты ({len(chats)}):",
|
|
||||||
chats_text,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=dashboard)]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_help(
|
async def handle_help(
|
||||||
|
|
@ -94,104 +41,55 @@ async def handle_help(
|
||||||
async def handle_settings_skills(
|
async def handle_settings_skills(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
settings = await settings_mgr.get(event.user_id)
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=build_skills_text(settings))]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings_connectors(
|
async def handle_settings_connectors(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
settings = await settings_mgr.get(event.user_id)
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
return [
|
|
||||||
OutgoingMessage(
|
|
||||||
chat_id=event.chat_id, text=_render_mapping("🔗 Коннекторы", settings.connectors)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings_soul(
|
async def handle_settings_soul(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
if len(event.args) >= 2:
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
field = event.args[0]
|
|
||||||
value = " ".join(event.args[1:])
|
|
||||||
await settings_mgr.apply(
|
|
||||||
event.user_id,
|
|
||||||
SettingsAction(action="set_soul", payload={"field": field, "value": value}),
|
|
||||||
)
|
|
||||||
return [
|
|
||||||
OutgoingMessage(chat_id=event.chat_id, text=f"Личность обновлена: {field} = {value}")
|
|
||||||
]
|
|
||||||
settings = await settings_mgr.get(event.user_id)
|
|
||||||
return [
|
|
||||||
OutgoingMessage(chat_id=event.chat_id, text=_render_mapping("🧠 Личность", settings.soul))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings_safety(
|
async def handle_settings_safety(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
if len(event.args) >= 2:
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
trigger = event.args[0]
|
|
||||||
enabled = _parse_bool(event.args[1])
|
|
||||||
await settings_mgr.apply(
|
|
||||||
event.user_id,
|
|
||||||
SettingsAction(action="set_safety", payload={"trigger": trigger, "enabled": enabled}),
|
|
||||||
)
|
|
||||||
state = "включена" if enabled else "выключена"
|
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=f"Безопасность {trigger} {state}")]
|
|
||||||
settings = await settings_mgr.get(event.user_id)
|
|
||||||
return [
|
|
||||||
OutgoingMessage(
|
|
||||||
chat_id=event.chat_id, text=_render_mapping("🔒 Безопасность", settings.safety)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings_plan(
|
async def handle_settings_plan(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
settings = await settings_mgr.get(event.user_id)
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=_render_mapping("💳 План", settings.plan))]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings_status(
|
async def handle_settings_status(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
chats = await chat_mgr.list_active(event.user_id)
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
settings = await settings_mgr.get(event.user_id)
|
|
||||||
text = "\n".join(
|
|
||||||
[
|
|
||||||
"📊 Статус",
|
|
||||||
f"Активных чатов: {len(chats)}",
|
|
||||||
f"Скиллов: {len(settings.skills)}",
|
|
||||||
f"Коннекторов: {len(settings.connectors)}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=text)]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_settings_whoami(
|
async def handle_settings_whoami(
|
||||||
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
) -> list:
|
) -> list:
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=f"👤 {event.platform}:{event.user_id}")]
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
|
|
||||||
|
|
||||||
async def handle_toggle_skill(event, auth_mgr, platform, chat_mgr, settings_mgr) -> list:
|
async def handle_toggle_skill(event, auth_mgr, platform, chat_mgr, settings_mgr) -> list:
|
||||||
settings = await settings_mgr.get(event.user_id)
|
return [OutgoingMessage(chat_id=event.chat_id, text=MVP_UNAVAILABLE_TEXT)]
|
||||||
keys = list(settings.skills.keys())
|
|
||||||
skill = event.payload.get("skill")
|
|
||||||
if not skill:
|
|
||||||
idx = event.payload.get("skill_index")
|
|
||||||
if isinstance(idx, int) and 1 <= idx <= len(keys):
|
|
||||||
skill = keys[idx - 1]
|
|
||||||
if not skill:
|
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text="Ошибка: не удалось определить навык.")]
|
|
||||||
|
|
||||||
enabled = not bool(settings.skills.get(skill, False))
|
|
||||||
await settings_mgr.apply(
|
async def handle_unknown_command(
|
||||||
event.user_id,
|
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
|
||||||
SettingsAction(action="toggle_skill", payload={"skill": skill, "enabled": enabled}),
|
) -> list:
|
||||||
|
return [
|
||||||
|
OutgoingMessage(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text="Неизвестная команда. Используй !help для списка поддерживаемых команд.",
|
||||||
)
|
)
|
||||||
state = "включён" if enabled else "выключен"
|
]
|
||||||
return [OutgoingMessage(chat_id=event.chat_id, text=f"Навык {skill} {state}.")]
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class AgentApiWrapper(AgentApi):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_ws_url(base_url: str, chat_id: int | str) -> str:
|
def _build_ws_url(base_url: str, chat_id: int | str) -> str:
|
||||||
return base_url.rstrip("/") + f"/v1/agent_ws/{chat_id}/"
|
return base_url.rstrip("/") + f"/agent_ws/?thread_id={chat_id}"
|
||||||
|
|
||||||
def for_chat(self, chat_id: int | str) -> "AgentApiWrapper":
|
def for_chat(self, chat_id: int | str) -> "AgentApiWrapper":
|
||||||
return type(self)(
|
return type(self)(
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ def test_agent_api_wrapper_falls_back_to_legacy_url_constructor(monkeypatch):
|
||||||
|
|
||||||
wrapper = AgentApiWrapper(
|
wrapper = AgentApiWrapper(
|
||||||
agent_id="agent-2",
|
agent_id="agent-2",
|
||||||
url="https://agent.example.com/v1/agent_ws/chat-9/",
|
url="https://agent.example.com/agent_ws/",
|
||||||
chat_id="chat-9",
|
chat_id="chat-9",
|
||||||
callback="cb",
|
callback="cb",
|
||||||
)
|
)
|
||||||
|
|
@ -167,7 +167,7 @@ def test_agent_api_wrapper_falls_back_to_legacy_url_constructor(monkeypatch):
|
||||||
assert calls == [
|
assert calls == [
|
||||||
{
|
{
|
||||||
"agent_id": "agent-2",
|
"agent_id": "agent-2",
|
||||||
"url": "https://agent.example.com/v1/agent_ws/chat-9/",
|
"url": "https://agent.example.com/agent_ws/?thread_id=chat-9",
|
||||||
"callback": "cb",
|
"callback": "cb",
|
||||||
"on_disconnect": None,
|
"on_disconnect": None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue