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/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
- from vibecore.context import VibecoreContext
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
- context: VibecoreContext,
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.context = context
102
- self.context.app = self # Set the app reference in context
103
- self.agent = agent
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
- # 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
82
+ super().__init__()
117
83
 
118
- session_id = f"chat-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
84
+ def on_mouse_up(self) -> None:
85
+ if not self.screen.selections:
86
+ return None
119
87
 
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")
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
- super().__init__()
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(self.session)
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 = asyncio.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.pop()
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 == "/clear":
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
- user_message = UserMessage(event.text)
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
- # Detect reasoning effort from prompt keywords
285
- detected_effort = detect_reasoning_effort(event.text)
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
- used = result.context_wrapper.usage.total_tokens
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: {used} / {max_ctx} total tokens")
331
- self.context.context_fullness = min(1.0, float(used) / float(max_ctx))
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(self.context.context_fullness)
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
- from vibecore.settings import settings
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(1.0)
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}")
@@ -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: JSONLSession):
31
+ def __init__(self, session: Session):
32
32
  """Initialize SessionLoader with a session.
33
33
 
34
34
  Args:
@@ -4,7 +4,7 @@ from typing import Any
4
4
 
5
5
  from agents import RunContextWrapper
6
6
 
7
- from vibecore.context import VibecoreContext
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[VibecoreContext], file_path: str, offset: int | None = None, limit: int | None = None
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[VibecoreContext], file_path: str, old_string: str, new_string: str, replace_all: bool = False
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(ctx: RunContextWrapper[VibecoreContext], file_path: str, edits: list[dict[str, Any]]) -> str:
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[VibecoreContext], file_path: str, content: str) -> str:
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:
@@ -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 VibecoreContext
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[VibecoreContext],
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[VibecoreContext],
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[VibecoreContext],
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[VibecoreContext],
161
+ ctx: RunContextWrapper[PathValidatorContext],
162
162
  file_path: str,
163
163
  content: str,
164
164
  ) -> str:
@@ -5,7 +5,7 @@ from io import BytesIO
5
5
 
6
6
  from agents import RunContextWrapper
7
7
 
8
- from vibecore.context import VibecoreContext
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[VibecoreContext], code: str) -> str:
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.
@@ -2,13 +2,13 @@
2
2
 
3
3
  from agents import RunContextWrapper, function_tool
4
4
 
5
- from vibecore.context import VibecoreContext
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[VibecoreContext], code: str) -> str:
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:
@@ -8,13 +8,13 @@ from pathlib import Path
8
8
 
9
9
  from agents import RunContextWrapper
10
10
 
11
- from vibecore.context import VibecoreContext
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[VibecoreContext], command: str, timeout: int | None = None
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[VibecoreContext], pattern: str, path: str | None = None) -> list[str]:
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[VibecoreContext], pattern: str, path: str | None = None, include: str | None = None
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[VibecoreContext], path: str, ignore: list[str] | None = None
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