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,639 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional, List, Tuple, Any, Dict
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseMarkdownFormatter(ABC):
|
|
7
|
+
"""Abstract base class for platform-specific markdown formatters"""
|
|
8
|
+
|
|
9
|
+
# Common formatting methods that work across platforms
|
|
10
|
+
def format_code_inline(self, text: str) -> str:
|
|
11
|
+
"""Format inline code - same for most platforms"""
|
|
12
|
+
return f"`{text}`"
|
|
13
|
+
|
|
14
|
+
def format_code_block(self, code: str, language: str = "") -> str:
|
|
15
|
+
"""Format code block - same for most platforms"""
|
|
16
|
+
return f"```{language}\n{code}\n```"
|
|
17
|
+
|
|
18
|
+
def format_emoji(self, emoji: str) -> str:
|
|
19
|
+
"""Format emoji - same for all platforms"""
|
|
20
|
+
return emoji
|
|
21
|
+
|
|
22
|
+
def format_quote(self, text: str) -> str:
|
|
23
|
+
"""Format quoted text - commonly using >"""
|
|
24
|
+
lines = text.split("\n")
|
|
25
|
+
return "\n".join(f"> {line}" for line in lines)
|
|
26
|
+
|
|
27
|
+
def format_list_item(self, text: str, level: int = 0) -> str:
|
|
28
|
+
"""Format list item with indentation"""
|
|
29
|
+
indent = " " * level
|
|
30
|
+
return f"{indent}⢠{text}"
|
|
31
|
+
|
|
32
|
+
def format_numbered_list_item(self, text: str, number: int, level: int = 0) -> str:
|
|
33
|
+
"""Format numbered list item"""
|
|
34
|
+
indent = " " * level
|
|
35
|
+
return f"{indent}{number}. {text}"
|
|
36
|
+
|
|
37
|
+
def format_horizontal_rule(self) -> str:
|
|
38
|
+
"""Format horizontal rule"""
|
|
39
|
+
return "---"
|
|
40
|
+
|
|
41
|
+
# Core text formatting method
|
|
42
|
+
def format_text(self, text: str, safe: bool = False) -> str:
|
|
43
|
+
"""Format plain text with automatic escaping
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
text: The text to format
|
|
47
|
+
safe: If True, text is already escaped/formatted and won't be escaped again
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Formatted text with special characters escaped
|
|
51
|
+
"""
|
|
52
|
+
if safe:
|
|
53
|
+
return text
|
|
54
|
+
return self.escape_special_chars(text)
|
|
55
|
+
|
|
56
|
+
def format_plain(self, text: str) -> str:
|
|
57
|
+
"""Alias for format_text - formats plain text with escaping"""
|
|
58
|
+
return self.format_text(text)
|
|
59
|
+
|
|
60
|
+
# Platform-specific abstract methods
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def format_bold(self, text: str) -> str:
|
|
63
|
+
"""Format bold text - platform specific"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def format_italic(self, text: str) -> str:
|
|
68
|
+
"""Format italic text - platform specific"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def format_strikethrough(self, text: str) -> str:
|
|
73
|
+
"""Format strikethrough text - platform specific"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def format_link(self, text: str, url: str) -> str:
|
|
78
|
+
"""Format hyperlink - platform specific"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def escape_special_chars(self, text: str) -> str:
|
|
83
|
+
"""Escape platform-specific special characters"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# High-level message composition methods
|
|
87
|
+
def format_message(self, *lines) -> str:
|
|
88
|
+
"""Compose a message from multiple lines
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
*lines: Variable number of lines to compose
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Formatted message with proper line breaks
|
|
95
|
+
"""
|
|
96
|
+
return "\n".join(str(line) for line in lines if line)
|
|
97
|
+
|
|
98
|
+
def format_bullet_list(self, items: List[str], escape: bool = True) -> List[str]:
|
|
99
|
+
"""Format a list of items as bullet points
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
items: List of items to format
|
|
103
|
+
escape: Whether to escape special characters in items
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of formatted bullet points
|
|
107
|
+
"""
|
|
108
|
+
formatted = []
|
|
109
|
+
for item in items:
|
|
110
|
+
if escape:
|
|
111
|
+
item = self.format_text(item)
|
|
112
|
+
formatted.append(f"⢠{item}")
|
|
113
|
+
return formatted
|
|
114
|
+
|
|
115
|
+
def format_definition_item(self, label: str, description: str) -> str:
|
|
116
|
+
"""Format a single definition item with label and description
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
label: The label/key text
|
|
120
|
+
description: The description text
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Formatted definition item
|
|
124
|
+
"""
|
|
125
|
+
# Default implementation - subclasses can override for platform-specific needs
|
|
126
|
+
return f"⢠{self.format_bold(label)} - {self.format_text(description)}"
|
|
127
|
+
|
|
128
|
+
def format_definition_list(
|
|
129
|
+
self, items: List[Tuple[str, str]], bold_key: bool = True
|
|
130
|
+
) -> List[str]:
|
|
131
|
+
"""Format a list of key-value pairs
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
items: List of (key, value) tuples
|
|
135
|
+
bold_key: Whether to make keys bold
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of formatted definition items
|
|
139
|
+
"""
|
|
140
|
+
formatted = []
|
|
141
|
+
for key, value in items:
|
|
142
|
+
if bold_key:
|
|
143
|
+
key_part = self.format_bold(key)
|
|
144
|
+
else:
|
|
145
|
+
key_part = self.format_text(key)
|
|
146
|
+
value_part = self.format_text(value)
|
|
147
|
+
formatted.append(f"⢠{key_part} - {value_part}")
|
|
148
|
+
return formatted
|
|
149
|
+
|
|
150
|
+
def format_info_message(
|
|
151
|
+
self,
|
|
152
|
+
title: str,
|
|
153
|
+
emoji: str = "",
|
|
154
|
+
items: List[Tuple[str, str]] = None,
|
|
155
|
+
footer: str = "",
|
|
156
|
+
) -> str:
|
|
157
|
+
"""Format a complete info message with title, items, and optional footer
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
title: Message title
|
|
161
|
+
emoji: Optional emoji for title
|
|
162
|
+
items: Optional list of (label, description) tuples
|
|
163
|
+
footer: Optional footer text
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Formatted info message
|
|
167
|
+
"""
|
|
168
|
+
lines = []
|
|
169
|
+
|
|
170
|
+
# Add header
|
|
171
|
+
if emoji:
|
|
172
|
+
lines.append(f"{emoji} {self.format_bold(title)}")
|
|
173
|
+
else:
|
|
174
|
+
lines.append(self.format_bold(title))
|
|
175
|
+
|
|
176
|
+
# Add blank line after header
|
|
177
|
+
if items or footer:
|
|
178
|
+
lines.append("")
|
|
179
|
+
|
|
180
|
+
# Add items
|
|
181
|
+
if items:
|
|
182
|
+
for label, description in items:
|
|
183
|
+
# Use a platform-specific separator method
|
|
184
|
+
lines.append(self.format_definition_item(label, description))
|
|
185
|
+
|
|
186
|
+
# Add footer
|
|
187
|
+
if footer:
|
|
188
|
+
if items:
|
|
189
|
+
lines.append("")
|
|
190
|
+
lines.append(self.format_text(footer))
|
|
191
|
+
|
|
192
|
+
return self.format_message(*lines)
|
|
193
|
+
|
|
194
|
+
# Convenience methods that combine formatting
|
|
195
|
+
def format_tool_name(self, tool_name: str, emoji: str = "š§") -> str:
|
|
196
|
+
"""Format tool name with emoji and styling"""
|
|
197
|
+
escaped_name = self.escape_special_chars(tool_name)
|
|
198
|
+
return f"{emoji} {self.format_bold('Tool')}: {self.format_code_inline(escaped_name)}"
|
|
199
|
+
|
|
200
|
+
def format_file_path(self, path: str, emoji: str = "š") -> str:
|
|
201
|
+
"""Format file path with emoji"""
|
|
202
|
+
escaped_path = self.escape_special_chars(path)
|
|
203
|
+
return f"{emoji} File: {self.format_code_inline(escaped_path)}"
|
|
204
|
+
|
|
205
|
+
def format_command(self, command: str) -> str:
|
|
206
|
+
"""Format shell command"""
|
|
207
|
+
# For multi-line or long commands, use code block
|
|
208
|
+
if "\n" in command or len(command) > 80:
|
|
209
|
+
return f"š» Command:\n{self.format_code_block(command, 'bash')}"
|
|
210
|
+
else:
|
|
211
|
+
escaped_cmd = self.escape_special_chars(command)
|
|
212
|
+
return f"š» Command: {self.format_code_inline(escaped_cmd)}"
|
|
213
|
+
|
|
214
|
+
def format_error(self, error_text: str) -> str:
|
|
215
|
+
"""Format error message"""
|
|
216
|
+
return (
|
|
217
|
+
f"ā {self.format_bold('Error')}: {self.escape_special_chars(error_text)}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def format_success(self, message: str) -> str:
|
|
221
|
+
"""Format success message"""
|
|
222
|
+
return f"ā
{self.escape_special_chars(message)}"
|
|
223
|
+
|
|
224
|
+
def format_warning(self, warning_text: str) -> str:
|
|
225
|
+
"""Format warning message"""
|
|
226
|
+
return f"ā ļø {self.format_bold('Warning')}: {self.escape_special_chars(warning_text)}"
|
|
227
|
+
|
|
228
|
+
def format_section_header(self, title: str, emoji: str = "") -> str:
|
|
229
|
+
"""Format section header"""
|
|
230
|
+
if emoji:
|
|
231
|
+
return f"{emoji} {self.format_bold(title)}"
|
|
232
|
+
return self.format_bold(title)
|
|
233
|
+
|
|
234
|
+
def format_key_value(self, key: str, value: str, inline: bool = True) -> str:
|
|
235
|
+
"""Format key-value pair"""
|
|
236
|
+
escaped_key = self.escape_special_chars(key)
|
|
237
|
+
escaped_value = self.escape_special_chars(value)
|
|
238
|
+
|
|
239
|
+
if inline:
|
|
240
|
+
return f"{self.format_bold(escaped_key)}: {escaped_value}"
|
|
241
|
+
else:
|
|
242
|
+
return f"{self.format_bold(escaped_key)}:\n{escaped_value}"
|
|
243
|
+
|
|
244
|
+
def truncate_text(
|
|
245
|
+
self, text: str, max_length: int = 50, suffix: str = "..."
|
|
246
|
+
) -> str:
|
|
247
|
+
"""Truncate text to specified length"""
|
|
248
|
+
if len(text) <= max_length:
|
|
249
|
+
return text
|
|
250
|
+
return text[:max_length] + suffix
|
|
251
|
+
|
|
252
|
+
# Claude message formatting methods
|
|
253
|
+
def format_system_message(
|
|
254
|
+
self, cwd: str, subtype: str, session_id: Optional[str] = None
|
|
255
|
+
) -> str:
|
|
256
|
+
"""Format system message"""
|
|
257
|
+
header = self.format_section_header(f"System {subtype}", "š§")
|
|
258
|
+
cwd_line = self.format_file_path(cwd, emoji="š").replace(
|
|
259
|
+
"File:", "Working directory:"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Add session ID if available
|
|
263
|
+
if session_id:
|
|
264
|
+
session_line = f"š Session ID: {self.format_code_inline(session_id)}"
|
|
265
|
+
ready_line = f"⨠{self.format_text('Ready to work!')}"
|
|
266
|
+
return f"{header}\n{cwd_line}\n{session_line}\n{ready_line}"
|
|
267
|
+
else:
|
|
268
|
+
ready_line = f"⨠{self.format_text('Ready to work!')}"
|
|
269
|
+
return f"{header}\n{cwd_line}\n{ready_line}"
|
|
270
|
+
|
|
271
|
+
def format_assistant_message(self, content_parts: List[str]) -> str:
|
|
272
|
+
"""Format assistant message"""
|
|
273
|
+
header = self.format_section_header("Assistant", "š¤")
|
|
274
|
+
# Escape content parts that are plain text
|
|
275
|
+
escaped_parts = []
|
|
276
|
+
for part in content_parts:
|
|
277
|
+
# Only escape if it's plain text (not already formatted with tool info)
|
|
278
|
+
if not part.startswith(
|
|
279
|
+
(
|
|
280
|
+
"š§",
|
|
281
|
+
"š»",
|
|
282
|
+
"š",
|
|
283
|
+
"š",
|
|
284
|
+
"āļø",
|
|
285
|
+
"š",
|
|
286
|
+
"š",
|
|
287
|
+
"š",
|
|
288
|
+
"š",
|
|
289
|
+
"ā
",
|
|
290
|
+
"ā",
|
|
291
|
+
"š¤",
|
|
292
|
+
"š",
|
|
293
|
+
"š",
|
|
294
|
+
"šŖ",
|
|
295
|
+
)
|
|
296
|
+
):
|
|
297
|
+
escaped_parts.append(self.escape_special_chars(part))
|
|
298
|
+
else:
|
|
299
|
+
# Already formatted tool output, don't escape
|
|
300
|
+
escaped_parts.append(part)
|
|
301
|
+
parts = [header] + escaped_parts
|
|
302
|
+
return "\n\n".join(parts)
|
|
303
|
+
|
|
304
|
+
def format_user_message(self, content_parts: List[str]) -> str:
|
|
305
|
+
"""Format user/response message"""
|
|
306
|
+
header = self.format_section_header("Response", "š¤")
|
|
307
|
+
# Escape content parts that are plain text
|
|
308
|
+
escaped_parts = []
|
|
309
|
+
for part in content_parts:
|
|
310
|
+
# Only escape if it's plain text (not already formatted)
|
|
311
|
+
if not part.startswith(
|
|
312
|
+
(
|
|
313
|
+
"š§",
|
|
314
|
+
"š»",
|
|
315
|
+
"š",
|
|
316
|
+
"š",
|
|
317
|
+
"āļø",
|
|
318
|
+
"š",
|
|
319
|
+
"š",
|
|
320
|
+
"š",
|
|
321
|
+
"š",
|
|
322
|
+
"ā
",
|
|
323
|
+
"ā",
|
|
324
|
+
"š¤",
|
|
325
|
+
"š",
|
|
326
|
+
"š",
|
|
327
|
+
"šŖ",
|
|
328
|
+
)
|
|
329
|
+
):
|
|
330
|
+
escaped_parts.append(self.escape_special_chars(part))
|
|
331
|
+
else:
|
|
332
|
+
# Already formatted output, don't escape
|
|
333
|
+
escaped_parts.append(part)
|
|
334
|
+
parts = [header] + escaped_parts
|
|
335
|
+
return "\n\n".join(parts)
|
|
336
|
+
|
|
337
|
+
def format_result_message(
|
|
338
|
+
self, subtype: str, duration_ms: int, result: Optional[str] = None
|
|
339
|
+
) -> str:
|
|
340
|
+
"""Format result message"""
|
|
341
|
+
# Calculate duration
|
|
342
|
+
total_seconds = duration_ms / 1000
|
|
343
|
+
minutes = int(total_seconds // 60)
|
|
344
|
+
seconds = int(total_seconds % 60)
|
|
345
|
+
|
|
346
|
+
if minutes > 0:
|
|
347
|
+
duration_str = f"{minutes}m {seconds}s"
|
|
348
|
+
else:
|
|
349
|
+
duration_str = f"{seconds}s"
|
|
350
|
+
|
|
351
|
+
# Format result - don't include subtype in parentheses to avoid escaping issues
|
|
352
|
+
header = self.format_section_header("Result", "š")
|
|
353
|
+
if subtype:
|
|
354
|
+
header += f" {self.format_italic(subtype)}"
|
|
355
|
+
duration_line = self.format_key_value("ā±ļø Duration", duration_str)
|
|
356
|
+
|
|
357
|
+
result_text = f"{header}\n{duration_line}"
|
|
358
|
+
|
|
359
|
+
if result:
|
|
360
|
+
result_text += f"\n\n{result}"
|
|
361
|
+
|
|
362
|
+
return result_text
|
|
363
|
+
|
|
364
|
+
def format_tool_result(self, is_error: bool, content: Optional[str] = None) -> str:
|
|
365
|
+
"""Format tool result block"""
|
|
366
|
+
emoji = "ā" if is_error else "ā
"
|
|
367
|
+
result_info = f"{emoji} {self.format_bold('Tool Result')}"
|
|
368
|
+
|
|
369
|
+
if content:
|
|
370
|
+
content_str = str(content)
|
|
371
|
+
if len(content_str) > 500:
|
|
372
|
+
content_str = content_str[:500] + "..."
|
|
373
|
+
result_info += f"\n{self.format_code_block(content_str)}"
|
|
374
|
+
|
|
375
|
+
return result_info
|
|
376
|
+
|
|
377
|
+
def format_toolcall(
|
|
378
|
+
self,
|
|
379
|
+
tool_name: str,
|
|
380
|
+
tool_input: Optional[Dict[str, Any]] = None,
|
|
381
|
+
get_relative_path: Optional[callable] = None,
|
|
382
|
+
) -> str:
|
|
383
|
+
"""Format a single-line tool call summary.
|
|
384
|
+
|
|
385
|
+
Intended for compact, append-only logs (Tool name + params).
|
|
386
|
+
"""
|
|
387
|
+
normalized_input: Dict[str, Any] = {}
|
|
388
|
+
for key, value in (tool_input or {}).items():
|
|
389
|
+
if (
|
|
390
|
+
isinstance(value, str)
|
|
391
|
+
and get_relative_path
|
|
392
|
+
and key
|
|
393
|
+
in {
|
|
394
|
+
"file_path",
|
|
395
|
+
"filePath",
|
|
396
|
+
"path",
|
|
397
|
+
"directory",
|
|
398
|
+
"cwd",
|
|
399
|
+
"workdir",
|
|
400
|
+
}
|
|
401
|
+
):
|
|
402
|
+
try:
|
|
403
|
+
normalized_input[key] = get_relative_path(value)
|
|
404
|
+
except Exception:
|
|
405
|
+
normalized_input[key] = value
|
|
406
|
+
else:
|
|
407
|
+
normalized_input[key] = value
|
|
408
|
+
|
|
409
|
+
params = json.dumps(
|
|
410
|
+
normalized_input,
|
|
411
|
+
ensure_ascii=False,
|
|
412
|
+
separators=(",", ":"),
|
|
413
|
+
)
|
|
414
|
+
if params == "{}":
|
|
415
|
+
return f"š§ {self.format_code_inline(tool_name)}"
|
|
416
|
+
return f"š§ {self.format_code_inline(tool_name)} {self.format_code_inline(params)}"
|
|
417
|
+
|
|
418
|
+
def format_todo_item(
|
|
419
|
+
self, status: str, priority: str, content: str, completed: bool = False
|
|
420
|
+
) -> str:
|
|
421
|
+
"""Format a todo item with status and priority"""
|
|
422
|
+
status_emoji = {"pending": "ā³", "in_progress": "š", "completed": "ā
"}.get(
|
|
423
|
+
status, "ā³"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
priority_emoji = {"high": "š“", "medium": "š”", "low": "š¢"}.get(priority, "š”")
|
|
427
|
+
|
|
428
|
+
# Truncate long content
|
|
429
|
+
if len(content) > 50:
|
|
430
|
+
content = content[:50] + "..."
|
|
431
|
+
|
|
432
|
+
# Apply strikethrough for completed items
|
|
433
|
+
if completed:
|
|
434
|
+
formatted_content = self.format_strikethrough(content)
|
|
435
|
+
else:
|
|
436
|
+
formatted_content = self.escape_special_chars(content)
|
|
437
|
+
|
|
438
|
+
return f"⢠{status_emoji} {priority_emoji} {formatted_content}"
|
|
439
|
+
|
|
440
|
+
def format_tool_use(
|
|
441
|
+
self,
|
|
442
|
+
tool_name: str,
|
|
443
|
+
tool_input: Dict[str, Any],
|
|
444
|
+
get_relative_path: Optional[callable] = None,
|
|
445
|
+
) -> str:
|
|
446
|
+
"""Format tool use block with inputs"""
|
|
447
|
+
# Determine tool emoji and category
|
|
448
|
+
if tool_name.startswith("mcp__"):
|
|
449
|
+
tool_category = tool_name.split("__")[1] if "__" in tool_name else "mcp"
|
|
450
|
+
|
|
451
|
+
emoji = "š§"
|
|
452
|
+
tool_info = f"{emoji} {tool_category} {self.format_bold('MCP Tool')}: {self.format_code_inline(tool_name)}"
|
|
453
|
+
else:
|
|
454
|
+
tool_emoji_map = {
|
|
455
|
+
"Task": "š¤",
|
|
456
|
+
"Bash": "š»",
|
|
457
|
+
"Glob": "š",
|
|
458
|
+
"Grep": "š",
|
|
459
|
+
"LS": "š",
|
|
460
|
+
"Read": "š",
|
|
461
|
+
"Edit": "āļø",
|
|
462
|
+
"MultiEdit": "š",
|
|
463
|
+
"Write": "š",
|
|
464
|
+
"NotebookRead": "š",
|
|
465
|
+
"NotebookEdit": "š",
|
|
466
|
+
"WebFetch": "š",
|
|
467
|
+
"WebSearch": "š",
|
|
468
|
+
"TodoWrite": "ā
",
|
|
469
|
+
"ExitPlanMode": "šŖ",
|
|
470
|
+
}
|
|
471
|
+
emoji = tool_emoji_map.get(tool_name, "š§")
|
|
472
|
+
tool_info = f"{emoji} {self.format_bold('Tool')}: {self.format_code_inline(tool_name)}"
|
|
473
|
+
|
|
474
|
+
# Format tool inputs
|
|
475
|
+
input_info = []
|
|
476
|
+
|
|
477
|
+
# File operations
|
|
478
|
+
if "file_path" in tool_input and tool_input["file_path"]:
|
|
479
|
+
path = tool_input["file_path"]
|
|
480
|
+
if get_relative_path:
|
|
481
|
+
path = get_relative_path(path)
|
|
482
|
+
input_info.append(self.format_file_path(path))
|
|
483
|
+
|
|
484
|
+
# Path operations
|
|
485
|
+
if "path" in tool_input and tool_input["path"]:
|
|
486
|
+
path = tool_input["path"]
|
|
487
|
+
if get_relative_path:
|
|
488
|
+
path = get_relative_path(path)
|
|
489
|
+
input_info.append(self.format_file_path(path, emoji="š"))
|
|
490
|
+
|
|
491
|
+
# Command operations
|
|
492
|
+
if "command" in tool_input and tool_input["command"]:
|
|
493
|
+
input_info.append(self.format_command(tool_input["command"]))
|
|
494
|
+
|
|
495
|
+
# Description
|
|
496
|
+
if "description" in tool_input and tool_input["description"]:
|
|
497
|
+
input_info.append(
|
|
498
|
+
f"š Description: {self.format_code_inline(tool_input['description'])}"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Pattern/Query
|
|
502
|
+
if "pattern" in tool_input and tool_input["pattern"]:
|
|
503
|
+
input_info.append(
|
|
504
|
+
f"š Pattern: {self.format_code_inline(tool_input['pattern'])}"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
if "query" in tool_input and tool_input["query"]:
|
|
508
|
+
query_str = str(tool_input["query"])
|
|
509
|
+
truncated = self.truncate_text(query_str, 50)
|
|
510
|
+
input_info.append(f"š Query: {self.format_code_inline(truncated)}")
|
|
511
|
+
|
|
512
|
+
# URL
|
|
513
|
+
if "url" in tool_input and tool_input["url"]:
|
|
514
|
+
input_info.append(
|
|
515
|
+
f"š URL: {self.format_code_inline(str(tool_input['url']))}"
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# Prompt
|
|
519
|
+
if "prompt" in tool_input and tool_input["prompt"]:
|
|
520
|
+
prompt_str = str(tool_input["prompt"])
|
|
521
|
+
truncated = self.truncate_text(prompt_str, 100)
|
|
522
|
+
input_info.append(f"š Prompt: {self.escape_special_chars(truncated)}")
|
|
523
|
+
|
|
524
|
+
# Edit operations
|
|
525
|
+
if "old_string" in tool_input and tool_input["old_string"]:
|
|
526
|
+
old_str = self.truncate_text(str(tool_input["old_string"]), 50)
|
|
527
|
+
input_info.append(f"š Old: {self.format_code_inline(old_str)}")
|
|
528
|
+
|
|
529
|
+
if "new_string" in tool_input and tool_input["new_string"]:
|
|
530
|
+
new_str = self.truncate_text(str(tool_input["new_string"]), 50)
|
|
531
|
+
input_info.append(f"āļø New: {self.format_code_inline(new_str)}")
|
|
532
|
+
|
|
533
|
+
# MultiEdit
|
|
534
|
+
if "edits" in tool_input and tool_input["edits"]:
|
|
535
|
+
edits_count = len(tool_input["edits"])
|
|
536
|
+
input_info.append(f"š Edits: {edits_count} changes")
|
|
537
|
+
|
|
538
|
+
# Other common parameters
|
|
539
|
+
if "limit" in tool_input and tool_input["limit"]:
|
|
540
|
+
input_info.append(f"š¢ Limit: {tool_input['limit']}")
|
|
541
|
+
|
|
542
|
+
if "offset" in tool_input and tool_input["offset"]:
|
|
543
|
+
input_info.append(f"š Offset: {tool_input['offset']}")
|
|
544
|
+
|
|
545
|
+
# Task tool
|
|
546
|
+
if "subagent_type" in tool_input and tool_input["subagent_type"]:
|
|
547
|
+
input_info.append(
|
|
548
|
+
f"š¤ Agent: {self.format_code_inline(str(tool_input['subagent_type']))}"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
if "plan" in tool_input and tool_input["plan"]:
|
|
552
|
+
plan_str = self.truncate_text(str(tool_input["plan"]), 100)
|
|
553
|
+
input_info.append(f"š Plan: {self.escape_special_chars(plan_str)}")
|
|
554
|
+
|
|
555
|
+
# Notebook operations
|
|
556
|
+
if "cell_id" in tool_input and tool_input["cell_id"]:
|
|
557
|
+
input_info.append(
|
|
558
|
+
f"š Cell ID: {self.format_code_inline(str(tool_input['cell_id']))}"
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
if "cell_type" in tool_input and tool_input["cell_type"]:
|
|
562
|
+
input_info.append(
|
|
563
|
+
f"š Cell Type: {self.format_code_inline(str(tool_input['cell_type']))}"
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# WebSearch
|
|
567
|
+
if "allowed_domains" in tool_input and tool_input["allowed_domains"]:
|
|
568
|
+
count = len(tool_input["allowed_domains"])
|
|
569
|
+
input_info.append(f"ā
Allowed domains: {count}")
|
|
570
|
+
|
|
571
|
+
if "blocked_domains" in tool_input and tool_input["blocked_domains"]:
|
|
572
|
+
count = len(tool_input["blocked_domains"])
|
|
573
|
+
input_info.append(f"š« Blocked domains: {count}")
|
|
574
|
+
|
|
575
|
+
# Grep specific
|
|
576
|
+
if "glob" in tool_input and tool_input["glob"]:
|
|
577
|
+
input_info.append(
|
|
578
|
+
f"šÆ Glob: {self.format_code_inline(str(tool_input['glob']))}"
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if "type" in tool_input and tool_input["type"]:
|
|
582
|
+
input_info.append(
|
|
583
|
+
f"š Type: {self.format_code_inline(str(tool_input['type']))}"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
if "output_mode" in tool_input and tool_input["output_mode"]:
|
|
587
|
+
input_info.append(
|
|
588
|
+
f"š Output mode: {self.format_code_inline(str(tool_input['output_mode']))}"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Combine tool info with inputs
|
|
592
|
+
if input_info:
|
|
593
|
+
tool_info += "\n" + "\n".join(input_info)
|
|
594
|
+
|
|
595
|
+
# Handle special tool content formatting
|
|
596
|
+
if tool_name == "TodoWrite" and "todos" in tool_input:
|
|
597
|
+
todos = tool_input["todos"]
|
|
598
|
+
tool_info += f"\nš {len(todos)} todo items:"
|
|
599
|
+
for todo in todos:
|
|
600
|
+
status = todo.get("status", "pending")
|
|
601
|
+
priority = todo.get("priority", "medium")
|
|
602
|
+
content = todo.get("content", "No content")
|
|
603
|
+
completed = status == "completed"
|
|
604
|
+
todo_line = self.format_todo_item(status, priority, content, completed)
|
|
605
|
+
tool_info += f"\n{todo_line}"
|
|
606
|
+
|
|
607
|
+
elif tool_name in ["Write", "Edit", "MultiEdit"] and "content" in tool_input:
|
|
608
|
+
content = str(tool_input["content"])
|
|
609
|
+
if len(content) > 300:
|
|
610
|
+
content = content[:300] + "..."
|
|
611
|
+
tool_info += f"\n{self.format_code_block(content)}"
|
|
612
|
+
|
|
613
|
+
elif self._should_show_json(tool_name, tool_input):
|
|
614
|
+
try:
|
|
615
|
+
input_json = json.dumps(tool_input, indent=2, ensure_ascii=False)
|
|
616
|
+
tool_info += f"\n{self.format_code_block(input_json, 'json')}"
|
|
617
|
+
except:
|
|
618
|
+
tool_info += f"\n{self.format_code_block(str(tool_input))}"
|
|
619
|
+
|
|
620
|
+
return tool_info
|
|
621
|
+
|
|
622
|
+
def _should_show_json(self, tool_name: str, tool_input: Dict[str, Any]) -> bool:
|
|
623
|
+
"""Determine if JSON should be shown for tool input"""
|
|
624
|
+
no_json_tools = [
|
|
625
|
+
"Bash",
|
|
626
|
+
"Read",
|
|
627
|
+
"Write",
|
|
628
|
+
"Edit",
|
|
629
|
+
"MultiEdit",
|
|
630
|
+
"LS",
|
|
631
|
+
"Glob",
|
|
632
|
+
"Grep",
|
|
633
|
+
"WebFetch",
|
|
634
|
+
"WebSearch",
|
|
635
|
+
"TodoWrite",
|
|
636
|
+
]
|
|
637
|
+
return (
|
|
638
|
+
tool_name not in no_json_tools and tool_input and len(str(tool_input)) < 200
|
|
639
|
+
)
|