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.
- 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.1.dist-info/METADATA +425 -0
- mindroom-0.1.1.dist-info/RECORD +152 -0
- {mindroom-0.0.0.dist-info → mindroom-0.1.1.dist-info}/WHEEL +1 -2
- mindroom-0.1.1.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/thread_utils.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""Utilities for thread analysis and agent detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from .constants import ROUTER_AGENT_NAME
|
|
8
|
+
from .matrix.identity import MatrixID, extract_agent_name
|
|
9
|
+
from .matrix.rooms import resolve_room_aliases
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import nio
|
|
13
|
+
|
|
14
|
+
from .config import Config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_agent_mentioned(event_source: dict, agent_id: MatrixID | None, config: Config) -> tuple[list[MatrixID], bool]:
|
|
18
|
+
"""Check if an agent is mentioned in a message.
|
|
19
|
+
|
|
20
|
+
Returns (mentioned_agents, am_i_mentioned).
|
|
21
|
+
"""
|
|
22
|
+
mentions = event_source.get("content", {}).get("m.mentions", {})
|
|
23
|
+
mentioned_agents = get_mentioned_agents(mentions, config)
|
|
24
|
+
am_i_mentioned = agent_id in mentioned_agents
|
|
25
|
+
return mentioned_agents, am_i_mentioned
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_session_id(room_id: str, thread_id: str | None) -> str:
|
|
29
|
+
"""Create a session ID with thread awareness."""
|
|
30
|
+
# Thread sessions include thread ID
|
|
31
|
+
return f"{room_id}:{thread_id}" if thread_id else room_id
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_agents_in_thread(thread_history: list[dict[str, Any]], config: Config) -> list[MatrixID]:
|
|
35
|
+
"""Get list of unique agents that have participated in thread.
|
|
36
|
+
|
|
37
|
+
Note: Router agent is excluded from the participant list as it's not
|
|
38
|
+
a conversation participant.
|
|
39
|
+
|
|
40
|
+
Preserves the order of first participation while preventing duplicates.
|
|
41
|
+
"""
|
|
42
|
+
agents: list[MatrixID] = []
|
|
43
|
+
seen_ids: set[str] = set()
|
|
44
|
+
|
|
45
|
+
for msg in thread_history:
|
|
46
|
+
sender: str = msg.get("sender", "")
|
|
47
|
+
agent_name = extract_agent_name(sender, config)
|
|
48
|
+
|
|
49
|
+
# Skip router agent and invalid senders
|
|
50
|
+
if not agent_name or agent_name == ROUTER_AGENT_NAME:
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
if sender not in seen_ids:
|
|
54
|
+
try:
|
|
55
|
+
matrix_id = MatrixID.parse(sender)
|
|
56
|
+
agents.append(matrix_id)
|
|
57
|
+
seen_ids.add(sender)
|
|
58
|
+
except ValueError:
|
|
59
|
+
# Skip invalid Matrix IDs
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
return agents
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_agent_matrix_ids_in_thread(thread_history: list[dict[str, Any]], config: Config) -> list[MatrixID]:
|
|
66
|
+
"""Get list of unique agent Matrix IDs that have participated in thread.
|
|
67
|
+
|
|
68
|
+
Note: Router agent is excluded from the participant list as it's not
|
|
69
|
+
a conversation participant.
|
|
70
|
+
|
|
71
|
+
Preserves the order of first participation while preventing duplicates.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of MatrixID objects for agents who participated in the thread.
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
agent_ids = []
|
|
78
|
+
seen_ids = set()
|
|
79
|
+
|
|
80
|
+
for msg in thread_history:
|
|
81
|
+
sender = msg.get("sender", "")
|
|
82
|
+
agent_name = extract_agent_name(sender, config)
|
|
83
|
+
|
|
84
|
+
# Skip router agent and invalid senders
|
|
85
|
+
if not agent_name or agent_name == ROUTER_AGENT_NAME:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
matrix_id = MatrixID.parse(sender)
|
|
90
|
+
if matrix_id.full_id not in seen_ids:
|
|
91
|
+
agent_ids.append(matrix_id)
|
|
92
|
+
seen_ids.add(matrix_id.full_id)
|
|
93
|
+
except ValueError:
|
|
94
|
+
# Skip invalid Matrix IDs
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
return agent_ids
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_mentioned_agents(mentions: dict[str, Any], config: Config) -> list[MatrixID]:
|
|
101
|
+
"""Extract agent names from mentions."""
|
|
102
|
+
user_ids = mentions.get("user_ids", [])
|
|
103
|
+
agents: list[MatrixID] = []
|
|
104
|
+
|
|
105
|
+
for user_id in user_ids:
|
|
106
|
+
mid = MatrixID.parse(user_id)
|
|
107
|
+
if mid.agent_name(config):
|
|
108
|
+
agents.append(mid)
|
|
109
|
+
|
|
110
|
+
return agents
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def has_user_responded_after_message(
|
|
114
|
+
thread_history: list[dict],
|
|
115
|
+
target_event_id: str,
|
|
116
|
+
user_id: MatrixID,
|
|
117
|
+
) -> bool:
|
|
118
|
+
"""Check if a user has sent any messages after a specific message in the thread.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
thread_history: List of messages in the thread
|
|
122
|
+
target_event_id: The event ID to check after
|
|
123
|
+
user_id: The user ID to check for
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
True if the user has responded after the target message
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
# Find the target message and check for user responses after it
|
|
130
|
+
found_target = False
|
|
131
|
+
for msg in thread_history:
|
|
132
|
+
if msg["event_id"] == target_event_id:
|
|
133
|
+
found_target = True
|
|
134
|
+
elif found_target and msg["sender"] == user_id.full_id:
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_available_agents_in_room(room: nio.MatrixRoom, config: Config) -> list[MatrixID]:
|
|
140
|
+
"""Get list of available agent MatrixIDs in a room.
|
|
141
|
+
|
|
142
|
+
Note: Router agent is excluded as it's not a regular conversation participant.
|
|
143
|
+
"""
|
|
144
|
+
agents: list[MatrixID] = []
|
|
145
|
+
|
|
146
|
+
for member_id in room.users:
|
|
147
|
+
mid = MatrixID.parse(member_id)
|
|
148
|
+
agent_name = mid.agent_name(config)
|
|
149
|
+
# Exclude router agent
|
|
150
|
+
if agent_name and agent_name != ROUTER_AGENT_NAME:
|
|
151
|
+
agents.append(mid)
|
|
152
|
+
|
|
153
|
+
return sorted(agents, key=lambda x: x.full_id)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_available_agent_matrix_ids_in_room(room: nio.MatrixRoom, config: Config) -> list[MatrixID]:
|
|
157
|
+
"""Get list of available agent Matrix IDs in a room.
|
|
158
|
+
|
|
159
|
+
Note: Router agent is excluded as it's not a regular conversation participant.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of MatrixID objects for agents in the room.
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
agent_ids = []
|
|
166
|
+
|
|
167
|
+
for member_id in room.users:
|
|
168
|
+
agent_name = extract_agent_name(member_id, config)
|
|
169
|
+
# Exclude router agent
|
|
170
|
+
if agent_name and agent_name != ROUTER_AGENT_NAME:
|
|
171
|
+
try:
|
|
172
|
+
matrix_id = MatrixID.parse(member_id)
|
|
173
|
+
agent_ids.append(matrix_id)
|
|
174
|
+
except ValueError:
|
|
175
|
+
# Skip invalid Matrix IDs
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
return sorted(agent_ids, key=lambda x: x.full_id)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_configured_agents_for_room(room_id: str, config: Config) -> list[MatrixID]:
|
|
182
|
+
"""Get list of agent MatrixIDs configured for a specific room.
|
|
183
|
+
|
|
184
|
+
This returns only agents that have the room in their configuration,
|
|
185
|
+
not just agents that happen to be present in the room.
|
|
186
|
+
|
|
187
|
+
Note: Router agent is excluded as it's not a regular conversation participant.
|
|
188
|
+
"""
|
|
189
|
+
configured_agents: list[MatrixID] = []
|
|
190
|
+
|
|
191
|
+
# Check which agents should be in this room
|
|
192
|
+
for agent_name, agent_config in config.agents.items():
|
|
193
|
+
if agent_name != ROUTER_AGENT_NAME:
|
|
194
|
+
resolved_rooms = resolve_room_aliases(agent_config.rooms)
|
|
195
|
+
if room_id in resolved_rooms:
|
|
196
|
+
configured_agents.append(config.ids[agent_name])
|
|
197
|
+
|
|
198
|
+
return sorted(configured_agents, key=lambda x: x.full_id)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def has_any_agent_mentions_in_thread(thread_history: list[dict[str, Any]], config: Config) -> bool:
|
|
202
|
+
"""Check if any agents are mentioned anywhere in the thread."""
|
|
203
|
+
for msg in thread_history:
|
|
204
|
+
content = msg.get("content", {})
|
|
205
|
+
mentions = content.get("m.mentions", {})
|
|
206
|
+
if get_mentioned_agents(mentions, config):
|
|
207
|
+
return True
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_all_mentioned_agents_in_thread(thread_history: list[dict[str, Any]], config: Config) -> list[MatrixID]:
|
|
212
|
+
"""Get all unique agent MatrixIDs that have been mentioned anywhere in the thread.
|
|
213
|
+
|
|
214
|
+
Preserves the order of first mention while preventing duplicates.
|
|
215
|
+
"""
|
|
216
|
+
mentioned_agents = []
|
|
217
|
+
seen_ids = set()
|
|
218
|
+
|
|
219
|
+
for msg in thread_history:
|
|
220
|
+
content = msg.get("content", {})
|
|
221
|
+
mentions = content.get("m.mentions", {})
|
|
222
|
+
agents = get_mentioned_agents(mentions, config)
|
|
223
|
+
|
|
224
|
+
# Add agents in order, but only if not seen before
|
|
225
|
+
for agent in agents:
|
|
226
|
+
if agent.full_id not in seen_ids:
|
|
227
|
+
mentioned_agents.append(agent)
|
|
228
|
+
seen_ids.add(agent.full_id)
|
|
229
|
+
|
|
230
|
+
return mentioned_agents
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def is_authorized_sender(sender_id: str, config: Config, room_id: str) -> bool:
|
|
234
|
+
"""Check if a sender is authorized to interact with agents.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
sender_id: Matrix ID of the message sender
|
|
238
|
+
config: Application configuration
|
|
239
|
+
room_id: Room ID for permission checks
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
True if the sender is authorized, False otherwise
|
|
243
|
+
|
|
244
|
+
"""
|
|
245
|
+
# Always allow mindroom_user on the current domain
|
|
246
|
+
mindroom_user_id = f"@mindroom_user:{config.domain}"
|
|
247
|
+
if sender_id == mindroom_user_id:
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
# Check if sender is an agent or team
|
|
251
|
+
agent_name = extract_agent_name(sender_id, config)
|
|
252
|
+
if agent_name:
|
|
253
|
+
# Agent is either in config.agents, config.teams, or is the router
|
|
254
|
+
return agent_name in config.agents or agent_name in config.teams or agent_name == ROUTER_AGENT_NAME
|
|
255
|
+
|
|
256
|
+
# Check global authorized users (they have access to all rooms)
|
|
257
|
+
if sender_id in config.authorization.global_users:
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
# Check room-specific permissions
|
|
261
|
+
if room_id in config.authorization.room_permissions:
|
|
262
|
+
return sender_id in config.authorization.room_permissions[room_id]
|
|
263
|
+
|
|
264
|
+
# Use default access for rooms not explicitly configured
|
|
265
|
+
return config.authorization.default_room_access
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def should_agent_respond(
|
|
269
|
+
agent_name: str,
|
|
270
|
+
am_i_mentioned: bool,
|
|
271
|
+
is_thread: bool,
|
|
272
|
+
room: nio.MatrixRoom,
|
|
273
|
+
thread_history: list[dict],
|
|
274
|
+
config: Config,
|
|
275
|
+
mentioned_agents: list[MatrixID] | None = None,
|
|
276
|
+
) -> bool:
|
|
277
|
+
"""Determine if an agent should respond to a message individually.
|
|
278
|
+
|
|
279
|
+
Team formation is handled elsewhere - this just determines individual responses.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
agent_name: Name of the agent checking if it should respond
|
|
283
|
+
am_i_mentioned: Whether this specific agent is mentioned
|
|
284
|
+
is_thread: Whether the message is in a thread
|
|
285
|
+
room: The Matrix room object
|
|
286
|
+
thread_history: History of messages in the thread
|
|
287
|
+
config: Application configuration
|
|
288
|
+
mentioned_agents: List of all agent MatrixIDs mentioned in the message
|
|
289
|
+
|
|
290
|
+
"""
|
|
291
|
+
# Always respond if mentioned
|
|
292
|
+
if am_i_mentioned:
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
# Never respond if other agents are mentioned but not this one
|
|
296
|
+
# (User explicitly wants a different agent)
|
|
297
|
+
if mentioned_agents:
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
# Non-thread messages: allow a single available agent to respond automatically
|
|
301
|
+
# This applies to both DM and regular rooms. Router is excluded from availability.
|
|
302
|
+
if not is_thread:
|
|
303
|
+
available_agents = get_available_agents_in_room(room, config)
|
|
304
|
+
return len(available_agents) == 1
|
|
305
|
+
|
|
306
|
+
agent_matrix_id = config.ids[agent_name]
|
|
307
|
+
|
|
308
|
+
# For threads, check if agents have already participated
|
|
309
|
+
if is_thread:
|
|
310
|
+
agents_in_thread = get_agents_in_thread(thread_history, config)
|
|
311
|
+
if agents_in_thread:
|
|
312
|
+
# Continue only if we're the single agent
|
|
313
|
+
return len(agents_in_thread) == 1 and agents_in_thread[0] == agent_matrix_id
|
|
314
|
+
|
|
315
|
+
# No agents in thread yet OR DM room without thread
|
|
316
|
+
# Respond if we're the only agent available
|
|
317
|
+
available_agents = get_available_agents_in_room(room, config)
|
|
318
|
+
return len(available_agents) == 1
|