cowork-dash 0.1.6__py3-none-any.whl → 0.1.7__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.
cowork_dash/tools.py CHANGED
@@ -3,12 +3,54 @@ import sys
3
3
  import io
4
4
  import traceback
5
5
  import subprocess
6
+ import threading
6
7
  from contextlib import redirect_stdout, redirect_stderr
7
8
 
8
- from .config import WORKSPACE_ROOT
9
+ from .config import WORKSPACE_ROOT, VIRTUAL_FS
9
10
  from .canvas import parse_canvas_object, generate_canvas_id
10
11
 
11
12
 
13
+ # Thread-local storage for current session context
14
+ # This allows tools to know which session they're operating in
15
+ _tool_context = threading.local()
16
+
17
+
18
+ def set_tool_session_context(session_id: Optional[str]) -> None:
19
+ """Set the current session context for tool operations.
20
+
21
+ This should be called before invoking agent tools in virtual FS mode.
22
+ """
23
+ _tool_context.session_id = session_id
24
+
25
+
26
+ def get_tool_session_context() -> Optional[str]:
27
+ """Get the current session context for tool operations."""
28
+ return getattr(_tool_context, 'session_id', None)
29
+
30
+
31
+ def clear_tool_session_context() -> None:
32
+ """Clear the current session context."""
33
+ _tool_context.session_id = None
34
+
35
+
36
+ def _get_workspace_root_for_context() -> Any:
37
+ """Get the appropriate workspace root based on current context.
38
+
39
+ Returns VirtualFilesystem in virtual FS mode with session context,
40
+ otherwise returns the physical WORKSPACE_ROOT path.
41
+ """
42
+ # Import config dynamically to get current value (in case it changed)
43
+ from . import config as cfg
44
+ if cfg.VIRTUAL_FS:
45
+ session_id = get_tool_session_context()
46
+ if session_id:
47
+ from .virtual_fs import get_session_manager
48
+ fs = get_session_manager().get_filesystem(session_id)
49
+ if fs is not None:
50
+ return fs
51
+ return cfg.WORKSPACE_ROOT
52
+
53
+
12
54
  # =============================================================================
13
55
  # JUPYTER-LIKE CODE EXECUTION TOOLS
14
56
  # =============================================================================
@@ -22,20 +64,58 @@ class NotebookState:
22
64
  - A persistent namespace for variable state across cells
23
65
  - Execution history and outputs
24
66
  - Canvas items generated during cell execution
67
+
68
+ In virtual filesystem mode, file operations are restricted and virtual
69
+ filesystem helpers are provided instead.
25
70
  """
26
71
 
27
- def __init__(self):
72
+ def __init__(self, session_id: Optional[str] = None):
28
73
  self._cells: List[Dict[str, Any]] = []
29
74
  self._namespace: Dict[str, Any] = {}
30
75
  self._execution_count: int = 0
31
76
  self._ipython_shell = None
32
77
  self._canvas_items: List[Dict[str, Any]] = [] # Collected canvas items
78
+ self._session_id = session_id
33
79
  self._initialize_namespace()
34
80
 
35
81
  def _initialize_namespace(self):
36
82
  """Initialize the namespace with common imports and utilities."""
37
83
  # Pre-populate with commonly used modules
38
- init_code = """
84
+ # In virtual FS mode, we provide virtual filesystem helpers
85
+ if VIRTUAL_FS:
86
+ init_code = """
87
+ import sys
88
+ import json
89
+
90
+ # Data science essentials (imported if available)
91
+ try:
92
+ import pandas as pd
93
+ except ImportError:
94
+ pass
95
+
96
+ try:
97
+ import numpy as np
98
+ except ImportError:
99
+ pass
100
+
101
+ try:
102
+ import matplotlib
103
+ matplotlib.use('Agg') # Non-interactive backend
104
+ import matplotlib.pyplot as plt
105
+ except ImportError:
106
+ pass
107
+
108
+ try:
109
+ import plotly.express as px
110
+ import plotly.graph_objects as go
111
+ except ImportError:
112
+ pass
113
+
114
+ # Note: In virtual filesystem mode, standard file operations (open, os.listdir, etc.)
115
+ # are not available. Use the provided vfs_* functions instead.
116
+ """
117
+ else:
118
+ init_code = """
39
119
  import sys
40
120
  import os
41
121
  import json
@@ -71,6 +151,10 @@ except ImportError:
71
151
  except Exception:
72
152
  pass # Ignore import errors for optional packages
73
153
 
154
+ # In virtual FS mode, inject virtual filesystem helpers
155
+ if VIRTUAL_FS:
156
+ self._inject_virtual_fs_helpers()
157
+
74
158
  # Inject add_to_canvas function that captures items
75
159
  def _add_to_canvas_wrapper(content: Any) -> Dict[str, Any]:
76
160
  """Add content to the canvas for visualization.
@@ -79,7 +163,16 @@ except ImportError:
79
163
  PIL images, and markdown strings.
80
164
  """
81
165
  try:
82
- parsed = parse_canvas_object(content, workspace_root=WORKSPACE_ROOT)
166
+ # Use session's VirtualFilesystem in virtual FS mode, otherwise physical path
167
+ if VIRTUAL_FS and self._session_id:
168
+ from .virtual_fs import get_session_manager
169
+ workspace_root = get_session_manager().get_filesystem(self._session_id)
170
+ if workspace_root is None:
171
+ raise RuntimeError(f"Session {self._session_id} not found")
172
+ else:
173
+ workspace_root = WORKSPACE_ROOT
174
+
175
+ parsed = parse_canvas_object(content, workspace_root=workspace_root)
83
176
  self._canvas_items.append(parsed)
84
177
  return parsed
85
178
  except Exception as e:
@@ -93,6 +186,67 @@ except ImportError:
93
186
 
94
187
  self._namespace["add_to_canvas"] = _add_to_canvas_wrapper
95
188
 
189
+ def _inject_virtual_fs_helpers(self):
190
+ """Inject virtual filesystem helper functions into the namespace."""
191
+ from .virtual_fs import get_session_manager
192
+
193
+ session_id = self._session_id
194
+
195
+ def vfs_read_file(path: str) -> str:
196
+ """Read a text file from the virtual filesystem."""
197
+ if not session_id:
198
+ raise RuntimeError("No session ID available for virtual filesystem")
199
+ fs = get_session_manager().get_filesystem(session_id)
200
+ if not fs:
201
+ raise RuntimeError(f"Session {session_id} not found")
202
+ return fs.read_text(path)
203
+
204
+ def vfs_write_file(path: str, content: str) -> int:
205
+ """Write content to a file in the virtual filesystem."""
206
+ if not session_id:
207
+ raise RuntimeError("No session ID available for virtual filesystem")
208
+ fs = get_session_manager().get_filesystem(session_id)
209
+ if not fs:
210
+ raise RuntimeError(f"Session {session_id} not found")
211
+ return fs.write_text(path, content)
212
+
213
+ def vfs_list_dir(path: str = "/workspace") -> list:
214
+ """List files in a directory in the virtual filesystem."""
215
+ if not session_id:
216
+ raise RuntimeError("No session ID available for virtual filesystem")
217
+ fs = get_session_manager().get_filesystem(session_id)
218
+ if not fs:
219
+ raise RuntimeError(f"Session {session_id} not found")
220
+ return fs.listdir(path)
221
+
222
+ def vfs_exists(path: str) -> bool:
223
+ """Check if a file or directory exists in the virtual filesystem."""
224
+ if not session_id:
225
+ raise RuntimeError("No session ID available for virtual filesystem")
226
+ fs = get_session_manager().get_filesystem(session_id)
227
+ if not fs:
228
+ raise RuntimeError(f"Session {session_id} not found")
229
+ return fs.exists(path)
230
+
231
+ def vfs_mkdir(path: str, parents: bool = True) -> None:
232
+ """Create a directory in the virtual filesystem."""
233
+ if not session_id:
234
+ raise RuntimeError("No session ID available for virtual filesystem")
235
+ fs = get_session_manager().get_filesystem(session_id)
236
+ if not fs:
237
+ raise RuntimeError(f"Session {session_id} not found")
238
+ fs.mkdir(path, parents=parents, exist_ok=True)
239
+
240
+ # Inject the helpers
241
+ self._namespace["vfs_read_file"] = vfs_read_file
242
+ self._namespace["vfs_write_file"] = vfs_write_file
243
+ self._namespace["vfs_list_dir"] = vfs_list_dir
244
+ self._namespace["vfs_exists"] = vfs_exists
245
+ self._namespace["vfs_mkdir"] = vfs_mkdir
246
+
247
+ # Add a notice about virtual FS mode
248
+ self._namespace["__VFS_MODE__"] = True
249
+
96
250
  def _get_ipython(self):
97
251
  """Get or create an IPython InteractiveShell for enhanced execution."""
98
252
  if self._ipython_shell is None:
@@ -339,8 +493,25 @@ except ImportError:
339
493
  return {"status": "reset", "message": "Notebook state cleared"}
340
494
 
341
495
 
342
- # Global notebook state instance
496
+ # Global notebook state instance (for physical FS mode)
497
+ # In virtual FS mode, each session should have its own NotebookState
343
498
  _notebook_state = NotebookState()
499
+ _session_notebook_states: Dict[str, NotebookState] = {}
500
+
501
+
502
+ def get_notebook_state(session_id: Optional[str] = None) -> NotebookState:
503
+ """Get the notebook state for a session.
504
+
505
+ In virtual FS mode, returns a session-specific NotebookState.
506
+ In physical FS mode, returns the global shared NotebookState.
507
+ """
508
+ if not VIRTUAL_FS or not session_id:
509
+ return _notebook_state
510
+
511
+ if session_id not in _session_notebook_states:
512
+ _session_notebook_states[session_id] = NotebookState(session_id=session_id)
513
+
514
+ return _session_notebook_states[session_id]
344
515
 
345
516
 
346
517
  def create_cell(code: str, cell_type: str = "code") -> Dict[str, Any]:
@@ -625,10 +796,13 @@ def add_to_canvas(content: Any, title: Optional[str] = None, item_id: Optional[s
625
796
  add_to_canvas(new_fig, item_id="growth_chart") # Replaces the previous chart
626
797
  """
627
798
  try:
799
+ # Get appropriate workspace root (VirtualFilesystem in virtual FS mode)
800
+ workspace_root = _get_workspace_root_for_context()
801
+
628
802
  # Parse the content into canvas format with optional title and ID
629
803
  parsed = parse_canvas_object(
630
804
  content,
631
- workspace_root=WORKSPACE_ROOT,
805
+ workspace_root=workspace_root,
632
806
  title=title,
633
807
  item_id=item_id
634
808
  )
@@ -668,9 +842,12 @@ def update_canvas_item(item_id: str, content: Any, title: Optional[str] = None)
668
842
  update_canvas_item("progress_chart", final_fig, title="Final Results")
669
843
  """
670
844
  try:
845
+ # Get appropriate workspace root (VirtualFilesystem in virtual FS mode)
846
+ workspace_root = _get_workspace_root_for_context()
847
+
671
848
  parsed = parse_canvas_object(
672
849
  content,
673
- workspace_root=WORKSPACE_ROOT,
850
+ workspace_root=workspace_root,
674
851
  title=title,
675
852
  item_id=item_id
676
853
  )
@@ -719,6 +896,8 @@ def bash(command: str, timeout: int = 60) -> Dict[str, Any]:
719
896
  Runs the command in the workspace directory. Use this for file operations,
720
897
  git commands, installing packages, or any shell operations.
721
898
 
899
+ Note: This tool is disabled in virtual filesystem mode for security reasons.
900
+
722
901
  Args:
723
902
  command: The bash command to execute
724
903
  timeout: Maximum time in seconds to wait for the command (default: 60)
@@ -743,6 +922,16 @@ def bash(command: str, timeout: int = 60) -> Dict[str, Any]:
743
922
  # Run a script
744
923
  bash("python script.py")
745
924
  """
925
+ # Disable bash in virtual filesystem mode for security
926
+ if VIRTUAL_FS:
927
+ return {
928
+ "stdout": "",
929
+ "stderr": "Bash commands are disabled in virtual filesystem mode for security reasons. "
930
+ "Use the built-in file tools (read_file, write_file, list_directory) instead.",
931
+ "return_code": 1,
932
+ "status": "error"
933
+ }
934
+
746
935
  try:
747
936
  result = subprocess.run(
748
937
  command,