nc1709 1.15.4__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.
Files changed (86) hide show
  1. nc1709/__init__.py +13 -0
  2. nc1709/agent/__init__.py +36 -0
  3. nc1709/agent/core.py +505 -0
  4. nc1709/agent/mcp_bridge.py +245 -0
  5. nc1709/agent/permissions.py +298 -0
  6. nc1709/agent/tools/__init__.py +21 -0
  7. nc1709/agent/tools/base.py +440 -0
  8. nc1709/agent/tools/bash_tool.py +367 -0
  9. nc1709/agent/tools/file_tools.py +454 -0
  10. nc1709/agent/tools/notebook_tools.py +516 -0
  11. nc1709/agent/tools/search_tools.py +322 -0
  12. nc1709/agent/tools/task_tool.py +284 -0
  13. nc1709/agent/tools/web_tools.py +555 -0
  14. nc1709/agents/__init__.py +17 -0
  15. nc1709/agents/auto_fix.py +506 -0
  16. nc1709/agents/test_generator.py +507 -0
  17. nc1709/checkpoints.py +372 -0
  18. nc1709/cli.py +3380 -0
  19. nc1709/cli_ui.py +1080 -0
  20. nc1709/cognitive/__init__.py +149 -0
  21. nc1709/cognitive/anticipation.py +594 -0
  22. nc1709/cognitive/context_engine.py +1046 -0
  23. nc1709/cognitive/council.py +824 -0
  24. nc1709/cognitive/learning.py +761 -0
  25. nc1709/cognitive/router.py +583 -0
  26. nc1709/cognitive/system.py +519 -0
  27. nc1709/config.py +155 -0
  28. nc1709/custom_commands.py +300 -0
  29. nc1709/executor.py +333 -0
  30. nc1709/file_controller.py +354 -0
  31. nc1709/git_integration.py +308 -0
  32. nc1709/github_integration.py +477 -0
  33. nc1709/image_input.py +446 -0
  34. nc1709/linting.py +519 -0
  35. nc1709/llm_adapter.py +667 -0
  36. nc1709/logger.py +192 -0
  37. nc1709/mcp/__init__.py +18 -0
  38. nc1709/mcp/client.py +370 -0
  39. nc1709/mcp/manager.py +407 -0
  40. nc1709/mcp/protocol.py +210 -0
  41. nc1709/mcp/server.py +473 -0
  42. nc1709/memory/__init__.py +20 -0
  43. nc1709/memory/embeddings.py +325 -0
  44. nc1709/memory/indexer.py +474 -0
  45. nc1709/memory/sessions.py +432 -0
  46. nc1709/memory/vector_store.py +451 -0
  47. nc1709/models/__init__.py +86 -0
  48. nc1709/models/detector.py +377 -0
  49. nc1709/models/formats.py +315 -0
  50. nc1709/models/manager.py +438 -0
  51. nc1709/models/registry.py +497 -0
  52. nc1709/performance/__init__.py +343 -0
  53. nc1709/performance/cache.py +705 -0
  54. nc1709/performance/pipeline.py +611 -0
  55. nc1709/performance/tiering.py +543 -0
  56. nc1709/plan_mode.py +362 -0
  57. nc1709/plugins/__init__.py +17 -0
  58. nc1709/plugins/agents/__init__.py +18 -0
  59. nc1709/plugins/agents/django_agent.py +912 -0
  60. nc1709/plugins/agents/docker_agent.py +623 -0
  61. nc1709/plugins/agents/fastapi_agent.py +887 -0
  62. nc1709/plugins/agents/git_agent.py +731 -0
  63. nc1709/plugins/agents/nextjs_agent.py +867 -0
  64. nc1709/plugins/base.py +359 -0
  65. nc1709/plugins/manager.py +411 -0
  66. nc1709/plugins/registry.py +337 -0
  67. nc1709/progress.py +443 -0
  68. nc1709/prompts/__init__.py +22 -0
  69. nc1709/prompts/agent_system.py +180 -0
  70. nc1709/prompts/task_prompts.py +340 -0
  71. nc1709/prompts/unified_prompt.py +133 -0
  72. nc1709/reasoning_engine.py +541 -0
  73. nc1709/remote_client.py +266 -0
  74. nc1709/shell_completions.py +349 -0
  75. nc1709/slash_commands.py +649 -0
  76. nc1709/task_classifier.py +408 -0
  77. nc1709/version_check.py +177 -0
  78. nc1709/web/__init__.py +8 -0
  79. nc1709/web/server.py +950 -0
  80. nc1709/web/templates/index.html +1127 -0
  81. nc1709-1.15.4.dist-info/METADATA +858 -0
  82. nc1709-1.15.4.dist-info/RECORD +86 -0
  83. nc1709-1.15.4.dist-info/WHEEL +5 -0
  84. nc1709-1.15.4.dist-info/entry_points.txt +2 -0
  85. nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
  86. nc1709-1.15.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,432 @@
1
+ """
2
+ Session Manager for NC1709
3
+ Handles conversation persistence and session management
4
+ """
5
+ import json
6
+ import uuid
7
+ from pathlib import Path
8
+ from datetime import datetime
9
+ from typing import List, Dict, Any, Optional
10
+ from dataclasses import dataclass, field, asdict
11
+
12
+
13
+ @dataclass
14
+ class Message:
15
+ """Represents a single message in a conversation"""
16
+ role: str # 'user' or 'assistant'
17
+ content: str
18
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
19
+ metadata: Dict[str, Any] = field(default_factory=dict)
20
+
21
+
22
+ @dataclass
23
+ class Session:
24
+ """Represents a conversation session"""
25
+ id: str
26
+ name: str
27
+ messages: List[Message] = field(default_factory=list)
28
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
29
+ updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
30
+ metadata: Dict[str, Any] = field(default_factory=dict)
31
+ project_path: Optional[str] = None
32
+
33
+ def add_message(self, role: str, content: str, metadata: Optional[Dict] = None):
34
+ """Add a message to the session"""
35
+ message = Message(role=role, content=content, metadata=metadata or {})
36
+ self.messages.append(message)
37
+ self.updated_at = datetime.now().isoformat()
38
+
39
+ def get_history(self, limit: Optional[int] = None) -> List[Dict[str, str]]:
40
+ """Get message history in format suitable for LLM
41
+
42
+ Args:
43
+ limit: Maximum number of messages to return
44
+
45
+ Returns:
46
+ List of message dicts with 'role' and 'content'
47
+ """
48
+ messages = self.messages[-limit:] if limit else self.messages
49
+ return [{"role": m.role, "content": m.content} for m in messages]
50
+
51
+ def to_dict(self) -> Dict[str, Any]:
52
+ """Convert session to dictionary"""
53
+ return {
54
+ "id": self.id,
55
+ "name": self.name,
56
+ "messages": [asdict(m) for m in self.messages],
57
+ "created_at": self.created_at,
58
+ "updated_at": self.updated_at,
59
+ "metadata": self.metadata,
60
+ "project_path": self.project_path
61
+ }
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: Dict[str, Any]) -> "Session":
65
+ """Create session from dictionary"""
66
+ messages = [Message(**m) for m in data.get("messages", [])]
67
+ return cls(
68
+ id=data["id"],
69
+ name=data["name"],
70
+ messages=messages,
71
+ created_at=data.get("created_at", datetime.now().isoformat()),
72
+ updated_at=data.get("updated_at", datetime.now().isoformat()),
73
+ metadata=data.get("metadata", {}),
74
+ project_path=data.get("project_path")
75
+ )
76
+
77
+ def to_markdown(self) -> str:
78
+ """Export session as markdown
79
+
80
+ Returns:
81
+ Markdown formatted conversation
82
+ """
83
+ lines = [
84
+ f"# Session: {self.name}",
85
+ f"",
86
+ f"**ID:** {self.id}",
87
+ f"**Created:** {self.created_at}",
88
+ f"**Updated:** {self.updated_at}",
89
+ f"",
90
+ "---",
91
+ ""
92
+ ]
93
+
94
+ for msg in self.messages:
95
+ if msg.role == "user":
96
+ lines.append(f"## User ({msg.timestamp})")
97
+ else:
98
+ lines.append(f"## Assistant ({msg.timestamp})")
99
+
100
+ lines.append("")
101
+ lines.append(msg.content)
102
+ lines.append("")
103
+ lines.append("---")
104
+ lines.append("")
105
+
106
+ return "\n".join(lines)
107
+
108
+
109
+ class SessionManager:
110
+ """Manages conversation sessions"""
111
+
112
+ def __init__(self, storage_path: Optional[str] = None):
113
+ """Initialize session manager
114
+
115
+ Args:
116
+ storage_path: Path to store sessions
117
+ """
118
+ if storage_path:
119
+ self.storage_path = Path(storage_path)
120
+ else:
121
+ self.storage_path = Path.home() / ".nc1709" / "sessions"
122
+
123
+ self.storage_path.mkdir(parents=True, exist_ok=True)
124
+ self.index_file = self.storage_path / "index.json"
125
+
126
+ # Currently active session
127
+ self.current_session: Optional[Session] = None
128
+
129
+ # Session index (id -> metadata)
130
+ self._index: Dict[str, Dict[str, Any]] = {}
131
+ self._load_index()
132
+
133
+ def create_session(
134
+ self,
135
+ name: Optional[str] = None,
136
+ project_path: Optional[str] = None
137
+ ) -> Session:
138
+ """Create a new session
139
+
140
+ Args:
141
+ name: Session name (auto-generated if None)
142
+ project_path: Associated project path
143
+
144
+ Returns:
145
+ New Session instance
146
+ """
147
+ session_id = str(uuid.uuid4())[:8]
148
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
149
+
150
+ if name is None:
151
+ name = f"session_{timestamp}"
152
+
153
+ session = Session(
154
+ id=session_id,
155
+ name=name,
156
+ project_path=project_path
157
+ )
158
+
159
+ # Update index
160
+ self._index[session_id] = {
161
+ "id": session_id,
162
+ "name": name,
163
+ "created_at": session.created_at,
164
+ "updated_at": session.updated_at,
165
+ "message_count": 0,
166
+ "project_path": project_path
167
+ }
168
+
169
+ # Save session
170
+ self._save_session(session)
171
+ self._save_index()
172
+
173
+ return session
174
+
175
+ def load_session(self, session_id: str) -> Optional[Session]:
176
+ """Load a session by ID
177
+
178
+ Args:
179
+ session_id: Session ID
180
+
181
+ Returns:
182
+ Session or None if not found
183
+ """
184
+ session_file = self.storage_path / f"{session_id}.json"
185
+
186
+ if not session_file.exists():
187
+ return None
188
+
189
+ try:
190
+ data = json.loads(session_file.read_text())
191
+ return Session.from_dict(data)
192
+ except Exception as e:
193
+ print(f"Error loading session {session_id}: {e}")
194
+ return None
195
+
196
+ def save_session(self, session: Session):
197
+ """Save a session
198
+
199
+ Args:
200
+ session: Session to save
201
+ """
202
+ self._save_session(session)
203
+
204
+ # Update index
205
+ self._index[session.id] = {
206
+ "id": session.id,
207
+ "name": session.name,
208
+ "created_at": session.created_at,
209
+ "updated_at": session.updated_at,
210
+ "message_count": len(session.messages),
211
+ "project_path": session.project_path
212
+ }
213
+ self._save_index()
214
+
215
+ def delete_session(self, session_id: str) -> bool:
216
+ """Delete a session
217
+
218
+ Args:
219
+ session_id: Session ID
220
+
221
+ Returns:
222
+ True if deleted
223
+ """
224
+ session_file = self.storage_path / f"{session_id}.json"
225
+
226
+ try:
227
+ if session_file.exists():
228
+ session_file.unlink()
229
+
230
+ if session_id in self._index:
231
+ del self._index[session_id]
232
+ self._save_index()
233
+
234
+ return True
235
+ except Exception:
236
+ return False
237
+
238
+ def list_sessions(
239
+ self,
240
+ limit: Optional[int] = None,
241
+ project_path: Optional[str] = None
242
+ ) -> List[Dict[str, Any]]:
243
+ """List all sessions
244
+
245
+ Args:
246
+ limit: Maximum number to return
247
+ project_path: Filter by project
248
+
249
+ Returns:
250
+ List of session metadata dicts
251
+ """
252
+ sessions = list(self._index.values())
253
+
254
+ # Filter by project
255
+ if project_path:
256
+ sessions = [s for s in sessions if s.get("project_path") == project_path]
257
+
258
+ # Sort by updated_at (newest first)
259
+ sessions.sort(key=lambda x: x.get("updated_at", ""), reverse=True)
260
+
261
+ # Limit
262
+ if limit:
263
+ sessions = sessions[:limit]
264
+
265
+ return sessions
266
+
267
+ def get_latest_session(self, project_path: Optional[str] = None) -> Optional[Session]:
268
+ """Get the most recent session
269
+
270
+ Args:
271
+ project_path: Filter by project
272
+
273
+ Returns:
274
+ Most recent session or None
275
+ """
276
+ sessions = self.list_sessions(limit=1, project_path=project_path)
277
+
278
+ if sessions:
279
+ return self.load_session(sessions[0]["id"])
280
+
281
+ return None
282
+
283
+ def start_session(
284
+ self,
285
+ session_id: Optional[str] = None,
286
+ name: Optional[str] = None,
287
+ project_path: Optional[str] = None
288
+ ) -> Session:
289
+ """Start a session (load existing or create new)
290
+
291
+ Args:
292
+ session_id: Existing session ID to resume
293
+ name: Name for new session
294
+ project_path: Project path
295
+
296
+ Returns:
297
+ Active session
298
+ """
299
+ if session_id:
300
+ session = self.load_session(session_id)
301
+ if session:
302
+ self.current_session = session
303
+ return session
304
+
305
+ # Create new session
306
+ self.current_session = self.create_session(name=name, project_path=project_path)
307
+ return self.current_session
308
+
309
+ def add_message(
310
+ self,
311
+ role: str,
312
+ content: str,
313
+ metadata: Optional[Dict] = None,
314
+ auto_save: bool = True
315
+ ):
316
+ """Add a message to the current session
317
+
318
+ Args:
319
+ role: 'user' or 'assistant'
320
+ content: Message content
321
+ metadata: Additional metadata
322
+ auto_save: Automatically save session
323
+ """
324
+ if self.current_session is None:
325
+ self.current_session = self.create_session()
326
+
327
+ self.current_session.add_message(role, content, metadata)
328
+
329
+ if auto_save:
330
+ self.save_session(self.current_session)
331
+
332
+ def get_current_history(self, limit: Optional[int] = None) -> List[Dict[str, str]]:
333
+ """Get history from current session
334
+
335
+ Args:
336
+ limit: Maximum messages
337
+
338
+ Returns:
339
+ Message history for LLM
340
+ """
341
+ if self.current_session is None:
342
+ return []
343
+
344
+ return self.current_session.get_history(limit)
345
+
346
+ def export_session(
347
+ self,
348
+ session_id: str,
349
+ output_path: str,
350
+ format: str = "markdown"
351
+ ) -> bool:
352
+ """Export a session to file
353
+
354
+ Args:
355
+ session_id: Session ID
356
+ output_path: Output file path
357
+ format: Export format ('markdown' or 'json')
358
+
359
+ Returns:
360
+ True if successful
361
+ """
362
+ session = self.load_session(session_id)
363
+
364
+ if session is None:
365
+ return False
366
+
367
+ output_file = Path(output_path)
368
+
369
+ try:
370
+ if format == "markdown":
371
+ content = session.to_markdown()
372
+ else:
373
+ content = json.dumps(session.to_dict(), indent=2)
374
+
375
+ output_file.write_text(content)
376
+ return True
377
+ except Exception as e:
378
+ print(f"Error exporting session: {e}")
379
+ return False
380
+
381
+ def search_sessions(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
382
+ """Search sessions by content
383
+
384
+ Args:
385
+ query: Search query
386
+ limit: Maximum results
387
+
388
+ Returns:
389
+ List of matching sessions with context
390
+ """
391
+ results = []
392
+ query_lower = query.lower()
393
+
394
+ for session_meta in self.list_sessions():
395
+ session = self.load_session(session_meta["id"])
396
+ if session is None:
397
+ continue
398
+
399
+ # Search in messages
400
+ matches = []
401
+ for i, msg in enumerate(session.messages):
402
+ if query_lower in msg.content.lower():
403
+ matches.append({
404
+ "message_index": i,
405
+ "role": msg.role,
406
+ "preview": msg.content[:200]
407
+ })
408
+
409
+ if matches:
410
+ results.append({
411
+ "session": session_meta,
412
+ "matches": matches[:3] # Limit matches per session
413
+ })
414
+
415
+ return results[:limit]
416
+
417
+ def _save_session(self, session: Session):
418
+ """Save session to disk"""
419
+ session_file = self.storage_path / f"{session.id}.json"
420
+ session_file.write_text(json.dumps(session.to_dict(), indent=2))
421
+
422
+ def _load_index(self):
423
+ """Load session index from disk"""
424
+ if self.index_file.exists():
425
+ try:
426
+ self._index = json.loads(self.index_file.read_text())
427
+ except Exception:
428
+ self._index = {}
429
+
430
+ def _save_index(self):
431
+ """Save session index to disk"""
432
+ self.index_file.write_text(json.dumps(self._index, indent=2))