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:
Lily Miller
2025-10-09 16:42:28 -06:00
parent cc8334f2c5
commit 782dce4b2d
3 changed files with 172 additions and 3 deletions

139
chat.py
View File

@@ -349,9 +349,146 @@ 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()

View File

@@ -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()

View File

@@ -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()