vibe-remote 2.1.6__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 (52) hide show
  1. config/__init__.py +37 -0
  2. config/paths.py +56 -0
  3. config/v2_compat.py +74 -0
  4. config/v2_config.py +206 -0
  5. config/v2_sessions.py +73 -0
  6. config/v2_settings.py +115 -0
  7. core/__init__.py +0 -0
  8. core/controller.py +736 -0
  9. core/handlers/__init__.py +13 -0
  10. core/handlers/command_handlers.py +342 -0
  11. core/handlers/message_handler.py +365 -0
  12. core/handlers/session_handler.py +233 -0
  13. core/handlers/settings_handler.py +362 -0
  14. modules/__init__.py +0 -0
  15. modules/agent_router.py +58 -0
  16. modules/agents/__init__.py +38 -0
  17. modules/agents/base.py +91 -0
  18. modules/agents/claude_agent.py +344 -0
  19. modules/agents/codex_agent.py +368 -0
  20. modules/agents/opencode_agent.py +2155 -0
  21. modules/agents/service.py +41 -0
  22. modules/agents/subagent_router.py +136 -0
  23. modules/claude_client.py +154 -0
  24. modules/im/__init__.py +63 -0
  25. modules/im/base.py +323 -0
  26. modules/im/factory.py +60 -0
  27. modules/im/formatters/__init__.py +4 -0
  28. modules/im/formatters/base_formatter.py +639 -0
  29. modules/im/formatters/slack_formatter.py +127 -0
  30. modules/im/slack.py +2091 -0
  31. modules/session_manager.py +138 -0
  32. modules/settings_manager.py +587 -0
  33. vibe/__init__.py +6 -0
  34. vibe/__main__.py +12 -0
  35. vibe/_version.py +34 -0
  36. vibe/api.py +412 -0
  37. vibe/cli.py +637 -0
  38. vibe/runtime.py +213 -0
  39. vibe/service_main.py +101 -0
  40. vibe/templates/slack_manifest.json +65 -0
  41. vibe/ui/dist/assets/index-8g3mNwMK.js +35 -0
  42. vibe/ui/dist/assets/index-M55aMB5R.css +1 -0
  43. vibe/ui/dist/assets/logo-BzryTZ7u.png +0 -0
  44. vibe/ui/dist/index.html +17 -0
  45. vibe/ui/dist/logo.png +0 -0
  46. vibe/ui/dist/vite.svg +1 -0
  47. vibe/ui_server.py +346 -0
  48. vibe_remote-2.1.6.dist-info/METADATA +295 -0
  49. vibe_remote-2.1.6.dist-info/RECORD +52 -0
  50. vibe_remote-2.1.6.dist-info/WHEEL +4 -0
  51. vibe_remote-2.1.6.dist-info/entry_points.txt +2 -0
  52. vibe_remote-2.1.6.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,138 @@
1
+ import asyncio
2
+ import logging
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List, Optional, Union, Any
5
+ from datetime import datetime
6
+ from claude_code_sdk import ClaudeSDKClient
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+
13
+ @dataclass
14
+ class UserSession:
15
+ user_id: Union[int, str]
16
+ chat_id: Union[int, str]
17
+ is_executing: bool = False
18
+ created_at: datetime = field(default_factory=datetime.now)
19
+ last_activity: datetime = field(default_factory=datetime.now)
20
+ # Map of session_id to ClaudeSDKClient instance
21
+ claude_clients: Dict[str, ClaudeSDKClient] = field(default_factory=dict)
22
+ # Map of session_id to message receiver task
23
+ receiver_tasks: Dict[str, asyncio.Task] = field(default_factory=dict)
24
+ # Map of session_id to boolean indicating if session is waiting for result
25
+ session_active: Dict[str, bool] = field(default_factory=dict)
26
+
27
+
28
+ async def cleanup_clients(self):
29
+ """Cleanup all Claude SDK clients and receiver tasks"""
30
+ # Cancel all receiver tasks first
31
+ for session_id, task in self.receiver_tasks.items():
32
+ if not task.done():
33
+ task.cancel()
34
+ try:
35
+ await task
36
+ except asyncio.CancelledError:
37
+ pass
38
+ logger.info(f"Cancelled receiver task for session {session_id}")
39
+
40
+ # Then disconnect clients
41
+ for session_id, client in self.claude_clients.items():
42
+ try:
43
+ await client.disconnect()
44
+ logger.info(f"Disconnected Claude client for session {session_id}")
45
+ except Exception as e:
46
+ logger.error(f"Error disconnecting Claude client for session {session_id}: {e}")
47
+
48
+ self.receiver_tasks.clear()
49
+
50
+ def get_status(self) -> str:
51
+ """Get session status summary"""
52
+ status = f"šŸ“Š Session Status\n"
53
+ status += f"━━━━━━━━━━━━━━━━\n"
54
+ status += f"User ID: {self.user_id}\n"
55
+ status += f"Active sessions: {len(self.claude_clients)}\n"
56
+ status += f"Status: {'🟢 Connected' if self.claude_clients else 'ā­• No active session'}\n"
57
+ status += f"Last activity: {self.last_activity.strftime('%Y-%m-%d %H:%M:%S')}"
58
+
59
+ if self.claude_clients:
60
+ status += "\n\nšŸ”— Active Claude sessions:"
61
+ for session_id in self.claude_clients:
62
+ status += f"\n• {session_id}"
63
+ else:
64
+ status += "\n\nšŸ’¬ Send a message to start a conversation"
65
+
66
+ return status
67
+
68
+
69
+ class SessionManager:
70
+ def __init__(self):
71
+ self.sessions: Dict[Union[int, str], UserSession] = {}
72
+ self._lock = asyncio.Lock()
73
+
74
+ async def get_or_create_session(self, user_id: Union[int, str], chat_id: Union[int, str]) -> UserSession:
75
+ """Get existing session or create new one"""
76
+ async with self._lock:
77
+ if user_id not in self.sessions:
78
+ self.sessions[user_id] = UserSession(user_id=user_id, chat_id=chat_id)
79
+ logger.info(f"Created new session for user {user_id}")
80
+
81
+ return self.sessions[user_id]
82
+
83
+
84
+ async def clear_session(self, user_id: Union[int, str]) -> str:
85
+ """Clear user's session and disconnect all Claude clients"""
86
+ if user_id not in self.sessions:
87
+ return "No active session found."
88
+
89
+ session = self.sessions[user_id]
90
+
91
+ # Cleanup all Claude SDK clients and receiver tasks
92
+ client_count = len(session.claude_clients)
93
+ await session.cleanup_clients()
94
+ session.claude_clients.clear()
95
+ session.receiver_tasks.clear()
96
+
97
+ return f"Cleared {client_count} active Claude session(s)."
98
+
99
+ async def get_status(self, user_id: Union[int, str]) -> str:
100
+ """Get user's session status"""
101
+ if user_id not in self.sessions:
102
+ return "No active session. Send a message to start."
103
+
104
+ session = self.sessions[user_id]
105
+ return session.get_status()
106
+
107
+ async def set_executing(self, user_id: Union[int, str], is_executing: bool):
108
+ """Set execution status for user session"""
109
+ if user_id in self.sessions:
110
+ async with self._lock:
111
+ self.sessions[user_id].is_executing = is_executing
112
+ self.sessions[user_id].last_activity = datetime.now()
113
+
114
+ async def is_executing(self, user_id: Union[int, str]) -> bool:
115
+ """Check if user has an active execution"""
116
+ if user_id not in self.sessions:
117
+ return False
118
+ return self.sessions[user_id].is_executing
119
+
120
+ async def cleanup_inactive_sessions(self, inactive_hours: int = 24):
121
+ """Clean up inactive sessions"""
122
+ async with self._lock:
123
+ current_time = datetime.now()
124
+ to_remove = []
125
+
126
+ for user_id, session in self.sessions.items():
127
+ time_diff = current_time - session.last_activity
128
+ if time_diff.total_seconds() > inactive_hours * 3600:
129
+ to_remove.append(user_id)
130
+
131
+ for user_id in to_remove:
132
+ session = self.sessions[user_id]
133
+ # Cleanup Claude SDK clients before removing session
134
+ await session.cleanup_clients()
135
+ del self.sessions[user_id]
136
+ logger.info(f"Cleaned up inactive session for user {user_id}")
137
+
138
+ return len(to_remove)