daita-agents 0.2.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.
Files changed (69) hide show
  1. daita/__init__.py +216 -0
  2. daita/agents/__init__.py +33 -0
  3. daita/agents/base.py +743 -0
  4. daita/agents/substrate.py +1141 -0
  5. daita/cli/__init__.py +145 -0
  6. daita/cli/__main__.py +7 -0
  7. daita/cli/ascii_art.py +44 -0
  8. daita/cli/core/__init__.py +0 -0
  9. daita/cli/core/create.py +254 -0
  10. daita/cli/core/deploy.py +473 -0
  11. daita/cli/core/deployments.py +309 -0
  12. daita/cli/core/import_detector.py +219 -0
  13. daita/cli/core/init.py +481 -0
  14. daita/cli/core/logs.py +239 -0
  15. daita/cli/core/managed_deploy.py +709 -0
  16. daita/cli/core/run.py +648 -0
  17. daita/cli/core/status.py +421 -0
  18. daita/cli/core/test.py +239 -0
  19. daita/cli/core/webhooks.py +172 -0
  20. daita/cli/main.py +588 -0
  21. daita/cli/utils.py +541 -0
  22. daita/config/__init__.py +62 -0
  23. daita/config/base.py +159 -0
  24. daita/config/settings.py +184 -0
  25. daita/core/__init__.py +262 -0
  26. daita/core/decision_tracing.py +701 -0
  27. daita/core/exceptions.py +480 -0
  28. daita/core/focus.py +251 -0
  29. daita/core/interfaces.py +76 -0
  30. daita/core/plugin_tracing.py +550 -0
  31. daita/core/relay.py +779 -0
  32. daita/core/reliability.py +381 -0
  33. daita/core/scaling.py +459 -0
  34. daita/core/tools.py +554 -0
  35. daita/core/tracing.py +770 -0
  36. daita/core/workflow.py +1144 -0
  37. daita/display/__init__.py +1 -0
  38. daita/display/console.py +160 -0
  39. daita/execution/__init__.py +58 -0
  40. daita/execution/client.py +856 -0
  41. daita/execution/exceptions.py +92 -0
  42. daita/execution/models.py +317 -0
  43. daita/llm/__init__.py +60 -0
  44. daita/llm/anthropic.py +291 -0
  45. daita/llm/base.py +530 -0
  46. daita/llm/factory.py +101 -0
  47. daita/llm/gemini.py +355 -0
  48. daita/llm/grok.py +219 -0
  49. daita/llm/mock.py +172 -0
  50. daita/llm/openai.py +220 -0
  51. daita/plugins/__init__.py +141 -0
  52. daita/plugins/base.py +37 -0
  53. daita/plugins/base_db.py +167 -0
  54. daita/plugins/elasticsearch.py +849 -0
  55. daita/plugins/mcp.py +481 -0
  56. daita/plugins/mongodb.py +520 -0
  57. daita/plugins/mysql.py +362 -0
  58. daita/plugins/postgresql.py +342 -0
  59. daita/plugins/redis_messaging.py +500 -0
  60. daita/plugins/rest.py +537 -0
  61. daita/plugins/s3.py +770 -0
  62. daita/plugins/slack.py +729 -0
  63. daita/utils/__init__.py +18 -0
  64. daita_agents-0.2.0.dist-info/METADATA +409 -0
  65. daita_agents-0.2.0.dist-info/RECORD +69 -0
  66. daita_agents-0.2.0.dist-info/WHEEL +5 -0
  67. daita_agents-0.2.0.dist-info/entry_points.txt +2 -0
  68. daita_agents-0.2.0.dist-info/licenses/LICENSE +56 -0
  69. daita_agents-0.2.0.dist-info/top_level.txt +1 -0
daita/plugins/slack.py ADDED
@@ -0,0 +1,729 @@
1
+ """
2
+ Slack plugin for Daita Agents.
3
+
4
+ Simple Slack messaging and collaboration - no over-engineering.
5
+ """
6
+ import logging
7
+ import os
8
+ import json
9
+ from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+
13
+ if TYPE_CHECKING:
14
+ from ..core.tools import AgentTool
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class SlackPlugin:
19
+ """
20
+ Simple Slack plugin for agents.
21
+
22
+ Handles Slack messaging, thread management, and file sharing with agent-specific features.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ token: str,
28
+ bot_user_oauth_token: Optional[str] = None,
29
+ app_token: Optional[str] = None,
30
+ default_channel: Optional[str] = None,
31
+ **kwargs
32
+ ):
33
+ """
34
+ Initialize Slack connection.
35
+
36
+ Args:
37
+ token: Slack bot token (xoxb-...)
38
+ bot_user_oauth_token: Bot user OAuth token (optional, for extended permissions)
39
+ app_token: App-level token (optional, for Socket Mode)
40
+ default_channel: Default channel for messages (optional)
41
+ **kwargs: Additional Slack client parameters
42
+ """
43
+ if not token or not token.strip():
44
+ raise ValueError("Slack token cannot be empty")
45
+
46
+ if not token.startswith(('xoxb-', 'xoxp-')):
47
+ raise ValueError("Invalid Slack token format. Expected bot token (xoxb-) or user token (xoxp-)")
48
+
49
+ self.token = token
50
+ self.bot_user_oauth_token = bot_user_oauth_token
51
+ self.app_token = app_token
52
+ self.default_channel = default_channel
53
+
54
+ # Store additional config
55
+ self.config = kwargs
56
+
57
+ self._client = None
58
+ self._user_info = None
59
+ self._channels_cache = {}
60
+
61
+ logger.debug(f"Slack plugin configured with token: {token[:12]}...")
62
+
63
+ async def connect(self):
64
+ """Initialize Slack client and validate connection."""
65
+ if self._client is not None:
66
+ return # Already connected
67
+
68
+ try:
69
+ from slack_sdk.web.async_client import AsyncWebClient
70
+ from slack_sdk.errors import SlackApiError
71
+
72
+ # Create Slack client
73
+ self._client = AsyncWebClient(token=self.token)
74
+
75
+ # Test connection and get bot info
76
+ try:
77
+ auth_response = await self._client.auth_test()
78
+ self._user_info = {
79
+ 'user_id': auth_response.get('user_id'),
80
+ 'team_id': auth_response.get('team_id'),
81
+ 'team': auth_response.get('team'),
82
+ 'user': auth_response.get('user'),
83
+ 'bot_id': auth_response.get('bot_id')
84
+ }
85
+
86
+ logger.info(f"Connected to Slack as {self._user_info['user']} on team {self._user_info['team']}")
87
+
88
+ except SlackApiError as e:
89
+ if e.response['error'] == 'invalid_auth':
90
+ raise RuntimeError("Invalid Slack token. Please check your bot token.")
91
+ elif e.response['error'] == 'account_inactive':
92
+ raise RuntimeError("Slack account is inactive.")
93
+ else:
94
+ raise RuntimeError(f"Slack authentication failed: {e.response['error']}")
95
+
96
+ except ImportError:
97
+ raise RuntimeError("slack-sdk not installed. Run: pip install slack-sdk")
98
+ except Exception as e:
99
+ raise RuntimeError(f"Failed to connect to Slack: {e}")
100
+
101
+ async def disconnect(self):
102
+ """Close Slack connection."""
103
+ if self._client:
104
+ # Slack SDK client doesn't need explicit closing
105
+ self._client = None
106
+ self._user_info = None
107
+ self._channels_cache = {}
108
+ logger.info("Disconnected from Slack")
109
+
110
+ async def send_message(
111
+ self,
112
+ channel: str,
113
+ text: Optional[str] = None,
114
+ blocks: Optional[List[Dict[str, Any]]] = None,
115
+ attachments: Optional[List[Dict[str, Any]]] = None,
116
+ thread_ts: Optional[str] = None,
117
+ reply_broadcast: bool = False,
118
+ **kwargs
119
+ ) -> Dict[str, Any]:
120
+ """
121
+ Send a message to a Slack channel.
122
+
123
+ Args:
124
+ channel: Channel ID or name (#channel, @user)
125
+ text: Message text (required if no blocks)
126
+ blocks: Slack Block Kit blocks for rich formatting
127
+ attachments: Message attachments (legacy, use blocks instead)
128
+ thread_ts: Timestamp of parent message (for threaded replies)
129
+ reply_broadcast: Whether to broadcast thread reply to channel
130
+ **kwargs: Additional message parameters
131
+
132
+ Returns:
133
+ Message response with timestamp and metadata
134
+
135
+ Example:
136
+ result = await slack.send_message("#alerts", "System update complete")
137
+ """
138
+ if self._client is None:
139
+ await self.connect()
140
+
141
+ # Use default channel if not specified
142
+ if not channel and self.default_channel:
143
+ channel = self.default_channel
144
+
145
+ if not channel:
146
+ raise ValueError("Channel must be specified or default_channel must be set")
147
+
148
+ # Validate message content
149
+ if not text and not blocks:
150
+ raise ValueError("Either text or blocks must be provided")
151
+
152
+ try:
153
+ # Prepare message arguments
154
+ message_args = {
155
+ 'channel': channel,
156
+ 'text': text,
157
+ 'thread_ts': thread_ts,
158
+ 'reply_broadcast': reply_broadcast,
159
+ **kwargs
160
+ }
161
+
162
+ # Add blocks if provided
163
+ if blocks:
164
+ message_args['blocks'] = blocks
165
+
166
+ # Add attachments if provided (legacy support)
167
+ if attachments:
168
+ message_args['attachments'] = attachments
169
+
170
+ # Send message
171
+ response = await self._client.chat_postMessage(**message_args)
172
+
173
+ result = {
174
+ 'ok': response['ok'],
175
+ 'ts': response['ts'],
176
+ 'channel': response['channel'],
177
+ 'message': response.get('message', {}),
178
+ 'thread_ts': thread_ts
179
+ }
180
+
181
+ logger.info(f"Sent message to {channel}: {text[:50] if text else 'blocks'}...")
182
+ return result
183
+
184
+ except Exception as e:
185
+ logger.error(f"Failed to send Slack message: {e}")
186
+ raise RuntimeError(f"Slack send_message failed: {e}")
187
+
188
+ async def send_agent_summary(
189
+ self,
190
+ channel: str,
191
+ agent_results: Dict[str, Any],
192
+ title: Optional[str] = None,
193
+ thread_ts: Optional[str] = None
194
+ ) -> Dict[str, Any]:
195
+ """
196
+ Send a formatted summary of agent results to Slack.
197
+
198
+ Args:
199
+ channel: Channel ID or name
200
+ agent_results: Agent execution results
201
+ title: Optional title for the summary
202
+ thread_ts: Optional thread timestamp
203
+
204
+ Returns:
205
+ Message response
206
+
207
+ Example:
208
+ result = await slack.send_agent_summary("#data-team", agent_results)
209
+ """
210
+ if self._client is None:
211
+ await self.connect()
212
+
213
+ try:
214
+ # Create formatted blocks for agent results
215
+ blocks = self._format_agent_results(agent_results, title)
216
+
217
+ # Send message with blocks
218
+ return await self.send_message(
219
+ channel=channel,
220
+ text=f"Agent Summary: {title or 'Results'}",
221
+ blocks=blocks,
222
+ thread_ts=thread_ts
223
+ )
224
+
225
+ except Exception as e:
226
+ logger.error(f"Failed to send agent summary: {e}")
227
+ raise RuntimeError(f"Slack send_agent_summary failed: {e}")
228
+
229
+ async def create_thread_from_workflow(
230
+ self,
231
+ channel: str,
232
+ workflow_results: Dict[str, Any],
233
+ title: Optional[str] = None
234
+ ) -> Dict[str, Any]:
235
+ """
236
+ Create a threaded discussion from workflow results.
237
+
238
+ Args:
239
+ channel: Channel ID or name
240
+ workflow_results: Workflow execution results
241
+ title: Optional title for the thread
242
+
243
+ Returns:
244
+ Thread creation response with thread_ts
245
+
246
+ Example:
247
+ result = await slack.create_thread_from_workflow("#workflows", workflow_results)
248
+ """
249
+ if self._client is None:
250
+ await self.connect()
251
+
252
+ try:
253
+ # Create initial thread message
254
+ thread_title = title or f"Workflow Results - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
255
+
256
+ # Create main thread message
257
+ main_blocks = self._format_workflow_summary(workflow_results, thread_title)
258
+
259
+ main_response = await self.send_message(
260
+ channel=channel,
261
+ text=f"Workflow Thread: {thread_title}",
262
+ blocks=main_blocks
263
+ )
264
+
265
+ thread_ts = main_response['ts']
266
+
267
+ # Add individual agent results as thread replies
268
+ agents = workflow_results.get('agents', {})
269
+ for agent_id, agent_result in agents.items():
270
+ agent_blocks = self._format_agent_results(agent_result, f"Agent: {agent_id}")
271
+
272
+ await self.send_message(
273
+ channel=channel,
274
+ text=f"Agent {agent_id} Results",
275
+ blocks=agent_blocks,
276
+ thread_ts=thread_ts
277
+ )
278
+
279
+ result = {
280
+ 'ok': True,
281
+ 'thread_ts': thread_ts,
282
+ 'channel': channel,
283
+ 'agent_count': len(agents),
284
+ 'main_message': main_response
285
+ }
286
+
287
+ logger.info(f"Created workflow thread in {channel} with {len(agents)} agents")
288
+ return result
289
+
290
+ except Exception as e:
291
+ logger.error(f"Failed to create workflow thread: {e}")
292
+ raise RuntimeError(f"Slack create_thread_from_workflow failed: {e}")
293
+
294
+ async def upload_file(
295
+ self,
296
+ channel: str,
297
+ file_path: str,
298
+ title: Optional[str] = None,
299
+ initial_comment: Optional[str] = None,
300
+ thread_ts: Optional[str] = None
301
+ ) -> Dict[str, Any]:
302
+ """
303
+ Upload a file to Slack.
304
+
305
+ Args:
306
+ channel: Channel ID or name
307
+ file_path: Path to file to upload
308
+ title: File title (defaults to filename)
309
+ initial_comment: Comment to add with file
310
+ thread_ts: Thread timestamp (for threaded uploads)
311
+
312
+ Returns:
313
+ File upload response
314
+
315
+ Example:
316
+ result = await slack.upload_file("#reports", "analysis.pdf", "Monthly Analysis")
317
+ """
318
+ if self._client is None:
319
+ await self.connect()
320
+
321
+ try:
322
+ if not os.path.exists(file_path):
323
+ raise FileNotFoundError(f"File not found: {file_path}")
324
+
325
+ # Get file info
326
+ file_name = os.path.basename(file_path)
327
+ file_title = title or file_name
328
+
329
+ # Upload file
330
+ response = await self._client.files_upload_v2(
331
+ channel=channel,
332
+ file=file_path,
333
+ title=file_title,
334
+ initial_comment=initial_comment,
335
+ thread_ts=thread_ts
336
+ )
337
+
338
+ result = {
339
+ 'ok': response['ok'],
340
+ 'file': response.get('file', {}),
341
+ 'file_id': response.get('file', {}).get('id'),
342
+ 'file_name': file_name,
343
+ 'file_title': file_title,
344
+ 'channel': channel,
345
+ 'thread_ts': thread_ts
346
+ }
347
+
348
+ logger.info(f"Uploaded file {file_name} to {channel}")
349
+ return result
350
+
351
+ except Exception as e:
352
+ logger.error(f"Failed to upload file to Slack: {e}")
353
+ raise RuntimeError(f"Slack upload_file failed: {e}")
354
+
355
+ async def get_channel_history(
356
+ self,
357
+ channel: str,
358
+ limit: int = 100,
359
+ cursor: Optional[str] = None,
360
+ oldest: Optional[str] = None,
361
+ latest: Optional[str] = None
362
+ ) -> List[Dict[str, Any]]:
363
+ """
364
+ Get channel message history.
365
+
366
+ Args:
367
+ channel: Channel ID or name
368
+ limit: Maximum number of messages to return
369
+ cursor: Pagination cursor
370
+ oldest: Oldest timestamp to include
371
+ latest: Latest timestamp to include
372
+
373
+ Returns:
374
+ List of messages
375
+
376
+ Example:
377
+ messages = await slack.get_channel_history("#alerts", limit=50)
378
+ """
379
+ if self._client is None:
380
+ await self.connect()
381
+
382
+ try:
383
+ # Get conversation history
384
+ response = await self._client.conversations_history(
385
+ channel=channel,
386
+ limit=limit,
387
+ cursor=cursor,
388
+ oldest=oldest,
389
+ latest=latest
390
+ )
391
+
392
+ messages = response.get('messages', [])
393
+
394
+ # Format messages for easier processing
395
+ formatted_messages = []
396
+ for msg in messages:
397
+ formatted_msg = {
398
+ 'ts': msg.get('ts'),
399
+ 'user': msg.get('user'),
400
+ 'text': msg.get('text', ''),
401
+ 'type': msg.get('type'),
402
+ 'subtype': msg.get('subtype'),
403
+ 'thread_ts': msg.get('thread_ts'),
404
+ 'reply_count': msg.get('reply_count', 0),
405
+ 'blocks': msg.get('blocks', []),
406
+ 'attachments': msg.get('attachments', [])
407
+ }
408
+ formatted_messages.append(formatted_msg)
409
+
410
+ logger.info(f"Retrieved {len(formatted_messages)} messages from {channel}")
411
+ return formatted_messages
412
+
413
+ except Exception as e:
414
+ logger.error(f"Failed to get channel history: {e}")
415
+ raise RuntimeError(f"Slack get_channel_history failed: {e}")
416
+
417
+ async def get_channels(self, types: str = "public_channel,private_channel") -> List[Dict[str, Any]]:
418
+ """
419
+ Get list of channels the bot has access to.
420
+
421
+ Args:
422
+ types: Channel types to include (comma-separated)
423
+
424
+ Returns:
425
+ List of channel information
426
+
427
+ Example:
428
+ channels = await slack.get_channels()
429
+ """
430
+ if self._client is None:
431
+ await self.connect()
432
+
433
+ try:
434
+ # Get conversations list
435
+ response = await self._client.conversations_list(types=types)
436
+
437
+ channels = response.get('channels', [])
438
+
439
+ # Format channel info
440
+ formatted_channels = []
441
+ for channel in channels:
442
+ formatted_channel = {
443
+ 'id': channel.get('id'),
444
+ 'name': channel.get('name'),
445
+ 'is_channel': channel.get('is_channel'),
446
+ 'is_private': channel.get('is_private'),
447
+ 'is_archived': channel.get('is_archived'),
448
+ 'num_members': channel.get('num_members'),
449
+ 'topic': channel.get('topic', {}).get('value', ''),
450
+ 'purpose': channel.get('purpose', {}).get('value', '')
451
+ }
452
+ formatted_channels.append(formatted_channel)
453
+
454
+ # Update cache
455
+ self._channels_cache = {ch['name']: ch['id'] for ch in formatted_channels}
456
+
457
+ logger.info(f"Retrieved {len(formatted_channels)} channels")
458
+ return formatted_channels
459
+
460
+ except Exception as e:
461
+ logger.error(f"Failed to get channels: {e}")
462
+ raise RuntimeError(f"Slack get_channels failed: {e}")
463
+
464
+ def _format_agent_results(self, agent_results: Dict[str, Any], title: str) -> List[Dict[str, Any]]:
465
+ """Format agent results as Slack Block Kit blocks."""
466
+ blocks = []
467
+
468
+ # Header block
469
+ blocks.append({
470
+ "type": "header",
471
+ "text": {
472
+ "type": "plain_text",
473
+ "text": title
474
+ }
475
+ })
476
+
477
+ # Agent status and timing
478
+ status = agent_results.get('status', 'unknown')
479
+ start_time = agent_results.get('start_time', 'N/A')
480
+ end_time = agent_results.get('end_time', 'N/A')
481
+ duration = agent_results.get('duration_ms', 0)
482
+
483
+ status_emoji = "" if status == "success" else "" if status == "error" else ""
484
+
485
+ blocks.append({
486
+ "type": "section",
487
+ "fields": [
488
+ {
489
+ "type": "mrkdwn",
490
+ "text": f"*Status:* {status_emoji} {status.title()}"
491
+ },
492
+ {
493
+ "type": "mrkdwn",
494
+ "text": f"*Duration:* {duration:.1f}ms"
495
+ },
496
+ {
497
+ "type": "mrkdwn",
498
+ "text": f"*Started:* {start_time}"
499
+ },
500
+ {
501
+ "type": "mrkdwn",
502
+ "text": f"*Completed:* {end_time}"
503
+ }
504
+ ]
505
+ })
506
+
507
+ # Results summary
508
+ if 'output' in agent_results:
509
+ output = agent_results['output']
510
+ if isinstance(output, dict):
511
+ output_text = json.dumps(output, indent=2)
512
+ else:
513
+ output_text = str(output)
514
+
515
+ # Truncate if too long
516
+ if len(output_text) > 2000:
517
+ output_text = output_text[:2000] + "..."
518
+
519
+ blocks.append({
520
+ "type": "section",
521
+ "text": {
522
+ "type": "mrkdwn",
523
+ "text": f"*Results:*\n```\n{output_text}\n```"
524
+ }
525
+ })
526
+
527
+ # Error information
528
+ if status == "error" and 'error' in agent_results:
529
+ blocks.append({
530
+ "type": "section",
531
+ "text": {
532
+ "type": "mrkdwn",
533
+ "text": f"*Error:*\n```\n{agent_results['error']}\n```"
534
+ }
535
+ })
536
+
537
+ return blocks
538
+
539
+ def _format_workflow_summary(self, workflow_results: Dict[str, Any], title: str) -> List[Dict[str, Any]]:
540
+ """Format workflow results as Slack Block Kit blocks."""
541
+ blocks = []
542
+
543
+ # Header block
544
+ blocks.append({
545
+ "type": "header",
546
+ "text": {
547
+ "type": "plain_text",
548
+ "text": title
549
+ }
550
+ })
551
+
552
+ # Workflow summary
553
+ total_agents = len(workflow_results.get('agents', {}))
554
+ successful_agents = sum(1 for agent in workflow_results.get('agents', {}).values()
555
+ if agent.get('status') == 'success')
556
+ failed_agents = total_agents - successful_agents
557
+
558
+ workflow_status = " Success" if failed_agents == 0 else f" {failed_agents} Failed"
559
+
560
+ blocks.append({
561
+ "type": "section",
562
+ "fields": [
563
+ {
564
+ "type": "mrkdwn",
565
+ "text": f"*Status:* {workflow_status}"
566
+ },
567
+ {
568
+ "type": "mrkdwn",
569
+ "text": f"*Total Agents:* {total_agents}"
570
+ },
571
+ {
572
+ "type": "mrkdwn",
573
+ "text": f"*Successful:* {successful_agents}"
574
+ },
575
+ {
576
+ "type": "mrkdwn",
577
+ "text": f"*Failed:* {failed_agents}"
578
+ }
579
+ ]
580
+ })
581
+
582
+ # Add divider
583
+ blocks.append({"type": "divider"})
584
+
585
+ # Agent summary
586
+ if total_agents > 0:
587
+ blocks.append({
588
+ "type": "section",
589
+ "text": {
590
+ "type": "mrkdwn",
591
+ "text": "*Agent Results:*\nSee thread replies for detailed results from each agent."
592
+ }
593
+ })
594
+
595
+ return blocks
596
+
597
+ def get_tools(self) -> List['AgentTool']:
598
+ """
599
+ Expose Slack operations as agent tools.
600
+
601
+ Returns:
602
+ List of AgentTool instances for Slack messaging operations
603
+ """
604
+ from ..core.tools import AgentTool
605
+
606
+ return [
607
+ AgentTool(
608
+ name="send_slack_message",
609
+ description="Send a message to a Slack channel. Use for notifications, alerts, or sharing information with team channels.",
610
+ parameters={
611
+ "channel": {
612
+ "type": "string",
613
+ "description": "Channel name (e.g., #general, #alerts) or channel ID",
614
+ "required": True
615
+ },
616
+ "text": {
617
+ "type": "string",
618
+ "description": "Message text to send",
619
+ "required": True
620
+ }
621
+ },
622
+ handler=self._tool_send_message,
623
+ category="communication",
624
+ source="plugin",
625
+ plugin_name="Slack",
626
+ timeout_seconds=30
627
+ ),
628
+ AgentTool(
629
+ name="send_slack_summary",
630
+ description="Send a formatted agent summary to Slack with results and metadata. Use for reporting agent execution results.",
631
+ parameters={
632
+ "channel": {
633
+ "type": "string",
634
+ "description": "Channel name or ID to send summary to",
635
+ "required": True
636
+ },
637
+ "summary": {
638
+ "type": "string",
639
+ "description": "Summary text describing the results",
640
+ "required": True
641
+ },
642
+ "results": {
643
+ "type": "object",
644
+ "description": "Results data to include in the summary",
645
+ "required": False
646
+ }
647
+ },
648
+ handler=self._tool_send_summary,
649
+ category="communication",
650
+ source="plugin",
651
+ plugin_name="Slack",
652
+ timeout_seconds=30
653
+ ),
654
+ AgentTool(
655
+ name="list_slack_channels",
656
+ description="List all Slack channels the bot has access to",
657
+ parameters={},
658
+ handler=self._tool_list_channels,
659
+ category="communication",
660
+ source="plugin",
661
+ plugin_name="Slack",
662
+ timeout_seconds=30
663
+ )
664
+ ]
665
+
666
+ async def _tool_send_message(self, args: Dict[str, Any]) -> Dict[str, Any]:
667
+ """Tool handler for send_slack_message"""
668
+ channel = args.get("channel")
669
+ text = args.get("text")
670
+
671
+ result = await self.send_message(channel, text)
672
+
673
+ return {
674
+ "success": True,
675
+ "channel": result.get("channel"),
676
+ "timestamp": result.get("ts"),
677
+ "message_sent": True
678
+ }
679
+
680
+ async def _tool_send_summary(self, args: Dict[str, Any]) -> Dict[str, Any]:
681
+ """Tool handler for send_slack_summary"""
682
+ channel = args.get("channel")
683
+ summary = args.get("summary")
684
+ results = args.get("results", {})
685
+
686
+ await self.send_agent_summary(
687
+ channel=channel,
688
+ agent_results={"summary": summary, "data": results}
689
+ )
690
+
691
+ return {
692
+ "success": True,
693
+ "channel": channel,
694
+ "summary_sent": True
695
+ }
696
+
697
+ async def _tool_list_channels(self, args: Dict[str, Any]) -> Dict[str, Any]:
698
+ """Tool handler for list_slack_channels"""
699
+ channels = await self.get_channels()
700
+
701
+ # Simplify channel data for LLM
702
+ simplified = [
703
+ {
704
+ "name": ch["name"],
705
+ "id": ch["id"],
706
+ "is_private": ch["is_private"],
707
+ "members": ch.get("num_members", 0)
708
+ }
709
+ for ch in channels
710
+ ]
711
+
712
+ return {
713
+ "success": True,
714
+ "channels": simplified,
715
+ "count": len(simplified)
716
+ }
717
+
718
+ # Context manager support
719
+ async def __aenter__(self):
720
+ await self.connect()
721
+ return self
722
+
723
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
724
+ await self.disconnect()
725
+
726
+
727
+ def slack(**kwargs) -> SlackPlugin:
728
+ """Create Slack plugin with simplified interface."""
729
+ return SlackPlugin(**kwargs)