feat: integrate knowledge store search and background multiplexer with autonomous monitoring into agent pipeline
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import time
|
||||
from pr.ui import Colors
|
||||
from pr.tools import read_file
|
||||
from pr.tools.base import get_tools_definition
|
||||
@@ -143,6 +144,9 @@ def handle_command(assistant, command):
|
||||
elif cmd == '/stats':
|
||||
show_system_stats(assistant)
|
||||
|
||||
elif cmd.startswith('/bg'):
|
||||
handle_background_command(assistant, command)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -389,3 +393,141 @@ def show_system_stats(assistant):
|
||||
print(f" API cache entries: {cache_stats['api_cache']['valid_entries']}")
|
||||
if 'tool_cache' in cache_stats:
|
||||
print(f" Tool cache entries: {cache_stats['tool_cache']['valid_entries']}")
|
||||
|
||||
def handle_background_command(assistant, command):
|
||||
"""Handle background multiplexer commands."""
|
||||
parts = command.strip().split(maxsplit=2)
|
||||
if len(parts) < 2:
|
||||
print(f"{Colors.RED}Usage: /bg <subcommand> [args]{Colors.RESET}")
|
||||
print(f"{Colors.GRAY}Available subcommands: start, list, status, output, input, kill, events{Colors.RESET}")
|
||||
return
|
||||
|
||||
subcmd = parts[1].lower()
|
||||
|
||||
try:
|
||||
if subcmd == 'start' and len(parts) >= 3:
|
||||
session_name = f"bg_{len(parts[2].split())}_{int(time.time())}"
|
||||
start_background_session(assistant, session_name, parts[2])
|
||||
elif subcmd == 'list':
|
||||
list_background_sessions(assistant)
|
||||
elif subcmd == 'status' and len(parts) >= 3:
|
||||
show_session_status(assistant, parts[2])
|
||||
elif subcmd == 'output' and len(parts) >= 3:
|
||||
show_session_output(assistant, parts[2])
|
||||
elif subcmd == 'input' and len(parts) >= 4:
|
||||
send_session_input(assistant, parts[2], parts[3])
|
||||
elif subcmd == 'kill' and len(parts) >= 3:
|
||||
kill_background_session(assistant, parts[2])
|
||||
elif subcmd == 'events':
|
||||
show_background_events(assistant)
|
||||
else:
|
||||
print(f"{Colors.RED}Unknown background command: {subcmd}{Colors.RESET}")
|
||||
print(f"{Colors.GRAY}Available: start, list, status, output, input, kill, events{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error executing background command: {e}{Colors.RESET}")
|
||||
|
||||
def start_background_session(assistant, session_name, command):
|
||||
"""Start a command in background."""
|
||||
try:
|
||||
from pr.multiplexer import start_background_process
|
||||
result = start_background_process(session_name, command)
|
||||
|
||||
if result['status'] == 'success':
|
||||
print(f"{Colors.GREEN}Started background session '{session_name}' with PID {result['pid']}{Colors.RESET}")
|
||||
else:
|
||||
print(f"{Colors.RED}Failed to start background session: {result.get('error', 'Unknown error')}{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error starting background session: {e}{Colors.RESET}")
|
||||
|
||||
def list_background_sessions(assistant):
|
||||
"""List all background sessions."""
|
||||
try:
|
||||
from pr.ui.display import display_multiplexer_status
|
||||
from pr.multiplexer import get_all_sessions
|
||||
|
||||
sessions = get_all_sessions()
|
||||
display_multiplexer_status(sessions)
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error listing background sessions: {e}{Colors.RESET}")
|
||||
|
||||
def show_session_status(assistant, session_name):
|
||||
"""Show status of a specific session."""
|
||||
try:
|
||||
from pr.multiplexer import get_session_info
|
||||
|
||||
info = get_session_info(session_name)
|
||||
if info:
|
||||
print(f"{Colors.BOLD}Session '{session_name}':{Colors.RESET}")
|
||||
print(f" Status: {info.get('status', 'unknown')}")
|
||||
print(f" PID: {info.get('pid', 'N/A')}")
|
||||
print(f" Command: {info.get('command', 'N/A')}")
|
||||
if 'start_time' in info:
|
||||
import time
|
||||
elapsed = time.time() - info['start_time']
|
||||
print(f" Running for: {elapsed:.1f}s")
|
||||
else:
|
||||
print(f"{Colors.YELLOW}Session '{session_name}' not found{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error getting session status: {e}{Colors.RESET}")
|
||||
|
||||
def show_session_output(assistant, session_name):
|
||||
"""Show output of a specific session."""
|
||||
try:
|
||||
from pr.multiplexer import get_session_output
|
||||
|
||||
output = get_session_output(session_name, lines=50)
|
||||
if output:
|
||||
print(f"{Colors.BOLD}Recent output from '{session_name}':{Colors.RESET}")
|
||||
print(f"{Colors.GRAY}{'─' * 60}{Colors.RESET}")
|
||||
for line in output:
|
||||
print(line)
|
||||
else:
|
||||
print(f"{Colors.YELLOW}No output available for session '{session_name}'{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error getting session output: {e}{Colors.RESET}")
|
||||
|
||||
def send_session_input(assistant, session_name, input_text):
|
||||
"""Send input to a background session."""
|
||||
try:
|
||||
from pr.multiplexer import send_input_to_session
|
||||
|
||||
result = send_input_to_session(session_name, input_text)
|
||||
if result['status'] == 'success':
|
||||
print(f"{Colors.GREEN}Input sent to session '{session_name}'{Colors.RESET}")
|
||||
else:
|
||||
print(f"{Colors.RED}Failed to send input: {result.get('error', 'Unknown error')}{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error sending input: {e}{Colors.RESET}")
|
||||
|
||||
def kill_background_session(assistant, session_name):
|
||||
"""Kill a background session."""
|
||||
try:
|
||||
from pr.multiplexer import kill_session
|
||||
|
||||
result = kill_session(session_name)
|
||||
if result['status'] == 'success':
|
||||
print(f"{Colors.GREEN}Session '{session_name}' terminated{Colors.RESET}")
|
||||
else:
|
||||
print(f"{Colors.RED}Failed to kill session: {result.get('error', 'Unknown error')}{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error killing session: {e}{Colors.RESET}")
|
||||
|
||||
def show_background_events(assistant):
|
||||
"""Show recent background events."""
|
||||
try:
|
||||
from pr.core.background_monitor import get_global_monitor
|
||||
|
||||
monitor = get_global_monitor()
|
||||
events = monitor.get_pending_events()
|
||||
|
||||
if events:
|
||||
print(f"{Colors.BOLD}Recent Background Events:{Colors.RESET}")
|
||||
print(f"{Colors.GRAY}{'─' * 60}{Colors.RESET}")
|
||||
|
||||
for event in events[-10:]: # Show last 10 events
|
||||
from pr.ui.display import display_background_event
|
||||
display_background_event(event)
|
||||
else:
|
||||
print(f"{Colors.GRAY}No recent background events{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error getting background events: {e}{Colors.RESET}")
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
from pr.tools.interactive_control import (
|
||||
list_active_sessions, get_session_status, read_session_output,
|
||||
send_input_to_session, close_interactive_session
|
||||
)
|
||||
from pr.multiplexer import get_multiplexer
|
||||
from pr.tools.prompt_detection import get_global_detector
|
||||
from pr.ui import Colors
|
||||
|
||||
def show_sessions(args=None):
|
||||
"""Show all active multiplexer sessions."""
|
||||
sessions = list_active_sessions()
|
||||
|
||||
if not sessions:
|
||||
print(f"{Colors.YELLOW}No active sessions.{Colors.RESET}")
|
||||
return
|
||||
|
||||
print(f"{Colors.BOLD}Active Sessions:{Colors.RESET}")
|
||||
print("-" * 80)
|
||||
|
||||
for session_name, session_data in sessions.items():
|
||||
metadata = session_data['metadata']
|
||||
output_summary = session_data['output_summary']
|
||||
|
||||
status = get_session_status(session_name)
|
||||
is_active = status.get('is_active', False) if status else False
|
||||
|
||||
status_color = Colors.GREEN if is_active else Colors.RED
|
||||
print(f"{Colors.CYAN}{session_name}{Colors.RESET}: {status_color}{metadata.get('process_type', 'unknown')}{Colors.RESET}")
|
||||
|
||||
if status and 'pid' in status:
|
||||
print(f" PID: {status['pid']}")
|
||||
|
||||
print(f" Age: {metadata.get('start_time', 0):.1f}s")
|
||||
print(f" Output: {output_summary['stdout_lines']} stdout, {output_summary['stderr_lines']} stderr lines")
|
||||
print(f" Interactions: {metadata.get('interaction_count', 0)}")
|
||||
print(f" State: {metadata.get('state', 'unknown')}")
|
||||
print()
|
||||
|
||||
def attach_session(args):
|
||||
"""Attach to a session (show its output and allow interaction)."""
|
||||
if not args or len(args) < 1:
|
||||
print(f"{Colors.RED}Usage: attach_session <session_name>{Colors.RESET}")
|
||||
return
|
||||
|
||||
session_name = args[0]
|
||||
status = get_session_status(session_name)
|
||||
|
||||
if not status:
|
||||
print(f"{Colors.RED}Session '{session_name}' not found.{Colors.RESET}")
|
||||
return
|
||||
|
||||
print(f"{Colors.BOLD}Attaching to session: {session_name}{Colors.RESET}")
|
||||
print(f"Process type: {status.get('metadata', {}).get('process_type', 'unknown')}")
|
||||
print("-" * 50)
|
||||
|
||||
# Show recent output
|
||||
try:
|
||||
output = read_session_output(session_name, lines=20)
|
||||
if output['stdout']:
|
||||
print(f"{Colors.GRAY}Recent stdout:{Colors.RESET}")
|
||||
for line in output['stdout'].split('\n'):
|
||||
if line.strip():
|
||||
print(f" {line}")
|
||||
if output['stderr']:
|
||||
print(f"{Colors.YELLOW}Recent stderr:{Colors.RESET}")
|
||||
for line in output['stderr'].split('\n'):
|
||||
if line.strip():
|
||||
print(f" {line}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error reading output: {e}{Colors.RESET}")
|
||||
|
||||
print(f"\n{Colors.CYAN}Session is {'active' if status.get('is_active') else 'inactive'}{Colors.RESET}")
|
||||
|
||||
def detach_session(args):
|
||||
"""Detach from a session (stop showing its output but keep it running)."""
|
||||
if not args or len(args) < 1:
|
||||
print(f"{Colors.RED}Usage: detach_session <session_name>{Colors.RESET}")
|
||||
return
|
||||
|
||||
session_name = args[0]
|
||||
mux = get_multiplexer(session_name)
|
||||
|
||||
if not mux:
|
||||
print(f"{Colors.RED}Session '{session_name}' not found.{Colors.RESET}")
|
||||
return
|
||||
|
||||
# In this implementation, detaching just means we stop displaying output
|
||||
# The session continues to run in the background
|
||||
mux.show_output = False
|
||||
print(f"{Colors.GREEN}Detached from session '{session_name}'. It continues running in background.{Colors.RESET}")
|
||||
|
||||
def kill_session(args):
|
||||
"""Kill a session forcefully."""
|
||||
if not args or len(args) < 1:
|
||||
print(f"{Colors.RED}Usage: kill_session <session_name>{Colors.RESET}")
|
||||
return
|
||||
|
||||
session_name = args[0]
|
||||
|
||||
try:
|
||||
close_interactive_session(session_name)
|
||||
print(f"{Colors.GREEN}Session '{session_name}' terminated.{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error terminating session '{session_name}': {e}{Colors.RESET}")
|
||||
|
||||
def send_command(args):
|
||||
"""Send a command to a session."""
|
||||
if not args or len(args) < 2:
|
||||
print(f"{Colors.RED}Usage: send_command <session_name> <command>{Colors.RESET}")
|
||||
return
|
||||
|
||||
session_name = args[0]
|
||||
command = ' '.join(args[1:])
|
||||
|
||||
try:
|
||||
send_input_to_session(session_name, command)
|
||||
print(f"{Colors.GREEN}Sent command to '{session_name}': {command}{Colors.RESET}")
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error sending command to '{session_name}': {e}{Colors.RESET}")
|
||||
|
||||
def show_session_log(args):
|
||||
"""Show the full log/output of a session."""
|
||||
if not args or len(args) < 1:
|
||||
print(f"{Colors.RED}Usage: show_session_log <session_name>{Colors.RESET}")
|
||||
return
|
||||
|
||||
session_name = args[0]
|
||||
|
||||
try:
|
||||
output = read_session_output(session_name) # Get all output
|
||||
print(f"{Colors.BOLD}Full log for session: {session_name}{Colors.RESET}")
|
||||
print("=" * 80)
|
||||
|
||||
if output['stdout']:
|
||||
print(f"{Colors.GRAY}STDOUT:{Colors.RESET}")
|
||||
print(output['stdout'])
|
||||
print()
|
||||
|
||||
if output['stderr']:
|
||||
print(f"{Colors.YELLOW}STDERR:{Colors.RESET}")
|
||||
print(output['stderr'])
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error reading log for '{session_name}': {e}{Colors.RESET}")
|
||||
|
||||
def show_session_status(args):
|
||||
"""Show detailed status of a session."""
|
||||
if not args or len(args) < 1:
|
||||
print(f"{Colors.RED}Usage: show_session_status <session_name>{Colors.RESET}")
|
||||
return
|
||||
|
||||
session_name = args[0]
|
||||
status = get_session_status(session_name)
|
||||
|
||||
if not status:
|
||||
print(f"{Colors.RED}Session '{session_name}' not found.{Colors.RESET}")
|
||||
return
|
||||
|
||||
print(f"{Colors.BOLD}Status for session: {session_name}{Colors.RESET}")
|
||||
print("-" * 50)
|
||||
|
||||
metadata = status.get('metadata', {})
|
||||
print(f"Process type: {metadata.get('process_type', 'unknown')}")
|
||||
print(f"Active: {status.get('is_active', False)}")
|
||||
|
||||
if 'pid' in status:
|
||||
print(f"PID: {status['pid']}")
|
||||
|
||||
print(f"Start time: {metadata.get('start_time', 0):.1f}")
|
||||
print(f"Last activity: {metadata.get('last_activity', 0):.1f}")
|
||||
print(f"Interaction count: {metadata.get('interaction_count', 0)}")
|
||||
print(f"State: {metadata.get('state', 'unknown')}")
|
||||
|
||||
output_summary = status.get('output_summary', {})
|
||||
print(f"Output lines: {output_summary.get('stdout_lines', 0)} stdout, {output_summary.get('stderr_lines', 0)} stderr")
|
||||
|
||||
# Show prompt detection info
|
||||
detector = get_global_detector()
|
||||
session_info = detector.get_session_info(session_name)
|
||||
if session_info:
|
||||
print(f"Current state: {session_info['current_state']}")
|
||||
print(f"Is waiting for input: {session_info['is_waiting']}")
|
||||
|
||||
def list_waiting_sessions(args=None):
|
||||
"""List sessions that appear to be waiting for input."""
|
||||
sessions = list_active_sessions()
|
||||
detector = get_global_detector()
|
||||
|
||||
waiting_sessions = []
|
||||
for session_name in sessions:
|
||||
if detector.is_waiting_for_input(session_name):
|
||||
waiting_sessions.append(session_name)
|
||||
|
||||
if not waiting_sessions:
|
||||
print(f"{Colors.GREEN}No sessions are currently waiting for input.{Colors.RESET}")
|
||||
return
|
||||
|
||||
print(f"{Colors.BOLD}Sessions waiting for input:{Colors.RESET}")
|
||||
for session_name in waiting_sessions:
|
||||
status = get_session_status(session_name)
|
||||
if status:
|
||||
process_type = status.get('metadata', {}).get('process_type', 'unknown')
|
||||
print(f" {Colors.CYAN}{session_name}{Colors.RESET} ({process_type})")
|
||||
|
||||
# Show suggestions
|
||||
session_info = detector.get_session_info(session_name)
|
||||
if session_info:
|
||||
suggestions = detector.get_response_suggestions({}, process_type)
|
||||
if suggestions:
|
||||
print(f" Suggested inputs: {', '.join(suggestions[:3])}") # Show first 3
|
||||
print()
|
||||
|
||||
# Command registry for the multiplexer commands
|
||||
MULTIPLEXER_COMMANDS = {
|
||||
'show_sessions': show_sessions,
|
||||
'attach_session': attach_session,
|
||||
'detach_session': detach_session,
|
||||
'kill_session': kill_session,
|
||||
'send_command': send_command,
|
||||
'show_session_log': show_session_log,
|
||||
'show_session_status': show_session_status,
|
||||
'list_waiting_sessions': list_waiting_sessions,
|
||||
}
|
||||
Reference in New Issue
Block a user