agent-dispatch 0.2.1__tar.gz → 0.2.2__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 (26) hide show
  1. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/PKG-INFO +1 -1
  2. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/pyproject.toml +1 -1
  3. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/__init__.py +1 -1
  4. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/cli.py +6 -4
  5. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/server.py +3 -2
  6. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/test_cli.py +34 -0
  7. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/test_server.py +21 -0
  8. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/.github/dependabot.yml +0 -0
  9. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/.github/workflows/ci.yml +0 -0
  10. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/.github/workflows/publish.yml +0 -0
  11. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/.gitignore +0 -0
  12. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/LICENSE +0 -0
  13. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/README.md +0 -0
  14. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/SECURITY.md +0 -0
  15. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/agents.example.yaml +0 -0
  16. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/assets/mascot.png +0 -0
  17. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/cache.py +0 -0
  18. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/config.py +0 -0
  19. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/models.py +0 -0
  20. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/src/agent_dispatch/runner.py +0 -0
  21. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/__init__.py +0 -0
  22. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/conftest.py +0 -0
  23. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/test_cache.py +0 -0
  24. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/test_config.py +0 -0
  25. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/test_models.py +0 -0
  26. {agent_dispatch-0.2.1 → agent_dispatch-0.2.2}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-dispatch
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: MCP server that lets Claude Code agents delegate tasks to agents in other project directories
5
5
  Project-URL: Homepage, https://github.com/ginkida/agent-dispatch
6
6
  Project-URL: Repository, https://github.com/ginkida/agent-dispatch
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-dispatch"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "MCP server that lets Claude Code agents delegate tasks to agents in other project directories"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """agent-dispatch: Delegate tasks between Claude Code agents across projects."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
@@ -202,10 +202,12 @@ def list_agents() -> None:
202
202
  click.echo(f" config: {', '.join(extras)}")
203
203
  if agent.permission_mode:
204
204
  click.echo(f" permission_mode: {agent.permission_mode}")
205
- if agent.allowed_tools:
206
- click.echo(f" allowed_tools: {', '.join(agent.allowed_tools)}")
207
- if agent.disallowed_tools:
208
- click.echo(f" disallowed_tools: {', '.join(agent.disallowed_tools)}")
205
+ if agent.allowed_tools is not None:
206
+ rendered = ", ".join(agent.allowed_tools) if agent.allowed_tools else "(none)"
207
+ click.echo(f" allowed_tools: {rendered}")
208
+ if agent.disallowed_tools is not None:
209
+ rendered = ", ".join(agent.disallowed_tools) if agent.disallowed_tools else "(none)"
210
+ click.echo(f" disallowed_tools: {rendered}")
209
211
  click.echo()
210
212
 
211
213
 
@@ -113,9 +113,10 @@ async def list_agents(ctx: Context | None = None) -> str:
113
113
  }
114
114
  if agent.permission_mode:
115
115
  entry["permission_mode"] = agent.permission_mode
116
- if agent.allowed_tools:
116
+ # Include when explicitly set (even []) to distinguish from inheriting defaults
117
+ if agent.allowed_tools is not None:
117
118
  entry["allowed_tools"] = agent.allowed_tools
118
- if agent.disallowed_tools:
119
+ if agent.disallowed_tools is not None:
119
120
  entry["disallowed_tools"] = agent.disallowed_tools
120
121
  agents.append(entry)
121
122
  if ctx:
@@ -145,6 +145,40 @@ class TestList:
145
145
  assert "permission_mode: bypassPermissions" in result.output
146
146
  assert "allowed_tools: Bash, Read" in result.output
147
147
 
148
+ def test_list_distinguishes_empty_from_inherit(self, tmp_path: Path):
149
+ """Explicit allowed_tools=[] should show '(none)' to distinguish from inherit (None)."""
150
+ agent_dir = tmp_path / "proj"
151
+ agent_dir.mkdir()
152
+ # Write config directly with explicit empty list
153
+ import yaml
154
+ from agent_dispatch.config import config_path
155
+ cfg_path = config_path()
156
+ cfg_path.parent.mkdir(parents=True, exist_ok=True)
157
+ yaml.safe_dump({
158
+ "agents": {
159
+ "proj": {
160
+ "directory": str(agent_dir),
161
+ "description": "Test",
162
+ "allowed_tools": [],
163
+ "disallowed_tools": [],
164
+ },
165
+ },
166
+ }, cfg_path.open("w"))
167
+
168
+ result = runner.invoke(cli, ["list"])
169
+ assert result.exit_code == 0
170
+ assert "allowed_tools: (none)" in result.output
171
+ assert "disallowed_tools: (none)" in result.output
172
+
173
+ def test_list_hides_tools_when_none(self, tmp_path: Path):
174
+ """allowed_tools=None (the default, meaning 'inherit') should not appear in list."""
175
+ agent_dir = tmp_path / "proj"
176
+ agent_dir.mkdir()
177
+ runner.invoke(cli, ["add", "proj", str(agent_dir), "-d", "Test"])
178
+ result = runner.invoke(cli, ["list"])
179
+ assert "allowed_tools" not in result.output
180
+ assert "disallowed_tools" not in result.output
181
+
148
182
 
149
183
  class TestUpdate:
150
184
  def test_update_permission_mode(self, tmp_path: Path):
@@ -593,6 +593,7 @@ class TestListAgentsPermissions:
593
593
 
594
594
  @pytest.mark.asyncio
595
595
  async def test_list_omits_empty_permissions(self, tmp_path: Path):
596
+ """allowed_tools=None (inherit) should NOT appear in response."""
596
597
  d = tmp_path / "proj"
597
598
  d.mkdir()
598
599
  config = DispatchConfig(
@@ -606,6 +607,26 @@ class TestListAgentsPermissions:
606
607
  assert "allowed_tools" not in agents[0]
607
608
  assert "disallowed_tools" not in agents[0]
608
609
 
610
+ @pytest.mark.asyncio
611
+ async def test_list_includes_explicit_empty_tools(self, tmp_path: Path):
612
+ """allowed_tools=[] (explicit empty) SHOULD appear as [] to signal override."""
613
+ d = tmp_path / "proj"
614
+ d.mkdir()
615
+ config = DispatchConfig(
616
+ agents={
617
+ "proj": AgentConfig(
618
+ directory=d, description="test",
619
+ allowed_tools=[], disallowed_tools=[],
620
+ ),
621
+ }
622
+ )
623
+ mock_ctx = AsyncMock()
624
+ with patch.object(server, "_get_config", return_value=config):
625
+ raw = await server.list_agents(ctx=mock_ctx)
626
+ agents = json.loads(raw)
627
+ assert agents[0]["allowed_tools"] == []
628
+ assert agents[0]["disallowed_tools"] == []
629
+
609
630
 
610
631
  class TestAddRemoveAgent:
611
632
  @pytest.mark.asyncio
File without changes
File without changes