hanzo 0.3.19__py3-none-any.whl → 0.3.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hanzo might be problematic. Click here for more details.

@@ -0,0 +1,425 @@
1
+ """
2
+ Memory management system for Hanzo Dev.
3
+ Provides persistent context and memory like Claude Desktop.
4
+ """
5
+
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Dict, List, Any, Optional
10
+ from datetime import datetime
11
+ from dataclasses import dataclass, asdict
12
+ import hashlib
13
+
14
+ @dataclass
15
+ class MemoryItem:
16
+ """A single memory item."""
17
+ id: str
18
+ content: str
19
+ type: str # 'context', 'instruction', 'fact', 'code'
20
+ created_at: str
21
+ tags: List[str]
22
+ priority: int = 0 # Higher priority items are kept longer
23
+
24
+ def to_dict(self) -> Dict:
25
+ return asdict(self)
26
+
27
+ @classmethod
28
+ def from_dict(cls, data: Dict) -> 'MemoryItem':
29
+ return cls(**data)
30
+
31
+
32
+ class MemoryManager:
33
+ """Manages persistent memory and context for AI conversations."""
34
+
35
+ def __init__(self, workspace_dir: str = None):
36
+ """Initialize memory manager."""
37
+ if workspace_dir:
38
+ self.memory_dir = Path(workspace_dir) / ".hanzo" / "memory"
39
+ else:
40
+ self.memory_dir = Path.home() / ".hanzo" / "memory"
41
+
42
+ self.memory_dir.mkdir(parents=True, exist_ok=True)
43
+ self.memory_file = self.memory_dir / "context.json"
44
+ self.session_file = self.memory_dir / "session.json"
45
+
46
+ self.memories: List[MemoryItem] = []
47
+ self.session_context: Dict[str, Any] = {}
48
+
49
+ self.load_memories()
50
+ self.load_session()
51
+
52
+ def load_memories(self):
53
+ """Load persistent memories from disk."""
54
+ if self.memory_file.exists():
55
+ try:
56
+ with open(self.memory_file, 'r') as f:
57
+ data = json.load(f)
58
+ self.memories = [MemoryItem.from_dict(item) for item in data.get('memories', [])]
59
+ except Exception as e:
60
+ print(f"Error loading memories: {e}")
61
+ self.memories = []
62
+ else:
63
+ # Initialize with default memories
64
+ self._init_default_memories()
65
+
66
+ def _init_default_memories(self):
67
+ """Initialize with helpful default memories."""
68
+ defaults = [
69
+ MemoryItem(
70
+ id=self._generate_id("system"),
71
+ content="I am Hanzo Dev, an AI coding assistant with multiple orchestrator modes.",
72
+ type="instruction",
73
+ created_at=datetime.now().isoformat(),
74
+ tags=["system", "identity"],
75
+ priority=10
76
+ ),
77
+ MemoryItem(
78
+ id=self._generate_id("capabilities"),
79
+ content="I can read/write files, search code, run commands, and use various AI models.",
80
+ type="fact",
81
+ created_at=datetime.now().isoformat(),
82
+ tags=["system", "capabilities"],
83
+ priority=9
84
+ ),
85
+ MemoryItem(
86
+ id=self._generate_id("help"),
87
+ content="Use /help for commands, #memory for context management, or just chat naturally.",
88
+ type="instruction",
89
+ created_at=datetime.now().isoformat(),
90
+ tags=["system", "usage"],
91
+ priority=8
92
+ ),
93
+ ]
94
+ self.memories = defaults
95
+ self.save_memories()
96
+
97
+ def save_memories(self):
98
+ """Save memories to disk."""
99
+ try:
100
+ data = {
101
+ 'memories': [m.to_dict() for m in self.memories],
102
+ 'updated_at': datetime.now().isoformat()
103
+ }
104
+ with open(self.memory_file, 'w') as f:
105
+ json.dump(data, f, indent=2)
106
+ except Exception as e:
107
+ print(f"Error saving memories: {e}")
108
+
109
+ def load_session(self):
110
+ """Load current session context."""
111
+ if self.session_file.exists():
112
+ try:
113
+ with open(self.session_file, 'r') as f:
114
+ self.session_context = json.load(f)
115
+ except:
116
+ self.session_context = {}
117
+ else:
118
+ self.session_context = {
119
+ 'started_at': datetime.now().isoformat(),
120
+ 'messages': [],
121
+ 'current_task': None,
122
+ 'preferences': {}
123
+ }
124
+
125
+ def save_session(self):
126
+ """Save session context."""
127
+ try:
128
+ with open(self.session_file, 'w') as f:
129
+ json.dump(self.session_context, f, indent=2)
130
+ except Exception as e:
131
+ print(f"Error saving session: {e}")
132
+
133
+ def add_memory(self, content: str, type: str = "context", tags: List[str] = None, priority: int = 0) -> str:
134
+ """Add a new memory item."""
135
+ memory_id = self._generate_id(content)
136
+
137
+ # Check if similar memory exists
138
+ for mem in self.memories:
139
+ if mem.content == content:
140
+ return mem.id # Don't duplicate
141
+
142
+ memory = MemoryItem(
143
+ id=memory_id,
144
+ content=content,
145
+ type=type,
146
+ created_at=datetime.now().isoformat(),
147
+ tags=tags or [],
148
+ priority=priority
149
+ )
150
+
151
+ self.memories.append(memory)
152
+ self.save_memories()
153
+
154
+ return memory_id
155
+
156
+ def remove_memory(self, memory_id: str) -> bool:
157
+ """Remove a memory by ID."""
158
+ for i, mem in enumerate(self.memories):
159
+ if mem.id == memory_id:
160
+ del self.memories[i]
161
+ self.save_memories()
162
+ return True
163
+ return False
164
+
165
+ def clear_memories(self, keep_system: bool = True):
166
+ """Clear all memories, optionally keeping system memories."""
167
+ if keep_system:
168
+ self.memories = [m for m in self.memories if "system" in m.tags]
169
+ else:
170
+ self.memories = []
171
+ self.save_memories()
172
+
173
+ def get_memories(self, type: str = None, tags: List[str] = None) -> List[MemoryItem]:
174
+ """Get memories filtered by type or tags."""
175
+ result = self.memories
176
+
177
+ if type:
178
+ result = [m for m in result if m.type == type]
179
+
180
+ if tags:
181
+ result = [m for m in result if any(tag in m.tags for tag in tags)]
182
+
183
+ # Sort by priority and creation date
184
+ result.sort(key=lambda m: (-m.priority, m.created_at), reverse=True)
185
+
186
+ return result
187
+
188
+ def get_context_string(self, max_tokens: int = 2000) -> str:
189
+ """Get a formatted context string for AI prompts."""
190
+ # Sort memories by priority
191
+ sorted_memories = sorted(self.memories, key=lambda m: -m.priority)
192
+
193
+ context_parts = []
194
+ token_count = 0
195
+
196
+ for memory in sorted_memories:
197
+ # Rough token estimation (4 chars = 1 token)
198
+ memory_tokens = len(memory.content) // 4
199
+
200
+ if token_count + memory_tokens > max_tokens:
201
+ break
202
+
203
+ if memory.type == "instruction":
204
+ context_parts.append(f"INSTRUCTION: {memory.content}")
205
+ elif memory.type == "fact":
206
+ context_parts.append(f"FACT: {memory.content}")
207
+ elif memory.type == "code":
208
+ context_parts.append(f"CODE CONTEXT:\n{memory.content}")
209
+ else:
210
+ context_parts.append(memory.content)
211
+
212
+ token_count += memory_tokens
213
+
214
+ return "\n\n".join(context_parts)
215
+
216
+ def add_message(self, role: str, content: str):
217
+ """Add a message to session history."""
218
+ self.session_context['messages'].append({
219
+ 'role': role,
220
+ 'content': content,
221
+ 'timestamp': datetime.now().isoformat()
222
+ })
223
+
224
+ # Keep only last 50 messages
225
+ if len(self.session_context['messages']) > 50:
226
+ self.session_context['messages'] = self.session_context['messages'][-50:]
227
+
228
+ self.save_session()
229
+
230
+ def get_recent_messages(self, count: int = 10) -> List[Dict]:
231
+ """Get recent messages from session."""
232
+ return self.session_context['messages'][-count:]
233
+
234
+ def set_preference(self, key: str, value: Any):
235
+ """Set a user preference."""
236
+ self.session_context['preferences'][key] = value
237
+ self.save_session()
238
+
239
+ def get_preference(self, key: str, default: Any = None) -> Any:
240
+ """Get a user preference."""
241
+ return self.session_context['preferences'].get(key, default)
242
+
243
+ def _generate_id(self, content: str) -> str:
244
+ """Generate a unique ID for a memory item."""
245
+ hash_input = f"{content}{datetime.now().isoformat()}"
246
+ return hashlib.md5(hash_input.encode()).hexdigest()[:8]
247
+
248
+ def summarize_for_ai(self) -> str:
249
+ """Create a summary suitable for AI context."""
250
+ summary = []
251
+
252
+ # Add system memories
253
+ system_memories = self.get_memories(tags=["system"])
254
+ if system_memories:
255
+ summary.append("SYSTEM CONTEXT:")
256
+ for mem in system_memories[:3]: # Top 3 system memories
257
+ summary.append(f"- {mem.content}")
258
+
259
+ # Add recent instructions
260
+ instructions = self.get_memories(type="instruction")
261
+ if instructions:
262
+ summary.append("\nINSTRUCTIONS:")
263
+ for mem in instructions[:3]: # Top 3 instructions
264
+ summary.append(f"- {mem.content}")
265
+
266
+ # Add important facts
267
+ facts = self.get_memories(type="fact")
268
+ if facts:
269
+ summary.append("\nKEY FACTS:")
270
+ for mem in facts[:5]: # Top 5 facts
271
+ summary.append(f"- {mem.content}")
272
+
273
+ # Add current task if set
274
+ if self.session_context.get('current_task'):
275
+ summary.append(f"\nCURRENT TASK: {self.session_context['current_task']}")
276
+
277
+ return "\n".join(summary)
278
+
279
+ def export_memories(self, file_path: str):
280
+ """Export memories to a file."""
281
+ data = {
282
+ 'memories': [m.to_dict() for m in self.memories],
283
+ 'session': self.session_context,
284
+ 'exported_at': datetime.now().isoformat()
285
+ }
286
+
287
+ with open(file_path, 'w') as f:
288
+ json.dump(data, f, indent=2)
289
+
290
+ def import_memories(self, file_path: str):
291
+ """Import memories from a file."""
292
+ with open(file_path, 'r') as f:
293
+ data = json.load(f)
294
+
295
+ # Merge memories (avoid duplicates)
296
+ existing_ids = {m.id for m in self.memories}
297
+
298
+ for mem_data in data.get('memories', []):
299
+ if mem_data['id'] not in existing_ids:
300
+ self.memories.append(MemoryItem.from_dict(mem_data))
301
+
302
+ # Merge session preferences
303
+ if 'session' in data and 'preferences' in data['session']:
304
+ self.session_context['preferences'].update(data['session']['preferences'])
305
+
306
+ self.save_memories()
307
+ self.save_session()
308
+
309
+
310
+ def handle_memory_command(command: str, memory_manager: MemoryManager, console) -> bool:
311
+ """
312
+ Handle #memory commands.
313
+ Returns True if command was handled, False otherwise.
314
+ """
315
+ from rich.table import Table
316
+ from rich.panel import Panel
317
+
318
+ parts = command.strip().split(maxsplit=2)
319
+
320
+ if len(parts) == 1 or parts[1] == "show":
321
+ # Show current memories
322
+ memories = memory_manager.get_memories()
323
+
324
+ if not memories:
325
+ console.print("[yellow]No memories stored.[/yellow]")
326
+ return True
327
+
328
+ table = Table(title="Current Memories", show_header=True,
329
+ header_style="bold magenta")
330
+ table.add_column("ID", style="cyan", width=10)
331
+ table.add_column("Type", width=12)
332
+ table.add_column("Content", width=50)
333
+ table.add_column("Priority", width=8)
334
+
335
+ for mem in memories[:10]: # Show top 10
336
+ content = mem.content[:47] + "..." if len(mem.content) > 50 else mem.content
337
+ table.add_row(mem.id, mem.type, content, str(mem.priority))
338
+
339
+ console.print(table)
340
+
341
+ if len(memories) > 10:
342
+ console.print(f"[dim]... and {len(memories) - 10} more[/dim]")
343
+
344
+ return True
345
+
346
+ elif parts[1] == "add":
347
+ if len(parts) < 3:
348
+ console.print("[red]Usage: #memory add <content>[/red]")
349
+ return True
350
+
351
+ content = parts[2]
352
+ memory_id = memory_manager.add_memory(content, type="context")
353
+ console.print(f"[green]Added memory: {memory_id}[/green]")
354
+ return True
355
+
356
+ elif parts[1] == "remove":
357
+ if len(parts) < 3:
358
+ console.print("[red]Usage: #memory remove <id>[/red]")
359
+ return True
360
+
361
+ memory_id = parts[2]
362
+ if memory_manager.remove_memory(memory_id):
363
+ console.print(f"[green]Removed memory: {memory_id}[/green]")
364
+ else:
365
+ console.print(f"[red]Memory not found: {memory_id}[/red]")
366
+ return True
367
+
368
+ elif parts[1] == "clear":
369
+ memory_manager.clear_memories(keep_system=True)
370
+ console.print("[green]Cleared all non-system memories.[/green]")
371
+ return True
372
+
373
+ elif parts[1] == "save":
374
+ memory_manager.save_memories()
375
+ memory_manager.save_session()
376
+ console.print("[green]Memories saved.[/green]")
377
+ return True
378
+
379
+ elif parts[1] == "export":
380
+ if len(parts) < 3:
381
+ file_path = "hanzo_memories.json"
382
+ else:
383
+ file_path = parts[2]
384
+
385
+ memory_manager.export_memories(file_path)
386
+ console.print(f"[green]Exported memories to {file_path}[/green]")
387
+ return True
388
+
389
+ elif parts[1] == "import":
390
+ if len(parts) < 3:
391
+ console.print("[red]Usage: #memory import <file_path>[/red]")
392
+ return True
393
+
394
+ file_path = parts[2]
395
+ try:
396
+ memory_manager.import_memories(file_path)
397
+ console.print(f"[green]Imported memories from {file_path}[/green]")
398
+ except Exception as e:
399
+ console.print(f"[red]Error importing: {e}[/red]")
400
+ return True
401
+
402
+ elif parts[1] == "context":
403
+ # Show AI context
404
+ context = memory_manager.summarize_for_ai()
405
+ console.print(Panel(context, title="[bold cyan]AI Context[/bold cyan]",
406
+ title_align="left", border_style="dim cyan"))
407
+ return True
408
+
409
+ elif parts[1] == "help":
410
+ help_text = """Memory Commands:
411
+ #memory [show] - Show current memories
412
+ #memory add <text> - Add new memory
413
+ #memory remove <id> - Remove memory by ID
414
+ #memory clear - Clear all memories (keep system)
415
+ #memory save - Save memories to disk
416
+ #memory export [file] - Export memories to file
417
+ #memory import <file> - Import memories from file
418
+ #memory context - Show AI context summary
419
+ #memory help - Show this help"""
420
+
421
+ console.print(Panel(help_text, title="[bold cyan]Memory Help[/bold cyan]",
422
+ title_align="left", border_style="dim cyan"))
423
+ return True
424
+
425
+ return False