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
@@ -349,9 +349,146 @@ class MachaChatSession:
|
|||||||
response = self.process_message(question, verbose=verbose)
|
response = self.process_message(question, verbose=verbose)
|
||||||
return response
|
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():
|
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 = MachaChatSession()
|
||||||
session.run_interactive()
|
session.run_interactive()
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
"""
|
"""
|
||||||
Macha conversation interface - legacy compatibility wrapper.
|
Macha conversation interface - legacy compatibility wrapper.
|
||||||
This module now uses the unified chat.py implementation.
|
This module now uses the unified chat.py implementation.
|
||||||
|
All discussion functionality has been moved to chat.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Import the unified implementation
|
# Import the unified implementation
|
||||||
from chat import ask_main
|
from chat import main as chat_main
|
||||||
|
|
||||||
# Entry point
|
# Entry point
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
ask_main()
|
chat_main()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from remote_monitor import RemoteMonitor
|
|||||||
from config_parser import ConfigParser
|
from config_parser import ConfigParser
|
||||||
from system_discovery import SystemDiscovery
|
from system_discovery import SystemDiscovery
|
||||||
from issue_tracker import IssueTracker
|
from issue_tracker import IssueTracker
|
||||||
|
from git_context import GitContext
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
@@ -63,6 +64,20 @@ class MachaOrchestrator:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log(f"Warning: Could not initialize config parser: {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
|
# Initialize components
|
||||||
self.monitor = SystemMonitor(state_dir)
|
self.monitor = SystemMonitor(state_dir)
|
||||||
self.agent = MachaAgent(
|
self.agent = MachaAgent(
|
||||||
@@ -667,6 +682,22 @@ class MachaOrchestrator:
|
|||||||
if self._cycle_count % 10 == 1: # First cycle and every 10th
|
if self._cycle_count % 10 == 1: # First cycle and every 10th
|
||||||
self._update_service_registry()
|
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
|
# Step 1: Monitor system
|
||||||
self._log("Collecting system health data...")
|
self._log("Collecting system health data...")
|
||||||
monitoring_data = self.monitor.collect_all()
|
monitoring_data = self.monitor.collect_all()
|
||||||
|
|||||||
Reference in New Issue
Block a user