cowork-dash 0.1.2__tar.gz → 0.1.4__tar.gz
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-0.1.2 → cowork_dash-0.1.4}/CHANGELOG.md +19 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/PKG-INFO +1 -1
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/agent.py +1 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/app.py +86 -28
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/config.py +8 -3
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/layout.py +2 -2
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/pyproject.toml +1 -1
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/.claude/settings.local.json +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/.gitignore +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/LICENSE +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/MANIFEST.in +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/README.md +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/__init__.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/__main__.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/assets/app.js +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/assets/favicon.svg +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/assets/styles.css +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/canvas.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/cli.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/components.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/file_utils.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/tools.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/docs/CLI_USAGE.md +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/examples/example_agent.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/examples/python_api_example.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/requirements.txt +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/templates/index.html +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/tests/__init__.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/tests/conftest.py +0 -0
- {cowork_dash-0.1.2 → cowork_dash-0.1.4}/tests/test_core.py +0 -0
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.4] - 2026-01-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- App title and subtitle can be set dynamically from agent `name` and `description` attributes
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Tool call error detection now uses precise patterns to avoid false positives (e.g., reading files about errors no longer marks tool as failed)
|
|
15
|
+
|
|
16
|
+
## [0.1.3] - 2026-01-20
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Support for Python module format in agent spec (e.g., `mypackage.module.agent`)
|
|
20
|
+
- Agent spec now accepts both file path (`file.py:object`) and module path formats
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Header layout on large screens - components now stay at edges instead of centering
|
|
24
|
+
|
|
8
25
|
## [0.1.2] - 2026-01-19
|
|
9
26
|
|
|
10
27
|
### Added
|
|
@@ -74,6 +91,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
74
91
|
- Resizable split-pane interface
|
|
75
92
|
- Upload/download functionality for files
|
|
76
93
|
|
|
94
|
+
[0.1.4]: https://github.com/dkedar7/cowork-dash/compare/v0.1.3...v0.1.4
|
|
95
|
+
[0.1.3]: https://github.com/dkedar7/cowork-dash/compare/v0.1.2...v0.1.3
|
|
77
96
|
[0.1.2]: https://github.com/dkedar7/cowork-dash/compare/v0.1.1...v0.1.2
|
|
78
97
|
[0.1.1]: https://github.com/dkedar7/cowork-dash/compare/v0.1.0...v0.1.1
|
|
79
98
|
[0.1.0]: https://github.com/dkedar7/cowork-dash/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cowork-dash
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: AI Agent Web Interface with Filesystem and Canvas Visualization
|
|
5
5
|
Project-URL: Homepage, https://github.com/dkedar7/cowork-dash
|
|
6
6
|
Project-URL: Documentation, https://github.com/dkedar7/cowork-dash/blob/main/README.md
|
|
@@ -122,43 +122,77 @@ Examples:
|
|
|
122
122
|
|
|
123
123
|
def load_agent_from_spec(agent_spec: str):
|
|
124
124
|
"""
|
|
125
|
-
Load agent from specification string
|
|
125
|
+
Load agent from specification string.
|
|
126
|
+
|
|
127
|
+
Supports two formats (both use colon separator):
|
|
128
|
+
1. File path format: "path/to/file.py:object_name"
|
|
129
|
+
2. Module format: "mypackage.module.submodule:object_name"
|
|
126
130
|
|
|
127
131
|
Args:
|
|
128
|
-
agent_spec: String like "agent.py:agent"
|
|
132
|
+
agent_spec: String like "agent.py:agent", "my_agents.py:custom_agent",
|
|
133
|
+
or "mypackage.agents:my_agent"
|
|
129
134
|
|
|
130
135
|
Returns:
|
|
131
136
|
tuple: (agent_object, error_message)
|
|
132
137
|
"""
|
|
133
138
|
try:
|
|
134
|
-
#
|
|
139
|
+
# Both formats use colon separator
|
|
135
140
|
if ":" not in agent_spec:
|
|
136
|
-
return None, f"Invalid agent spec '{agent_spec}'. Expected format: 'path/to/file.py:
|
|
141
|
+
return None, f"Invalid agent spec '{agent_spec}'. Expected format: 'path/to/file.py:object' or 'module.path:object'"
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
file_path = Path(file_path).resolve()
|
|
143
|
+
left_part, object_name = agent_spec.rsplit(":", 1)
|
|
140
144
|
|
|
141
|
-
if
|
|
142
|
-
|
|
145
|
+
# Determine if it's a file path or module path
|
|
146
|
+
# File paths end with .py or contain path separators
|
|
147
|
+
if left_part.endswith(".py") or "/" in left_part or "\\" in left_part:
|
|
148
|
+
return _load_agent_from_file(left_part, object_name)
|
|
149
|
+
else:
|
|
150
|
+
return _load_agent_from_module(left_part, object_name)
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if spec is None or spec.loader is None:
|
|
147
|
-
return None, f"Failed to load module from {file_path}"
|
|
152
|
+
except Exception as e:
|
|
153
|
+
return None, f"Failed to load agent from {agent_spec}: {e}"
|
|
148
154
|
|
|
149
|
-
module = importlib.util.module_from_spec(spec)
|
|
150
|
-
sys.modules["custom_agent_module"] = module
|
|
151
|
-
spec.loader.exec_module(module)
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
def _load_agent_from_file(file_path_str: str, object_name: str):
|
|
157
|
+
"""Load agent from file path format: 'path/to/file.py:object_name'"""
|
|
158
|
+
file_path = Path(file_path_str).resolve()
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
return
|
|
160
|
+
if not file_path.exists():
|
|
161
|
+
return None, f"Agent file not found: {file_path}"
|
|
159
162
|
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
# Load the module
|
|
164
|
+
spec = importlib.util.spec_from_file_location("custom_agent_module", file_path)
|
|
165
|
+
if spec is None or spec.loader is None:
|
|
166
|
+
return None, f"Failed to load module from {file_path}"
|
|
167
|
+
|
|
168
|
+
module = importlib.util.module_from_spec(spec)
|
|
169
|
+
sys.modules["custom_agent_module"] = module
|
|
170
|
+
spec.loader.exec_module(module)
|
|
171
|
+
|
|
172
|
+
# Get the object
|
|
173
|
+
if not hasattr(module, object_name):
|
|
174
|
+
return None, f"Object '{object_name}' not found in {file_path}"
|
|
175
|
+
|
|
176
|
+
agent = getattr(module, object_name)
|
|
177
|
+
return agent, None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _load_agent_from_module(module_path: str, object_name: str):
|
|
181
|
+
"""Load agent from module format: 'mypackage.module:object_name'"""
|
|
182
|
+
try:
|
|
183
|
+
# Import the module
|
|
184
|
+
module = importlib.import_module(module_path)
|
|
185
|
+
except ModuleNotFoundError as e:
|
|
186
|
+
return None, f"Module '{module_path}' not found: {e}"
|
|
187
|
+
except ImportError as e:
|
|
188
|
+
return None, f"Failed to import module '{module_path}': {e}"
|
|
189
|
+
|
|
190
|
+
# Get the object
|
|
191
|
+
if not hasattr(module, object_name):
|
|
192
|
+
return None, f"Object '{object_name}' not found in module '{module_path}'"
|
|
193
|
+
|
|
194
|
+
agent = getattr(module, object_name)
|
|
195
|
+
return agent, None
|
|
162
196
|
|
|
163
197
|
# Module-level configuration (uses config defaults)
|
|
164
198
|
WORKSPACE_ROOT = config.WORKSPACE_ROOT
|
|
@@ -348,13 +382,27 @@ def _run_agent_stream(message: str, resume_data: Dict = None):
|
|
|
348
382
|
# Update tool call status when we get the result
|
|
349
383
|
tool_call_id = getattr(last_msg, 'tool_call_id', None)
|
|
350
384
|
if tool_call_id:
|
|
351
|
-
# Determine status
|
|
385
|
+
# Determine status - check message status attribute first
|
|
352
386
|
content = last_msg.content
|
|
353
387
|
status = "success"
|
|
354
|
-
|
|
388
|
+
|
|
389
|
+
# Check if ToolMessage has explicit status (e.g., from LangGraph)
|
|
390
|
+
msg_status = getattr(last_msg, 'status', None)
|
|
391
|
+
if msg_status == 'error':
|
|
355
392
|
status = "error"
|
|
393
|
+
# Check for dict with explicit error field
|
|
356
394
|
elif isinstance(content, dict) and content.get("error"):
|
|
357
395
|
status = "error"
|
|
396
|
+
# Check for common error patterns at the START of the message
|
|
397
|
+
# (not just anywhere, to avoid false positives)
|
|
398
|
+
elif isinstance(content, str):
|
|
399
|
+
content_lower = content.lower().strip()
|
|
400
|
+
# Only mark as error if it starts with error indicators
|
|
401
|
+
if (content_lower.startswith("error:") or
|
|
402
|
+
content_lower.startswith("failed:") or
|
|
403
|
+
content_lower.startswith("exception:") or
|
|
404
|
+
content_lower.startswith("traceback")):
|
|
405
|
+
status = "error"
|
|
358
406
|
|
|
359
407
|
# Truncate result for display
|
|
360
408
|
result_display = str(content)
|
|
@@ -760,10 +808,14 @@ app.index_string = '''<!DOCTYPE html>
|
|
|
760
808
|
|
|
761
809
|
def create_layout():
|
|
762
810
|
"""Create the app layout with current configuration."""
|
|
811
|
+
# Use agent's name/description if available, otherwise fall back to config
|
|
812
|
+
title = getattr(agent, 'name', None) or APP_TITLE
|
|
813
|
+
subtitle = getattr(agent, 'description', None) or APP_SUBTITLE
|
|
814
|
+
|
|
763
815
|
return create_layout_component(
|
|
764
816
|
workspace_root=WORKSPACE_ROOT,
|
|
765
|
-
app_title=
|
|
766
|
-
app_subtitle=
|
|
817
|
+
app_title=title,
|
|
818
|
+
app_subtitle=subtitle,
|
|
767
819
|
colors=COLORS,
|
|
768
820
|
styles=STYLES,
|
|
769
821
|
agent=agent
|
|
@@ -1621,7 +1673,10 @@ def run_app(
|
|
|
1621
1673
|
Args:
|
|
1622
1674
|
agent_instance (object, optional): Agent object instance (Python API only)
|
|
1623
1675
|
workspace (str, optional): Workspace directory path
|
|
1624
|
-
agent_spec (str, optional): Agent specification
|
|
1676
|
+
agent_spec (str, optional): Agent specification (overrides agent_instance).
|
|
1677
|
+
Supports two formats (both use colon separator):
|
|
1678
|
+
- File path: "path/to/file.py:object_name"
|
|
1679
|
+
- Module path: "mypackage.module:object_name"
|
|
1625
1680
|
port (int, optional): Port number
|
|
1626
1681
|
host (str, optional): Host to bind to
|
|
1627
1682
|
debug (bool, optional): Debug mode
|
|
@@ -1638,9 +1693,12 @@ def run_app(
|
|
|
1638
1693
|
>>> my_agent = MyAgent()
|
|
1639
1694
|
>>> run_app(my_agent, workspace="~/my-workspace")
|
|
1640
1695
|
|
|
1641
|
-
>>> # Using agent spec
|
|
1696
|
+
>>> # Using agent spec (file path format)
|
|
1642
1697
|
>>> run_app(agent_spec="my_agent.py:agent", port=8080)
|
|
1643
1698
|
|
|
1699
|
+
>>> # Using agent spec (module format)
|
|
1700
|
+
>>> run_app(agent_spec="mypackage.agents:my_agent", port=8080)
|
|
1701
|
+
|
|
1644
1702
|
>>> # Without agent (manual mode)
|
|
1645
1703
|
>>> run_app(workspace="~/my-workspace", debug=True)
|
|
1646
1704
|
"""
|
|
@@ -53,11 +53,16 @@ def get_config(key: str, default=None, type_cast=None):
|
|
|
53
53
|
_workspace_path = get_config("workspace_root", default="./")
|
|
54
54
|
WORKSPACE_ROOT = Path(_workspace_path).resolve() if _workspace_path else Path("./").resolve()
|
|
55
55
|
|
|
56
|
-
# Agent specification (
|
|
56
|
+
# Agent specification - supports two formats (both use colon separator):
|
|
57
|
+
# 1. File path: "path/to/file.py:object_name"
|
|
58
|
+
# 2. Module path: "mypackage.module:object_name"
|
|
57
59
|
# Environment variable: DEEPAGENT_SPEC (or DEEPAGENT_AGENT_SPEC for backwards compatibility)
|
|
58
60
|
# CLI argument: --agent
|
|
59
|
-
# Default:
|
|
60
|
-
#
|
|
61
|
+
# Default: package's built-in agent
|
|
62
|
+
# Examples:
|
|
63
|
+
# - "my_agents.py:agent" (file in current directory)
|
|
64
|
+
# - "/path/to/agent.py:my_agent" (absolute path)
|
|
65
|
+
# - "mypackage.agents:my_agent" (installed Python module)
|
|
61
66
|
_default_agent = str(Path(__file__).parent / "agent.py") + ":agent"
|
|
62
67
|
AGENT_SPEC = get_config("spec", default=None) or get_config("agent_spec", default=None) or _default_agent
|
|
63
68
|
|
|
@@ -100,8 +100,8 @@ Let's get started!"""
|
|
|
100
100
|
], style={"display": "flex", "alignItems": "center"})
|
|
101
101
|
], style={
|
|
102
102
|
"display": "flex", "justifyContent": "space-between",
|
|
103
|
-
"alignItems": "center", "
|
|
104
|
-
"
|
|
103
|
+
"alignItems": "center", "width": "100%",
|
|
104
|
+
"padding": "0 12px",
|
|
105
105
|
})
|
|
106
106
|
], id="header", style={
|
|
107
107
|
"background": "var(--mantine-color-body)",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|