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.
Files changed (30) hide show
  1. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/CHANGELOG.md +19 -0
  2. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/PKG-INFO +1 -1
  3. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/agent.py +1 -0
  4. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/app.py +86 -28
  5. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/config.py +8 -3
  6. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/layout.py +2 -2
  7. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/pyproject.toml +1 -1
  8. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/.claude/settings.local.json +0 -0
  9. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/.gitignore +0 -0
  10. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/LICENSE +0 -0
  11. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/MANIFEST.in +0 -0
  12. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/README.md +0 -0
  13. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/__init__.py +0 -0
  14. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/__main__.py +0 -0
  15. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/assets/app.js +0 -0
  16. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/assets/favicon.svg +0 -0
  17. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/assets/styles.css +0 -0
  18. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/canvas.py +0 -0
  19. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/cli.py +0 -0
  20. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/components.py +0 -0
  21. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/file_utils.py +0 -0
  22. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/cowork_dash/tools.py +0 -0
  23. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/docs/CLI_USAGE.md +0 -0
  24. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/examples/example_agent.py +0 -0
  25. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/examples/python_api_example.py +0 -0
  26. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/requirements.txt +0 -0
  27. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/templates/index.html +0 -0
  28. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/tests/__init__.py +0 -0
  29. {cowork_dash-0.1.2 → cowork_dash-0.1.4}/tests/conftest.py +0 -0
  30. {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.2
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
@@ -96,6 +96,7 @@ backend = FilesystemBackend(root_dir=str("./"), virtual_mode=True)
96
96
 
97
97
  agent = create_deep_agent(
98
98
  system_prompt=SYSTEM_PROMPT,
99
+ name="Cowork Dash",
99
100
  backend=backend,
100
101
  tools=[
101
102
  add_to_canvas,
@@ -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 in format "path/to/file.py:object_name".
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" or "my_agents.py:custom_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
- # Parse the spec
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:object_name'"
141
+ return None, f"Invalid agent spec '{agent_spec}'. Expected format: 'path/to/file.py:object' or 'module.path:object'"
137
142
 
138
- file_path, object_name = agent_spec.rsplit(":", 1)
139
- file_path = Path(file_path).resolve()
143
+ left_part, object_name = agent_spec.rsplit(":", 1)
140
144
 
141
- if not file_path.exists():
142
- return None, f"Agent file not found: {file_path}"
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
- # Load the module
145
- spec = importlib.util.spec_from_file_location("custom_agent_module", file_path)
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
- # Get the object
154
- if not hasattr(module, object_name):
155
- return None, f"Object '{object_name}' not found in {file_path}"
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
- agent = getattr(module, object_name)
158
- return agent, None
160
+ if not file_path.exists():
161
+ return None, f"Agent file not found: {file_path}"
159
162
 
160
- except Exception as e:
161
- return None, f"Failed to load agent from {agent_spec}: {e}"
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 based on content
385
+ # Determine status - check message status attribute first
352
386
  content = last_msg.content
353
387
  status = "success"
354
- if isinstance(content, str) and ("error" in content.lower() or "Error:" in content):
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=APP_TITLE,
766
- app_subtitle=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 as "path:object" (overrides agent_instance)
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 (format: "module_path:variable_name")
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: None (manual mode, no agent)
60
- # Example: "mymodule:agent" or "/path/to/agent.py:my_agent"
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", "maxWidth": "1600px",
104
- "margin": "0 auto", "padding": "0 12px",
103
+ "alignItems": "center", "width": "100%",
104
+ "padding": "0 12px",
105
105
  })
106
106
  ], id="header", style={
107
107
  "background": "var(--mantine-color-body)",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cowork-dash"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "AI Agent Web Interface with Filesystem and Canvas Visualization"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes