Refactor cockpit to use DockerTmuxController pattern
Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
205
lib/chat_intent_parser.py
Normal file
205
lib/chat_intent_parser.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Chat Intent Parser - Determine what type of query the user is making
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, Tuple
|
||||
|
||||
|
||||
class ChatIntentParser:
|
||||
"""Parse user queries to determine intent and scope"""
|
||||
|
||||
# Patterns for different intents
|
||||
PATTERNS = {
|
||||
'kg_search': {
|
||||
'patterns': [
|
||||
r'(search|find|look for|show me).*in.*knowledge|what.*entity|find.*entity',
|
||||
r'(entity|concept|topic).*named?',
|
||||
],
|
||||
'keywords': ['entity', 'concept', 'topic', 'knowledge', 'search']
|
||||
},
|
||||
'project_info': {
|
||||
'patterns': [
|
||||
r'(project|projects).*info|tell.*project',
|
||||
r'what.*project|list.*project|show.*project',
|
||||
],
|
||||
'keywords': ['project', 'projects']
|
||||
},
|
||||
'system_status': {
|
||||
'patterns': [
|
||||
r'(system|status|health|running|services)',
|
||||
r'(disk|memory|cpu|load|uptime)',
|
||||
r'(docker|container|process)',
|
||||
],
|
||||
'keywords': ['system', 'status', 'health', 'disk', 'memory', 'running']
|
||||
},
|
||||
'architecture': {
|
||||
'patterns': [
|
||||
r'(architecture|structure|how.*work|design)',
|
||||
r'(component|module|service).*architecture',
|
||||
],
|
||||
'keywords': ['architecture', 'structure', 'design', 'component']
|
||||
},
|
||||
'help': {
|
||||
'patterns': [
|
||||
r'(help|what can|commands|available)',
|
||||
r'(how.*use|guide|tutorial)',
|
||||
],
|
||||
'keywords': ['help', 'commands', 'guide']
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize parser"""
|
||||
pass
|
||||
|
||||
def parse(self, query: str) -> Dict:
|
||||
"""Parse query and determine intent"""
|
||||
query_lower = query.lower().strip()
|
||||
|
||||
result = {
|
||||
'original_query': query,
|
||||
'query_lower': query_lower,
|
||||
'intent': 'general',
|
||||
'confidence': 0.0,
|
||||
'scope': 'all',
|
||||
'keywords': self._extract_keywords(query_lower),
|
||||
'suggestions': []
|
||||
}
|
||||
|
||||
# Check for explicit scope flags
|
||||
if query_lower.startswith('--kg ') or ' --kg ' in query_lower:
|
||||
result['scope'] = 'kg'
|
||||
query_lower = query_lower.replace('--kg ', '').replace(' --kg ', '')
|
||||
elif query_lower.startswith('--local ') or ' --local ' in query_lower:
|
||||
result['scope'] = 'local_memory'
|
||||
query_lower = query_lower.replace('--local ', '').replace(' --local ', '')
|
||||
elif query_lower.startswith('--bash ') or ' --bash ' in query_lower:
|
||||
result['scope'] = 'bash'
|
||||
query_lower = query_lower.replace('--bash ', '').replace(' --bash ', '')
|
||||
elif query_lower.startswith('--think ') or ' --think ' in query_lower:
|
||||
result['scope'] = 'reasoning'
|
||||
query_lower = query_lower.replace('--think ', '').replace(' --think ', '')
|
||||
|
||||
# Detect intent from patterns
|
||||
best_intent = 'general'
|
||||
best_score = 0.0
|
||||
|
||||
for intent, config in self.PATTERNS.items():
|
||||
score = self._calculate_score(query_lower, config)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_intent = intent
|
||||
|
||||
result['intent'] = best_intent
|
||||
result['confidence'] = min(1.0, best_score)
|
||||
|
||||
# Generate suggestions
|
||||
result['suggestions'] = self._suggest_queries(best_intent, query_lower)
|
||||
|
||||
return result
|
||||
|
||||
def _extract_keywords(self, query: str) -> list:
|
||||
"""Extract important keywords from query"""
|
||||
# Simple keyword extraction - words longer than 4 characters
|
||||
words = re.findall(r'\b[a-z_]{4,}\b', query)
|
||||
# Remove common stop words
|
||||
stop_words = {'what', 'that', 'this', 'with', 'from', 'show', 'tell', 'give', 'find'}
|
||||
keywords = [w for w in words if w not in stop_words]
|
||||
return list(set(keywords))[:5] # Return top 5 unique keywords
|
||||
|
||||
def _calculate_score(self, query: str, config: Dict) -> float:
|
||||
"""Calculate how well query matches intent"""
|
||||
score = 0.0
|
||||
|
||||
# Check patterns
|
||||
for pattern in config['patterns']:
|
||||
if re.search(pattern, query, re.IGNORECASE):
|
||||
score += 0.4
|
||||
|
||||
# Check keywords
|
||||
query_words = set(query.lower().split())
|
||||
matching_keywords = sum(1 for kw in config['keywords'] if kw in query_words)
|
||||
score += min(0.6, matching_keywords * 0.2)
|
||||
|
||||
return score
|
||||
|
||||
def _suggest_queries(self, intent: str, query: str) -> list:
|
||||
"""Suggest related queries based on intent"""
|
||||
suggestions = {
|
||||
'kg_search': [
|
||||
'List all research entities',
|
||||
'Show me recent findings',
|
||||
'What is stored in the sysadmin domain'
|
||||
],
|
||||
'project_info': [
|
||||
'List all projects',
|
||||
'Show project structure',
|
||||
'What projects are active'
|
||||
],
|
||||
'system_status': [
|
||||
'Show disk usage',
|
||||
'List running services',
|
||||
'What is the system load',
|
||||
'Show memory usage'
|
||||
],
|
||||
'architecture': [
|
||||
'Tell me about the system architecture',
|
||||
'Show me the component structure',
|
||||
'How do services communicate'
|
||||
],
|
||||
'help': [
|
||||
'What commands are available',
|
||||
'Show me examples',
|
||||
'How do I search the knowledge graph'
|
||||
]
|
||||
}
|
||||
|
||||
return suggestions.get(intent, [])
|
||||
|
||||
def extract_search_term(self, query: str) -> str:
|
||||
"""Extract main search term from query"""
|
||||
# Remove common prefixes/suffixes
|
||||
query = re.sub(r'^(show|find|search|list|tell|what|how)\s+', '', query, flags=re.IGNORECASE)
|
||||
query = re.sub(r'\s+(please|thanks|help|info|details)$', '', query, flags=re.IGNORECASE)
|
||||
|
||||
# Extract quoted terms first
|
||||
quoted = re.findall(r'"([^"]+)"', query)
|
||||
if quoted:
|
||||
return quoted[0]
|
||||
|
||||
# Otherwise return first significant phrase
|
||||
words = [w for w in query.split() if len(w) > 3]
|
||||
return words[0] if words else query.strip()
|
||||
|
||||
def is_multi_turn(self, query: str) -> bool:
|
||||
"""Check if query suggests multi-turn conversation"""
|
||||
multi_turn_indicators = [
|
||||
'more', 'also', 'next', 'then', 'tell me more',
|
||||
'what else', 'continue', 'go on', 'further'
|
||||
]
|
||||
query_lower = query.lower()
|
||||
return any(indicator in query_lower for indicator in multi_turn_indicators)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
parser = ChatIntentParser()
|
||||
|
||||
test_queries = [
|
||||
'what is the system status',
|
||||
'find me entities in the KG',
|
||||
'list all projects',
|
||||
'tell me about the architecture',
|
||||
'--bash show disk usage',
|
||||
'--think analyze performance patterns'
|
||||
]
|
||||
|
||||
for query in test_queries:
|
||||
result = parser.parse(query)
|
||||
print(f"Query: {query}")
|
||||
print(f"Intent: {result['intent']} (confidence: {result['confidence']:.2f})")
|
||||
print(f"Scope: {result['scope']}")
|
||||
print(f"Keywords: {result['keywords']}")
|
||||
print()
|
||||
Reference in New Issue
Block a user