mindroom 0.0.0__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
- mindroom/__init__.py +3 -0
- mindroom/agent_prompts.py +963 -0
- mindroom/agents.py +248 -0
- mindroom/ai.py +421 -0
- mindroom/api/__init__.py +1 -0
- mindroom/api/credentials.py +137 -0
- mindroom/api/google_integration.py +355 -0
- mindroom/api/google_tools_helper.py +40 -0
- mindroom/api/homeassistant_integration.py +421 -0
- mindroom/api/integrations.py +189 -0
- mindroom/api/main.py +506 -0
- mindroom/api/matrix_operations.py +219 -0
- mindroom/api/tools.py +94 -0
- mindroom/background_tasks.py +87 -0
- mindroom/bot.py +2470 -0
- mindroom/cli.py +86 -0
- mindroom/commands.py +377 -0
- mindroom/config.py +343 -0
- mindroom/config_commands.py +324 -0
- mindroom/config_confirmation.py +411 -0
- mindroom/constants.py +52 -0
- mindroom/credentials.py +146 -0
- mindroom/credentials_sync.py +134 -0
- mindroom/custom_tools/__init__.py +8 -0
- mindroom/custom_tools/config_manager.py +765 -0
- mindroom/custom_tools/gmail.py +92 -0
- mindroom/custom_tools/google_calendar.py +92 -0
- mindroom/custom_tools/google_sheets.py +92 -0
- mindroom/custom_tools/homeassistant.py +341 -0
- mindroom/error_handling.py +35 -0
- mindroom/file_watcher.py +49 -0
- mindroom/interactive.py +313 -0
- mindroom/logging_config.py +207 -0
- mindroom/matrix/__init__.py +1 -0
- mindroom/matrix/client.py +782 -0
- mindroom/matrix/event_info.py +173 -0
- mindroom/matrix/identity.py +149 -0
- mindroom/matrix/large_messages.py +267 -0
- mindroom/matrix/mentions.py +141 -0
- mindroom/matrix/message_builder.py +94 -0
- mindroom/matrix/message_content.py +209 -0
- mindroom/matrix/presence.py +178 -0
- mindroom/matrix/rooms.py +311 -0
- mindroom/matrix/state.py +77 -0
- mindroom/matrix/typing.py +91 -0
- mindroom/matrix/users.py +217 -0
- mindroom/memory/__init__.py +21 -0
- mindroom/memory/config.py +137 -0
- mindroom/memory/functions.py +396 -0
- mindroom/py.typed +0 -0
- mindroom/response_tracker.py +128 -0
- mindroom/room_cleanup.py +139 -0
- mindroom/routing.py +107 -0
- mindroom/scheduling.py +758 -0
- mindroom/stop.py +207 -0
- mindroom/streaming.py +203 -0
- mindroom/teams.py +749 -0
- mindroom/thread_utils.py +318 -0
- mindroom/tools/__init__.py +520 -0
- mindroom/tools/agentql.py +64 -0
- mindroom/tools/airflow.py +57 -0
- mindroom/tools/apify.py +49 -0
- mindroom/tools/arxiv.py +64 -0
- mindroom/tools/aws_lambda.py +41 -0
- mindroom/tools/aws_ses.py +57 -0
- mindroom/tools/baidusearch.py +87 -0
- mindroom/tools/brightdata.py +116 -0
- mindroom/tools/browserbase.py +62 -0
- mindroom/tools/cal_com.py +98 -0
- mindroom/tools/calculator.py +112 -0
- mindroom/tools/cartesia.py +84 -0
- mindroom/tools/composio.py +166 -0
- mindroom/tools/config_manager.py +44 -0
- mindroom/tools/confluence.py +73 -0
- mindroom/tools/crawl4ai.py +101 -0
- mindroom/tools/csv.py +104 -0
- mindroom/tools/custom_api.py +106 -0
- mindroom/tools/dalle.py +85 -0
- mindroom/tools/daytona.py +180 -0
- mindroom/tools/discord.py +81 -0
- mindroom/tools/docker.py +73 -0
- mindroom/tools/duckdb.py +124 -0
- mindroom/tools/duckduckgo.py +99 -0
- mindroom/tools/e2b.py +121 -0
- mindroom/tools/eleven_labs.py +77 -0
- mindroom/tools/email.py +74 -0
- mindroom/tools/exa.py +246 -0
- mindroom/tools/fal.py +50 -0
- mindroom/tools/file.py +80 -0
- mindroom/tools/financial_datasets_api.py +112 -0
- mindroom/tools/firecrawl.py +124 -0
- mindroom/tools/gemini.py +85 -0
- mindroom/tools/giphy.py +49 -0
- mindroom/tools/github.py +376 -0
- mindroom/tools/gmail.py +102 -0
- mindroom/tools/google_calendar.py +55 -0
- mindroom/tools/google_maps.py +112 -0
- mindroom/tools/google_sheets.py +86 -0
- mindroom/tools/googlesearch.py +83 -0
- mindroom/tools/groq.py +77 -0
- mindroom/tools/hackernews.py +54 -0
- mindroom/tools/jina.py +108 -0
- mindroom/tools/jira.py +70 -0
- mindroom/tools/linear.py +103 -0
- mindroom/tools/linkup.py +65 -0
- mindroom/tools/lumalabs.py +71 -0
- mindroom/tools/mem0.py +82 -0
- mindroom/tools/modelslabs.py +85 -0
- mindroom/tools/moviepy_video_tools.py +62 -0
- mindroom/tools/newspaper4k.py +63 -0
- mindroom/tools/openai.py +143 -0
- mindroom/tools/openweather.py +89 -0
- mindroom/tools/oxylabs.py +54 -0
- mindroom/tools/pandas.py +35 -0
- mindroom/tools/pubmed.py +64 -0
- mindroom/tools/python.py +120 -0
- mindroom/tools/reddit.py +155 -0
- mindroom/tools/replicate.py +56 -0
- mindroom/tools/resend.py +55 -0
- mindroom/tools/scrapegraph.py +87 -0
- mindroom/tools/searxng.py +120 -0
- mindroom/tools/serpapi.py +55 -0
- mindroom/tools/serper.py +81 -0
- mindroom/tools/shell.py +46 -0
- mindroom/tools/slack.py +80 -0
- mindroom/tools/sleep.py +38 -0
- mindroom/tools/spider.py +62 -0
- mindroom/tools/sql.py +138 -0
- mindroom/tools/tavily.py +104 -0
- mindroom/tools/telegram.py +54 -0
- mindroom/tools/todoist.py +103 -0
- mindroom/tools/trello.py +121 -0
- mindroom/tools/twilio.py +97 -0
- mindroom/tools/web_browser_tools.py +37 -0
- mindroom/tools/webex.py +63 -0
- mindroom/tools/website.py +45 -0
- mindroom/tools/whatsapp.py +81 -0
- mindroom/tools/wikipedia.py +45 -0
- mindroom/tools/x.py +97 -0
- mindroom/tools/yfinance.py +121 -0
- mindroom/tools/youtube.py +81 -0
- mindroom/tools/zendesk.py +62 -0
- mindroom/tools/zep.py +107 -0
- mindroom/tools/zoom.py +62 -0
- mindroom/tools_metadata.json +7643 -0
- mindroom/tools_metadata.py +220 -0
- mindroom/topic_generator.py +153 -0
- mindroom/voice_handler.py +266 -0
- mindroom-0.1.0.dist-info/METADATA +425 -0
- mindroom-0.1.0.dist-info/RECORD +152 -0
- {mindroom-0.0.0.dist-info → mindroom-0.1.0.dist-info}/WHEEL +1 -2
- mindroom-0.1.0.dist-info/entry_points.txt +2 -0
- mindroom-0.0.0.dist-info/METADATA +0 -24
- mindroom-0.0.0.dist-info/RECORD +0 -4
- mindroom-0.0.0.dist-info/top_level.txt +0 -1
mindroom/room_cleanup.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Room cleanup utilities for removing stale bot memberships from Matrix rooms.
|
|
2
|
+
|
|
3
|
+
With the new self-managing agent pattern, agents handle their own room
|
|
4
|
+
memberships. This module only handles cleanup of stale/orphaned bots.
|
|
5
|
+
|
|
6
|
+
DM rooms are preserved and not cleaned up.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import nio
|
|
14
|
+
|
|
15
|
+
from .logging_config import get_logger
|
|
16
|
+
from .matrix.client import get_joined_rooms, get_room_members
|
|
17
|
+
from .matrix.identity import MatrixID
|
|
18
|
+
from .matrix.rooms import is_dm_room
|
|
19
|
+
from .matrix.state import MatrixState
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from .config import Config
|
|
23
|
+
|
|
24
|
+
logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_all_known_bot_usernames() -> set[str]:
|
|
28
|
+
"""Get all bot usernames that have ever been created (from matrix_state.yaml).
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Set of all known bot usernames
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
state = MatrixState.load()
|
|
35
|
+
bot_usernames = set()
|
|
36
|
+
|
|
37
|
+
# Get all agent accounts from state
|
|
38
|
+
for key in state.accounts:
|
|
39
|
+
# Skip the user account (agent_user is the human user, not a bot)
|
|
40
|
+
if key.startswith("agent_") and key != "agent_user":
|
|
41
|
+
account = state.accounts[key]
|
|
42
|
+
bot_usernames.add(account.username)
|
|
43
|
+
|
|
44
|
+
return bot_usernames
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def _cleanup_orphaned_bots_in_room(
|
|
48
|
+
client: nio.AsyncClient,
|
|
49
|
+
room_id: str,
|
|
50
|
+
config: Config,
|
|
51
|
+
) -> list[str]:
|
|
52
|
+
"""Remove orphaned bots from a single room.
|
|
53
|
+
|
|
54
|
+
When DM mode is enabled, actual DM rooms are skipped to preserve them.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
client: An authenticated Matrix client with kick permissions
|
|
58
|
+
room_id: The room to check
|
|
59
|
+
config: Current configuration
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
List of bot usernames that were kicked
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
# When DM mode is enabled, check if this is actually a DM room
|
|
66
|
+
if await is_dm_room(client, room_id):
|
|
67
|
+
logger.debug(f"Skipping DM room {room_id} cleanup")
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
# Get room members
|
|
71
|
+
member_ids = await get_room_members(client, room_id)
|
|
72
|
+
if not member_ids:
|
|
73
|
+
logger.warning(f"No members found or failed to get members for room {room_id}")
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
# Get configured bots for this room
|
|
77
|
+
configured_bots = config.get_configured_bots_for_room(room_id)
|
|
78
|
+
known_bot_usernames = _get_all_known_bot_usernames()
|
|
79
|
+
|
|
80
|
+
kicked_bots = []
|
|
81
|
+
|
|
82
|
+
for user_id in member_ids:
|
|
83
|
+
matrix_id = MatrixID.parse(user_id)
|
|
84
|
+
|
|
85
|
+
# Check if this is a mindroom bot and shouldn't be in this room
|
|
86
|
+
if matrix_id.username in known_bot_usernames and matrix_id.username not in configured_bots:
|
|
87
|
+
logger.info(
|
|
88
|
+
f"Found orphaned bot {matrix_id.username} in room {room_id} "
|
|
89
|
+
f"(configured bots for this room: {configured_bots})",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Kick the bot
|
|
93
|
+
kick_response = await client.room_kick(room_id, user_id, reason="Bot no longer configured for this room")
|
|
94
|
+
|
|
95
|
+
if isinstance(kick_response, nio.RoomKickResponse):
|
|
96
|
+
logger.info(f"Kicked {matrix_id.username} from {room_id}")
|
|
97
|
+
kicked_bots.append(matrix_id.username)
|
|
98
|
+
else:
|
|
99
|
+
logger.error(f"Failed to kick {matrix_id.username} from {room_id}: {kick_response}")
|
|
100
|
+
|
|
101
|
+
return kicked_bots
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def cleanup_all_orphaned_bots(
|
|
105
|
+
client: nio.AsyncClient,
|
|
106
|
+
config: Config,
|
|
107
|
+
) -> dict[str, list[str]]:
|
|
108
|
+
"""Remove all orphaned bots from all rooms the client has access to.
|
|
109
|
+
|
|
110
|
+
This should be called by a user or bot with admin/moderator permissions
|
|
111
|
+
in the rooms that need cleaning.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dictionary mapping room IDs to lists of kicked bot usernames
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
# Track what we're doing
|
|
118
|
+
kicked_bots: dict[str, list[str]] = {}
|
|
119
|
+
|
|
120
|
+
# Get all rooms the client is in
|
|
121
|
+
joined_rooms = await get_joined_rooms(client)
|
|
122
|
+
if joined_rooms is None:
|
|
123
|
+
return kicked_bots
|
|
124
|
+
|
|
125
|
+
logger.info(f"Checking {len(joined_rooms)} rooms for orphaned bots")
|
|
126
|
+
|
|
127
|
+
for room_id in joined_rooms:
|
|
128
|
+
room_kicked = await _cleanup_orphaned_bots_in_room(client, room_id, config)
|
|
129
|
+
if room_kicked:
|
|
130
|
+
kicked_bots[room_id] = room_kicked
|
|
131
|
+
|
|
132
|
+
# Summary
|
|
133
|
+
total_kicked = sum(len(bots) for bots in kicked_bots.values())
|
|
134
|
+
if total_kicked > 0:
|
|
135
|
+
logger.info(f"Kicked {total_kicked} orphaned bots from {len(kicked_bots)} rooms")
|
|
136
|
+
else:
|
|
137
|
+
logger.info("No orphaned bots found in any room")
|
|
138
|
+
|
|
139
|
+
return kicked_bots
|
mindroom/routing.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Simple AI routing for multi-agent threads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from agno.agent import Agent
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from .agents import describe_agent
|
|
11
|
+
from .ai import get_model_instance
|
|
12
|
+
from .logging_config import get_logger
|
|
13
|
+
from .matrix.identity import MatrixID
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .config import Config
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentSuggestion(BaseModel):
|
|
22
|
+
"""Structured output for agent routing decisions."""
|
|
23
|
+
|
|
24
|
+
agent_name: str = Field(description="The name of the agent that should respond")
|
|
25
|
+
reasoning: str = Field(description="Brief explanation of why this agent was chosen")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def suggest_agent_for_message(
|
|
29
|
+
message: str,
|
|
30
|
+
available_agents: list[MatrixID],
|
|
31
|
+
config: Config,
|
|
32
|
+
thread_context: list[dict[str, Any]] | None = None,
|
|
33
|
+
) -> str | None:
|
|
34
|
+
"""Use AI to suggest which agent should respond to a message."""
|
|
35
|
+
try:
|
|
36
|
+
# The available_agents list already contains only configured agents
|
|
37
|
+
agent_names = [mid.agent_name(config) for mid in available_agents]
|
|
38
|
+
|
|
39
|
+
# Build agent descriptions
|
|
40
|
+
agent_descriptions = []
|
|
41
|
+
for agent_name in agent_names:
|
|
42
|
+
assert agent_name is not None
|
|
43
|
+
description = describe_agent(agent_name, config)
|
|
44
|
+
agent_descriptions.append(f"{agent_name}:\n {description}")
|
|
45
|
+
|
|
46
|
+
agents_info = "\n\n".join(agent_descriptions)
|
|
47
|
+
|
|
48
|
+
prompt = f"""Decide which agent should respond to this message.
|
|
49
|
+
|
|
50
|
+
Available agents and their capabilities:
|
|
51
|
+
|
|
52
|
+
{agents_info}
|
|
53
|
+
|
|
54
|
+
Message: "{message}"
|
|
55
|
+
|
|
56
|
+
Choose the most appropriate agent based on their role, tools, and instructions."""
|
|
57
|
+
|
|
58
|
+
if thread_context:
|
|
59
|
+
context = "Previous messages:\n"
|
|
60
|
+
for msg in thread_context[-3:]: # Last 3 messages
|
|
61
|
+
sender = msg.get("sender", "")
|
|
62
|
+
# For display, just show the username or domain
|
|
63
|
+
if sender.startswith("@") and ":" in sender:
|
|
64
|
+
sender_id = MatrixID.parse(sender)
|
|
65
|
+
# Show agent name or just domain for users
|
|
66
|
+
sender = sender_id.agent_name(config) or sender_id.domain
|
|
67
|
+
body = msg.get("body", "")[:100]
|
|
68
|
+
context += f"{sender}: {body}\n"
|
|
69
|
+
prompt = context + "\n" + prompt
|
|
70
|
+
|
|
71
|
+
# Get router model from config
|
|
72
|
+
router_model_name = config.router.model
|
|
73
|
+
|
|
74
|
+
model = get_model_instance(config, router_model_name)
|
|
75
|
+
logger.info(f"Using router model: {router_model_name} -> {model.__class__.__name__}(id={model.id})")
|
|
76
|
+
|
|
77
|
+
agent = Agent(
|
|
78
|
+
name="Router",
|
|
79
|
+
role="Route messages to appropriate agents",
|
|
80
|
+
model=model,
|
|
81
|
+
response_model=AgentSuggestion,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
response = await agent.arun(prompt, session_id="routing")
|
|
85
|
+
suggestion = response.content
|
|
86
|
+
|
|
87
|
+
# With response_model, we should always get the correct type
|
|
88
|
+
if not isinstance(suggestion, AgentSuggestion):
|
|
89
|
+
logger.error(
|
|
90
|
+
"Unexpected response type from AI routing",
|
|
91
|
+
expected="AgentSuggestion",
|
|
92
|
+
actual=type(suggestion).__name__,
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
# The AI should only suggest agents from the available list
|
|
97
|
+
if suggestion.agent_name not in agent_names:
|
|
98
|
+
logger.warning("AI suggested invalid agent", suggested=suggestion.agent_name, available=agent_names)
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
logger.info("Routing decision", agent=suggestion.agent_name, reason=suggestion.reasoning)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
# Log error and return None - the router will fall back to not routing
|
|
104
|
+
logger.exception("Routing failed", error=str(e))
|
|
105
|
+
return None
|
|
106
|
+
else:
|
|
107
|
+
return suggestion.agent_name
|