Spaces:
Running
Running
| """ | |
| π± Imperial Compaction Engine (μ§λ₯ν 컨ν μ€νΈ μμΆ μμ§) | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Claw Codeμ 'Compaction' κΈ°μ μ μλμ° λΈλ μΈ μ½μ΄μ μ΅μ ννμ¬ μ΄μν λͺ¨λμ λλ€. | |
| λνκ° κΈΈμ΄μ§ λ ν΅μ¬ 컨ν μ€νΈ(νμΌ, TODO, νμλΌμΈ)λ₯Ό 보쑴νλ©° ν ν°μ μ μ½ν©λλ€. | |
| """ | |
| import os | |
| import json | |
| import time | |
| import re | |
| from typing import List, Dict, Any, Optional | |
| class CompactionEngine: | |
| """λν λ΄μμ λΆμνκ³ μ§λ₯μ μΌλ‘ μμΆνλ ν΅μ¬ μμ§.""" | |
| PREAMBLE = "μ΄ μΈμ μ μ΄μ λνμμ 컨ν μ€νΈκ° λΆμ‘±νμ¬ μμ½λ³ΈμΌλ‘λΆν° μ΄μ΄μ§λ λ΄μ©μ λλ€.\n\n" | |
| def __init__(self, project_root: str): | |
| self.project_root = project_root | |
| self.settings_file = os.path.join(project_root, "docker_data", "shared_workspace", "JarvisRun", "JARVIS_SHADOW_SETTINGS.json") | |
| def _get_settings(self) -> Dict[str, Any]: | |
| """μ€μ νμΌμμ ν«-λ‘λ(Hot-Reload)λ‘ μ€μ μ μ½μ΄μ΅λλ€.""" | |
| defaults = { | |
| "compaction_enabled": True, | |
| "compaction_threshold": 50, # λ©μμ§ κ°μ κΈ°μ€ | |
| "preserve_recent": 5 # μμΆ μ 보쑴ν μ΅μ λ©μμ§ μ | |
| } | |
| if os.path.exists(self.settings_file): | |
| try: | |
| with open(self.settings_file, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| # merge with defaults | |
| for k, v in defaults.items(): | |
| if k not in data: data[k] = v | |
| return data | |
| except: pass | |
| return defaults | |
| def should_compact(self, messages: List[Dict[str, Any]]) -> bool: | |
| """μμΆμ΄ νμν μμ μΈμ§ νλ¨ν©λλ€.""" | |
| settings = self._get_settings() | |
| if not settings.get("compaction_enabled", True): | |
| return False | |
| threshold = int(settings.get("compaction_threshold", 50)) | |
| return len(messages) >= threshold | |
| def extract_metadata(self, messages: List[Dict[str, Any]]) -> Dict[str, Any]: | |
| """λν λ΄μμμ ν΅μ¬ λ©νλ°μ΄ν°(νμΌ, TODO, νμλΌμΈ, κ°μ )λ₯Ό μΆμΆν©λλ€.""" | |
| files = set() | |
| todos = [] | |
| timeline = [] | |
| emotions = {} # senderλ³ μ£Όλ‘ ννλ κ°μ | |
| # νμΌ νμ₯μ μ κ·μ | |
| file_pattern = re.compile(r'[\w/._-]+\.(?:py|rs|dart|js|ts|md|html|css|json|yaml|yml|sh|ps1|bat)') | |
| for msg in messages: | |
| content = msg.get('text', '') or '' | |
| role = msg.get('relayGuardian') or msg.get('sender_name') or msg.get('sender_id') or 'unknown' | |
| emotion = msg.get('emotion') | |
| # 1. νμΌ κ²½λ‘ μΆμΆ | |
| found_files = file_pattern.findall(content) | |
| for f in found_files: | |
| if len(f) > 3: files.add(f) | |
| # 2. TODO/Next Step μΆλ‘ | |
| lower_content = content.lower() | |
| if any(kw in lower_content for kw in ['todo', 'λ€μ', 'next', 'ν μΌ', 'pending', 'remain']): | |
| lines = [line.strip() for line in content.split('\n') if line.strip()] | |
| for line in lines: | |
| if any(kw in line.lower() for kw in ['todo', 'λ€μ', 'next', 'ν μΌ']): | |
| todos.append(line[:150] + "..." if len(line) > 150 else line) | |
| # 3. κ°μ μΆμ | |
| if emotion: | |
| if role not in emotions: emotions[role] = [] | |
| emotions[role].append(emotion) | |
| # 4. νμλΌμΈ (ν΅μ¬ μ΄λ²€νΈ νλ¦ μμ½) | |
| snippet = content.replace('\n', ' ').strip() | |
| if len(snippet) > 80: snippet = snippet[:77] + "..." | |
| emo_str = f" ({emotion})" if emotion else "" | |
| timeline.append(f"{role}{emo_str}: {snippet}") | |
| # κ°μ₯ λΉλ²ν κ°μ μμ½ | |
| summary_emotions = {} | |
| for role, emo_list in emotions.items(): | |
| if emo_list: | |
| from collections import Counter | |
| most_common = Counter(emo_list).most_common(1)[0][0] | |
| summary_emotions[role] = most_common | |
| return { | |
| "files": sorted(list(files))[:15], | |
| "todos": todos[-5:], | |
| "timeline": timeline[-20:], | |
| "emotions": summary_emotions, | |
| "count": len(messages) | |
| } | |
| def generate_summary_message(self, metadata: Dict[str, Any]) -> str: | |
| """μΆμΆλ λ©νλ°μ΄ν°λ₯Ό μ¬μ©νμ¬ λ§ν¬λ€μ΄ μμ½λ³Έμ μμ±ν©λλ€.""" | |
| summary = ["<summary>", "π± **System Context Summary (μ΄μ λν μμ½)**"] | |
| summary.append(f"\n- **μμΆ λ²μ**: μ΄μ {metadata['count']}κ°μ λ©μμ§ ν΅ν©") | |
| if metadata['emotions']: | |
| emo_lines = [f"{role}: {emo}" for role, emo in metadata['emotions'].items()] | |
| summary.append(f"- **μ£Όμ κ°μ μν**: {', '.join(emo_lines)}") | |
| if metadata['files']: | |
| summary.append(f"- **μ°Έμ‘°λ μ£Όμ νμΌ**: {', '.join([f'`{f}`' for f in metadata['files']])}") | |
| if metadata['todos']: | |
| summary.append("- **λ¨μ κ³Όμ λ° TODO**:") | |
| for todo in metadata['todos']: | |
| summary.append(f" - {todo}") | |
| summary.append("- **μ£Όμ νμλΌμΈ**:") | |
| for event in metadata['timeline']: | |
| summary.append(f" - {event}") | |
| summary.append("\n</summary>") | |
| return self.PREAMBLE + "\n".join(summary) | |
| def compact(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| """λ©μμ§ λ¦¬μ€νΈλ₯Ό μλ²λ¦° νλ€μ€ λ°©μμΌλ‘ μμΆν©λλ€.""" | |
| if not self.should_compact(messages): | |
| return messages | |
| settings = self._get_settings() | |
| preserve_count = int(settings.get("preserve_recent", 5)) | |
| # μμΆ λμκ³Ό 보쑴 λμ λΆλ¦¬ | |
| split_idx = len(messages) - preserve_count | |
| to_compact = messages[:split_idx] | |
| preserved = messages[split_idx:] | |
| # μμ½ μμ± | |
| metadata = self.extract_metadata(to_compact) | |
| summary_text = self.generate_summary_message(metadata) | |
| # μμ€ν μμ½ λ©μμ§ μμ± | |
| summary_msg = { | |
| "text": summary_text, | |
| "type": 3, # π± Imperial Type 3: System Context Summary | |
| "relayGuardian": "System", | |
| "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "sender_id": "97157d36-0b88-473f-8131-fab3e46aa560" # Shadow Brain ID | |
| } | |
| return [summary_msg] + preserved | |
| # Global Singleton for Engine | |
| import os | |
| core_root = os.path.dirname(os.path.abspath(__file__)) | |
| compaction_engine = CompactionEngine(core_root) | |