Add action explanation and follow-up functionality to MachaChatSession
- Introduced `explain_action` method to provide detailed explanations for pending actions in the approval queue. - Added `answer_action_followup` method to handle user follow-up questions regarding proposed actions. - Updated `main` function to support discussion mode with action explanations and follow-ups. - Refactored `conversation.py` to utilize the unified chat implementation from `chat.py`, enhancing compatibility and functionality. - Enhanced error handling for file operations and user input validation in both new methods.
This commit is contained in:
139
chat.py
139
chat.py
@@ -348,10 +348,147 @@ class MachaChatSession:
|
||||
"""
|
||||
response = self.process_message(question, verbose=verbose)
|
||||
return response
|
||||
|
||||
def explain_action(self, action_index: int) -> str:
|
||||
"""Explain a pending action from the approval queue"""
|
||||
# Get action from approval queue
|
||||
approval_queue_file = self.agent.state_dir / "approval_queue.json"
|
||||
|
||||
if not approval_queue_file.exists():
|
||||
return "Error: No approval queue found."
|
||||
|
||||
try:
|
||||
with open(approval_queue_file, 'r') as f:
|
||||
queue = json.load(f)
|
||||
|
||||
if not (0 <= action_index < len(queue)):
|
||||
return f"Error: Action #{action_index} not found in approval queue (queue has {len(queue)} items)."
|
||||
|
||||
action_item = queue[action_index]
|
||||
except Exception as e:
|
||||
return f"Error reading approval queue: {e}"
|
||||
|
||||
action = action_item.get("action", {})
|
||||
context = action_item.get("context", {})
|
||||
timestamp = action_item.get("timestamp", "unknown")
|
||||
|
||||
# Build explanation prompt
|
||||
prompt = f"""You are Macha, explaining a proposed system action to the user.
|
||||
|
||||
ACTION DETAILS:
|
||||
- Proposed Action: {action.get('proposed_action', 'N/A')}
|
||||
- Action Type: {action.get('action_type', 'N/A')}
|
||||
- Risk Level: {action.get('risk_level', 'N/A')}
|
||||
- Diagnosis: {action.get('diagnosis', 'N/A')}
|
||||
- Commands to execute: {', '.join(action.get('commands', []))}
|
||||
- Timestamp: {timestamp}
|
||||
|
||||
SYSTEM CONTEXT:
|
||||
{json.dumps(context, indent=2)}
|
||||
|
||||
Please provide a clear, concise explanation of:
|
||||
1. What problem was detected
|
||||
2. What this action will do to fix it
|
||||
3. Why this approach was chosen
|
||||
4. Any potential risks or side effects
|
||||
5. Expected outcome
|
||||
|
||||
Be conversational and helpful. Use plain language, not technical jargon unless necessary."""
|
||||
|
||||
try:
|
||||
response = self.agent._query_ollama(prompt, temperature=0.7)
|
||||
return response
|
||||
except Exception as e:
|
||||
return f"Error generating explanation: {e}"
|
||||
|
||||
def answer_action_followup(self, action_index: int, user_question: str) -> str:
|
||||
"""Answer a follow-up question about a pending action"""
|
||||
# Get action from approval queue
|
||||
approval_queue_file = self.agent.state_dir / "approval_queue.json"
|
||||
|
||||
if not approval_queue_file.exists():
|
||||
return "Error: No approval queue found."
|
||||
|
||||
try:
|
||||
with open(approval_queue_file, 'r') as f:
|
||||
queue = json.load(f)
|
||||
|
||||
if not (0 <= action_index < len(queue)):
|
||||
return f"Error: Action #{action_index} not found."
|
||||
|
||||
action_item = queue[action_index]
|
||||
except Exception as e:
|
||||
return f"Error reading approval queue: {e}"
|
||||
|
||||
action = action_item.get("action", {})
|
||||
context = action_item.get("context", {})
|
||||
|
||||
# Build follow-up prompt
|
||||
prompt = f"""You are Macha, answering a follow-up question about a proposed action.
|
||||
|
||||
ACTION SUMMARY:
|
||||
- Proposed: {action.get('proposed_action', 'N/A')}
|
||||
- Type: {action.get('action_type', 'N/A')}
|
||||
- Risk: {action.get('risk_level', 'N/A')}
|
||||
- Diagnosis: {action.get('diagnosis', 'N/A')}
|
||||
- Commands: {', '.join(action.get('commands', []))}
|
||||
|
||||
SYSTEM CONTEXT:
|
||||
{json.dumps(context, indent=2)[:2000]}
|
||||
|
||||
USER'S QUESTION:
|
||||
{user_question}
|
||||
|
||||
Please answer the user's question clearly and honestly. If you're uncertain about something, say so. Focus on helping them make an informed decision about whether to approve this action."""
|
||||
|
||||
try:
|
||||
response = self.agent._query_ollama(prompt, temperature=0.7)
|
||||
return response
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for macha-chat"""
|
||||
"""Main entry point for macha-chat and conversation.py"""
|
||||
|
||||
# Check for --discuss flag (used by macha-approve discuss)
|
||||
if "--discuss" in sys.argv:
|
||||
try:
|
||||
discuss_index = sys.argv.index("--discuss")
|
||||
if discuss_index + 1 >= len(sys.argv):
|
||||
print("Error: --discuss requires an action number", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
action_number = int(sys.argv[discuss_index + 1])
|
||||
|
||||
session = MachaChatSession()
|
||||
|
||||
# Check if this is a follow-up question or initial explanation
|
||||
if "--follow-up" in sys.argv:
|
||||
followup_index = sys.argv.index("--follow-up")
|
||||
if followup_index + 1 >= len(sys.argv):
|
||||
print("Error: --follow-up requires a question", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Get the rest of the arguments as the question
|
||||
question = " ".join(sys.argv[followup_index + 1:])
|
||||
response = session.answer_action_followup(action_number, question)
|
||||
print(response)
|
||||
else:
|
||||
# Initial explanation
|
||||
explanation = session.explain_action(action_number)
|
||||
print(explanation)
|
||||
|
||||
return
|
||||
|
||||
except (ValueError, IndexError) as e:
|
||||
print(f"Error: Invalid action number: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Normal interactive chat mode
|
||||
session = MachaChatSession()
|
||||
session.run_interactive()
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
"""
|
||||
Macha conversation interface - legacy compatibility wrapper.
|
||||
This module now uses the unified chat.py implementation.
|
||||
All discussion functionality has been moved to chat.py.
|
||||
"""
|
||||
|
||||
# Import the unified implementation
|
||||
from chat import ask_main
|
||||
from chat import main as chat_main
|
||||
|
||||
# Entry point
|
||||
if __name__ == "__main__":
|
||||
ask_main()
|
||||
chat_main()
|
||||
|
||||
@@ -20,6 +20,7 @@ from remote_monitor import RemoteMonitor
|
||||
from config_parser import ConfigParser
|
||||
from system_discovery import SystemDiscovery
|
||||
from issue_tracker import IssueTracker
|
||||
from git_context import GitContext
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -63,6 +64,20 @@ class MachaOrchestrator:
|
||||
except Exception as e:
|
||||
self._log(f"Warning: Could not initialize config parser: {e}")
|
||||
|
||||
# Initialize git context
|
||||
self.git_context = None
|
||||
if self.config_parser:
|
||||
try:
|
||||
# Use the same local repo path as config_parser
|
||||
local_repo_path = Path("/var/lib/macha/config-repo")
|
||||
if local_repo_path.exists():
|
||||
self.git_context = GitContext(repo_path=str(local_repo_path))
|
||||
self._log(f"Git context initialized for {local_repo_path}")
|
||||
else:
|
||||
self._log(f"Warning: Config repo not found at {local_repo_path}")
|
||||
except Exception as e:
|
||||
self._log(f"Warning: Could not initialize git context: {e}")
|
||||
|
||||
# Initialize components
|
||||
self.monitor = SystemMonitor(state_dir)
|
||||
self.agent = MachaAgent(
|
||||
@@ -667,6 +682,22 @@ class MachaOrchestrator:
|
||||
if self._cycle_count % 10 == 1: # First cycle and every 10th
|
||||
self._update_service_registry()
|
||||
|
||||
# Refresh configuration repository every 3 cycles (~15 min) to keep git context current
|
||||
# This ensures git_context has up-to-date information about recent config changes
|
||||
if self._cycle_count % 3 == 1 and self.config_parser:
|
||||
try:
|
||||
self._log("Refreshing configuration repository...")
|
||||
if self.config_parser.ensure_repo():
|
||||
self._log("✓ Configuration repository updated")
|
||||
# Reinitialize git_context if it exists to pick up fresh data
|
||||
if self.git_context:
|
||||
local_repo_path = Path("/var/lib/macha/config-repo")
|
||||
self.git_context = GitContext(repo_path=str(local_repo_path))
|
||||
else:
|
||||
self._log("⚠ Could not refresh configuration repository")
|
||||
except Exception as e:
|
||||
self._log(f"⚠ Error refreshing config repo: {e}")
|
||||
|
||||
# Step 1: Monitor system
|
||||
self._log("Collecting system health data...")
|
||||
monitoring_data = self.monitor.collect_all()
|
||||
|
||||
Reference in New Issue
Block a user