mindroom 0.0.0__py3-none-any.whl → 0.1.1__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.
Files changed (155) hide show
  1. mindroom/__init__.py +3 -0
  2. mindroom/agent_prompts.py +963 -0
  3. mindroom/agents.py +248 -0
  4. mindroom/ai.py +421 -0
  5. mindroom/api/__init__.py +1 -0
  6. mindroom/api/credentials.py +137 -0
  7. mindroom/api/google_integration.py +355 -0
  8. mindroom/api/google_tools_helper.py +40 -0
  9. mindroom/api/homeassistant_integration.py +421 -0
  10. mindroom/api/integrations.py +189 -0
  11. mindroom/api/main.py +506 -0
  12. mindroom/api/matrix_operations.py +219 -0
  13. mindroom/api/tools.py +94 -0
  14. mindroom/background_tasks.py +87 -0
  15. mindroom/bot.py +2470 -0
  16. mindroom/cli.py +86 -0
  17. mindroom/commands.py +377 -0
  18. mindroom/config.py +343 -0
  19. mindroom/config_commands.py +324 -0
  20. mindroom/config_confirmation.py +411 -0
  21. mindroom/constants.py +52 -0
  22. mindroom/credentials.py +146 -0
  23. mindroom/credentials_sync.py +134 -0
  24. mindroom/custom_tools/__init__.py +8 -0
  25. mindroom/custom_tools/config_manager.py +765 -0
  26. mindroom/custom_tools/gmail.py +92 -0
  27. mindroom/custom_tools/google_calendar.py +92 -0
  28. mindroom/custom_tools/google_sheets.py +92 -0
  29. mindroom/custom_tools/homeassistant.py +341 -0
  30. mindroom/error_handling.py +35 -0
  31. mindroom/file_watcher.py +49 -0
  32. mindroom/interactive.py +313 -0
  33. mindroom/logging_config.py +207 -0
  34. mindroom/matrix/__init__.py +1 -0
  35. mindroom/matrix/client.py +782 -0
  36. mindroom/matrix/event_info.py +173 -0
  37. mindroom/matrix/identity.py +149 -0
  38. mindroom/matrix/large_messages.py +267 -0
  39. mindroom/matrix/mentions.py +141 -0
  40. mindroom/matrix/message_builder.py +94 -0
  41. mindroom/matrix/message_content.py +209 -0
  42. mindroom/matrix/presence.py +178 -0
  43. mindroom/matrix/rooms.py +311 -0
  44. mindroom/matrix/state.py +77 -0
  45. mindroom/matrix/typing.py +91 -0
  46. mindroom/matrix/users.py +217 -0
  47. mindroom/memory/__init__.py +21 -0
  48. mindroom/memory/config.py +137 -0
  49. mindroom/memory/functions.py +396 -0
  50. mindroom/py.typed +0 -0
  51. mindroom/response_tracker.py +128 -0
  52. mindroom/room_cleanup.py +139 -0
  53. mindroom/routing.py +107 -0
  54. mindroom/scheduling.py +758 -0
  55. mindroom/stop.py +207 -0
  56. mindroom/streaming.py +203 -0
  57. mindroom/teams.py +749 -0
  58. mindroom/thread_utils.py +318 -0
  59. mindroom/tools/__init__.py +520 -0
  60. mindroom/tools/agentql.py +64 -0
  61. mindroom/tools/airflow.py +57 -0
  62. mindroom/tools/apify.py +49 -0
  63. mindroom/tools/arxiv.py +64 -0
  64. mindroom/tools/aws_lambda.py +41 -0
  65. mindroom/tools/aws_ses.py +57 -0
  66. mindroom/tools/baidusearch.py +87 -0
  67. mindroom/tools/brightdata.py +116 -0
  68. mindroom/tools/browserbase.py +62 -0
  69. mindroom/tools/cal_com.py +98 -0
  70. mindroom/tools/calculator.py +112 -0
  71. mindroom/tools/cartesia.py +84 -0
  72. mindroom/tools/composio.py +166 -0
  73. mindroom/tools/config_manager.py +44 -0
  74. mindroom/tools/confluence.py +73 -0
  75. mindroom/tools/crawl4ai.py +101 -0
  76. mindroom/tools/csv.py +104 -0
  77. mindroom/tools/custom_api.py +106 -0
  78. mindroom/tools/dalle.py +85 -0
  79. mindroom/tools/daytona.py +180 -0
  80. mindroom/tools/discord.py +81 -0
  81. mindroom/tools/docker.py +73 -0
  82. mindroom/tools/duckdb.py +124 -0
  83. mindroom/tools/duckduckgo.py +99 -0
  84. mindroom/tools/e2b.py +121 -0
  85. mindroom/tools/eleven_labs.py +77 -0
  86. mindroom/tools/email.py +74 -0
  87. mindroom/tools/exa.py +246 -0
  88. mindroom/tools/fal.py +50 -0
  89. mindroom/tools/file.py +80 -0
  90. mindroom/tools/financial_datasets_api.py +112 -0
  91. mindroom/tools/firecrawl.py +124 -0
  92. mindroom/tools/gemini.py +85 -0
  93. mindroom/tools/giphy.py +49 -0
  94. mindroom/tools/github.py +376 -0
  95. mindroom/tools/gmail.py +102 -0
  96. mindroom/tools/google_calendar.py +55 -0
  97. mindroom/tools/google_maps.py +112 -0
  98. mindroom/tools/google_sheets.py +86 -0
  99. mindroom/tools/googlesearch.py +83 -0
  100. mindroom/tools/groq.py +77 -0
  101. mindroom/tools/hackernews.py +54 -0
  102. mindroom/tools/jina.py +108 -0
  103. mindroom/tools/jira.py +70 -0
  104. mindroom/tools/linear.py +103 -0
  105. mindroom/tools/linkup.py +65 -0
  106. mindroom/tools/lumalabs.py +71 -0
  107. mindroom/tools/mem0.py +82 -0
  108. mindroom/tools/modelslabs.py +85 -0
  109. mindroom/tools/moviepy_video_tools.py +62 -0
  110. mindroom/tools/newspaper4k.py +63 -0
  111. mindroom/tools/openai.py +143 -0
  112. mindroom/tools/openweather.py +89 -0
  113. mindroom/tools/oxylabs.py +54 -0
  114. mindroom/tools/pandas.py +35 -0
  115. mindroom/tools/pubmed.py +64 -0
  116. mindroom/tools/python.py +120 -0
  117. mindroom/tools/reddit.py +155 -0
  118. mindroom/tools/replicate.py +56 -0
  119. mindroom/tools/resend.py +55 -0
  120. mindroom/tools/scrapegraph.py +87 -0
  121. mindroom/tools/searxng.py +120 -0
  122. mindroom/tools/serpapi.py +55 -0
  123. mindroom/tools/serper.py +81 -0
  124. mindroom/tools/shell.py +46 -0
  125. mindroom/tools/slack.py +80 -0
  126. mindroom/tools/sleep.py +38 -0
  127. mindroom/tools/spider.py +62 -0
  128. mindroom/tools/sql.py +138 -0
  129. mindroom/tools/tavily.py +104 -0
  130. mindroom/tools/telegram.py +54 -0
  131. mindroom/tools/todoist.py +103 -0
  132. mindroom/tools/trello.py +121 -0
  133. mindroom/tools/twilio.py +97 -0
  134. mindroom/tools/web_browser_tools.py +37 -0
  135. mindroom/tools/webex.py +63 -0
  136. mindroom/tools/website.py +45 -0
  137. mindroom/tools/whatsapp.py +81 -0
  138. mindroom/tools/wikipedia.py +45 -0
  139. mindroom/tools/x.py +97 -0
  140. mindroom/tools/yfinance.py +121 -0
  141. mindroom/tools/youtube.py +81 -0
  142. mindroom/tools/zendesk.py +62 -0
  143. mindroom/tools/zep.py +107 -0
  144. mindroom/tools/zoom.py +62 -0
  145. mindroom/tools_metadata.json +7643 -0
  146. mindroom/tools_metadata.py +220 -0
  147. mindroom/topic_generator.py +153 -0
  148. mindroom/voice_handler.py +266 -0
  149. mindroom-0.1.1.dist-info/METADATA +425 -0
  150. mindroom-0.1.1.dist-info/RECORD +152 -0
  151. {mindroom-0.0.0.dist-info → mindroom-0.1.1.dist-info}/WHEEL +1 -2
  152. mindroom-0.1.1.dist-info/entry_points.txt +2 -0
  153. mindroom-0.0.0.dist-info/METADATA +0 -24
  154. mindroom-0.0.0.dist-info/RECORD +0 -4
  155. mindroom-0.0.0.dist-info/top_level.txt +0 -1
@@ -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