1717 pip install harbor praisonaiagents
1818"""
1919
20- import asyncio
21- from typing import Any , Dict , Optional
22- from pathlib import Path
20+ import os
21+ from typing import Any
2322
2423try :
2524 from harbor .agents .base import BaseAgent
@@ -51,7 +50,11 @@ def name() -> str:
5150 def version (self ) -> str | None :
5251 try :
5352 import praisonaiagents
54- return getattr (praisonaiagents , "__version__" , None )
53+ return (
54+ getattr (praisonaiagents , "__version__" , None )
55+ or getattr (praisonaiagents , "version" , None )
56+ or "unknown"
57+ )
5558 except ImportError :
5659 return None
5760
@@ -71,6 +74,14 @@ async def run(
7174 This method bridges Harbor's BaseEnvironment.exec() to PraisonAI's tool system.
7275 """
7376
77+ # Inject API keys from Harbor's --ae env vars into host os.environ
78+ # so litellm can pick them up (--ae only sets them inside Docker, not the host)
79+ agent_env = getattr (context , 'env' , {}) or {}
80+ for key , val in agent_env .items ():
81+ if key not in os .environ and val :
82+ os .environ [key ] = val
83+ print (f"[ENV] Set { key } from Harbor agent env" )
84+
7485 # Set auto-approval for container-isolated execution
7586 # Harbor's container provides isolation, so we can safely auto-approve shell commands
7687 registry = get_approval_registry ()
@@ -84,9 +95,10 @@ async def bash_tool(command: str) -> str:
8495 if not command .strip ():
8596 return "Error: Empty command provided"
8697
98+ print (f"[CMD] { command [:200 ]} " )
8799 try :
88100 # Execute command in Harbor's container
89- result = await environment .exec (command = command , timeout_sec = 30 )
101+ result = await environment .exec (command = command , timeout_sec = 300 )
90102
91103 # Format output similar to PraisonAI's execute_command tool
92104 output_parts = []
@@ -97,9 +109,12 @@ async def bash_tool(command: str) -> str:
97109 if result .return_code != 0 :
98110 output_parts .append (f"[exit_code]: { result .return_code } " )
99111
100- return "\n " .join (output_parts ) if output_parts else "(no output)"
112+ output = "\n " .join (output_parts ) if output_parts else "(no output)"
113+ print (f"[OUT] { output [:300 ]} " )
114+ return output
101115
102116 except Exception as e :
117+ print (f"[ERR] { str (e )} " )
103118 return f"Error executing command: { str (e )} "
104119
105120 # Create PraisonAI agent with the bash tool
@@ -108,16 +123,48 @@ async def bash_tool(command: str) -> str:
108123 instructions = (
109124 "You are an expert terminal agent working on coding and system administration tasks. "
110125 "Use the bash_tool to execute shell commands in the sandboxed environment. "
111- "Be precise, verify your work, and complete the task step by step. "
112- "Always check if your solution works by running appropriate tests."
126+ "\n \n CRITICAL RULES:"
127+ "\n 1. ALWAYS start by running: ls /app/ && ls /tests/ 2>/dev/null && cat /app/instruction.md 2>/dev/null || true"
128+ " to understand what files exist before doing anything else."
129+ "\n 2. For writing files with special characters (quotes, backslashes, parentheses), "
130+ " ALWAYS use Python or a heredoc instead of echo. Example: "
131+ " python3 -c \" with open('/app/out.html','w') as f: f.write('<html>...</html>')\" "
132+ " OR: cat > /app/file.txt << 'HEREDOC'\\ n...content...\\ nHEREDOC"
133+ "\n 3. For long-running commands (compile, install, git clone), they have up to 5 minutes "
134+ " to complete — be patient and check the result."
135+ "\n 4. After completing the task, ALWAYS run the verification script and check its output. "
136+ " If /app/test_outputs.py exists, run it and show its full output. "
137+ " Only stop when the test passes or you have exhausted all approaches."
138+ "\n 5. Read error messages carefully — they tell you exactly what to fix next."
139+ "\n 6. Read ALL relevant source files in /app/ before attempting a solution. "
140+ " For example: cat /app/*.py to understand exactly how the code works, "
141+ " what it checks, and what edge cases or quirks you can exploit or work around."
142+ "\n 7. NEVER stop to describe or explain what you plan to do. ALWAYS immediately call "
143+ " bash_tool to execute commands. Do NOT write analysis or explanations without "
144+ " also running the actual commands. Keep using bash_tool until the task is fully "
145+ " complete and verified. The task is NOT done until you have run the verification."
113146 ),
114147 tools = [bash_tool ],
115148 llm = self .model_name or "openai/gpt-4o" ,
116149 )
117150
118- # Execute the agent
151+ # Execute the agent - loop until done or max iterations
119152 print (f"🚀 PraisonAI Agent starting task: { instruction [:100 ]} ..." )
120- result = await agent .astart (instruction )
153+ result = await agent .achat (instruction )
154+ for _iter in range (49 ):
155+ done_signals = [
156+ "task complete" , "task is complete" , "task done" ,
157+ "verification passed" , "test passed" , "score:" , "reward:" ,
158+ "DONE" , "completed successfully" , "all tests pass" ,
159+ ]
160+ result_lower = str (result ).lower ()
161+ if any (sig .lower () in result_lower for sig in done_signals ):
162+ break
163+ result = await agent .achat (
164+ "Continue working on the task. If you haven't completed it yet, "
165+ "keep running bash_tool commands. Only stop when the verification "
166+ "test passes. What is your next action?"
167+ )
121168 print (f"✅ PraisonAI Agent completed task" )
122169
123170 # Populate Harbor context with metadata
@@ -142,16 +189,18 @@ def _populate_context(self, agent: Agent, context: AgentContext, result: Any) ->
142189 """
143190 try :
144191 # Extract token usage and cost from agent
145- summary = agent .cost_summary ()
146- if summary :
147- context .n_input_tokens = summary .get ('tokens_in' )
148- context .n_output_tokens = summary .get ('tokens_out' )
149- context .cost_usd = summary .get ('cost' )
150- else :
151- # Fallback to direct properties
152- context .n_input_tokens = getattr (agent , '_total_tokens_in' , 0 )
153- context .n_output_tokens = getattr (agent , '_total_tokens_out' , 0 )
154- context .cost_usd = agent .total_cost
192+ try :
193+ summary = agent .cost_summary () if callable (getattr (agent , 'cost_summary' , None )) else None
194+ if isinstance (summary , dict ):
195+ context .n_input_tokens = summary .get ('tokens_in' )
196+ context .n_output_tokens = summary .get ('tokens_out' )
197+ context .cost_usd = summary .get ('cost' )
198+ else :
199+ context .n_input_tokens = getattr (agent , '_total_tokens_in' , 0 )
200+ context .n_output_tokens = getattr (agent , '_total_tokens_out' , 0 )
201+ context .cost_usd = getattr (agent , 'total_cost' , None )
202+ except Exception :
203+ pass
155204
156205 # Store result summary and agent info
157206 context .metadata = {
0 commit comments