vibecore 0.4.2__py3-none-any.whl → 0.6.0__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 +335 -73
- vibecore/handlers/stream_handler.py +0 -10
- vibecore/main.py +46 -272
- vibecore/session/jsonl_session.py +3 -1
- 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.4.2.dist-info → vibecore-0.6.0.dist-info}/METADATA +91 -31
- {vibecore-0.4.2.dist-info → vibecore-0.6.0.dist-info}/RECORD +26 -26
- {vibecore-0.4.2.dist-info → vibecore-0.6.0.dist-info}/WHEEL +0 -0
- {vibecore-0.4.2.dist-info → vibecore-0.6.0.dist-info}/entry_points.txt +0 -0
- {vibecore-0.4.2.dist-info → vibecore-0.6.0.dist-info}/licenses/LICENSE +0 -0
vibecore/main.py
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
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
|
|
@@ -20,11 +16,11 @@ from textual.reactive import reactive
|
|
|
20
16
|
from textual.widgets import Header
|
|
21
17
|
from textual.worker import Worker
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from vibecore.flow import TWorkflowReturn, VibecoreTextualRunner
|
|
21
|
+
|
|
24
22
|
from vibecore.handlers import AgentStreamHandler
|
|
25
|
-
from vibecore.session import JSONLSession
|
|
26
23
|
from vibecore.session.loader import SessionLoader
|
|
27
|
-
from vibecore.settings import settings
|
|
28
24
|
from vibecore.utils.text import TextExtractor
|
|
29
25
|
from vibecore.widgets.core import AppFooter, MainScroll, MyTextArea
|
|
30
26
|
from vibecore.widgets.info import Welcome
|
|
@@ -37,28 +33,6 @@ class AppIsExiting(Exception):
|
|
|
37
33
|
pass
|
|
38
34
|
|
|
39
35
|
|
|
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
36
|
class VibecoreApp(App):
|
|
63
37
|
"""A Textual app to manage stopwatches."""
|
|
64
38
|
|
|
@@ -83,10 +57,7 @@ class VibecoreApp(App):
|
|
|
83
57
|
|
|
84
58
|
def __init__(
|
|
85
59
|
self,
|
|
86
|
-
|
|
87
|
-
agent: Agent,
|
|
88
|
-
session_id: str | None = None,
|
|
89
|
-
print_mode: bool = False,
|
|
60
|
+
runner: "VibecoreTextualRunner[TWorkflowReturn]",
|
|
90
61
|
show_welcome: bool = True,
|
|
91
62
|
) -> None:
|
|
92
63
|
"""Initialize the Vibecore app with context and agent.
|
|
@@ -95,35 +66,16 @@ class VibecoreApp(App):
|
|
|
95
66
|
context: The VibecoreContext instance
|
|
96
67
|
agent: The Agent instance to use
|
|
97
68
|
session_id: Optional session ID to load existing session
|
|
98
|
-
print_mode: Whether to run in print mode (useful for pipes)
|
|
99
69
|
show_welcome: Whether to show the welcome message (default: True)
|
|
100
70
|
"""
|
|
101
|
-
self.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.input_items: list[TResponseInputItem] = []
|
|
71
|
+
self.runner = runner
|
|
72
|
+
if runner.context:
|
|
73
|
+
runner.context.app = self # Set the app reference in context
|
|
105
74
|
self.current_result: RunResultStreaming | None = None
|
|
106
75
|
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
76
|
self.show_welcome = show_welcome
|
|
110
77
|
self.message_queue: deque[str] = deque() # Queue for user messages
|
|
111
|
-
|
|
112
|
-
# Initialize session based on settings
|
|
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
|
|
117
|
-
|
|
118
|
-
session_id = f"chat-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
119
|
-
|
|
120
|
-
self.session = JSONLSession(
|
|
121
|
-
session_id=session_id,
|
|
122
|
-
project_path=None, # Will use current working directory
|
|
123
|
-
base_dir=settings.session.base_dir,
|
|
124
|
-
)
|
|
125
|
-
else:
|
|
126
|
-
raise NotImplementedError("SQLite session support will be added later")
|
|
78
|
+
self.user_input_event = asyncio.Event() # Initialize event for user input coordination
|
|
127
79
|
|
|
128
80
|
super().__init__()
|
|
129
81
|
|
|
@@ -135,32 +87,6 @@ class VibecoreApp(App):
|
|
|
135
87
|
if self.show_welcome:
|
|
136
88
|
yield Welcome()
|
|
137
89
|
|
|
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
90
|
def extract_text_from_content(self, content: list[Content]) -> str:
|
|
165
91
|
"""Extract text from various content formats."""
|
|
166
92
|
return TextExtractor.extract_from_content(content)
|
|
@@ -180,11 +106,6 @@ class VibecoreApp(App):
|
|
|
180
106
|
"""Add a message widget to the main scroll area."""
|
|
181
107
|
await self.add_message(message)
|
|
182
108
|
|
|
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
109
|
async def handle_agent_error(self, error: Exception) -> None:
|
|
189
110
|
"""Handle errors during streaming."""
|
|
190
111
|
log(f"Error during agent response: {type(error).__name__}: {error!s}")
|
|
@@ -213,9 +134,9 @@ class VibecoreApp(App):
|
|
|
213
134
|
# No messages to clean up
|
|
214
135
|
pass
|
|
215
136
|
|
|
216
|
-
async def load_session_history(self) -> None:
|
|
137
|
+
async def load_session_history(self, session: Session) -> None:
|
|
217
138
|
"""Load and display messages from session history."""
|
|
218
|
-
loader = SessionLoader(
|
|
139
|
+
loader = SessionLoader(session)
|
|
219
140
|
messages = await loader.load_history()
|
|
220
141
|
|
|
221
142
|
# Remove Welcome widget if we have messages
|
|
@@ -238,10 +159,24 @@ class VibecoreApp(App):
|
|
|
238
159
|
|
|
239
160
|
async def wait_for_user_input(self) -> str:
|
|
240
161
|
"""Used in flow mode. See examples/basic_agent.py"""
|
|
162
|
+
if self.message_queue:
|
|
163
|
+
user_input = self.message_queue.popleft()
|
|
164
|
+
|
|
165
|
+
user_message = UserMessage(user_input)
|
|
166
|
+
await self.add_message(user_message)
|
|
167
|
+
user_message.scroll_visible()
|
|
168
|
+
|
|
169
|
+
return user_input
|
|
170
|
+
|
|
241
171
|
self.agent_status = "waiting_user_input"
|
|
242
|
-
self.user_input_event
|
|
172
|
+
self.user_input_event.clear() # Reset the event for next wait
|
|
243
173
|
await self.user_input_event.wait()
|
|
244
|
-
user_input = self.message_queue.
|
|
174
|
+
user_input = self.message_queue.popleft()
|
|
175
|
+
|
|
176
|
+
user_message = UserMessage(user_input)
|
|
177
|
+
await self.add_message(user_message)
|
|
178
|
+
user_message.scroll_visible()
|
|
179
|
+
|
|
245
180
|
return user_input
|
|
246
181
|
|
|
247
182
|
async def on_my_text_area_user_message(self, event: MyTextArea.UserMessage) -> None:
|
|
@@ -249,12 +184,8 @@ class VibecoreApp(App):
|
|
|
249
184
|
if event.text:
|
|
250
185
|
# Check for special commands
|
|
251
186
|
text_strip = event.text.strip()
|
|
252
|
-
if text_strip == "/
|
|
253
|
-
await self.handle_clear_command()
|
|
254
|
-
return
|
|
255
|
-
elif text_strip == "/help":
|
|
187
|
+
if text_strip == "/help":
|
|
256
188
|
help_text = "Available commands:\n"
|
|
257
|
-
help_text += "• /clear - Clear the current session and start a new one\n"
|
|
258
189
|
help_text += "• /help - Show this help message\n\n"
|
|
259
190
|
help_text += "Keyboard shortcuts:\n"
|
|
260
191
|
help_text += "• Esc - Cancel current agent operation\n"
|
|
@@ -263,14 +194,7 @@ class VibecoreApp(App):
|
|
|
263
194
|
await self.add_message(SystemMessage(help_text))
|
|
264
195
|
return
|
|
265
196
|
|
|
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":
|
|
197
|
+
if self.agent_status == "running":
|
|
274
198
|
# If agent is running, queue the message
|
|
275
199
|
self.message_queue.append(event.text)
|
|
276
200
|
log(f"Message queued: {event.text}")
|
|
@@ -281,41 +205,8 @@ class VibecoreApp(App):
|
|
|
281
205
|
status="Generating…", metadata=f"{queued_count} message{'s' if queued_count > 1 else ''} queued"
|
|
282
206
|
)
|
|
283
207
|
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)
|
|
208
|
+
self.message_queue.append(event.text)
|
|
209
|
+
self.user_input_event.set()
|
|
319
210
|
|
|
320
211
|
@work(exclusive=True)
|
|
321
212
|
async def handle_streamed_response(self, result: RunResultStreaming) -> None:
|
|
@@ -325,42 +216,27 @@ class VibecoreApp(App):
|
|
|
325
216
|
self.agent_stream_handler = AgentStreamHandler(self)
|
|
326
217
|
await self.agent_stream_handler.process_stream(result)
|
|
327
218
|
|
|
328
|
-
|
|
219
|
+
# Determine usage based on the last model response rather than the aggregated usage
|
|
220
|
+
# from the entire session so that context fullness reflects the most recent request.
|
|
221
|
+
used_tokens: float = 0.0
|
|
222
|
+
if result.raw_responses:
|
|
223
|
+
last_response = result.raw_responses[-1]
|
|
224
|
+
last_usage = getattr(last_response, "usage", None)
|
|
225
|
+
if last_usage:
|
|
226
|
+
used_tokens = float(last_usage.total_tokens)
|
|
227
|
+
|
|
329
228
|
max_ctx = self._get_model_context_window()
|
|
330
|
-
log(f"Context usage: {
|
|
331
|
-
|
|
229
|
+
log(f"Context usage: {used_tokens} / {max_ctx} total tokens")
|
|
230
|
+
context_fullness = min(1.0, used_tokens / float(max_ctx))
|
|
332
231
|
footer = self.query_one(AppFooter)
|
|
333
|
-
footer.set_context_progress(
|
|
232
|
+
footer.set_context_progress(context_fullness)
|
|
334
233
|
|
|
335
234
|
self.agent_status = "idle"
|
|
336
235
|
self.current_result = None
|
|
337
236
|
self.current_worker = None
|
|
338
237
|
|
|
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
238
|
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}")
|
|
239
|
+
# TODO(serialx): Implement later
|
|
364
240
|
return 200000
|
|
365
241
|
|
|
366
242
|
def action_toggle_dark(self) -> None:
|
|
@@ -400,7 +276,7 @@ class VibecoreApp(App):
|
|
|
400
276
|
"""Reset exit confirmation after 1 second and remove the message."""
|
|
401
277
|
try:
|
|
402
278
|
# Wait for 1 second
|
|
403
|
-
await asyncio.sleep(
|
|
279
|
+
await asyncio.sleep(2.0)
|
|
404
280
|
|
|
405
281
|
# Reset confirmation state
|
|
406
282
|
self._exit_confirmation_active = False
|
|
@@ -411,55 +287,6 @@ class VibecoreApp(App):
|
|
|
411
287
|
# Task was cancelled (new Ctrl-D pressed)
|
|
412
288
|
pass
|
|
413
289
|
|
|
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
290
|
async def handle_task_tool_event(self, tool_name: str, tool_call_id: str, event: StreamEvent) -> None:
|
|
464
291
|
"""Handle streaming events from task tool sub-agents.
|
|
465
292
|
|
|
@@ -471,56 +298,3 @@ class VibecoreApp(App):
|
|
|
471
298
|
Note: The main app receives this event from the agent's task tool handler.
|
|
472
299
|
"""
|
|
473
300
|
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}")
|
|
@@ -5,6 +5,8 @@ import logging
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from agents import Session
|
|
9
|
+
|
|
8
10
|
if TYPE_CHECKING:
|
|
9
11
|
from openai.types.responses import ResponseInputItemParam as TResponseInputItem
|
|
10
12
|
|
|
@@ -14,7 +16,7 @@ from .path_utils import get_session_file_path
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
class JSONLSession:
|
|
19
|
+
class JSONLSession(Session):
|
|
18
20
|
"""JSONL-based implementation of the agents.Session protocol.
|
|
19
21
|
|
|
20
22
|
Stores conversation history in JSON Lines format, with one JSON object
|
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
|
|