chore: remove verbose prints and add agent/memory tool registration in assistant core

This commit is contained in:
2025-11-04 04:57:23 +00:00
parent 5d42e8d377
commit e815e2e2a3
16 changed files with 787 additions and 55 deletions
+2 -2
View File
@@ -164,7 +164,7 @@ Break down the task and delegate subtasks to appropriate agents. Coordinate thei
return results
def get_session_summary(self) -> Dict[str, Any]:
def get_session_summary(self) -> str:
summary = {
'session_id': self.session_id,
'active_agents': len(self.active_agents),
@@ -178,7 +178,7 @@ Break down the task and delegate subtasks to appropriate agents. Coordinate thei
for agent_id, agent in self.active_agents.items()
]
}
return summary
return json.dumps(summary)
def clear_session(self):
self.active_agents.clear()
+1 -19
View File
@@ -14,12 +14,9 @@ def run_autonomous_mode(assistant, task):
logger.debug(f"=== AUTONOMOUS MODE START ===")
logger.debug(f"Task: {task}")
if assistant.verbose:
print_autonomous_header(task)
assistant.messages.append({
"role": "user",
"content": f"AUTONOMOUS TASK: {task}\n\nPlease work on this task step by step. Use tools as needed. When the task is fully complete, clearly state 'Task complete'."
"content": f"{task}"
})
try:
@@ -29,17 +26,11 @@ def run_autonomous_mode(assistant, task):
logger.debug(f"--- Autonomous iteration {assistant.autonomous_iterations} ---")
logger.debug(f"Messages before context management: {len(assistant.messages)}")
if assistant.verbose:
print(f"\n{Colors.BOLD}{Colors.MAGENTA}{'' * 3} Iteration {assistant.autonomous_iterations} {'' * 3}{Colors.RESET}\n")
from pr.core.context import manage_context_window
assistant.messages = manage_context_window(assistant.messages, assistant.verbose)
logger.debug(f"Messages after context management: {len(assistant.messages)}")
if assistant.verbose:
print(f"{Colors.GRAY}Calling API...{Colors.RESET}")
from pr.core.api import call_api
from pr.tools.base import get_tools_definition
response = call_api(
@@ -67,9 +58,6 @@ def run_autonomous_mode(assistant, task):
logger.debug(f"=== AUTONOMOUS MODE COMPLETE ===")
logger.debug(f"Total iterations: {assistant.autonomous_iterations}")
logger.debug(f"Final message count: {len(assistant.messages)}")
print(f"{Colors.BOLD}Total Iterations:{Colors.RESET} {assistant.autonomous_iterations}")
print(f"{Colors.BOLD}Messages in Context:{Colors.RESET} {len(assistant.messages)}\n")
break
result = process_response_autonomous(assistant, response)
@@ -97,16 +85,12 @@ def process_response_autonomous(assistant, response):
assistant.messages.append(message)
if 'tool_calls' in message and message['tool_calls']:
print(f"{Colors.BOLD}{Colors.CYAN}🔧 Executing {len(message['tool_calls'])} tool(s)...{Colors.RESET}\n")
tool_results = []
for tool_call in message['tool_calls']:
func_name = tool_call['function']['name']
arguments = json.loads(tool_call['function']['arguments'])
display_tool_call(func_name, arguments, "running")
result = execute_single_tool(assistant, func_name, arguments)
result = truncate_tool_result(result)
@@ -121,8 +105,6 @@ def process_response_autonomous(assistant, response):
for result in tool_results:
assistant.messages.append(result)
print(f"{Colors.GRAY}Processing tool results...{Colors.RESET}\n")
from pr.core.api import call_api
from pr.tools.base import get_tools_definition
follow_up = call_api(
+2
View File
@@ -47,6 +47,8 @@ class AdvancedContextManager:
return complexity
def extract_key_sentences(self, text: str, top_k: int = 5) -> List[str]:
if not text.strip():
return []
sentences = re.split(r'(?<=[.!?])\s+', text)
if not sentences:
return []
+18 -1
View File
@@ -133,6 +133,18 @@ class Assistant:
'display_edit_summary': lambda **kw: display_edit_summary(),
'display_edit_timeline': lambda **kw: display_edit_timeline(**kw),
'clear_edit_tracker': lambda **kw: clear_edit_tracker(),
'create_agent': lambda **kw: create_agent(**kw),
'list_agents': lambda **kw: list_agents(**kw),
'execute_agent_task': lambda **kw: execute_agent_task(**kw),
'remove_agent': lambda **kw: remove_agent(**kw),
'collaborate_agents': lambda **kw: collaborate_agents(**kw),
'add_knowledge_entry': lambda **kw: add_knowledge_entry(**kw),
'get_knowledge_entry': lambda **kw: get_knowledge_entry(**kw),
'search_knowledge': lambda **kw: search_knowledge(**kw),
'get_knowledge_by_category': lambda **kw: get_knowledge_by_category(**kw),
'update_knowledge_importance': lambda **kw: update_knowledge_importance(**kw),
'delete_knowledge_entry': lambda **kw: delete_knowledge_entry(**kw),
'get_knowledge_statistics': lambda **kw: get_knowledge_statistics(**kw),
}
if func_name in func_map:
@@ -230,6 +242,7 @@ class Assistant:
path_options = [p + os.sep if os.path.isdir(p) else p for p in path_options]
combined_options = sorted(list(set(options + path_options)))
#combined_options.extend(self.commands)
if state < len(combined_options):
return combined_options[state]
@@ -279,7 +292,8 @@ class Assistant:
else:
message = sys.stdin.read()
process_message(self, message)
from pr.autonomous.mode import run_autonomous_mode
run_autonomous_mode(self, message)
def cleanup(self):
if hasattr(self, 'enhanced') and self.enhanced:
@@ -299,9 +313,12 @@ class Assistant:
def run(self):
try:
print(f"DEBUG: interactive={self.args.interactive}, message={self.args.message}, isatty={sys.stdin.isatty()}")
if self.args.interactive or (not self.args.message and sys.stdin.isatty()):
print("DEBUG: calling run_repl")
self.run_repl()
else:
print("DEBUG: calling run_single")
self.run_single()
finally:
self.cleanup()
+6
View File
@@ -166,6 +166,8 @@ class RPEditor:
def save_file(self):
"""Thread-safe save file command."""
if not self.running:
return self._save_file()
try:
self.client_sock.send(pickle.dumps({'command': 'save_file'}))
except:
@@ -630,6 +632,10 @@ class RPEditor:
def set_text(self, text):
"""Thread-safe text setting."""
if not self.running:
with self.lock:
self._set_text(text)
return
try:
self.client_sock.send(pickle.dumps({'command': 'set_text', 'text': text}))
except:
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""
Advanced input handler for PR Assistant with editor mode, file inclusion, and image support.
"""
import os
import re
import base64
import mimetypes
import readline
import glob
from pathlib import Path
from typing import Optional
# from pr.ui.colors import Colors # Avoid import issues
class AdvancedInputHandler:
"""Handles advanced input with editor mode, file inclusion, and image support."""
def __init__(self):
self.editor_mode = False
self.setup_readline()
def setup_readline(self):
"""Setup readline with basic completer."""
try:
# Simple completer that doesn't interfere
def completer(text, state):
return None
readline.set_completer(completer)
readline.parse_and_bind('tab: complete')
except:
pass # Readline not available
def toggle_editor_mode(self):
"""Toggle between simple and editor input modes."""
self.editor_mode = not self.editor_mode
mode = "Editor" if self.editor_mode else "Simple"
print(f"\nSwitched to {mode.lower()} input mode.")
def get_input(self, prompt: str = "You> ") -> Optional[str]:
"""Get input from user, handling different modes."""
try:
if self.editor_mode:
return self._get_editor_input(prompt)
else:
return self._get_simple_input(prompt)
except KeyboardInterrupt:
return None
except EOFError:
return None
def _get_simple_input(self, prompt: str) -> Optional[str]:
"""Get simple input with file completion."""
try:
user_input = input(prompt).strip()
if not user_input:
return ""
# Check for special commands
if user_input.lower() == '/editor':
self.toggle_editor_mode()
return self.get_input(prompt) # Recurse to get new input
# Process file inclusions and images
processed_input = self._process_input(user_input)
return processed_input
except KeyboardInterrupt:
return None
def _get_editor_input(self, prompt: str) -> Optional[str]:
"""Get multi-line input for editor mode."""
try:
print("Editor mode: Enter your message. Type 'END' on a new line to finish.")
print("Type '/simple' to switch back to simple mode.")
lines = []
while True:
try:
line = input()
if line.strip().lower() == 'end':
break
elif line.strip().lower() == '/simple':
self.toggle_editor_mode()
return self.get_input(prompt) # Switch back and get input
lines.append(line)
except EOFError:
break
content = '\n'.join(lines).strip()
if not content:
return ""
# Process file inclusions and images
processed_content = self._process_input(content)
return processed_content
except KeyboardInterrupt:
return None
def _process_input(self, text: str) -> str:
"""Process input text for file inclusions and images."""
# Process @[filename] inclusions
text = self._process_file_inclusions(text)
# Process image inclusions (look for image file paths)
text = self._process_image_inclusions(text)
return text
def _process_file_inclusions(self, text: str) -> str:
"""Replace @[filename] with file contents."""
def replace_file(match):
filename = match.group(1).strip()
try:
path = Path(filename).expanduser().resolve()
if path.exists() and path.is_file():
with open(path, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
return f"\n--- File: {filename} ---\n{content}\n--- End of {filename} ---\n"
else:
return f"[File not found: {filename}]"
except Exception as e:
return f"[Error reading file {filename}: {e}]"
# Replace @[filename] patterns
pattern = r'@\[([^\]]+)\]'
return re.sub(pattern, replace_file, text)
def _process_image_inclusions(self, text: str) -> str:
"""Process image file references and encode them."""
# Find potential image file paths
words = text.split()
processed_parts = []
for word in words:
# Check if it's a file path that exists and is an image
try:
path = Path(word.strip()).expanduser().resolve()
if path.exists() and path.is_file():
mime_type, _ = mimetypes.guess_type(str(path))
if mime_type and mime_type.startswith('image/'):
# Encode image
with open(path, 'rb') as f:
image_data = base64.b64encode(f.read()).decode('utf-8')
# Replace with data URL
processed_parts.append(f"[Image: {path.name}]\ndata:{mime_type};base64,{image_data}\n")
continue
except:
pass
processed_parts.append(word)
return ' '.join(processed_parts)
# Global instance
input_handler = AdvancedInputHandler()
def get_advanced_input(prompt: str = "You> ") -> Optional[str]:
"""Get advanced input from user."""
return input_handler.get_input(prompt)
+5 -1
View File
@@ -8,6 +8,8 @@ from pr.tools.database import db_set, db_get, db_query
from pr.tools.web import http_fetch, web_search, web_search_news
from pr.tools.python_exec import python_exec
from pr.tools.patch import apply_patch, create_diff
from pr.tools.agents import create_agent, list_agents, execute_agent_task, remove_agent, collaborate_agents
from pr.tools.memory import add_knowledge_entry, get_knowledge_entry, search_knowledge, get_knowledge_by_category, update_knowledge_importance, delete_knowledge_entry, get_knowledge_statistics
__all__ = [
'get_tools_definition',
@@ -17,5 +19,7 @@ __all__ = [
'db_set', 'db_get', 'db_query',
'http_fetch', 'web_search', 'web_search_news',
'python_exec','tail_process', 'kill_process',
'apply_patch', 'create_diff'
'apply_patch', 'create_diff',
'create_agent', 'list_agents', 'execute_agent_task', 'remove_agent', 'collaborate_agents',
'add_knowledge_entry', 'get_knowledge_entry', 'search_knowledge', 'get_knowledge_by_category', 'update_knowledge_importance', 'delete_knowledge_entry', 'get_knowledge_statistics'
]
+64
View File
@@ -0,0 +1,64 @@
import os
from typing import Dict, Any, List
from pr.agents.agent_manager import AgentManager
from pr.core.api import call_api
def create_agent(role_name: str, agent_id: str = None) -> Dict[str, Any]:
"""Create a new agent with the specified role."""
try:
# Get db_path from environment or default
db_path = os.environ.get('ASSISTANT_DB_PATH', '~/.assistant_db.sqlite')
db_path = os.path.expanduser(db_path)
manager = AgentManager(db_path, call_api)
agent_id = manager.create_agent(role_name, agent_id)
return {"status": "success", "agent_id": agent_id, "role": role_name}
except Exception as e:
return {"status": "error", "error": str(e)}
def list_agents() -> Dict[str, Any]:
"""List all active agents."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
manager = AgentManager(db_path, call_api)
agents = []
for agent_id, agent in manager.active_agents.items():
agents.append({
"agent_id": agent_id,
"role": agent.role.name,
"task_count": agent.task_count,
"message_count": len(agent.message_history)
})
return {"status": "success", "agents": agents}
except Exception as e:
return {"status": "error", "error": str(e)}
def execute_agent_task(agent_id: str, task: str, context: Dict[str, Any] = None) -> Dict[str, Any]:
"""Execute a task with the specified agent."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
manager = AgentManager(db_path, call_api)
result = manager.execute_agent_task(agent_id, task, context)
return result
except Exception as e:
return {"status": "error", "error": str(e)}
def remove_agent(agent_id: str) -> Dict[str, Any]:
"""Remove an agent."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
manager = AgentManager(db_path, call_api)
success = manager.remove_agent(agent_id)
return {"status": "success" if success else "not_found", "agent_id": agent_id}
except Exception as e:
return {"status": "error", "error": str(e)}
def collaborate_agents(orchestrator_id: str, task: str, agent_roles: List[str]) -> Dict[str, Any]:
"""Collaborate multiple agents on a task."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
manager = AgentManager(db_path, call_api)
result = manager.collaborate_agents(orchestrator_id, task, agent_roles)
return result
except Exception as e:
return {"status": "error", "error": str(e)}
+99
View File
@@ -0,0 +1,99 @@
import os
from typing import Dict, Any, List
from pr.memory.knowledge_store import KnowledgeStore, KnowledgeEntry
import time
import uuid
def add_knowledge_entry(category: str, content: str, metadata: Dict[str, Any] = None, entry_id: str = None) -> Dict[str, Any]:
"""Add a new entry to the knowledge base."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
if entry_id is None:
entry_id = str(uuid.uuid4())[:16]
entry = KnowledgeEntry(
entry_id=entry_id,
category=category,
content=content,
metadata=metadata or {},
created_at=time.time(),
updated_at=time.time()
)
store.add_entry(entry)
return {"status": "success", "entry_id": entry_id}
except Exception as e:
return {"status": "error", "error": str(e)}
def get_knowledge_entry(entry_id: str) -> Dict[str, Any]:
"""Retrieve a knowledge entry by ID."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
entry = store.get_entry(entry_id)
if entry:
return {"status": "success", "entry": entry.to_dict()}
else:
return {"status": "not_found", "entry_id": entry_id}
except Exception as e:
return {"status": "error", "error": str(e)}
def search_knowledge(query: str, category: str = None, top_k: int = 5) -> Dict[str, Any]:
"""Search the knowledge base semantically."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
entries = store.search_entries(query, category, top_k)
results = [entry.to_dict() for entry in entries]
return {"status": "success", "results": results}
except Exception as e:
return {"status": "error", "error": str(e)}
def get_knowledge_by_category(category: str, limit: int = 20) -> Dict[str, Any]:
"""Get knowledge entries by category."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
entries = store.get_by_category(category, limit)
results = [entry.to_dict() for entry in entries]
return {"status": "success", "entries": results}
except Exception as e:
return {"status": "error", "error": str(e)}
def update_knowledge_importance(entry_id: str, importance_score: float) -> Dict[str, Any]:
"""Update the importance score of a knowledge entry."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
store.update_importance(entry_id, importance_score)
return {"status": "success", "entry_id": entry_id, "importance_score": importance_score}
except Exception as e:
return {"status": "error", "error": str(e)}
def delete_knowledge_entry(entry_id: str) -> Dict[str, Any]:
"""Delete a knowledge entry."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
success = store.delete_entry(entry_id)
return {"status": "success" if success else "not_found", "entry_id": entry_id}
except Exception as e:
return {"status": "error", "error": str(e)}
def get_knowledge_statistics() -> Dict[str, Any]:
"""Get statistics about the knowledge base."""
try:
db_path = os.path.expanduser('~/.assistant_db.sqlite')
store = KnowledgeStore(db_path)
stats = store.get_statistics()
return {"status": "success", "statistics": stats}
except Exception as e:
return {"status": "error", "error": str(e)}
+7 -32
View File
@@ -3,41 +3,16 @@ from typing import Dict, Any
from pr.ui.colors import Colors
def display_tool_call(tool_name, arguments, status="running", result=None):
status_icons = {
"running": ("", Colors.YELLOW),
"success": ("", Colors.GREEN),
"error": ("", Colors.RED)
}
if status == "running":
return
icon, color = status_icons.get(status, ("", Colors.WHITE))
args_str = ", ".join([f"{k}={str(v)[:20]}" for k, v in list(arguments.items())[:2]])
line = f"{tool_name}({args_str})"
print(f"\n{Colors.BOLD}{'' * 80}{Colors.RESET}")
print(f"{color}{icon} {Colors.BOLD}{Colors.CYAN}TOOL: {tool_name}{Colors.RESET}")
print(f"{Colors.BOLD}{'' * 80}{Colors.RESET}")
if len(line) > 80:
line = line[:77] + "..."
if arguments:
print(f"{Colors.YELLOW}Parameters:{Colors.RESET}")
for key, value in arguments.items():
value_str = str(value)
if len(value_str) > 100:
value_str = value_str[:100] + "..."
print(f" {Colors.CYAN}{key}:{Colors.RESET} {value_str}")
if result is not None and status != "running":
print(f"\n{Colors.YELLOW}Result:{Colors.RESET}")
result_str = json.dumps(result, indent=2) if isinstance(result, dict) else str(result)
if len(result_str) > 500:
result_str = result_str[:500] + f"\n{Colors.GRAY}... (truncated){Colors.RESET}"
if status == "success":
print(f"{Colors.GREEN}{result_str}{Colors.RESET}")
elif status == "error":
print(f"{Colors.RED}{result_str}{Colors.RESET}")
else:
print(result_str)
print(f"{Colors.BOLD}{'' * 80}{Colors.RESET}\n")
print(f"{Colors.GRAY}{line}{Colors.RESET}")
def print_autonomous_header(task):
print(f"{Colors.BOLD}Task:{Colors.RESET} {task}")