Skip to content

Commit 8907022

Browse files
committed
fix(review-chain): resolve merge conflicts on review-chain.md
2 parents a2b44dc + bb0d196 commit 8907022

File tree

32 files changed

+1314
-88
lines changed

32 files changed

+1314
-88
lines changed

.github/workflows/auto-pr-comment.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ jobs:
409409
permissions:
410410
pull-requests: write
411411
steps:
412-
- name: Trigger CodeRabbit, Qodo, and Gemini reviews
412+
- name: Trigger CodeRabbit and Qodo reviews
413413
uses: actions/github-script@v7
414414
with:
415415
github-token: ${{ secrets.GH_TOKEN }}
@@ -432,12 +432,12 @@ jobs:
432432
});
433433
console.log(`Triggered Qodo for bot PR #${pr}`);
434434
435-
// 3. Trigger Gemini review
436-
await github.rest.issues.createComment({
437-
issue_number: pr, owner, repo,
438-
body: '@gemini review this PR'
439-
});
440-
console.log(`Triggered Gemini for bot PR #${pr}`);
435+
// 3. Trigger Gemini review (commented out)
436+
// await github.rest.issues.createComment({
437+
// issue_number: pr, owner, repo,
438+
// body: '@gemini review this PR'
439+
// });
440+
// console.log(`Triggered Gemini for bot PR #${pr}`);
441441
442442
// NOTE: @copilot is NOT triggered here.
443443
// It will be triggered by copilot-after-coderabbit or copilot-after-qodo

api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ from praisonaiagents import Agent, AutoAgents, AutoRagAgent, ContextAgent, DeepR
2828

2929
Methods:
3030

31+
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">aclose</a>() -> None</code>
3132
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">agent_id</a>() -> str</code>
3233
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">analyze_prompt</a>(prompt: str) -> set</code>
3334
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">auto_memory</a>() -> Optional[bool]</code>
@@ -37,6 +38,7 @@ Methods:
3738
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">chat_with_context</a>(message: str, context: 'ContextPack', **kwargs) -> str</code>
3839
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">checkpoints</a>() -> Optional[bool]</code>
3940
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">checkpoints</a>(value: Optional[bool]) -> None</code>
41+
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">close</a>() -> None</code>
4042
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">console</a>() -> Optional[Any]</code>
4143
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">context_manager</a>() -> Optional[Any]</code>
4244
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">context_manager</a>(value)</code>
@@ -52,6 +54,7 @@ Methods:
5254
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">get_skills_prompt</a>() -> str</code>
5355
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">handoff_to</a>(target_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None, config: Optional['HandoffConfig'] = None) -> 'HandoffResult'</code>
5456
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">handoff_to_async</a>(target_agent: 'Agent', prompt: str, context: Optional[Dict[str, Any]] = None, config: Optional['HandoffConfig'] = None) -> 'HandoffResult'</code>
57+
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">is_closed</a>() -> bool</code>
5558
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">llm_model</a>() -> Optional[str]</code>
5659
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>() -> Optional[str]</code>
5760
* <code title="class Agent">Agent.<a href="./src/praisonai-agents/praisonaiagents/agent/agent.py">output_style</a>(value: Optional[str]) -> None</code>

docker/Dockerfile.chat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
1616
# Install Python packages (using latest versions)
1717
RUN pip install --no-cache-dir \
1818
praisonai_tools \
19-
"praisonai>=4.5.126" \
19+
"praisonai>=4.5.128" \
2020
"praisonai[chat]" \
2121
"embedchain[github,youtube]"
2222

docker/Dockerfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
2020
# Install Python packages (using latest versions)
2121
RUN pip install --no-cache-dir \
2222
praisonai_tools \
23-
"praisonai>=4.5.126" \
23+
"praisonai>=4.5.128" \
2424
"praisonai[ui]" \
2525
"praisonai[chat]" \
2626
"praisonai[realtime]" \

docker/Dockerfile.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
1616
# Install Python packages (using latest versions)
1717
RUN pip install --no-cache-dir \
1818
praisonai_tools \
19-
"praisonai>=4.5.126" \
19+
"praisonai>=4.5.128" \
2020
"praisonai[ui]" \
2121
"praisonai[crewai]"
2222

src/praisonai-agents/.agent/workflows/review-chain.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ PR opened
1111
1212
CodeRabbit (@coderabbitai) ─── auto for human PRs, triggered via comment for bot PRs
1313
Qodo (/review) ─── auto for human PRs, triggered via comment for bot PRs
14-
Gemini (@gemini) ─── triggered via comment for bot PRs/Issues
15-
PraisonAI (@praisonai) ─── triggered via comment (parallel with Gemini)
14+
<!-- Gemini (@gemini) ─── triggered via comment for bot PRs/Issues
15+
↓ (~3-5 min) ↓ (auto triggers Claude on completion) -->
16+
PraisonAI (@praisonai) ─── triggered via comment
1617
↓ (~3-5 min) ↓ (auto triggers Claude on completion)
1718
Copilot (@copilot) ─── triggered ONLY after CodeRabbit or Qodo post their review
1819

src/praisonai-agents/praisonaiagents/agent/agent.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4464,6 +4464,146 @@ def _get_tools_cache_key(self, tools):
44644464
@contextlib.contextmanager
44654465

44664466

4467+
# -------------------------------------------------------------------------
4468+
# Resource Lifecycle Management
4469+
# -------------------------------------------------------------------------
4470+
4471+
def close(self) -> None:
4472+
"""Synchronously close the agent and clean up resources."""
4473+
if getattr(self, '_closed', False):
4474+
return
4475+
4476+
# Memory cleanup
4477+
try:
4478+
memory = getattr(self, "_memory_instance", None)
4479+
if memory and hasattr(memory, 'close_connections'):
4480+
memory.close_connections()
4481+
except Exception as e:
4482+
logger.warning(f"Memory cleanup failed: {e}")
4483+
4484+
# MCP cleanup
4485+
try:
4486+
if hasattr(self, '_mcp_clients') and self._mcp_clients:
4487+
for client_name, client in self._mcp_clients.items():
4488+
if hasattr(client, 'close'):
4489+
client.close()
4490+
self._mcp_clients.clear()
4491+
except Exception as e:
4492+
logger.warning(f"MCP cleanup failed: {e}")
4493+
4494+
# Server registry cleanup
4495+
try:
4496+
self._cleanup_server_registrations()
4497+
except Exception as e:
4498+
logger.warning(f"Server cleanup failed: {e}")
4499+
4500+
# Background tasks cleanup
4501+
try:
4502+
for task in getattr(self, '_background_tasks', []):
4503+
if hasattr(task, 'cancel'):
4504+
task.cancel()
4505+
except Exception as e:
4506+
logger.warning(f"Task cleanup failed: {e}")
4507+
4508+
# Always set closed flag
4509+
self._closed = True
4510+
4511+
async def aclose(self) -> None:
4512+
"""Async version of close() for async context managers."""
4513+
try:
4514+
# Close memory connections asynchronously if supported
4515+
if hasattr(self, 'memory') and self.memory:
4516+
if hasattr(self.memory, 'aclose'):
4517+
await self.memory.aclose()
4518+
elif hasattr(self.memory, 'close_connections'):
4519+
self.memory.close_connections()
4520+
4521+
# Close MCP sessions asynchronously if supported
4522+
if hasattr(self, '_mcp_clients') and self._mcp_clients:
4523+
for client in self._mcp_clients.values():
4524+
if hasattr(client, 'aclose'):
4525+
await client.aclose()
4526+
elif hasattr(client, 'close'):
4527+
client.close()
4528+
self._mcp_clients.clear()
4529+
4530+
# Clean up server registrations and tasks
4531+
self._cleanup_server_registrations()
4532+
4533+
if hasattr(self, '_background_tasks'):
4534+
for task in self._background_tasks:
4535+
if hasattr(task, 'cancel'):
4536+
task.cancel()
4537+
# Wait for cancellation to complete
4538+
try:
4539+
await task
4540+
except asyncio.CancelledError:
4541+
pass
4542+
4543+
self._closed = True
4544+
4545+
except Exception as e:
4546+
logger.warning(f"Error during async agent cleanup: {e}")
4547+
4548+
def _cleanup_server_registrations(self) -> None:
4549+
"""Clean up global server registry entries for this agent."""
4550+
try:
4551+
agent_id = self.agent_id
4552+
with _server_lock:
4553+
# Remove from _registered_agents
4554+
ports_to_clean = []
4555+
for port, path_dict in _registered_agents.items():
4556+
paths_to_remove = []
4557+
for path, registered_id in path_dict.items():
4558+
if registered_id == agent_id:
4559+
paths_to_remove.append(path)
4560+
4561+
for path in paths_to_remove:
4562+
del path_dict[path]
4563+
4564+
# If no paths left for this port, mark port for cleanup
4565+
if not path_dict:
4566+
ports_to_clean.append(port)
4567+
4568+
# Clean up empty port entries
4569+
for port in ports_to_clean:
4570+
_registered_agents.pop(port, None)
4571+
_server_started.pop(port, None)
4572+
# Note: We don't clean up _shared_apps here as other agents might be using them
4573+
4574+
except Exception as e:
4575+
logger.warning(f"Error cleaning up server registrations: {e}")
4576+
4577+
def __enter__(self):
4578+
"""Context manager entry."""
4579+
return self
4580+
4581+
def __exit__(self, exc_type, exc_val, exc_tb):
4582+
"""Context manager exit - clean up resources."""
4583+
self.close()
4584+
4585+
async def __aenter__(self):
4586+
"""Async context manager entry."""
4587+
return self
4588+
4589+
async def __aexit__(self, exc_type, exc_val, exc_tb):
4590+
"""Async context manager exit - clean up resources."""
4591+
await self.aclose()
4592+
4593+
def __del__(self):
4594+
"""Destructor - ensure resources are cleaned up."""
4595+
try:
4596+
if not getattr(self, '_closed', False):
4597+
self.close()
4598+
except Exception:
4599+
# Ignore errors in destructor to avoid issues during shutdown
4600+
pass
4601+
4602+
@property
4603+
def is_closed(self) -> bool:
4604+
"""Returns True if the agent has been closed."""
4605+
return getattr(self, '_closed', False)
4606+
44674607
def __str__(self):
44684608
return f"Agent(name='{self.name}', role='{self.role}', goal='{self.goal}')"
44694609

src/praisonai-agents/praisonaiagents/agent/execution_mixin.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ def _get_display_functions():
5454
class ExecutionMixin:
5555
"""Mixin providing execution methods for the Agent class."""
5656

57+
def _safe_sleep(self, duration: float) -> None:
58+
"""Synchronous sleep - safe for sync contexts only."""
59+
time.sleep(duration)
60+
61+
async def _safe_sleep_async(self, duration: float) -> None:
62+
"""Async sleep - use this in async contexts."""
63+
await asyncio.sleep(duration)
64+
5765
def generate_task(self) -> 'Task':
5866
"""Generate a Task object from the agent's instructions"""
5967
from ..task.task import Task
@@ -954,10 +962,10 @@ def run_server():
954962
server_thread.start()
955963

956964
# Wait for a moment to allow the server to start and register endpoints
957-
time.sleep(0.5)
965+
self._safe_sleep(0.5)
958966
else:
959967
# If server is already running, wait a moment to make sure the endpoint is registered
960-
time.sleep(0.1)
968+
self._safe_sleep(0.1)
961969
print(f"🔌 Available endpoints on port {port}: {', '.join(list(_registered_agents[port].keys()))}")
962970

963971
# Get the stack frame to check if this is the last launch() call in the script
@@ -986,7 +994,7 @@ def run_server():
986994
try:
987995
print("\nAll agents registered for HTTP mode. Press Ctrl+C to stop the servers.")
988996
while True:
989-
time.sleep(1)
997+
self._safe_sleep(1)
990998
except KeyboardInterrupt:
991999
print("\nServers stopped")
9921000
except Exception as e:
@@ -995,7 +1003,7 @@ def run_server():
9951003
try:
9961004
print("\nKeeping HTTP servers alive. Press Ctrl+C to stop.")
9971005
while True:
998-
time.sleep(1)
1006+
self._safe_sleep(1)
9991007
except KeyboardInterrupt:
10001008
print("\nServers stopped")
10011009
return None
@@ -1055,12 +1063,12 @@ def run_mcp_server():
10551063

10561064
server_thread = threading.Thread(target=run_mcp_server, daemon=True)
10571065
server_thread.start()
1058-
time.sleep(0.5)
1066+
self._safe_sleep(0.5)
10591067

10601068
try:
10611069
print("\nKeeping MCP server alive. Press Ctrl+C to stop.")
10621070
while True:
1063-
time.sleep(1)
1071+
self._safe_sleep(1)
10641072
except KeyboardInterrupt:
10651073
print("\nMCP Server stopped")
10661074
return None
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""
2+
Gateway adapters package for PraisonAI.
3+
4+
This package contains adapters that bridge different systems with the Gateway infrastructure.
5+
"""
6+
7+
from .recipe_adapter import RecipeBotAdapter
8+
9+
__all__ = ["RecipeBotAdapter"]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Adapter to wrap a RecipeRuntime behind the standard Agent duck-typing for Gateway and BotOS.
3+
"""
4+
5+
import logging
6+
from typing import Optional, Any
7+
from praisonaiagents.streaming import StreamEventEmitter
8+
9+
logger = logging.getLogger(__name__)
10+
11+
class RecipeBotAdapter:
12+
"""Wraps RecipeRuntime to act like an Agent for gateway/bot compatibility."""
13+
14+
def __init__(self, recipe_name: str, **kwargs):
15+
self.recipe_name = recipe_name
16+
self.config = kwargs
17+
self._runtime = None
18+
self.stream_emitter = StreamEventEmitter()
19+
20+
@property
21+
def name(self) -> str:
22+
return f"recipe-{self.recipe_name}"
23+
24+
def _get_runtime(self):
25+
"""Lazy load and instantiate RecipeRuntime."""
26+
if self._runtime is None:
27+
try:
28+
from praisonai.recipe.core import RecipeRuntime
29+
self._runtime = RecipeRuntime(recipe_name=self.recipe_name, **self.config)
30+
except ImportError as e:
31+
raise ImportError(
32+
f"Failed to load PraisonAI recipe components: {e}. "
33+
"Make sure you have praisonai installed."
34+
) from e
35+
return self._runtime
36+
37+
def chat(self, message: str) -> str:
38+
"""Process the message via the recipe runtime."""
39+
try:
40+
runtime = self._get_runtime()
41+
42+
# Forward the user_input to the recipe's variables
43+
variables = runtime.context.variables if hasattr(runtime, 'context') else {}
44+
variables["user_input"] = message
45+
46+
# Currently we rely on string return matching standard Agent
47+
# Note: We don't have direct streaming from recipe to emitter yet; this binds them generically.
48+
result = runtime.run()
49+
50+
if hasattr(result, "final_output"):
51+
return str(result.final_output)
52+
return str(result)
53+
54+
except Exception as e:
55+
logger.error(f"Recipe execution failed: {e}")
56+
return f"Error executing recipe: {e}"

0 commit comments

Comments
 (0)