vibecore 0.5.0__py3-none-any.whl → 0.6.1__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 vibecore might be problematic. Click here for more details.
- vibecore/agents/default.py +3 -3
- vibecore/agents/task.py +3 -3
- vibecore/cli.py +68 -43
- vibecore/context.py +41 -15
- vibecore/flow.py +144 -122
- vibecore/handlers/stream_handler.py +0 -10
- vibecore/main.py +63 -270
- vibecore/session/loader.py +2 -2
- vibecore/tools/file/executor.py +13 -5
- vibecore/tools/file/tools.py +5 -5
- vibecore/tools/python/helpers.py +2 -2
- vibecore/tools/python/tools.py +2 -2
- vibecore/tools/shell/executor.py +5 -5
- vibecore/tools/shell/tools.py +5 -5
- vibecore/tools/task/executor.py +2 -2
- vibecore/tools/task/tools.py +2 -2
- vibecore/tools/todo/tools.py +3 -3
- vibecore/tools/webfetch/tools.py +1 -4
- vibecore/tools/websearch/tools.py +1 -4
- vibecore/widgets/core.py +2 -9
- {vibecore-0.5.0.dist-info → vibecore-0.6.1.dist-info}/METADATA +91 -31
- {vibecore-0.5.0.dist-info → vibecore-0.6.1.dist-info}/RECORD +25 -25
- {vibecore-0.5.0.dist-info → vibecore-0.6.1.dist-info}/WHEEL +0 -0
- {vibecore-0.5.0.dist-info → vibecore-0.6.1.dist-info}/entry_points.txt +0 -0
- {vibecore-0.5.0.dist-info → vibecore-0.6.1.dist-info}/licenses/LICENSE +0 -0
vibecore/main.py
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import traceback
|
|
3
3
|
from collections import deque
|
|
4
|
-
from typing import ClassVar, Literal
|
|
4
|
+
from typing import TYPE_CHECKING, ClassVar, Literal
|
|
5
5
|
|
|
6
6
|
from agents import (
|
|
7
|
-
Agent,
|
|
8
|
-
ModelSettings,
|
|
9
|
-
Runner,
|
|
10
7
|
RunResultStreaming,
|
|
8
|
+
Session,
|
|
11
9
|
StreamEvent,
|
|
12
|
-
TResponseInputItem,
|
|
13
10
|
)
|
|
14
|
-
from openai.types import Reasoning
|
|
15
11
|
from openai.types.responses.response_output_message import Content
|
|
16
12
|
from textual import log, work
|
|
17
13
|
from textual.app import App, ComposeResult
|
|
18
14
|
from textual.binding import Binding
|
|
19
15
|
from textual.reactive import reactive
|
|
16
|
+
from textual.selection import Selection
|
|
17
|
+
from textual.widget import Widget
|
|
20
18
|
from textual.widgets import Header
|
|
21
19
|
from textual.worker import Worker
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from vibecore.flow import TWorkflowReturn, VibecoreTextualRunner
|
|
23
|
+
|
|
24
24
|
from vibecore.handlers import AgentStreamHandler
|
|
25
|
-
from vibecore.session import JSONLSession
|
|
26
25
|
from vibecore.session.loader import SessionLoader
|
|
27
|
-
from vibecore.settings import settings
|
|
28
26
|
from vibecore.utils.text import TextExtractor
|
|
29
27
|
from vibecore.widgets.core import AppFooter, MainScroll, MyTextArea
|
|
30
28
|
from vibecore.widgets.info import Welcome
|
|
@@ -37,28 +35,6 @@ class AppIsExiting(Exception):
|
|
|
37
35
|
pass
|
|
38
36
|
|
|
39
37
|
|
|
40
|
-
def detect_reasoning_effort(prompt: str) -> Literal["low", "medium", "high"] | None:
|
|
41
|
-
"""Detect reasoning effort level from user prompt keywords.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
prompt: User input text
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Reasoning effort level or None if no keywords detected
|
|
48
|
-
"""
|
|
49
|
-
prompt_lower = prompt.lower()
|
|
50
|
-
|
|
51
|
-
# Check for highest priority keywords first
|
|
52
|
-
if "ultrathink" in prompt_lower:
|
|
53
|
-
return "high"
|
|
54
|
-
elif "think hard" in prompt_lower:
|
|
55
|
-
return "medium"
|
|
56
|
-
elif "think" in prompt_lower:
|
|
57
|
-
return "low"
|
|
58
|
-
|
|
59
|
-
return None
|
|
60
|
-
|
|
61
|
-
|
|
62
38
|
class VibecoreApp(App):
|
|
63
39
|
"""A Textual app to manage stopwatches."""
|
|
64
40
|
|
|
@@ -83,10 +59,7 @@ class VibecoreApp(App):
|
|
|
83
59
|
|
|
84
60
|
def __init__(
|
|
85
61
|
self,
|
|
86
|
-
|
|
87
|
-
agent: Agent,
|
|
88
|
-
session_id: str | None = None,
|
|
89
|
-
print_mode: bool = False,
|
|
62
|
+
runner: "VibecoreTextualRunner[TWorkflowReturn]",
|
|
90
63
|
show_welcome: bool = True,
|
|
91
64
|
) -> None:
|
|
92
65
|
"""Initialize the Vibecore app with context and agent.
|
|
@@ -95,37 +68,35 @@ class VibecoreApp(App):
|
|
|
95
68
|
context: The VibecoreContext instance
|
|
96
69
|
agent: The Agent instance to use
|
|
97
70
|
session_id: Optional session ID to load existing session
|
|
98
|
-
print_mode: Whether to run in print mode (useful for pipes)
|
|
99
71
|
show_welcome: Whether to show the welcome message (default: True)
|
|
100
72
|
"""
|
|
101
|
-
self.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.input_items: list[TResponseInputItem] = []
|
|
73
|
+
self.runner = runner
|
|
74
|
+
if runner.context:
|
|
75
|
+
runner.context.app = self # Set the app reference in context
|
|
105
76
|
self.current_result: RunResultStreaming | None = None
|
|
106
77
|
self.current_worker: Worker[None] | None = None
|
|
107
|
-
self._session_id_provided = session_id is not None # Track if continuing session
|
|
108
|
-
self.print_mode = print_mode
|
|
109
78
|
self.show_welcome = show_welcome
|
|
110
79
|
self.message_queue: deque[str] = deque() # Queue for user messages
|
|
80
|
+
self.user_input_event = asyncio.Event() # Initialize event for user input coordination
|
|
111
81
|
|
|
112
|
-
|
|
113
|
-
if settings.session.storage_type == "jsonl":
|
|
114
|
-
if session_id is None:
|
|
115
|
-
# Generate a new session ID based on current date/time
|
|
116
|
-
import datetime
|
|
82
|
+
super().__init__()
|
|
117
83
|
|
|
118
|
-
|
|
84
|
+
def on_mouse_up(self) -> None:
|
|
85
|
+
if not self.screen.selections:
|
|
86
|
+
return None
|
|
119
87
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
88
|
+
widget_text: list[str] = []
|
|
89
|
+
for widget, selection in self.screen.selections.items():
|
|
90
|
+
assert isinstance(widget, Widget) and isinstance(selection, Selection)
|
|
91
|
+
if "copy-button" in widget.classes: # Skip copy buttons
|
|
92
|
+
continue
|
|
93
|
+
selected_text_in_widget = widget.get_selection(selection)
|
|
94
|
+
if selected_text_in_widget is not None:
|
|
95
|
+
widget_text.extend(selected_text_in_widget)
|
|
127
96
|
|
|
128
|
-
|
|
97
|
+
selected_text = "".join(widget_text)
|
|
98
|
+
self.copy_to_clipboard(selected_text)
|
|
99
|
+
self.notify("Copied to clipboard")
|
|
129
100
|
|
|
130
101
|
def compose(self) -> ComposeResult:
|
|
131
102
|
"""Create child widgets for the app."""
|
|
@@ -135,32 +106,6 @@ class VibecoreApp(App):
|
|
|
135
106
|
if self.show_welcome:
|
|
136
107
|
yield Welcome()
|
|
137
108
|
|
|
138
|
-
async def on_mount(self) -> None:
|
|
139
|
-
"""Called when the app is mounted."""
|
|
140
|
-
# Connect to MCP servers if configured
|
|
141
|
-
if self.context.mcp_manager:
|
|
142
|
-
try:
|
|
143
|
-
await self.context.mcp_manager.connect()
|
|
144
|
-
log(f"Connected to {len(self.context.mcp_manager.servers)} MCP servers")
|
|
145
|
-
except Exception as e:
|
|
146
|
-
log(f"Failed to connect to MCP servers: {e}")
|
|
147
|
-
# Continue without MCP servers rather than crashing
|
|
148
|
-
|
|
149
|
-
# Load session history if we're continuing from a previous session
|
|
150
|
-
if self._session_id_provided:
|
|
151
|
-
await self.load_session_history()
|
|
152
|
-
|
|
153
|
-
async def on_unmount(self) -> None:
|
|
154
|
-
"""Called when the app is being unmounted (shutdown)."""
|
|
155
|
-
# Cleanup MCP servers during unmount
|
|
156
|
-
if self.context.mcp_manager:
|
|
157
|
-
try:
|
|
158
|
-
log("Disconnecting from MCP servers...")
|
|
159
|
-
await self.context.mcp_manager.disconnect()
|
|
160
|
-
log("Disconnected from MCP servers")
|
|
161
|
-
except Exception as e:
|
|
162
|
-
log(f"Error disconnecting from MCP servers during unmount: {e}")
|
|
163
|
-
|
|
164
109
|
def extract_text_from_content(self, content: list[Content]) -> str:
|
|
165
110
|
"""Extract text from various content formats."""
|
|
166
111
|
return TextExtractor.extract_from_content(content)
|
|
@@ -180,11 +125,6 @@ class VibecoreApp(App):
|
|
|
180
125
|
"""Add a message widget to the main scroll area."""
|
|
181
126
|
await self.add_message(message)
|
|
182
127
|
|
|
183
|
-
async def handle_agent_update(self, new_agent: Agent) -> None:
|
|
184
|
-
"""Handle agent updates."""
|
|
185
|
-
log(f"Agent updated: {new_agent.name}")
|
|
186
|
-
self.agent = new_agent
|
|
187
|
-
|
|
188
128
|
async def handle_agent_error(self, error: Exception) -> None:
|
|
189
129
|
"""Handle errors during streaming."""
|
|
190
130
|
log(f"Error during agent response: {type(error).__name__}: {error!s}")
|
|
@@ -213,9 +153,9 @@ class VibecoreApp(App):
|
|
|
213
153
|
# No messages to clean up
|
|
214
154
|
pass
|
|
215
155
|
|
|
216
|
-
async def load_session_history(self) -> None:
|
|
156
|
+
async def load_session_history(self, session: Session) -> None:
|
|
217
157
|
"""Load and display messages from session history."""
|
|
218
|
-
loader = SessionLoader(
|
|
158
|
+
loader = SessionLoader(session)
|
|
219
159
|
messages = await loader.load_history()
|
|
220
160
|
|
|
221
161
|
# Remove Welcome widget if we have messages
|
|
@@ -238,10 +178,24 @@ class VibecoreApp(App):
|
|
|
238
178
|
|
|
239
179
|
async def wait_for_user_input(self) -> str:
|
|
240
180
|
"""Used in flow mode. See examples/basic_agent.py"""
|
|
181
|
+
if self.message_queue:
|
|
182
|
+
user_input = self.message_queue.popleft()
|
|
183
|
+
|
|
184
|
+
user_message = UserMessage(user_input)
|
|
185
|
+
await self.add_message(user_message)
|
|
186
|
+
user_message.scroll_visible()
|
|
187
|
+
|
|
188
|
+
return user_input
|
|
189
|
+
|
|
241
190
|
self.agent_status = "waiting_user_input"
|
|
242
|
-
self.user_input_event
|
|
191
|
+
self.user_input_event.clear() # Reset the event for next wait
|
|
243
192
|
await self.user_input_event.wait()
|
|
244
|
-
user_input = self.message_queue.
|
|
193
|
+
user_input = self.message_queue.popleft()
|
|
194
|
+
|
|
195
|
+
user_message = UserMessage(user_input)
|
|
196
|
+
await self.add_message(user_message)
|
|
197
|
+
user_message.scroll_visible()
|
|
198
|
+
|
|
245
199
|
return user_input
|
|
246
200
|
|
|
247
201
|
async def on_my_text_area_user_message(self, event: MyTextArea.UserMessage) -> None:
|
|
@@ -249,12 +203,8 @@ class VibecoreApp(App):
|
|
|
249
203
|
if event.text:
|
|
250
204
|
# Check for special commands
|
|
251
205
|
text_strip = event.text.strip()
|
|
252
|
-
if text_strip == "/
|
|
253
|
-
await self.handle_clear_command()
|
|
254
|
-
return
|
|
255
|
-
elif text_strip == "/help":
|
|
206
|
+
if text_strip == "/help":
|
|
256
207
|
help_text = "Available commands:\n"
|
|
257
|
-
help_text += "• /clear - Clear the current session and start a new one\n"
|
|
258
208
|
help_text += "• /help - Show this help message\n\n"
|
|
259
209
|
help_text += "Keyboard shortcuts:\n"
|
|
260
210
|
help_text += "• Esc - Cancel current agent operation\n"
|
|
@@ -263,14 +213,7 @@ class VibecoreApp(App):
|
|
|
263
213
|
await self.add_message(SystemMessage(help_text))
|
|
264
214
|
return
|
|
265
215
|
|
|
266
|
-
|
|
267
|
-
await self.add_message(user_message)
|
|
268
|
-
user_message.scroll_visible()
|
|
269
|
-
|
|
270
|
-
if self.agent_status == "waiting_user_input":
|
|
271
|
-
self.message_queue.append(event.text)
|
|
272
|
-
self.user_input_event.set()
|
|
273
|
-
elif self.agent_status == "running":
|
|
216
|
+
if self.agent_status == "running":
|
|
274
217
|
# If agent is running, queue the message
|
|
275
218
|
self.message_queue.append(event.text)
|
|
276
219
|
log(f"Message queued: {event.text}")
|
|
@@ -281,41 +224,8 @@ class VibecoreApp(App):
|
|
|
281
224
|
status="Generating…", metadata=f"{queued_count} message{'s' if queued_count > 1 else ''} queued"
|
|
282
225
|
)
|
|
283
226
|
else:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
reasoning_effort = detected_effort or settings.reasoning_effort
|
|
287
|
-
|
|
288
|
-
# Create agent with appropriate reasoning effort
|
|
289
|
-
agent_to_use = self.agent
|
|
290
|
-
if reasoning_effort is not None:
|
|
291
|
-
# Create a copy of the agent with updated model settings
|
|
292
|
-
current_settings = self.agent.model_settings or ModelSettings()
|
|
293
|
-
new_reasoning = Reasoning(effort=reasoning_effort, summary=settings.reasoning_summary)
|
|
294
|
-
updated_settings = ModelSettings(
|
|
295
|
-
include_usage=current_settings.include_usage,
|
|
296
|
-
reasoning=new_reasoning,
|
|
297
|
-
)
|
|
298
|
-
agent_to_use = Agent[VibecoreContext](
|
|
299
|
-
name=self.agent.name,
|
|
300
|
-
handoff_description=self.agent.handoff_description,
|
|
301
|
-
instructions=self.agent.instructions,
|
|
302
|
-
tools=self.agent.tools,
|
|
303
|
-
model=self.agent.model,
|
|
304
|
-
model_settings=updated_settings,
|
|
305
|
-
handoffs=self.agent.handoffs,
|
|
306
|
-
mcp_servers=self.agent.mcp_servers,
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
# Process the message immediately
|
|
310
|
-
result = Runner.run_streamed(
|
|
311
|
-
agent_to_use,
|
|
312
|
-
input=event.text, # Pass string directly when using session
|
|
313
|
-
context=self.context,
|
|
314
|
-
max_turns=settings.max_turns,
|
|
315
|
-
session=self.session,
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
self.current_worker = self.handle_streamed_response(result)
|
|
227
|
+
self.message_queue.append(event.text)
|
|
228
|
+
self.user_input_event.set()
|
|
319
229
|
|
|
320
230
|
@work(exclusive=True)
|
|
321
231
|
async def handle_streamed_response(self, result: RunResultStreaming) -> None:
|
|
@@ -325,42 +235,27 @@ class VibecoreApp(App):
|
|
|
325
235
|
self.agent_stream_handler = AgentStreamHandler(self)
|
|
326
236
|
await self.agent_stream_handler.process_stream(result)
|
|
327
237
|
|
|
328
|
-
|
|
238
|
+
# Determine usage based on the last model response rather than the aggregated usage
|
|
239
|
+
# from the entire session so that context fullness reflects the most recent request.
|
|
240
|
+
used_tokens: float = 0.0
|
|
241
|
+
if result.raw_responses:
|
|
242
|
+
last_response = result.raw_responses[-1]
|
|
243
|
+
last_usage = getattr(last_response, "usage", None)
|
|
244
|
+
if last_usage:
|
|
245
|
+
used_tokens = float(last_usage.total_tokens)
|
|
246
|
+
|
|
329
247
|
max_ctx = self._get_model_context_window()
|
|
330
|
-
log(f"Context usage: {
|
|
331
|
-
|
|
248
|
+
log(f"Context usage: {used_tokens} / {max_ctx} total tokens")
|
|
249
|
+
context_fullness = min(1.0, used_tokens / float(max_ctx))
|
|
332
250
|
footer = self.query_one(AppFooter)
|
|
333
|
-
footer.set_context_progress(
|
|
251
|
+
footer.set_context_progress(context_fullness)
|
|
334
252
|
|
|
335
253
|
self.agent_status = "idle"
|
|
336
254
|
self.current_result = None
|
|
337
255
|
self.current_worker = None
|
|
338
256
|
|
|
339
|
-
await self.process_message_queue()
|
|
340
|
-
|
|
341
|
-
async def process_message_queue(self) -> None:
|
|
342
|
-
"""Process any messages that were queued while the agent was running."""
|
|
343
|
-
if self.message_queue:
|
|
344
|
-
# Get the next message from the queue
|
|
345
|
-
next_message = self.message_queue.popleft()
|
|
346
|
-
log(f"Processing queued message: {next_message}")
|
|
347
|
-
|
|
348
|
-
# Process the message
|
|
349
|
-
result = Runner.run_streamed(
|
|
350
|
-
self.agent,
|
|
351
|
-
input=next_message,
|
|
352
|
-
context=self.context,
|
|
353
|
-
max_turns=settings.max_turns,
|
|
354
|
-
session=self.session,
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
self.current_worker = self.handle_streamed_response(result)
|
|
358
|
-
|
|
359
257
|
def _get_model_context_window(self) -> int:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
model_name = settings.default_model
|
|
363
|
-
log(f"Getting context window for model: {model_name}")
|
|
258
|
+
# TODO(serialx): Implement later
|
|
364
259
|
return 200000
|
|
365
260
|
|
|
366
261
|
def action_toggle_dark(self) -> None:
|
|
@@ -400,7 +295,7 @@ class VibecoreApp(App):
|
|
|
400
295
|
"""Reset exit confirmation after 1 second and remove the message."""
|
|
401
296
|
try:
|
|
402
297
|
# Wait for 1 second
|
|
403
|
-
await asyncio.sleep(
|
|
298
|
+
await asyncio.sleep(2.0)
|
|
404
299
|
|
|
405
300
|
# Reset confirmation state
|
|
406
301
|
self._exit_confirmation_active = False
|
|
@@ -411,55 +306,6 @@ class VibecoreApp(App):
|
|
|
411
306
|
# Task was cancelled (new Ctrl-D pressed)
|
|
412
307
|
pass
|
|
413
308
|
|
|
414
|
-
async def run_print(self, prompt: str | None = None) -> str:
|
|
415
|
-
"""Run the agent and return the raw output for printing.
|
|
416
|
-
|
|
417
|
-
Args:
|
|
418
|
-
prompt: Optional prompt text. If not provided, reads from stdin.
|
|
419
|
-
|
|
420
|
-
Returns:
|
|
421
|
-
The agent's text output as a string
|
|
422
|
-
"""
|
|
423
|
-
import sys
|
|
424
|
-
|
|
425
|
-
# Use provided prompt or read from stdin
|
|
426
|
-
input_text = prompt.strip() if prompt else sys.stdin.read().strip()
|
|
427
|
-
|
|
428
|
-
if not input_text:
|
|
429
|
-
return ""
|
|
430
|
-
|
|
431
|
-
# Import needed event types
|
|
432
|
-
from agents import RawResponsesStreamEvent
|
|
433
|
-
from openai.types.responses import ResponseTextDeltaEvent
|
|
434
|
-
|
|
435
|
-
if self.context.mcp_manager:
|
|
436
|
-
await self.context.mcp_manager.connect()
|
|
437
|
-
|
|
438
|
-
# Run the agent
|
|
439
|
-
result = Runner.run_streamed(
|
|
440
|
-
self.agent,
|
|
441
|
-
input=input_text,
|
|
442
|
-
context=self.context,
|
|
443
|
-
max_turns=settings.max_turns,
|
|
444
|
-
session=self.session,
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
# Collect all agent text output
|
|
448
|
-
agent_output = ""
|
|
449
|
-
|
|
450
|
-
async for event in result.stream_events():
|
|
451
|
-
# Handle text output from agent
|
|
452
|
-
match event:
|
|
453
|
-
case RawResponsesStreamEvent(data=data):
|
|
454
|
-
match data:
|
|
455
|
-
case ResponseTextDeltaEvent(delta=delta) if delta:
|
|
456
|
-
agent_output += delta
|
|
457
|
-
|
|
458
|
-
if self.context.mcp_manager:
|
|
459
|
-
await self.context.mcp_manager.disconnect()
|
|
460
|
-
|
|
461
|
-
return agent_output.strip()
|
|
462
|
-
|
|
463
309
|
async def handle_task_tool_event(self, tool_name: str, tool_call_id: str, event: StreamEvent) -> None:
|
|
464
310
|
"""Handle streaming events from task tool sub-agents.
|
|
465
311
|
|
|
@@ -471,56 +317,3 @@ class VibecoreApp(App):
|
|
|
471
317
|
Note: The main app receives this event from the agent's task tool handler.
|
|
472
318
|
"""
|
|
473
319
|
await self.agent_stream_handler.handle_task_tool_event(tool_name, tool_call_id, event)
|
|
474
|
-
|
|
475
|
-
async def handle_clear_command(self) -> None:
|
|
476
|
-
"""Handle the /clear command to create a new session and clear the UI."""
|
|
477
|
-
log("Clearing session and creating new session")
|
|
478
|
-
|
|
479
|
-
# Cancel any running agent
|
|
480
|
-
if self.agent_status == "running":
|
|
481
|
-
self.action_cancel_agent()
|
|
482
|
-
|
|
483
|
-
# Clear message queue
|
|
484
|
-
self.message_queue.clear()
|
|
485
|
-
|
|
486
|
-
# Generate a new session ID
|
|
487
|
-
import datetime
|
|
488
|
-
|
|
489
|
-
new_session_id = f"chat-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
490
|
-
|
|
491
|
-
# Create new session
|
|
492
|
-
if settings.session.storage_type == "jsonl":
|
|
493
|
-
self.session = JSONLSession(
|
|
494
|
-
session_id=new_session_id,
|
|
495
|
-
project_path=None, # Will use current working directory
|
|
496
|
-
base_dir=settings.session.base_dir,
|
|
497
|
-
)
|
|
498
|
-
else:
|
|
499
|
-
raise NotImplementedError("SQLite session support will be added later")
|
|
500
|
-
|
|
501
|
-
# Reset context state
|
|
502
|
-
self.context.reset_state()
|
|
503
|
-
|
|
504
|
-
# Clear input items
|
|
505
|
-
self.input_items.clear()
|
|
506
|
-
|
|
507
|
-
# Clear the UI - remove all messages and add welcome back
|
|
508
|
-
main_scroll = self.query_one("#messages", MainScroll)
|
|
509
|
-
|
|
510
|
-
# Remove all existing messages
|
|
511
|
-
for message in main_scroll.query("BaseMessage"):
|
|
512
|
-
message.remove()
|
|
513
|
-
|
|
514
|
-
# Remove welcome if it exists
|
|
515
|
-
for welcome in main_scroll.query("Welcome"):
|
|
516
|
-
welcome.remove()
|
|
517
|
-
|
|
518
|
-
# Add welcome widget back if show_welcome is True
|
|
519
|
-
if self.show_welcome:
|
|
520
|
-
await main_scroll.mount(Welcome())
|
|
521
|
-
|
|
522
|
-
# Show system message to confirm the clear operation
|
|
523
|
-
system_message = SystemMessage(f"✨ Session cleared! Started new session: {new_session_id}")
|
|
524
|
-
await main_scroll.mount(system_message)
|
|
525
|
-
|
|
526
|
-
log(f"New session created: {new_session_id}")
|
vibecore/session/loader.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
+
from agents import Session
|
|
5
6
|
from openai.types.responses import (
|
|
6
7
|
ResponseFunctionToolCall,
|
|
7
8
|
ResponseInputItemParam,
|
|
@@ -12,7 +13,6 @@ from openai.types.responses import (
|
|
|
12
13
|
from pydantic import TypeAdapter
|
|
13
14
|
from textual import log
|
|
14
15
|
|
|
15
|
-
from vibecore.session.jsonl_session import JSONLSession
|
|
16
16
|
from vibecore.utils.text import TextExtractor
|
|
17
17
|
from vibecore.widgets.messages import (
|
|
18
18
|
AgentMessage,
|
|
@@ -28,7 +28,7 @@ from vibecore.widgets.tool_messages import BaseToolMessage
|
|
|
28
28
|
class SessionLoader:
|
|
29
29
|
"""Loads and parses session history into message widgets."""
|
|
30
30
|
|
|
31
|
-
def __init__(self, session:
|
|
31
|
+
def __init__(self, session: Session):
|
|
32
32
|
"""Initialize SessionLoader with a session.
|
|
33
33
|
|
|
34
34
|
Args:
|
vibecore/tools/file/executor.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from agents import RunContextWrapper
|
|
6
6
|
|
|
7
|
-
from vibecore.context import
|
|
7
|
+
from vibecore.context import PathValidatorContext
|
|
8
8
|
from vibecore.settings import settings
|
|
9
9
|
from vibecore.tools.file.utils import PathValidationError
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ from .utils import format_line_with_number
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
async def read_file(
|
|
15
|
-
ctx: RunContextWrapper[
|
|
15
|
+
ctx: RunContextWrapper[PathValidatorContext], file_path: str, offset: int | None = None, limit: int | None = None
|
|
16
16
|
) -> str:
|
|
17
17
|
"""Read a file and return its contents in cat -n format.
|
|
18
18
|
|
|
@@ -100,7 +100,11 @@ async def read_file(
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
async def edit_file(
|
|
103
|
-
ctx: RunContextWrapper[
|
|
103
|
+
ctx: RunContextWrapper[PathValidatorContext],
|
|
104
|
+
file_path: str,
|
|
105
|
+
old_string: str,
|
|
106
|
+
new_string: str,
|
|
107
|
+
replace_all: bool = False,
|
|
104
108
|
) -> str:
|
|
105
109
|
"""Edit a file by replacing strings.
|
|
106
110
|
|
|
@@ -182,7 +186,11 @@ async def edit_file(
|
|
|
182
186
|
return f"Error: Unexpected error editing file: {e}"
|
|
183
187
|
|
|
184
188
|
|
|
185
|
-
async def multi_edit_file(
|
|
189
|
+
async def multi_edit_file(
|
|
190
|
+
ctx: RunContextWrapper[PathValidatorContext],
|
|
191
|
+
file_path: str,
|
|
192
|
+
edits: list[dict[str, Any]],
|
|
193
|
+
) -> str:
|
|
186
194
|
"""Edit a file by applying multiple replacements sequentially.
|
|
187
195
|
|
|
188
196
|
Args:
|
|
@@ -270,7 +278,7 @@ async def multi_edit_file(ctx: RunContextWrapper[VibecoreContext], file_path: st
|
|
|
270
278
|
return f"Error: Unexpected error editing file: {e}"
|
|
271
279
|
|
|
272
280
|
|
|
273
|
-
async def write_file(ctx: RunContextWrapper[
|
|
281
|
+
async def write_file(ctx: RunContextWrapper[PathValidatorContext], file_path: str, content: str) -> str:
|
|
274
282
|
"""Write content to a file.
|
|
275
283
|
|
|
276
284
|
Args:
|
vibecore/tools/file/tools.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from agents import RunContextWrapper, function_tool
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
6
|
-
from vibecore.context import
|
|
6
|
+
from vibecore.context import PathValidatorContext
|
|
7
7
|
|
|
8
8
|
from .executor import edit_file, multi_edit_file, read_file, write_file
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ class EditOperation(BaseModel):
|
|
|
18
18
|
|
|
19
19
|
@function_tool
|
|
20
20
|
async def read(
|
|
21
|
-
ctx: RunContextWrapper[
|
|
21
|
+
ctx: RunContextWrapper[PathValidatorContext],
|
|
22
22
|
file_path: str,
|
|
23
23
|
offset: int | None = None,
|
|
24
24
|
limit: int | None = None,
|
|
@@ -54,7 +54,7 @@ async def read(
|
|
|
54
54
|
|
|
55
55
|
@function_tool
|
|
56
56
|
async def edit(
|
|
57
|
-
ctx: RunContextWrapper[
|
|
57
|
+
ctx: RunContextWrapper[PathValidatorContext],
|
|
58
58
|
file_path: str,
|
|
59
59
|
old_string: str,
|
|
60
60
|
new_string: str,
|
|
@@ -91,7 +91,7 @@ async def edit(
|
|
|
91
91
|
|
|
92
92
|
@function_tool
|
|
93
93
|
async def multi_edit(
|
|
94
|
-
ctx: RunContextWrapper[
|
|
94
|
+
ctx: RunContextWrapper[PathValidatorContext],
|
|
95
95
|
file_path: str,
|
|
96
96
|
edits: list[EditOperation],
|
|
97
97
|
) -> str:
|
|
@@ -158,7 +158,7 @@ async def multi_edit(
|
|
|
158
158
|
|
|
159
159
|
@function_tool
|
|
160
160
|
async def write(
|
|
161
|
-
ctx: RunContextWrapper[
|
|
161
|
+
ctx: RunContextWrapper[PathValidatorContext],
|
|
162
162
|
file_path: str,
|
|
163
163
|
content: str,
|
|
164
164
|
) -> str:
|
vibecore/tools/python/helpers.py
CHANGED
|
@@ -5,7 +5,7 @@ from io import BytesIO
|
|
|
5
5
|
|
|
6
6
|
from agents import RunContextWrapper
|
|
7
7
|
|
|
8
|
-
from vibecore.context import
|
|
8
|
+
from vibecore.context import PythonToolContext
|
|
9
9
|
|
|
10
10
|
try:
|
|
11
11
|
from PIL import Image # type: ignore[import-not-found]
|
|
@@ -16,7 +16,7 @@ except ImportError:
|
|
|
16
16
|
TERM_IMAGE_AVAILABLE = False
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
async def execute_python_helper(ctx: RunContextWrapper[
|
|
19
|
+
async def execute_python_helper(ctx: RunContextWrapper[PythonToolContext], code: str) -> str:
|
|
20
20
|
"""Helper function to execute Python code.
|
|
21
21
|
|
|
22
22
|
This is the actual implementation extracted from the tool decorator.
|
vibecore/tools/python/tools.py
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from agents import RunContextWrapper, function_tool
|
|
4
4
|
|
|
5
|
-
from vibecore.context import
|
|
5
|
+
from vibecore.context import PythonToolContext
|
|
6
6
|
|
|
7
7
|
from .helpers import execute_python_helper
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@function_tool
|
|
11
|
-
async def execute_python(ctx: RunContextWrapper[
|
|
11
|
+
async def execute_python(ctx: RunContextWrapper[PythonToolContext], code: str) -> str:
|
|
12
12
|
"""Execute Python code with persistent context across the session.
|
|
13
13
|
|
|
14
14
|
The execution environment maintains state between calls, allowing you to:
|
vibecore/tools/shell/executor.py
CHANGED
|
@@ -8,13 +8,13 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
from agents import RunContextWrapper
|
|
10
10
|
|
|
11
|
-
from vibecore.context import
|
|
11
|
+
from vibecore.context import PathValidatorContext
|
|
12
12
|
from vibecore.settings import settings
|
|
13
13
|
from vibecore.tools.file.utils import PathValidationError, validate_file_path
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
async def bash_executor(
|
|
17
|
-
ctx: RunContextWrapper[
|
|
17
|
+
ctx: RunContextWrapper[PathValidatorContext], command: str, timeout: int | None = None
|
|
18
18
|
) -> tuple[str, int]:
|
|
19
19
|
"""Execute a bash command asynchronously.
|
|
20
20
|
|
|
@@ -80,7 +80,7 @@ async def bash_executor(
|
|
|
80
80
|
return f"Error executing command: {e}", 1
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
async def glob_files(ctx: RunContextWrapper[
|
|
83
|
+
async def glob_files(ctx: RunContextWrapper[PathValidatorContext], pattern: str, path: str | None = None) -> list[str]:
|
|
84
84
|
"""Find files matching a glob pattern.
|
|
85
85
|
|
|
86
86
|
Args:
|
|
@@ -142,7 +142,7 @@ async def glob_files(ctx: RunContextWrapper[VibecoreContext], pattern: str, path
|
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
async def grep_files(
|
|
145
|
-
ctx: RunContextWrapper[
|
|
145
|
+
ctx: RunContextWrapper[PathValidatorContext], pattern: str, path: str | None = None, include: str | None = None
|
|
146
146
|
) -> list[str]:
|
|
147
147
|
"""Search file contents using regular expressions.
|
|
148
148
|
|
|
@@ -226,7 +226,7 @@ async def grep_files(
|
|
|
226
226
|
|
|
227
227
|
|
|
228
228
|
async def list_directory(
|
|
229
|
-
ctx: RunContextWrapper[
|
|
229
|
+
ctx: RunContextWrapper[PathValidatorContext], path: str, ignore: list[str] | None = None
|
|
230
230
|
) -> list[str]:
|
|
231
231
|
"""List files and directories in a given path.
|
|
232
232
|
|