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/agent.py +65 -24
- cowork_dash/app.py +602 -333
- cowork_dash/assets/styles.css +12 -0
- cowork_dash/backends.py +435 -0
- cowork_dash/canvas.py +96 -37
- cowork_dash/cli.py +23 -12
- cowork_dash/components.py +0 -1
- cowork_dash/config.py +21 -0
- cowork_dash/file_utils.py +147 -18
- cowork_dash/layout.py +9 -2
- cowork_dash/tools.py +196 -7
- cowork_dash/virtual_fs.py +468 -0
- {cowork_dash-0.1.6.dist-info → cowork_dash-0.1.7.dist-info}/METADATA +1 -1
- cowork_dash-0.1.7.dist-info/RECORD +22 -0
- cowork_dash-0.1.6.dist-info/RECORD +0 -20
- {cowork_dash-0.1.6.dist-info → cowork_dash-0.1.7.dist-info}/WHEEL +0 -0
- {cowork_dash-0.1.6.dist-info → cowork_dash-0.1.7.dist-info}/entry_points.txt +0 -0
- {cowork_dash-0.1.6.dist-info → cowork_dash-0.1.7.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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,
|