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.
- config/__init__.py +37 -0
- config/paths.py +56 -0
- config/v2_compat.py +74 -0
- config/v2_config.py +206 -0
- config/v2_sessions.py +73 -0
- config/v2_settings.py +115 -0
- core/__init__.py +0 -0
- core/controller.py +736 -0
- core/handlers/__init__.py +13 -0
- core/handlers/command_handlers.py +342 -0
- core/handlers/message_handler.py +365 -0
- core/handlers/session_handler.py +233 -0
- core/handlers/settings_handler.py +362 -0
- modules/__init__.py +0 -0
- modules/agent_router.py +58 -0
- modules/agents/__init__.py +38 -0
- modules/agents/base.py +91 -0
- modules/agents/claude_agent.py +344 -0
- modules/agents/codex_agent.py +368 -0
- modules/agents/opencode_agent.py +2155 -0
- modules/agents/service.py +41 -0
- modules/agents/subagent_router.py +136 -0
- modules/claude_client.py +154 -0
- modules/im/__init__.py +63 -0
- modules/im/base.py +323 -0
- modules/im/factory.py +60 -0
- modules/im/formatters/__init__.py +4 -0
- modules/im/formatters/base_formatter.py +639 -0
- modules/im/formatters/slack_formatter.py +127 -0
- modules/im/slack.py +2091 -0
- modules/session_manager.py +138 -0
- modules/settings_manager.py +587 -0
- vibe/__init__.py +6 -0
- vibe/__main__.py +12 -0
- vibe/_version.py +34 -0
- vibe/api.py +412 -0
- vibe/cli.py +637 -0
- vibe/runtime.py +213 -0
- vibe/service_main.py +101 -0
- vibe/templates/slack_manifest.json +65 -0
- vibe/ui/dist/assets/index-8g3mNwMK.js +35 -0
- vibe/ui/dist/assets/index-M55aMB5R.css +1 -0
- vibe/ui/dist/assets/logo-BzryTZ7u.png +0 -0
- vibe/ui/dist/index.html +17 -0
- vibe/ui/dist/logo.png +0 -0
- vibe/ui/dist/vite.svg +1 -0
- vibe/ui_server.py +346 -0
- vibe_remote-2.1.6.dist-info/METADATA +295 -0
- vibe_remote-2.1.6.dist-info/RECORD +52 -0
- vibe_remote-2.1.6.dist-info/WHEEL +4 -0
- vibe_remote-2.1.6.dist-info/entry_points.txt +2 -0
- vibe_remote-2.1.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Handler modules for organizing controller functionality"""
|
|
2
|
+
|
|
3
|
+
from .command_handlers import CommandHandlers
|
|
4
|
+
from .session_handler import SessionHandler
|
|
5
|
+
from .settings_handler import SettingsHandler
|
|
6
|
+
from .message_handler import MessageHandler
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'CommandHandlers',
|
|
10
|
+
'SessionHandler',
|
|
11
|
+
'SettingsHandler',
|
|
12
|
+
'MessageHandler'
|
|
13
|
+
]
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Command handlers for bot commands like /start, /clear, /cwd, etc."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from modules.agents import AgentRequest, get_agent_display_name
|
|
7
|
+
from modules.im import MessageContext, InlineKeyboard, InlineButton
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CommandHandlers:
|
|
13
|
+
"""Handles all bot command operations"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, controller):
|
|
16
|
+
"""Initialize with reference to main controller"""
|
|
17
|
+
self.controller = controller
|
|
18
|
+
self.config = controller.config
|
|
19
|
+
self.im_client = controller.im_client
|
|
20
|
+
self.session_manager = controller.session_manager
|
|
21
|
+
self.settings_manager = controller.settings_manager
|
|
22
|
+
|
|
23
|
+
def _get_channel_context(self, context: MessageContext) -> MessageContext:
|
|
24
|
+
"""Get context for channel messages (no thread)"""
|
|
25
|
+
# For Slack: send command responses directly to channel, not in thread
|
|
26
|
+
if self.config.platform == "slack":
|
|
27
|
+
return MessageContext(
|
|
28
|
+
user_id=context.user_id,
|
|
29
|
+
channel_id=context.channel_id,
|
|
30
|
+
thread_id=None, # No thread for command responses
|
|
31
|
+
platform_specific=context.platform_specific,
|
|
32
|
+
)
|
|
33
|
+
# For other platforms, keep original context
|
|
34
|
+
return context
|
|
35
|
+
|
|
36
|
+
async def handle_start(self, context: MessageContext, args: str = ""):
|
|
37
|
+
"""Handle /start command with interactive buttons"""
|
|
38
|
+
platform_name = self.config.platform.capitalize()
|
|
39
|
+
|
|
40
|
+
# Get user and channel info
|
|
41
|
+
try:
|
|
42
|
+
user_info = await self.im_client.get_user_info(context.user_id)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(f"Failed to get user info: {e}")
|
|
45
|
+
user_info = {"id": context.user_id}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
channel_info = await self.im_client.get_channel_info(context.channel_id)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.warning(f"Failed to get channel info: {e}")
|
|
51
|
+
channel_info = {
|
|
52
|
+
"id": context.channel_id,
|
|
53
|
+
"name": (
|
|
54
|
+
"Direct Message"
|
|
55
|
+
if context.channel_id.startswith("D")
|
|
56
|
+
else context.channel_id
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
agent_name = self.controller.resolve_agent_for_context(context)
|
|
61
|
+
default_agent = getattr(self.controller.agent_service, "default_agent", None)
|
|
62
|
+
agent_display_name = get_agent_display_name(
|
|
63
|
+
agent_name, fallback=default_agent or "Unknown"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# For non-Slack platforms, use traditional text message
|
|
67
|
+
if self.config.platform != "slack":
|
|
68
|
+
formatter = self.im_client.formatter
|
|
69
|
+
|
|
70
|
+
# Build welcome message using formatter to handle escaping properly
|
|
71
|
+
lines = [
|
|
72
|
+
formatter.format_bold("Welcome to Vibe Remote!"),
|
|
73
|
+
"",
|
|
74
|
+
f"Platform: {formatter.format_text(platform_name)}",
|
|
75
|
+
f"Agent: {formatter.format_text(agent_display_name)}",
|
|
76
|
+
f"User ID: {formatter.format_code_inline(context.user_id)}",
|
|
77
|
+
f"Channel/Chat ID: {formatter.format_code_inline(context.channel_id)}",
|
|
78
|
+
"",
|
|
79
|
+
formatter.format_bold("Commands:"),
|
|
80
|
+
formatter.format_text("@Vibe Remote /start - Show this message"),
|
|
81
|
+
formatter.format_text("@Vibe Remote /clear - Reset session and start fresh"),
|
|
82
|
+
formatter.format_text("@Vibe Remote /cwd - Show current working directory"),
|
|
83
|
+
formatter.format_text("@Vibe Remote /set_cwd <path> - Set working directory"),
|
|
84
|
+
formatter.format_text("@Vibe Remote /settings - Personalization settings"),
|
|
85
|
+
formatter.format_text(
|
|
86
|
+
f"@Vibe Remote /stop - Interrupt {agent_display_name} execution"
|
|
87
|
+
),
|
|
88
|
+
"",
|
|
89
|
+
formatter.format_bold("How it works:"),
|
|
90
|
+
formatter.format_text(
|
|
91
|
+
f"âĸ Send any message and it's immediately sent to {agent_display_name}"
|
|
92
|
+
),
|
|
93
|
+
formatter.format_text(
|
|
94
|
+
"âĸ Each chat maintains its own conversation context"
|
|
95
|
+
),
|
|
96
|
+
formatter.format_text("âĸ Use /clear to reset the conversation"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
message_text = formatter.format_message(*lines)
|
|
100
|
+
channel_context = self._get_channel_context(context)
|
|
101
|
+
await self.im_client.send_message(channel_context, message_text)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
# For Slack, create interactive buttons using Block Kit
|
|
105
|
+
user_name = user_info.get("real_name") or user_info.get("name") or "User"
|
|
106
|
+
|
|
107
|
+
# Create interactive buttons for commands
|
|
108
|
+
buttons = [
|
|
109
|
+
# Row 1: Directory management
|
|
110
|
+
[
|
|
111
|
+
InlineButton(text="đ Current Dir", callback_data="cmd_cwd"),
|
|
112
|
+
InlineButton(text="đ Change Work Dir", callback_data="cmd_change_cwd"),
|
|
113
|
+
],
|
|
114
|
+
# Row 2: Session and Settings
|
|
115
|
+
[
|
|
116
|
+
InlineButton(text="đ Clear All Session", callback_data="cmd_clear"),
|
|
117
|
+
InlineButton(text="âī¸ Settings", callback_data="cmd_settings"),
|
|
118
|
+
],
|
|
119
|
+
# Row 3: Agent/Model switching
|
|
120
|
+
[InlineButton(text="đ¤ Agent Settings", callback_data="cmd_routing")],
|
|
121
|
+
# Row 4: Help
|
|
122
|
+
[InlineButton(text="âšī¸ How it Works", callback_data="info_how_it_works")],
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
keyboard = InlineKeyboard(buttons=buttons)
|
|
126
|
+
|
|
127
|
+
welcome_text = f"""đ **Welcome to Vibe Remote!**
|
|
128
|
+
|
|
129
|
+
đ Hello **{user_name}**!
|
|
130
|
+
đ§ Platform: **{platform_name}**
|
|
131
|
+
đ¤ Agent: **{agent_display_name}**
|
|
132
|
+
đ Channel: **{channel_info.get('name', 'Unknown')}**
|
|
133
|
+
|
|
134
|
+
**Quick Actions:**
|
|
135
|
+
Use the buttons below to manage your {agent_display_name} sessions, or simply type any message to start chatting with {agent_display_name}!"""
|
|
136
|
+
|
|
137
|
+
# Send command response to channel (not in thread)
|
|
138
|
+
channel_context = self._get_channel_context(context)
|
|
139
|
+
await self.im_client.send_message_with_buttons(
|
|
140
|
+
channel_context, welcome_text, keyboard
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
async def handle_clear(self, context: MessageContext, args: str = ""):
|
|
144
|
+
"""Handle clear command - clears all sessions across configured agents"""
|
|
145
|
+
try:
|
|
146
|
+
# Get the correct settings key (channel_id for Slack, not user_id)
|
|
147
|
+
settings_key = self.controller._get_settings_key(context)
|
|
148
|
+
|
|
149
|
+
cleared = await self.controller.agent_service.clear_sessions(settings_key)
|
|
150
|
+
if not cleared:
|
|
151
|
+
full_response = (
|
|
152
|
+
"đ No active sessions to clear.\nđ Session state has been reset."
|
|
153
|
+
)
|
|
154
|
+
else:
|
|
155
|
+
details = "\n".join(
|
|
156
|
+
f"âĸ {agent} â {count} session(s)" for agent, count in cleared.items()
|
|
157
|
+
)
|
|
158
|
+
full_response = (
|
|
159
|
+
"â
Cleared active sessions for:\n" f"{details}\nđ All sessions reset."
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
channel_context = self._get_channel_context(context)
|
|
163
|
+
await self.im_client.send_message(channel_context, full_response)
|
|
164
|
+
logger.info(f"Sent clear response to user {context.user_id}")
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.error(f"Error clearing session: {e}", exc_info=True)
|
|
168
|
+
try:
|
|
169
|
+
channel_context = self._get_channel_context(context)
|
|
170
|
+
await self.im_client.send_message(
|
|
171
|
+
channel_context, f"â Error clearing session: {str(e)}"
|
|
172
|
+
)
|
|
173
|
+
except Exception as send_error:
|
|
174
|
+
logger.error(
|
|
175
|
+
f"Failed to send error message: {send_error}", exc_info=True
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async def handle_cwd(self, context: MessageContext, args: str = ""):
|
|
179
|
+
"""Handle cwd command - show current working directory"""
|
|
180
|
+
try:
|
|
181
|
+
# Get CWD based on context (channel/chat)
|
|
182
|
+
absolute_path = self.controller.get_cwd(context)
|
|
183
|
+
|
|
184
|
+
# Build response using formatter to avoid escaping issues
|
|
185
|
+
formatter = self.im_client.formatter
|
|
186
|
+
|
|
187
|
+
# Format path properly with code block
|
|
188
|
+
path_line = f"đ Current Working Directory:\n{formatter.format_code_inline(absolute_path)}"
|
|
189
|
+
|
|
190
|
+
# Build status lines
|
|
191
|
+
status_lines = []
|
|
192
|
+
if os.path.exists(absolute_path):
|
|
193
|
+
status_lines.append("â
Directory exists")
|
|
194
|
+
else:
|
|
195
|
+
status_lines.append("â ī¸ Directory does not exist")
|
|
196
|
+
|
|
197
|
+
status_lines.append("đĄ This is where Agent will execute commands")
|
|
198
|
+
|
|
199
|
+
# Combine all parts
|
|
200
|
+
response_text = path_line + "\n" + "\n".join(status_lines)
|
|
201
|
+
|
|
202
|
+
channel_context = self._get_channel_context(context)
|
|
203
|
+
await self.im_client.send_message(channel_context, response_text)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.error(f"Error getting cwd: {e}")
|
|
206
|
+
channel_context = self._get_channel_context(context)
|
|
207
|
+
await self.im_client.send_message(
|
|
208
|
+
channel_context, f"Error getting working directory: {str(e)}"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
async def handle_set_cwd(self, context: MessageContext, args: str):
|
|
212
|
+
"""Handle set_cwd command - change working directory"""
|
|
213
|
+
try:
|
|
214
|
+
if not args:
|
|
215
|
+
channel_context = self._get_channel_context(context)
|
|
216
|
+
await self.im_client.send_message(
|
|
217
|
+
channel_context, "Usage: /set_cwd <path>"
|
|
218
|
+
)
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
new_path = args.strip()
|
|
222
|
+
|
|
223
|
+
# Expand user path and get absolute path
|
|
224
|
+
expanded_path = os.path.expanduser(new_path)
|
|
225
|
+
absolute_path = os.path.abspath(expanded_path)
|
|
226
|
+
|
|
227
|
+
# Check if directory exists
|
|
228
|
+
if not os.path.exists(absolute_path):
|
|
229
|
+
# Try to create it
|
|
230
|
+
try:
|
|
231
|
+
os.makedirs(absolute_path, exist_ok=True)
|
|
232
|
+
logger.info(f"Created directory: {absolute_path}")
|
|
233
|
+
except Exception as e:
|
|
234
|
+
channel_context = self._get_channel_context(context)
|
|
235
|
+
await self.im_client.send_message(
|
|
236
|
+
channel_context, f"â Cannot create directory: {str(e)}"
|
|
237
|
+
)
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
if not os.path.isdir(absolute_path):
|
|
241
|
+
formatter = self.im_client.formatter
|
|
242
|
+
error_text = f"â Path exists but is not a directory: {formatter.format_code_inline(absolute_path)}"
|
|
243
|
+
channel_context = self._get_channel_context(context)
|
|
244
|
+
await self.im_client.send_message(channel_context, error_text)
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# Save to user settings
|
|
248
|
+
settings_key = self.controller._get_settings_key(context)
|
|
249
|
+
self.settings_manager.set_custom_cwd(settings_key, absolute_path)
|
|
250
|
+
|
|
251
|
+
logger.info(f"User {context.user_id} changed cwd to: {absolute_path}")
|
|
252
|
+
|
|
253
|
+
formatter = self.im_client.formatter
|
|
254
|
+
response_text = (
|
|
255
|
+
f"â
Working directory changed to:\n"
|
|
256
|
+
f"{formatter.format_code_inline(absolute_path)}"
|
|
257
|
+
)
|
|
258
|
+
channel_context = self._get_channel_context(context)
|
|
259
|
+
await self.im_client.send_message(channel_context, response_text)
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.error(f"Error setting cwd: {e}")
|
|
263
|
+
channel_context = self._get_channel_context(context)
|
|
264
|
+
await self.im_client.send_message(
|
|
265
|
+
channel_context, f"â Error setting working directory: {str(e)}"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
async def handle_change_cwd_modal(self, context: MessageContext):
|
|
269
|
+
"""Handle Change Work Dir button - open modal for Slack"""
|
|
270
|
+
if self.config.platform != "slack":
|
|
271
|
+
# For non-Slack platforms, just send instructions
|
|
272
|
+
channel_context = self._get_channel_context(context)
|
|
273
|
+
await self.im_client.send_message(
|
|
274
|
+
channel_context,
|
|
275
|
+
"đ To change working directory, use:\n`/set_cwd <path>`\n\nExample:\n`/set_cwd ~/projects/myapp`",
|
|
276
|
+
)
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# For Slack, open a modal dialog
|
|
280
|
+
trigger_id = (
|
|
281
|
+
context.platform_specific.get("trigger_id")
|
|
282
|
+
if context.platform_specific
|
|
283
|
+
else None
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if trigger_id and hasattr(self.im_client, "open_change_cwd_modal"):
|
|
287
|
+
try:
|
|
288
|
+
# Get current CWD based on context
|
|
289
|
+
current_cwd = self.controller.get_cwd(context)
|
|
290
|
+
|
|
291
|
+
await self.im_client.open_change_cwd_modal(
|
|
292
|
+
trigger_id, current_cwd, context.channel_id
|
|
293
|
+
)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.error(f"Error opening change CWD modal: {e}")
|
|
296
|
+
channel_context = self._get_channel_context(context)
|
|
297
|
+
await self.im_client.send_message(
|
|
298
|
+
channel_context,
|
|
299
|
+
"â Failed to open directory change dialog. Please try again.",
|
|
300
|
+
)
|
|
301
|
+
else:
|
|
302
|
+
# No trigger_id, show instructions
|
|
303
|
+
channel_context = self._get_channel_context(context)
|
|
304
|
+
await self.im_client.send_message(
|
|
305
|
+
channel_context,
|
|
306
|
+
"đ Click the 'Change Work Dir' button in the @Vibe Remote /start menu to change working directory.",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
async def handle_stop(self, context: MessageContext, args: str = ""):
|
|
310
|
+
"""Handle /stop command - send interrupt message to the active agent"""
|
|
311
|
+
try:
|
|
312
|
+
session_handler = self.controller.session_handler
|
|
313
|
+
base_session_id, working_path, composite_key = (
|
|
314
|
+
session_handler.get_session_info(context)
|
|
315
|
+
)
|
|
316
|
+
settings_key = self.controller._get_settings_key(context)
|
|
317
|
+
agent_name = self.controller.resolve_agent_for_context(context)
|
|
318
|
+
request = AgentRequest(
|
|
319
|
+
context=context,
|
|
320
|
+
message="stop",
|
|
321
|
+
working_path=working_path,
|
|
322
|
+
base_session_id=base_session_id,
|
|
323
|
+
composite_session_id=composite_key,
|
|
324
|
+
settings_key=settings_key,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
handled = await self.controller.agent_service.handle_stop(
|
|
328
|
+
agent_name, request
|
|
329
|
+
)
|
|
330
|
+
if not handled:
|
|
331
|
+
channel_context = self._get_channel_context(context)
|
|
332
|
+
await self.im_client.send_message(
|
|
333
|
+
channel_context, "âšī¸ No active session to stop for this channel."
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Error sending stop command: {e}", exc_info=True)
|
|
338
|
+
# For errors, still use original context to maintain thread consistency
|
|
339
|
+
await self.im_client.send_message(
|
|
340
|
+
context, # Use original context
|
|
341
|
+
f"â Error sending stop command: {str(e)}",
|
|
342
|
+
)
|