solana-agent 11.1.0__py3-none-any.whl → 11.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.
solana_agent/ai.py CHANGED
@@ -15,6 +15,7 @@ import datetime
15
15
  import json
16
16
  import re
17
17
  import traceback
18
+ from unittest.mock import AsyncMock
18
19
  import uuid
19
20
  from enum import Enum
20
21
  from typing import (
@@ -1355,6 +1356,15 @@ class MongoTicketRepository:
1355
1356
  """Count tickets matching query."""
1356
1357
  return self.db.count_documents(self.collection, query)
1357
1358
 
1359
+ def find_stalled_tickets(self, cutoff_time, statuses):
1360
+ """Find tickets that haven't been updated since the cutoff time."""
1361
+ query = {
1362
+ "status": {"$in": [status.value if isinstance(status, Enum) else status for status in statuses]},
1363
+ "updated_at": {"$lt": cutoff_time}
1364
+ }
1365
+ tickets = self.db_adapter.find("tickets", query)
1366
+ return [Ticket(**ticket) for ticket in tickets]
1367
+
1358
1368
 
1359
1369
  class MongoHandoffRepository:
1360
1370
  """MongoDB implementation of HandoffRepository."""
@@ -3018,27 +3028,6 @@ class ProjectApprovalService:
3018
3028
  if agent_id in self.human_agent_registry.get_all_human_agents():
3019
3029
  self.approvers.append(agent_id)
3020
3030
 
3021
- async def submit_for_approval(self, ticket: Ticket) -> None:
3022
- """Submit a project for human approval."""
3023
- # Update ticket status
3024
- self.ticket_repository.update(
3025
- ticket.id,
3026
- {
3027
- "status": TicketStatus.PENDING,
3028
- "approval_status": "awaiting_approval",
3029
- "updated_at": datetime.datetime.now(datetime.timezone.utc),
3030
- },
3031
- )
3032
-
3033
- # Notify approvers
3034
- if self.notification_service:
3035
- for approver_id in self.approvers:
3036
- self.notification_service.send_notification(
3037
- approver_id,
3038
- f"New project requires approval: {ticket.query}",
3039
- {"ticket_id": ticket.id, "type": "approval_request"},
3040
- )
3041
-
3042
3031
  async def process_approval(
3043
3032
  self, ticket_id: str, approver_id: str, approved: bool, comments: str = ""
3044
3033
  ) -> None:
@@ -3073,6 +3062,27 @@ class ProjectApprovalService:
3073
3062
  },
3074
3063
  )
3075
3064
 
3065
+ async def submit_for_approval(self, ticket: Ticket) -> None:
3066
+ """Submit a project for human approval."""
3067
+ # Update ticket status
3068
+ self.ticket_repository.update(
3069
+ ticket.id,
3070
+ {
3071
+ "status": TicketStatus.PENDING,
3072
+ "approval_status": "awaiting_approval",
3073
+ "updated_at": datetime.datetime.now(datetime.timezone.utc),
3074
+ },
3075
+ )
3076
+
3077
+ # Notify approvers
3078
+ if self.notification_service and self.approvers:
3079
+ for approver_id in self.approvers:
3080
+ await self.notification_service.send_notification(
3081
+ approver_id,
3082
+ f"New project requires approval: {ticket.query}",
3083
+ {"ticket_id": ticket.id, "type": "approval_request"},
3084
+ )
3085
+
3076
3086
 
3077
3087
  class ProjectSimulationService:
3078
3088
  """Service for simulating project feasibility and requirements using historical data."""
@@ -4446,6 +4456,7 @@ class QueryProcessor:
4446
4456
  project_simulation_service: Optional[ProjectSimulationService] = None,
4447
4457
  require_human_approval: bool = False,
4448
4458
  scheduling_service: Optional[SchedulingService] = None,
4459
+ stalled_ticket_timeout: Optional[int] = 60,
4449
4460
  ):
4450
4461
  self.agent_service = agent_service
4451
4462
  self.routing_service = routing_service
@@ -4463,11 +4474,37 @@ class QueryProcessor:
4463
4474
  self.require_human_approval = require_human_approval
4464
4475
  self._shutdown_event = asyncio.Event()
4465
4476
  self.scheduling_service = scheduling_service
4477
+ self.stalled_ticket_timeout = stalled_ticket_timeout
4478
+
4479
+ self._stalled_ticket_task = None
4480
+
4481
+ # Start background task for stalled ticket detection if not already running
4482
+ if self.stalled_ticket_timeout is not None and self._stalled_ticket_task is None:
4483
+ try:
4484
+ self._stalled_ticket_task = asyncio.create_task(
4485
+ self._run_stalled_ticket_checks())
4486
+ except RuntimeError:
4487
+ # No running event loop - likely in test environment
4488
+ pass
4466
4489
 
4467
4490
  async def process(
4468
4491
  self, user_id: str, user_text: str, timezone: str = None
4469
4492
  ) -> AsyncGenerator[str, None]:
4470
4493
  """Process the user request with appropriate agent and handle ticket management."""
4494
+ # Start background task for stalled ticket detection if not already running
4495
+ if self.stalled_ticket_timeout is not None and self._stalled_ticket_task is None:
4496
+ try:
4497
+ loop = asyncio.get_running_loop()
4498
+ self._stalled_ticket_task = loop.create_task(
4499
+ self._run_stalled_ticket_checks())
4500
+ except RuntimeError:
4501
+ # No running event loop - likely in test environment
4502
+ # Instead of just passing, log a message for clarity
4503
+ import logging
4504
+ logging.warning(
4505
+ "No running event loop available for stalled ticket checker.")
4506
+ # Don't try to create the task - this prevents the coroutine warning
4507
+
4471
4508
  try:
4472
4509
  # Handle human agent messages differently
4473
4510
  if await self._is_human_agent(user_id):
@@ -4515,7 +4552,9 @@ class QueryProcessor:
4515
4552
  except Exception as e:
4516
4553
  print(f"Error in request processing: {str(e)}")
4517
4554
  print(traceback.format_exc())
4518
- yield "\n\nI apologize for the technical difficulty.\n\n"
4555
+ # Use yield instead of direct function calling to avoid coroutine warning
4556
+ error_msg = "\n\nI apologize for the technical difficulty.\n\n"
4557
+ yield error_msg
4519
4558
 
4520
4559
  async def _is_human_agent(self, user_id: str) -> bool:
4521
4560
  """Check if the user is a registered human agent."""
@@ -4583,6 +4622,69 @@ class QueryProcessor:
4583
4622
 
4584
4623
  return response
4585
4624
 
4625
+ async def shutdown(self):
4626
+ """Clean shutdown of the query processor."""
4627
+ self._shutdown_event.set()
4628
+
4629
+ # Cancel the stalled ticket task if running
4630
+ if hasattr(self, '_stalled_ticket_task') and self._stalled_ticket_task is not None:
4631
+ self._stalled_ticket_task.cancel()
4632
+ try:
4633
+ await self._stalled_ticket_task
4634
+ except (asyncio.CancelledError, TypeError):
4635
+ # Either properly cancelled coroutine or a mock that can't be awaited
4636
+ pass
4637
+
4638
+ async def _run_stalled_ticket_checks(self):
4639
+ """Run periodic checks for stalled tickets."""
4640
+ try:
4641
+ while not self._shutdown_event.is_set():
4642
+ await self._check_for_stalled_tickets()
4643
+ # Check every 5 minutes or half the timeout period, whichever is smaller
4644
+ check_interval = min(
4645
+ 300, self.stalled_ticket_timeout * 30) if self.stalled_ticket_timeout else 300
4646
+ await asyncio.sleep(check_interval)
4647
+ except asyncio.CancelledError:
4648
+ # Task was cancelled, clean exit
4649
+ pass
4650
+ except Exception as e:
4651
+ print(f"Error in stalled ticket check: {e}")
4652
+
4653
+ async def _check_for_stalled_tickets(self):
4654
+ """Check for tickets that haven't been updated in a while and reassign them."""
4655
+ # If stalled ticket detection is disabled, exit early
4656
+ if self.stalled_ticket_timeout is None:
4657
+ return
4658
+
4659
+ # Find tickets that haven't been updated in the configured time
4660
+ stalled_cutoff = datetime.datetime.now(
4661
+ datetime.timezone.utc) - datetime.timedelta(minutes=self.stalled_ticket_timeout)
4662
+
4663
+ # Query for stalled tickets using the find_stalled_tickets method
4664
+ stalled_tickets = await self.ticket_service.ticket_repository.find_stalled_tickets(
4665
+ stalled_cutoff, [TicketStatus.ACTIVE, TicketStatus.TRANSFERRED]
4666
+ )
4667
+
4668
+ for ticket in stalled_tickets:
4669
+ # Re-route using routing service to find the optimal agent
4670
+ new_agent = await self.routing_service.route_query(ticket.query)
4671
+
4672
+ # Skip if the routing didn't change
4673
+ if new_agent == ticket.assigned_to:
4674
+ continue
4675
+
4676
+ # Process as handoff
4677
+ await self.handoff_service.process_handoff(
4678
+ ticket.id,
4679
+ ticket.assigned_to or "unassigned",
4680
+ new_agent,
4681
+ f"Automatically reassigned after {self.stalled_ticket_timeout} minutes of inactivity"
4682
+ )
4683
+
4684
+ # Log the reassignment
4685
+ print(
4686
+ f"Stalled ticket {ticket.id} reassigned from {ticket.assigned_to or 'unassigned'} to {new_agent}")
4687
+
4586
4688
  async def _process_system_commands(
4587
4689
  self, user_id: str, user_text: str
4588
4690
  ) -> Optional[str]:
@@ -4739,154 +4841,157 @@ class QueryProcessor:
4739
4841
  )
4740
4842
  return f"Project {ticket_id} has been {'approved' if approved else 'rejected'}."
4741
4843
 
4742
- elif command == "!schedule" and args and self.scheduling_service:
4743
- # Format: !schedule task_id [agent_id] [YYYY-MM-DD HH:MM]
4744
- parts = args.strip().split(" ", 2)
4745
- if len(parts) < 1:
4746
- return "Usage: !schedule task_id [agent_id] [YYYY-MM-DD HH:MM]"
4747
-
4748
- task_id = parts[0]
4749
- agent_id = parts[1] if len(parts) > 1 else None
4750
- time_str = parts[2] if len(parts) > 2 else None
4751
-
4752
- # Fetch the task from ticket repository
4753
- ticket = self.ticket_service.ticket_repository.get_by_id(task_id)
4754
- if not ticket:
4755
- return f"Task {task_id} not found."
4756
-
4757
- # Convert ticket to scheduled task
4758
- scheduled_task = ScheduledTask(
4759
- task_id=task_id,
4760
- title=ticket.query[:50] +
4761
- "..." if len(ticket.query) > 50 else ticket.query,
4762
- description=ticket.query,
4763
- estimated_minutes=ticket.complexity.get(
4764
- "estimated_minutes", 30) if ticket.complexity else 30,
4765
- priority=5, # Default priority
4766
- assigned_to=agent_id or ticket.assigned_to,
4767
- # Use current agent as a specialization tag
4768
- specialization_tags=[ticket.assigned_to],
4769
- )
4844
+ elif command == "!schedule" and args and self.scheduling_service:
4845
+ # Format: !schedule task_id [agent_id] [YYYY-MM-DD HH:MM]
4846
+ parts = args.strip().split(" ", 2)
4847
+ if len(parts) < 1:
4848
+ return "Usage: !schedule task_id [agent_id] [YYYY-MM-DD HH:MM]"
4849
+
4850
+ task_id = parts[0]
4851
+ agent_id = parts[1] if len(parts) > 1 else None
4852
+ time_str = parts[2] if len(parts) > 2 else None
4853
+
4854
+ # Fetch the task from ticket repository
4855
+ ticket = self.ticket_service.ticket_repository.get_by_id(
4856
+ task_id)
4857
+ if not ticket:
4858
+ return f"Task {task_id} not found."
4859
+
4860
+ # Convert ticket to scheduled task
4861
+ scheduled_task = ScheduledTask(
4862
+ task_id=task_id,
4863
+ title=ticket.query[:50] +
4864
+ "..." if len(ticket.query) > 50 else ticket.query,
4865
+ description=ticket.query,
4866
+ estimated_minutes=ticket.complexity.get(
4867
+ "estimated_minutes", 30) if ticket.complexity else 30,
4868
+ priority=5, # Default priority
4869
+ assigned_to=agent_id or ticket.assigned_to,
4870
+ # Use current agent as a specialization tag
4871
+ specialization_tags=[ticket.assigned_to],
4872
+ )
4770
4873
 
4771
- # Set scheduled time if provided
4772
- if time_str:
4773
- try:
4774
- scheduled_time = datetime.datetime.fromisoformat(time_str)
4775
- scheduled_task.scheduled_start = scheduled_time
4776
- scheduled_task.scheduled_end = scheduled_time + datetime.timedelta(
4777
- minutes=scheduled_task.estimated_minutes
4778
- )
4779
- except ValueError:
4780
- return "Invalid date format. Use YYYY-MM-DD HH:MM."
4874
+ # Set scheduled time if provided
4875
+ if time_str:
4876
+ try:
4877
+ scheduled_time = datetime.datetime.fromisoformat(
4878
+ time_str)
4879
+ scheduled_task.scheduled_start = scheduled_time
4880
+ scheduled_task.scheduled_end = scheduled_time + datetime.timedelta(
4881
+ minutes=scheduled_task.estimated_minutes
4882
+ )
4883
+ except ValueError:
4884
+ return "Invalid date format. Use YYYY-MM-DD HH:MM."
4781
4885
 
4782
- # Schedule the task
4783
- result = await self.scheduling_service.schedule_task(
4784
- scheduled_task, preferred_agent_id=agent_id
4785
- )
4886
+ # Schedule the task
4887
+ result = await self.scheduling_service.schedule_task(
4888
+ scheduled_task, preferred_agent_id=agent_id
4889
+ )
4786
4890
 
4787
- # Update ticket with scheduling info
4788
- self.ticket_service.update_ticket_status(
4789
- task_id,
4790
- ticket.status,
4791
- scheduled_start=result.scheduled_start,
4792
- scheduled_agent=result.assigned_to
4793
- )
4891
+ # Update ticket with scheduling info
4892
+ self.ticket_service.update_ticket_status(
4893
+ task_id,
4894
+ ticket.status,
4895
+ scheduled_start=result.scheduled_start,
4896
+ scheduled_agent=result.assigned_to
4897
+ )
4794
4898
 
4795
- # Format response
4796
- response = "# Task Scheduled\n\n"
4797
- response += f"**Task:** {scheduled_task.title}\n"
4798
- response += f"**Assigned to:** {result.assigned_to}\n"
4799
- response += f"**Scheduled start:** {result.scheduled_start.strftime('%Y-%m-%d %H:%M')}\n"
4800
- response += f"**Estimated duration:** {result.estimated_minutes} minutes"
4899
+ # Format response
4900
+ response = "# Task Scheduled\n\n"
4901
+ response += f"**Task:** {scheduled_task.title}\n"
4902
+ response += f"**Assigned to:** {result.assigned_to}\n"
4903
+ response += f"**Scheduled start:** {result.scheduled_start.strftime('%Y-%m-%d %H:%M')}\n"
4904
+ response += f"**Estimated duration:** {result.estimated_minutes} minutes"
4801
4905
 
4802
- return response
4906
+ return response
4803
4907
 
4804
- elif command == "!timeoff" and args and self.scheduling_service:
4805
- # Format: !timeoff request YYYY-MM-DD HH:MM YYYY-MM-DD HH:MM reason
4806
- # or: !timeoff cancel request_id
4807
- parts = args.strip().split(" ", 1)
4808
- if len(parts) < 2:
4809
- return "Usage: \n- !timeoff request START_DATE END_DATE reason\n- !timeoff cancel request_id"
4908
+ elif command == "!timeoff" and args and self.scheduling_service:
4909
+ # Format: !timeoff request YYYY-MM-DD HH:MM YYYY-MM-DD HH:MM reason
4910
+ # or: !timeoff cancel request_id
4911
+ parts = args.strip().split(" ", 1)
4912
+ if len(parts) < 2:
4913
+ return "Usage: \n- !timeoff request START_DATE END_DATE reason\n- !timeoff cancel request_id"
4810
4914
 
4811
- action = parts[0].lower()
4812
- action_args = parts[1]
4915
+ action = parts[0].lower()
4916
+ action_args = parts[1]
4813
4917
 
4814
- if action == "request":
4815
- # Parse request args
4816
- request_parts = action_args.split(" ", 2)
4817
- if len(request_parts) < 3:
4818
- return "Usage: !timeoff request YYYY-MM-DD YYYY-MM-DD reason"
4918
+ if action == "request":
4919
+ # Parse request args
4920
+ request_parts = action_args.split(" ", 2)
4921
+ if len(request_parts) < 3:
4922
+ return "Usage: !timeoff request YYYY-MM-DD YYYY-MM-DD reason"
4819
4923
 
4820
- start_str = request_parts[0]
4821
- end_str = request_parts[1]
4822
- reason = request_parts[2]
4924
+ start_str = request_parts[0]
4925
+ end_str = request_parts[1]
4926
+ reason = request_parts[2]
4823
4927
 
4824
- try:
4825
- start_time = datetime.datetime.fromisoformat(start_str)
4826
- end_time = datetime.datetime.fromisoformat(end_str)
4827
- except ValueError:
4828
- return "Invalid date format. Use YYYY-MM-DD HH:MM."
4829
-
4830
- # Submit time off request
4831
- success, status, request_id = await self.scheduling_service.request_time_off(
4832
- user_id, start_time, end_time, reason
4833
- )
4928
+ try:
4929
+ start_time = datetime.datetime.fromisoformat(start_str)
4930
+ end_time = datetime.datetime.fromisoformat(end_str)
4931
+ except ValueError:
4932
+ return "Invalid date format. Use YYYY-MM-DD HH:MM."
4933
+
4934
+ # Submit time off request
4935
+ success, status, request_id = await self.scheduling_service.request_time_off(
4936
+ user_id, start_time, end_time, reason
4937
+ )
4834
4938
 
4835
- if success:
4836
- return f"Time off request submitted and automatically approved. Request ID: {request_id}"
4837
- else:
4838
- return f"Time off request {status}. Request ID: {request_id}"
4939
+ if success:
4940
+ return f"Time off request submitted and automatically approved. Request ID: {request_id}"
4941
+ else:
4942
+ return f"Time off request {status}. Request ID: {request_id}"
4839
4943
 
4840
- elif action == "cancel":
4841
- request_id = action_args.strip()
4842
- success, status = await self.scheduling_service.cancel_time_off_request(
4843
- user_id, request_id
4844
- )
4944
+ elif action == "cancel":
4945
+ request_id = action_args.strip()
4946
+ success, status = await self.scheduling_service.cancel_time_off_request(
4947
+ user_id, request_id
4948
+ )
4845
4949
 
4846
- if success:
4847
- return f"Time off request {request_id} cancelled successfully."
4848
- else:
4849
- return f"Failed to cancel request: {status}"
4950
+ if success:
4951
+ return f"Time off request {request_id} cancelled successfully."
4952
+ else:
4953
+ return f"Failed to cancel request: {status}"
4850
4954
 
4851
- return "Unknown timeoff action. Use 'request' or 'cancel'."
4955
+ return "Unknown timeoff action. Use 'request' or 'cancel'."
4852
4956
 
4853
- elif command == "!schedule-view" and self.scheduling_service:
4854
- # View agent's schedule for the next week
4855
- # Default to current user if no agent specified
4856
- agent_id = args.strip() if args else user_id
4957
+ elif command == "!schedule-view" and self.scheduling_service:
4958
+ # View agent's schedule for the next week
4959
+ # Default to current user if no agent specified
4960
+ agent_id = args.strip() if args else user_id
4857
4961
 
4858
- start_time = datetime.datetime.now(datetime.timezone.utc)
4859
- end_time = start_time + datetime.timedelta(days=7)
4962
+ start_time = datetime.datetime.now(datetime.timezone.utc)
4963
+ end_time = start_time + datetime.timedelta(days=7)
4860
4964
 
4861
- # Get tasks for the specified time period
4862
- tasks = await self.scheduling_service.get_agent_tasks(
4863
- agent_id, start_time, end_time, include_completed=False
4864
- )
4965
+ # Get tasks for the specified time period
4966
+ tasks = await self.scheduling_service.get_agent_tasks(
4967
+ agent_id, start_time, end_time, include_completed=False
4968
+ )
4865
4969
 
4866
- if not tasks:
4867
- return f"No scheduled tasks found for {agent_id} in the next 7 days."
4970
+ if not tasks:
4971
+ return f"No scheduled tasks found for {agent_id} in the next 7 days."
4868
4972
 
4869
- # Sort by start time
4870
- tasks.sort(key=lambda t: t.scheduled_start or datetime.datetime.max)
4973
+ # Sort by start time
4974
+ tasks.sort(
4975
+ key=lambda t: t.scheduled_start or datetime.datetime.max)
4871
4976
 
4872
- # Format response
4873
- response = f"# Schedule for {agent_id}\n\n"
4977
+ # Format response
4978
+ response = f"# Schedule for {agent_id}\n\n"
4874
4979
 
4875
- current_day = None
4876
- for task in tasks:
4877
- # Group by day
4878
- task_day = task.scheduled_start.strftime(
4879
- "%Y-%m-%d") if task.scheduled_start else "Unscheduled"
4980
+ current_day = None
4981
+ for task in tasks:
4982
+ # Group by day
4983
+ task_day = task.scheduled_start.strftime(
4984
+ "%Y-%m-%d") if task.scheduled_start else "Unscheduled"
4880
4985
 
4881
- if task_day != current_day:
4882
- response += f"\n## {task_day}\n\n"
4883
- current_day = task_day
4986
+ if task_day != current_day:
4987
+ response += f"\n## {task_day}\n\n"
4988
+ current_day = task_day
4884
4989
 
4885
- start_time = task.scheduled_start.strftime(
4886
- "%H:%M") if task.scheduled_start else "TBD"
4887
- response += f"- **{start_time}** ({task.estimated_minutes} min): {task.title}\n"
4990
+ start_time = task.scheduled_start.strftime(
4991
+ "%H:%M") if task.scheduled_start else "TBD"
4992
+ response += f"- **{start_time}** ({task.estimated_minutes} min): {task.title}\n"
4888
4993
 
4889
- return response
4994
+ return response
4890
4995
 
4891
4996
  return None
4892
4997
 
@@ -5589,7 +5694,8 @@ class SolanaAgentFactory:
5589
5694
  project_approval_service=project_approval_service,
5590
5695
  project_simulation_service=project_simulation_service,
5591
5696
  require_human_approval=config.get("require_human_approval", False),
5592
- scheduling_service=scheduling_service
5697
+ scheduling_service=scheduling_service,
5698
+ stalled_ticket_timeout=config.get("stalled_ticket_timeout", 60),
5593
5699
  )
5594
5700
 
5595
5701
  return query_processor
@@ -5876,6 +5982,22 @@ class TenantManager:
5876
5982
  llm_provider, task_planning_service, ticket_repo
5877
5983
  )
5878
5984
 
5985
+ # Create scheduling repository and service
5986
+ tenant_db_adapter = self._create_tenant_db_adapter(
5987
+ tenant) # Get the DB adapter properly
5988
+ scheduling_repository = SchedulingRepository(
5989
+ tenant_db_adapter) # Use the correct adapter
5990
+
5991
+ scheduling_service = SchedulingService(
5992
+ scheduling_repository=scheduling_repository,
5993
+ task_planning_service=task_planning_service,
5994
+ agent_service=agent_service
5995
+ )
5996
+
5997
+ # Update task_planning_service with scheduling_service if needed
5998
+ if task_planning_service:
5999
+ task_planning_service.scheduling_service = scheduling_service
6000
+
5879
6001
  # Create query processor
5880
6002
  return QueryProcessor(
5881
6003
  agent_service=agent_service,
@@ -5895,6 +6017,9 @@ class TenantManager:
5895
6017
  require_human_approval=tenant.get_config_value(
5896
6018
  "require_human_approval", False
5897
6019
  ),
6020
+ scheduling_service=scheduling_service,
6021
+ stalled_ticket_timeout=tenant.get_config_value(
6022
+ "stalled_ticket_timeout"),
5898
6023
  )
5899
6024
 
5900
6025
  def _deep_merge(self, target: Dict, source: Dict) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solana-agent
3
- Version: 11.1.0
3
+ Version: 11.2.0
4
4
  Summary: The Future of Work
5
5
  License: MIT
6
6
  Keywords: ai,openai,ai agents,agi
@@ -263,7 +263,6 @@ Solana Agent transforms organizations into living systems that continuously lear
263
263
  Tool registry for AI agent capability extension.
264
264
  Permission-based tool access for security and control.
265
265
  Clean interface for third-party integrations through standard Python APIs.
266
- Built-in internet search capabilities via Perplexity API.
267
266
  Runtime tool discovery without service restarts.
268
267
 
269
268
  ## Implementation Technologies
@@ -325,7 +324,7 @@ config = {
325
324
  "api_key": "your-pinecone-key",
326
325
  "index": "your-index"
327
326
  },
328
- "plugins_dir": "plugins",
327
+ "stalled_ticket_timeout": 60,
329
328
  "ai_agents": [
330
329
  {
331
330
  "name": "research_specialist",
@@ -0,0 +1,6 @@
1
+ solana_agent/__init__.py,sha256=zpfnWqANd3OHGWm7NCF5Y6m01BWG4NkNk8SK9Ex48nA,18
2
+ solana_agent/ai.py,sha256=SPHPCQPqk1wuBymB3StNRYp84s9pAa7LOeROBSHiJfQ,243608
3
+ solana_agent-11.2.0.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
4
+ solana_agent-11.2.0.dist-info/METADATA,sha256=quIlcgag0-hHIAy7-ibsh-JWxpBmravXIeoCp4zXxDw,18468
5
+ solana_agent-11.2.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
6
+ solana_agent-11.2.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- solana_agent/__init__.py,sha256=zpfnWqANd3OHGWm7NCF5Y6m01BWG4NkNk8SK9Ex48nA,18
2
- solana_agent/ai.py,sha256=5bmnf1ipsdk3aQsiYFDhLUSVyUCQPamxAJ6QEawKp5s,237431
3
- solana_agent-11.1.0.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
4
- solana_agent-11.1.0.dist-info/METADATA,sha256=ijTLk43YwFb_fC-zvOGnxVgv4hFBCprnHzB0J6lYxQA,18528
5
- solana_agent-11.1.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
6
- solana_agent-11.1.0.dist-info/RECORD,,