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:
215
lib/chat_memory_lookup.py
Normal file
215
lib/chat_memory_lookup.py
Normal file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Chat Memory Lookup - Fast local memory queries
|
||||
Queries shared project memory without external calls
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
import time
|
||||
|
||||
|
||||
class ChatMemoryLookup:
|
||||
"""Query local project memory for chat interface"""
|
||||
|
||||
MEMORY_DB = Path('/etc/zen-swarm/memory/projects.db')
|
||||
|
||||
def __init__(self, timeout_ms: int = 150):
|
||||
"""Initialize with query timeout"""
|
||||
self.timeout_ms = timeout_ms
|
||||
self.timeout_seconds = timeout_ms / 1000.0
|
||||
|
||||
def search_entities(self, query: str, limit: int = 10) -> Dict:
|
||||
"""Search for entities by name"""
|
||||
if not self.MEMORY_DB.exists():
|
||||
return {'error': 'Memory database not found', 'entities': []}
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(self.MEMORY_DB), timeout=self.timeout_seconds)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(
|
||||
"SELECT id, name, type FROM entities WHERE name LIKE ? LIMIT ?",
|
||||
(f'%{query}%', limit)
|
||||
)
|
||||
|
||||
entities = [
|
||||
{
|
||||
'id': row['id'],
|
||||
'name': row['name'],
|
||||
'type': row['type']
|
||||
}
|
||||
for row in cursor.fetchall()
|
||||
]
|
||||
|
||||
conn.close()
|
||||
return {'entities': entities, 'count': len(entities)}
|
||||
|
||||
except Exception as e:
|
||||
return {'error': str(e), 'entities': []}
|
||||
|
||||
def get_entity(self, entity_name: str) -> Dict:
|
||||
"""Get entity and its relations"""
|
||||
if not self.MEMORY_DB.exists():
|
||||
return {'error': 'Memory database not found'}
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(self.MEMORY_DB), timeout=self.timeout_seconds)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get entity
|
||||
cursor.execute(
|
||||
"SELECT id, name, type FROM entities WHERE name = ?",
|
||||
(entity_name,)
|
||||
)
|
||||
entity_row = cursor.fetchone()
|
||||
|
||||
if not entity_row:
|
||||
conn.close()
|
||||
return {'error': f'Entity {entity_name} not found'}
|
||||
|
||||
entity_id = entity_row['id']
|
||||
entity = {
|
||||
'name': entity_row['name'],
|
||||
'type': entity_row['type'],
|
||||
'relations': []
|
||||
}
|
||||
|
||||
# Get relations (join to get entity names)
|
||||
cursor.execute("""
|
||||
SELECT e1.name as from_name, e2.name as to_name, r.relation, r.context
|
||||
FROM relations r
|
||||
JOIN entities e1 ON r.source_id = e1.id
|
||||
JOIN entities e2 ON r.target_id = e2.id
|
||||
WHERE r.source_id = ? OR r.target_id = ?
|
||||
LIMIT 20
|
||||
""", (entity_id, entity_id))
|
||||
|
||||
for row in cursor.fetchall():
|
||||
entity['relations'].append({
|
||||
'from': row['from_name'],
|
||||
'to': row['to_name'],
|
||||
'type': row['relation'],
|
||||
'context': row['context']
|
||||
})
|
||||
|
||||
conn.close()
|
||||
return entity
|
||||
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
|
||||
def get_project_info(self, project_name: str) -> Dict:
|
||||
"""Get project-specific information"""
|
||||
if not self.MEMORY_DB.exists():
|
||||
return {'error': 'Memory database not found'}
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(self.MEMORY_DB), timeout=self.timeout_seconds)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get project entity
|
||||
cursor.execute(
|
||||
"SELECT id, name, type FROM entities WHERE name = ? AND type = 'project'",
|
||||
(project_name,)
|
||||
)
|
||||
project_row = cursor.fetchone()
|
||||
|
||||
if not project_row:
|
||||
conn.close()
|
||||
return {'error': f'Project {project_name} not found'}
|
||||
|
||||
project_id = project_row['id']
|
||||
project = {
|
||||
'name': project_row['name'],
|
||||
'type': project_row['type'],
|
||||
'related_entities': []
|
||||
}
|
||||
|
||||
# Get related entities
|
||||
cursor.execute("""
|
||||
SELECT e.name FROM entities e
|
||||
JOIN relations r ON r.target_id = e.id
|
||||
WHERE r.source_id = ?
|
||||
LIMIT 10
|
||||
""", (project_id,))
|
||||
|
||||
for row in cursor.fetchall():
|
||||
project['related_entities'].append(row['name'])
|
||||
|
||||
conn.close()
|
||||
return project
|
||||
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
|
||||
def list_all_projects(self) -> Dict:
|
||||
"""List all projects in memory"""
|
||||
if not self.MEMORY_DB.exists():
|
||||
return {'error': 'Memory database not found', 'projects': []}
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(self.MEMORY_DB), timeout=self.timeout_seconds)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(
|
||||
"SELECT name, type FROM entities WHERE type = 'project' OR type = 'Project' LIMIT 50"
|
||||
)
|
||||
|
||||
projects = [
|
||||
{
|
||||
'name': row['name'],
|
||||
'type': row['type']
|
||||
}
|
||||
for row in cursor.fetchall()
|
||||
]
|
||||
|
||||
conn.close()
|
||||
return {'projects': projects, 'count': len(projects)}
|
||||
|
||||
except Exception as e:
|
||||
return {'error': str(e), 'projects': []}
|
||||
|
||||
def memory_statistics(self) -> Dict:
|
||||
"""Get memory database statistics"""
|
||||
if not self.MEMORY_DB.exists():
|
||||
return {'available': False}
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(self.MEMORY_DB), timeout=self.timeout_seconds)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM entities")
|
||||
entity_count = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM relations")
|
||||
relation_count = cursor.fetchone()[0]
|
||||
|
||||
stats = {
|
||||
'available': True,
|
||||
'entities': entity_count,
|
||||
'relations': relation_count
|
||||
}
|
||||
|
||||
conn.close()
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
return {'available': False, 'error': str(e)}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
lookup = ChatMemoryLookup()
|
||||
|
||||
print("Memory Statistics:")
|
||||
print(json.dumps(lookup.memory_statistics(), indent=2))
|
||||
print()
|
||||
|
||||
print("List Projects:")
|
||||
print(json.dumps(lookup.list_all_projects(), indent=2))
|
||||
Reference in New Issue
Block a user