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/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
- from vibecore.context import VibecoreContext
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
- context: VibecoreContext,
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.context = context
102
- self.context.app = self # Set the app reference in context
103
- self.agent = agent
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(self.session)
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 = asyncio.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.pop()
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 == "/clear":
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
- 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":
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
- # 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)
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
- used = result.context_wrapper.usage.total_tokens
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: {used} / {max_ctx} total tokens")
331
- self.context.context_fullness = min(1.0, float(used) / float(max_ctx))
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(self.context.context_fullness)
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
- from vibecore.settings import settings
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(1.0)
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
@@ -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