cade-cli 0.20.7__tar.gz → 0.21.0__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.
- {cade_cli-0.20.7 → cade_cli-0.21.0}/PKG-INFO +1 -1
- {cade_cli-0.20.7 → cade_cli-0.21.0}/pyproject.toml +1 -1
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/_metadata.py +14 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/sandbox.py +25 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/server.py +12 -22
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/__init__.py +4 -14
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/agents.py +65 -60
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/shell.py +3 -13
- cade_cli-0.21.0/src/cade_mcp_local/tools/tasks.py +164 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/utils.py +8 -3
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ai/prompts.py +2 -2
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/agents.py +18 -2
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/tools.py +80 -16
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/agent.py +3 -1
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/spawn.py +3 -3
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/allowlist.py +52 -8
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/config.py +4 -1
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/message_pump.py +1 -4
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/orchestrator.py +24 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/serve/serve.toml.example +3 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/composite.py +313 -27
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/mcp.py +1 -1
- cade_cli-0.21.0/src/cadecoder/tools/manager/surface.py +353 -0
- cade_cli-0.21.0/src/cadecoder/tools/merged.py +60 -0
- cade_cli-0.21.0/src/cadecoder/tools/profiles.py +74 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/session.py +5 -3
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/session.py +15 -0
- cade_cli-0.20.7/src/cade_mcp_local/tools/tasks.py +0 -167
- {cade_cli-0.20.7 → cade_cli-0.21.0}/.gitignore +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/LICENSE +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/README.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/grants.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/access.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/app.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/agent_group.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/auth.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/daemon.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/grant.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/mcp.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/mem.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/mcp_hosts.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/ui.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/binary.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/text.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/base.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/factory.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/imessage.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/registry.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/stub.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/telegram.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/attach.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/chunker.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/client.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/commands.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/control.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/cron.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/elicitation.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/ensure.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/format.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/app.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/auth.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/constants.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/elicitation.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/exposure.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/host_agent.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/limits.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/middleware.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/principal.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/resources.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/subscriptions.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/turn_bridge.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/grants.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/inbox.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/inbox_cron.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/install.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/lock.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/mesh.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/observability.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/owner.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/progress_sink.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/registry.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/router.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/runner.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/runner_main.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/secrets.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/secrets_broker.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/server.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/services.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/sinks.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/supervisor.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/tokens.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/traffic.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/turns.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/wake.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.claude-plugin/marketplace.json +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.claude-plugin/plugin.json +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/marketplace.json +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/plugin.json +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/agents/operator.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/commands/ask.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/commands/memory.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/commands/tasks.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/mcp.json +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/rules/gateway.mdc +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/skills/ask/SKILL.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/skills/memory/SKILL.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/skills/tasks/SKILL.md +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/doctor.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/install.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/paths.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/state.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/http_client.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/telemetry/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/telemetry/otel.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/config.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/tts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cade-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.21.0
|
|
4
4
|
Summary: Cade - The CLI Agent from Arcade.dev (install: curl -fsSL https://arcadeagent.dev/install | sh)
|
|
5
5
|
Project-URL: Homepage, https://arcade.dev
|
|
6
6
|
Project-URL: Documentation, https://arcadeagent.dev/docs
|
|
@@ -59,6 +59,18 @@ DELETE_META = _meta(
|
|
|
59
59
|
)
|
|
60
60
|
"""Removes a local resource. Destructive."""
|
|
61
61
|
|
|
62
|
+
READ_OR_CREATE_META = _meta(
|
|
63
|
+
operations=[Operation.READ, Operation.CREATE], read_only=False, idempotent=False
|
|
64
|
+
)
|
|
65
|
+
"""Action-dispatching tool whose actions either read or create (e.g. Agent)."""
|
|
66
|
+
|
|
67
|
+
CRUD_META = _meta(
|
|
68
|
+
operations=[Operation.CREATE, Operation.READ, Operation.UPDATE, Operation.DELETE],
|
|
69
|
+
read_only=False,
|
|
70
|
+
destructive=True,
|
|
71
|
+
)
|
|
72
|
+
"""One tool covering a resource's whole lifecycle via an ``action`` argument (e.g. Task)."""
|
|
73
|
+
|
|
62
74
|
OPAQUE_META = ToolMetadata(
|
|
63
75
|
classification=Classification(service_domains=None),
|
|
64
76
|
behavior=Behavior(
|
|
@@ -76,6 +88,8 @@ OPAQUE_META = ToolMetadata(
|
|
|
76
88
|
__all__ = (
|
|
77
89
|
"CREATE_META",
|
|
78
90
|
"CREATE_OR_UPDATE_META",
|
|
91
|
+
"CRUD_META",
|
|
92
|
+
"READ_OR_CREATE_META",
|
|
79
93
|
"DELETE_META",
|
|
80
94
|
"OPAQUE_META",
|
|
81
95
|
"READ_META",
|
|
@@ -68,6 +68,29 @@ def sandbox_available() -> bool:
|
|
|
68
68
|
return backend_name() is not None
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def command_sandbox_required() -> bool:
|
|
72
|
+
"""Whether shell commands must run inside an OS sandbox.
|
|
73
|
+
|
|
74
|
+
The global path-restrictions kill switch also lifts the command sandbox:
|
|
75
|
+
it is the existing single "I know what I'm doing" toggle, and holding
|
|
76
|
+
bash to a tighter boundary than the file tools would only confuse.
|
|
77
|
+
"""
|
|
78
|
+
from cade_mcp_local.config import path_restrictions_enabled
|
|
79
|
+
|
|
80
|
+
if sandbox_disabled_by_env():
|
|
81
|
+
return False
|
|
82
|
+
return path_restrictions_enabled()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def shell_usable() -> bool:
|
|
86
|
+
"""True when ``Bash`` / ``ScratchBash`` can run on this host at all.
|
|
87
|
+
|
|
88
|
+
They refuse (fail closed) when a sandbox is required but no backend is
|
|
89
|
+
installed; this is the same decision, made without running a command.
|
|
90
|
+
"""
|
|
91
|
+
return not command_sandbox_required() or sandbox_available()
|
|
92
|
+
|
|
93
|
+
|
|
71
94
|
def wrap_command(script: str, spec: SandboxSpec) -> tuple[list[str], str | None]:
|
|
72
95
|
"""Return (argv, cleanup_path) running ``script`` inside the OS sandbox.
|
|
73
96
|
|
|
@@ -265,9 +288,11 @@ __all__ = [
|
|
|
265
288
|
"SANDBOX_DISABLE_ENV",
|
|
266
289
|
"SandboxSpec",
|
|
267
290
|
"backend_name",
|
|
291
|
+
"command_sandbox_required",
|
|
268
292
|
"describe_backend",
|
|
269
293
|
"prepare_spec",
|
|
270
294
|
"sandbox_available",
|
|
271
295
|
"sandbox_disabled_by_env",
|
|
296
|
+
"shell_usable",
|
|
272
297
|
"wrap_command",
|
|
273
298
|
]
|
|
@@ -19,9 +19,7 @@ patch_frozen_return_detection()
|
|
|
19
19
|
|
|
20
20
|
from cade_mcp_local.resources import register_resources # noqa: E402
|
|
21
21
|
from cade_mcp_local.tools import ( # noqa: E402
|
|
22
|
-
|
|
23
|
-
agent_send_tool,
|
|
24
|
-
agent_spawn_tool,
|
|
22
|
+
agent_tool,
|
|
25
23
|
ask_user_question_tool,
|
|
26
24
|
bash_tool,
|
|
27
25
|
edit_tool,
|
|
@@ -33,10 +31,7 @@ from cade_mcp_local.tools import ( # noqa: E402
|
|
|
33
31
|
retrieve_tool_result_tool,
|
|
34
32
|
scratch_bash_tool,
|
|
35
33
|
search_tool,
|
|
36
|
-
|
|
37
|
-
task_delete_tool,
|
|
38
|
-
task_list_tool,
|
|
39
|
-
task_update_tool,
|
|
34
|
+
task_tool,
|
|
40
35
|
tool_schema_tool,
|
|
41
36
|
web_fetch_tool,
|
|
42
37
|
web_search_tool,
|
|
@@ -52,14 +47,14 @@ is running. Use these tools when you need to:
|
|
|
52
47
|
- Search code in the local project directory
|
|
53
48
|
- Execute shell commands or bash scripts locally
|
|
54
49
|
- Check git status, diff, log, or branches of the local repository
|
|
55
|
-
- File-backed tasks for durable multi-step work (
|
|
56
|
-
|
|
50
|
+
- File-backed tasks for durable multi-step work (Task with action
|
|
51
|
+
create/list/get/update/delete) — the host registers it as Local_Task
|
|
57
52
|
- PushNotification to inject a structured notice into the agent context at the next
|
|
58
53
|
tool boundary
|
|
59
|
-
-
|
|
60
|
-
one (ACL-gated; delivered to the target agent's inbox)
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
- Agent to coordinate with other agents: send to / list agents the user has
|
|
55
|
+
linked to this one (ACL-gated; delivered to the target agent's inbox), or
|
|
56
|
+
spawn a child agent for a self-contained sub-task and get back just its
|
|
57
|
+
result (requires this agent to allow spawning)
|
|
63
58
|
- AskUserQuestion when you need structured clarification from the user (host:
|
|
64
59
|
Local_AskUserQuestion)
|
|
65
60
|
- WebFetch to read an external URL (Firecrawl via Arcade by default, httpx
|
|
@@ -132,19 +127,14 @@ def create_app() -> MCPApp:
|
|
|
132
127
|
app.add_tool(tool_schema_tool)
|
|
133
128
|
|
|
134
129
|
# Task coordination (file-backed task store)
|
|
135
|
-
app.add_tool(
|
|
136
|
-
app.add_tool(task_list_tool)
|
|
137
|
-
app.add_tool(task_update_tool)
|
|
138
|
-
app.add_tool(task_delete_tool)
|
|
130
|
+
app.add_tool(task_tool)
|
|
139
131
|
|
|
140
132
|
# Notifications (inject XML into context at next tool boundary)
|
|
141
133
|
app.add_tool(push_notification_tool)
|
|
142
134
|
|
|
143
|
-
# Cross-agent coordination (ACL-gated
|
|
144
|
-
|
|
145
|
-
app.add_tool(
|
|
146
|
-
# Spawn an ephemeral child task-agent (opt-in via the agent's allow_spawn)
|
|
147
|
-
app.add_tool(agent_spawn_tool)
|
|
135
|
+
# Cross-agent coordination: send/list (ACL-gated inbox delivery) and
|
|
136
|
+
# spawn (ephemeral child task-agent, opt-in via the agent's allow_spawn)
|
|
137
|
+
app.add_tool(agent_tool)
|
|
148
138
|
|
|
149
139
|
# Structured clarification question for the user
|
|
150
140
|
app.add_tool(ask_user_question_tool)
|
|
@@ -6,7 +6,7 @@ commands on the same machine as the running cade process.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from cade_mcp_local.tools.access import request_access_tool
|
|
9
|
-
from cade_mcp_local.tools.agents import
|
|
9
|
+
from cade_mcp_local.tools.agents import agent_tool
|
|
10
10
|
from cade_mcp_local.tools.filesystem import (
|
|
11
11
|
edit_tool,
|
|
12
12
|
list_files_tool,
|
|
@@ -24,12 +24,7 @@ from cade_mcp_local.tools.results import retrieve_tool_result_tool
|
|
|
24
24
|
from cade_mcp_local.tools.schemas import tool_schema_tool
|
|
25
25
|
from cade_mcp_local.tools.search import search_tool
|
|
26
26
|
from cade_mcp_local.tools.shell import bash_tool, scratch_bash_tool
|
|
27
|
-
from cade_mcp_local.tools.tasks import
|
|
28
|
-
task_create_tool,
|
|
29
|
-
task_delete_tool,
|
|
30
|
-
task_list_tool,
|
|
31
|
-
task_update_tool,
|
|
32
|
-
)
|
|
27
|
+
from cade_mcp_local.tools.tasks import task_tool
|
|
33
28
|
from cade_mcp_local.tools.web_fetch import web_fetch_tool
|
|
34
29
|
from cade_mcp_local.tools.web_search import web_search_tool
|
|
35
30
|
|
|
@@ -54,16 +49,11 @@ __all__ = [
|
|
|
54
49
|
# Tool Schemas
|
|
55
50
|
"tool_schema_tool",
|
|
56
51
|
# Tasks
|
|
57
|
-
"
|
|
58
|
-
"task_list_tool",
|
|
59
|
-
"task_update_tool",
|
|
60
|
-
"task_delete_tool",
|
|
52
|
+
"task_tool",
|
|
61
53
|
# Notifications
|
|
62
54
|
"push_notification_tool",
|
|
63
55
|
# Cross-agent coordination
|
|
64
|
-
"
|
|
65
|
-
"agent_list_tool",
|
|
66
|
-
"agent_spawn_tool",
|
|
56
|
+
"agent_tool",
|
|
67
57
|
# Interactive
|
|
68
58
|
"ask_user_question_tool",
|
|
69
59
|
# Web
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
"""MCP
|
|
1
|
+
"""MCP tool for cross-agent coordination.
|
|
2
|
+
|
|
3
|
+
One ``Agent`` tool with an ``action`` (send | list | spawn), replacing the
|
|
4
|
+
former AgentSend / AgentList / AgentSpawn tools; the host's composite tool
|
|
5
|
+
manager rewrites calls to those legacy names, and trims the ``action`` enum
|
|
6
|
+
to the actions this agent can actually use (see
|
|
7
|
+
``cadecoder.tools.manager.surface``).
|
|
2
8
|
|
|
3
9
|
Lets the model address other agents the user has explicitly linked. Delivery
|
|
4
10
|
rides the durable per-agent inbox (so the message survives until the target
|
|
@@ -8,12 +14,13 @@ a shared channel. The default is isolation.
|
|
|
8
14
|
"""
|
|
9
15
|
|
|
10
16
|
import logging
|
|
17
|
+
from enum import Enum
|
|
11
18
|
from typing import Annotated, Any
|
|
12
19
|
|
|
13
20
|
from arcade_mcp_server import Context, tool
|
|
14
21
|
from typing_extensions import TypedDict
|
|
15
22
|
|
|
16
|
-
from cade_mcp_local._metadata import
|
|
23
|
+
from cade_mcp_local._metadata import READ_OR_CREATE_META
|
|
17
24
|
from cade_mcp_local.errors import InvalidInputError
|
|
18
25
|
from cadecoder.core import agent as agent_mod
|
|
19
26
|
from cadecoder.core import comms as comms_mod
|
|
@@ -23,23 +30,28 @@ from cadecoder.core.paths import get_paths
|
|
|
23
30
|
log = logging.getLogger(__name__)
|
|
24
31
|
|
|
25
32
|
|
|
26
|
-
class
|
|
33
|
+
class AgentAction(str, Enum):
|
|
34
|
+
"""Agent coordination operation."""
|
|
35
|
+
|
|
36
|
+
SEND = "send"
|
|
37
|
+
LIST = "list"
|
|
38
|
+
SPAWN = "spawn"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AgentOutput(TypedDict, total=False):
|
|
42
|
+
# send
|
|
27
43
|
delivered: bool
|
|
28
44
|
target: str
|
|
29
45
|
target_id: str
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
# list
|
|
47
|
+
agents: list[dict[str, Any]]
|
|
48
|
+
count: int
|
|
49
|
+
# spawn
|
|
33
50
|
agent: str
|
|
34
51
|
id: str
|
|
35
52
|
result: str
|
|
36
53
|
|
|
37
54
|
|
|
38
|
-
class AgentListOutput(TypedDict):
|
|
39
|
-
agents: list[dict[str, Any]]
|
|
40
|
-
count: int
|
|
41
|
-
|
|
42
|
-
|
|
43
55
|
def _agent_from_request_meta() -> str | None:
|
|
44
56
|
"""Per-request acting agent from MCP ``_meta['cade/agent']``, if the host set it.
|
|
45
57
|
|
|
@@ -65,20 +77,36 @@ def _current_agent_name() -> str:
|
|
|
65
77
|
|
|
66
78
|
|
|
67
79
|
@tool(
|
|
68
|
-
name="
|
|
80
|
+
name="Agent",
|
|
69
81
|
desc=(
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
82
|
+
"Coordinate with other agents. send: message an agent a user has linked "
|
|
83
|
+
"to this one (via `cade agent link` or a shared channel), delivered at its "
|
|
84
|
+
"next turn. list: agents you can message. spawn: delegate a self-contained "
|
|
85
|
+
"sub-task to a fresh child agent in its own context and block until it "
|
|
86
|
+
"returns just the result."
|
|
74
87
|
),
|
|
75
|
-
metadata=
|
|
88
|
+
metadata=READ_OR_CREATE_META,
|
|
76
89
|
)
|
|
77
|
-
async def
|
|
90
|
+
async def agent_tool(
|
|
78
91
|
context: Context,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
action: Annotated[AgentAction, "Coordination operation."],
|
|
93
|
+
target: Annotated[str, "[send] Target agent name, id, or unique id prefix."] = "",
|
|
94
|
+
message: Annotated[str, "[send] Message to deliver to the target agent."] = "",
|
|
95
|
+
task: Annotated[str, "[spawn] The self-contained sub-task for the child agent."] = "",
|
|
96
|
+
) -> Annotated[AgentOutput, "Coordination result."]:
|
|
97
|
+
"""Dispatch an agent coordination operation."""
|
|
98
|
+
if action == AgentAction.SEND:
|
|
99
|
+
return await _send(target, message)
|
|
100
|
+
if action == AgentAction.LIST:
|
|
101
|
+
return _list()
|
|
102
|
+
if action == AgentAction.SPAWN:
|
|
103
|
+
return await _spawn(task)
|
|
104
|
+
raise InvalidInputError("action", f"Unknown action: {action}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def _send(target: str, message: str) -> AgentOutput:
|
|
108
|
+
if not target.strip():
|
|
109
|
+
raise InvalidInputError("target", "required for send")
|
|
82
110
|
if not message.strip():
|
|
83
111
|
raise InvalidInputError("message", "cannot be empty")
|
|
84
112
|
sender = _current_agent_name()
|
|
@@ -122,45 +150,7 @@ async def agent_send_tool(
|
|
|
122
150
|
return {"delivered": True, "target": target_agent.name, "target_id": target_agent.id}
|
|
123
151
|
|
|
124
152
|
|
|
125
|
-
|
|
126
|
-
name="AgentSpawn",
|
|
127
|
-
desc=(
|
|
128
|
-
"Delegate a focused sub-task to a fresh child agent that runs in its own "
|
|
129
|
-
"process and context, returning just the result (keeps your context "
|
|
130
|
-
"clean). Only works if this agent was created with --allow-spawn. Blocks "
|
|
131
|
-
"until the child finishes."
|
|
132
|
-
),
|
|
133
|
-
metadata=CREATE_META,
|
|
134
|
-
)
|
|
135
|
-
async def agent_spawn_tool(
|
|
136
|
-
context: Context,
|
|
137
|
-
task: Annotated[str, "The self-contained sub-task for the child agent."],
|
|
138
|
-
) -> Annotated[AgentSpawnOutput, "The child agent and its result."]:
|
|
139
|
-
from cadecoder.core.spawn import SpawnError, spawn_task_agent
|
|
140
|
-
|
|
141
|
-
if not task.strip():
|
|
142
|
-
raise InvalidInputError("task", "cannot be empty")
|
|
143
|
-
# Resolve the parent from the per-request agent identity so the child is
|
|
144
|
-
# attributed to the calling agent, not this subprocess's env/state.
|
|
145
|
-
parent_name = _agent_from_request_meta()
|
|
146
|
-
parent = agent_mod.resolve(parent_name) if parent_name else None
|
|
147
|
-
try:
|
|
148
|
-
return await spawn_task_agent(task, parent=parent) # type: ignore[return-value]
|
|
149
|
-
except SpawnError as e:
|
|
150
|
-
raise InvalidInputError("task", str(e)) from e
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@tool(
|
|
154
|
-
name="AgentList",
|
|
155
|
-
desc=(
|
|
156
|
-
"List the agents you can message — i.e. agents a user has linked to this "
|
|
157
|
-
"one through a shared channel. Returns an empty list when you are isolated."
|
|
158
|
-
),
|
|
159
|
-
metadata=READ_META,
|
|
160
|
-
)
|
|
161
|
-
def agent_list_tool(
|
|
162
|
-
context: Context,
|
|
163
|
-
) -> Annotated[AgentListOutput, "Reachable agents."]:
|
|
153
|
+
def _list() -> AgentOutput:
|
|
164
154
|
sender = _current_agent_name()
|
|
165
155
|
reachable: list[dict[str, Any]] = []
|
|
166
156
|
for candidate in agent_mod.list_agents():
|
|
@@ -175,3 +165,18 @@ def agent_list_tool(
|
|
|
175
165
|
}
|
|
176
166
|
)
|
|
177
167
|
return {"agents": reachable, "count": len(reachable)}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def _spawn(task: str) -> AgentOutput:
|
|
171
|
+
from cadecoder.core.spawn import SpawnError, spawn_task_agent
|
|
172
|
+
|
|
173
|
+
if not task.strip():
|
|
174
|
+
raise InvalidInputError("task", "cannot be empty")
|
|
175
|
+
# Resolve the parent from the per-request agent identity so the child is
|
|
176
|
+
# attributed to the calling agent, not this subprocess's env/state.
|
|
177
|
+
parent_name = _agent_from_request_meta()
|
|
178
|
+
parent = agent_mod.resolve(parent_name) if parent_name else None
|
|
179
|
+
try:
|
|
180
|
+
return await spawn_task_agent(task, parent=parent) # type: ignore[return-value]
|
|
181
|
+
except SpawnError as e:
|
|
182
|
+
raise InvalidInputError("task", str(e)) from e
|
|
@@ -33,7 +33,6 @@ from cade_mcp_local._metadata import OPAQUE_META
|
|
|
33
33
|
from cade_mcp_local.config import (
|
|
34
34
|
DEFAULT_TIMEOUT,
|
|
35
35
|
get_project_root,
|
|
36
|
-
path_restrictions_enabled,
|
|
37
36
|
)
|
|
38
37
|
from cade_mcp_local.errors import (
|
|
39
38
|
CommandExecutionError,
|
|
@@ -42,10 +41,10 @@ from cade_mcp_local.errors import (
|
|
|
42
41
|
)
|
|
43
42
|
from cade_mcp_local.grants import current_grant_set, ensure_scratch_dir
|
|
44
43
|
from cade_mcp_local.sandbox import (
|
|
44
|
+
command_sandbox_required,
|
|
45
45
|
describe_backend,
|
|
46
46
|
prepare_spec,
|
|
47
47
|
sandbox_available,
|
|
48
|
-
sandbox_disabled_by_env,
|
|
49
48
|
sandbox_tmpdir,
|
|
50
49
|
wrap_command,
|
|
51
50
|
)
|
|
@@ -122,17 +121,8 @@ def _check_dangerous(script: str) -> str | None:
|
|
|
122
121
|
|
|
123
122
|
|
|
124
123
|
def _sandboxing_active() -> bool:
|
|
125
|
-
"""Whether per-command OS sandboxing should be applied.
|
|
126
|
-
|
|
127
|
-
The global path-restrictions kill switch also lifts the command sandbox:
|
|
128
|
-
it is the existing single "I know what I'm doing" toggle, and holding
|
|
129
|
-
bash to a tighter boundary than the file tools would only confuse.
|
|
130
|
-
"""
|
|
131
|
-
if sandbox_disabled_by_env():
|
|
132
|
-
return False
|
|
133
|
-
if not path_restrictions_enabled():
|
|
134
|
-
return False
|
|
135
|
-
return True
|
|
124
|
+
"""Whether per-command OS sandboxing should be applied."""
|
|
125
|
+
return command_sandbox_required()
|
|
136
126
|
|
|
137
127
|
|
|
138
128
|
def _scratch_network_enabled() -> bool:
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""MCP tool for LLM-driven file-based task management.
|
|
2
|
+
|
|
3
|
+
One ``Task`` tool with an ``action`` (create | list | get | update | delete)
|
|
4
|
+
so the model can coordinate multi-step work via the active context's
|
|
5
|
+
persistent task store. It replaces the former TaskCreate / TaskList /
|
|
6
|
+
TaskUpdate / TaskDelete tools, which shared most of their parameters; the
|
|
7
|
+
host's composite tool manager rewrites calls to those legacy names.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Annotated, Any
|
|
13
|
+
|
|
14
|
+
from arcade_mcp_server import Context, tool
|
|
15
|
+
from typing_extensions import TypedDict
|
|
16
|
+
|
|
17
|
+
from cade_mcp_local._metadata import CRUD_META
|
|
18
|
+
from cade_mcp_local.errors import InvalidInputError
|
|
19
|
+
from cadecoder.tasks import TaskStatus, TaskStore
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TaskStatusFilter(str, Enum):
|
|
25
|
+
"""TaskStatus values plus an ANY sentinel for optional status params."""
|
|
26
|
+
|
|
27
|
+
ANY = ""
|
|
28
|
+
PENDING = "pending"
|
|
29
|
+
IN_PROGRESS = "in_progress"
|
|
30
|
+
COMPLETED = "completed"
|
|
31
|
+
FAILED = "failed"
|
|
32
|
+
BLOCKED = "blocked"
|
|
33
|
+
CANCELLED = "cancelled"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _status_enum(value: TaskStatusFilter | str) -> TaskStatus:
|
|
37
|
+
raw = value.value if isinstance(value, TaskStatusFilter) else str(value)
|
|
38
|
+
try:
|
|
39
|
+
return TaskStatus(raw)
|
|
40
|
+
except ValueError as e:
|
|
41
|
+
valid = ", ".join(s.value for s in TaskStatus)
|
|
42
|
+
raise InvalidInputError("status", f"must be one of {valid}") from e
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TaskAction(str, Enum):
|
|
46
|
+
"""Task operation to perform."""
|
|
47
|
+
|
|
48
|
+
CREATE = "create"
|
|
49
|
+
LIST = "list"
|
|
50
|
+
GET = "get"
|
|
51
|
+
UPDATE = "update"
|
|
52
|
+
DELETE = "delete"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TaskOutput(TypedDict, total=False):
|
|
56
|
+
task: dict[str, Any]
|
|
57
|
+
tasks: list[dict[str, Any]]
|
|
58
|
+
count: int
|
|
59
|
+
found: bool
|
|
60
|
+
deleted: bool
|
|
61
|
+
task_id: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@tool(
|
|
65
|
+
name="Task",
|
|
66
|
+
desc=(
|
|
67
|
+
"Durable task store for multi-step work; progress survives sessions and "
|
|
68
|
+
"is visible to the user. create | list | get | update | delete. "
|
|
69
|
+
"Setting status='completed' unblocks dependent tasks."
|
|
70
|
+
),
|
|
71
|
+
metadata=CRUD_META,
|
|
72
|
+
)
|
|
73
|
+
def task_tool(
|
|
74
|
+
context: Context,
|
|
75
|
+
action: Annotated[TaskAction, "Task operation."],
|
|
76
|
+
task_id: Annotated[str, "[get|update|delete] Task ID."] = "",
|
|
77
|
+
subject: Annotated[str, "[create] Imperative one-line summary, e.g. 'Fix auth race'."] = "",
|
|
78
|
+
description: Annotated[str, "[create] Optional longer explanation."] = "",
|
|
79
|
+
active_form: Annotated[
|
|
80
|
+
str, "[create] Present-continuous phrasing for spinners, e.g. 'Fixing auth race'."
|
|
81
|
+
] = "",
|
|
82
|
+
blocks: Annotated[
|
|
83
|
+
list[str], "[create] IDs of tasks that cannot start until this completes."
|
|
84
|
+
] = [], # noqa: B006
|
|
85
|
+
blocked_by: Annotated[
|
|
86
|
+
list[str], "[create] IDs of tasks that must complete before this can start."
|
|
87
|
+
] = [], # noqa: B006
|
|
88
|
+
status: Annotated[
|
|
89
|
+
TaskStatusFilter, "[list] Filter by status. [update] New status. ANY = none."
|
|
90
|
+
] = TaskStatusFilter.ANY,
|
|
91
|
+
owner: Annotated[str, "[list] Filter by owner. [update] New owner/agent label."] = "",
|
|
92
|
+
result: Annotated[str, "[update] Result summary for completed or failed tasks."] = "",
|
|
93
|
+
scope: Annotated[str, "Task scope (team/session). Empty = default."] = "",
|
|
94
|
+
) -> Annotated[TaskOutput, "Task operation result."]:
|
|
95
|
+
"""Dispatch a task-store operation."""
|
|
96
|
+
store = TaskStore(scope=scope or None)
|
|
97
|
+
if action == TaskAction.CREATE:
|
|
98
|
+
return _create(store, subject, description, active_form, blocks, blocked_by)
|
|
99
|
+
if action == TaskAction.LIST:
|
|
100
|
+
return _list(store, status, owner)
|
|
101
|
+
if action == TaskAction.GET:
|
|
102
|
+
task = store.get(_require_id(task_id))
|
|
103
|
+
if task is None:
|
|
104
|
+
return {"found": False, "task_id": task_id}
|
|
105
|
+
return {"found": True, "task": task.model_dump(mode="json")}
|
|
106
|
+
if action == TaskAction.UPDATE:
|
|
107
|
+
return _update(store, _require_id(task_id), status, owner, result)
|
|
108
|
+
if action == TaskAction.DELETE:
|
|
109
|
+
return {"deleted": store.delete(_require_id(task_id)), "task_id": task_id}
|
|
110
|
+
raise InvalidInputError("action", f"Unknown action: {action}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _require_id(task_id: str) -> str:
|
|
114
|
+
if not task_id.strip():
|
|
115
|
+
raise InvalidInputError("task_id", "required for this action")
|
|
116
|
+
return task_id
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _create(
|
|
120
|
+
store: TaskStore,
|
|
121
|
+
subject: str,
|
|
122
|
+
description: str,
|
|
123
|
+
active_form: str,
|
|
124
|
+
blocks: list[str],
|
|
125
|
+
blocked_by: list[str],
|
|
126
|
+
) -> TaskOutput:
|
|
127
|
+
if not subject.strip():
|
|
128
|
+
raise InvalidInputError("subject", "cannot be empty")
|
|
129
|
+
task = store.create(
|
|
130
|
+
subject=subject,
|
|
131
|
+
description=description,
|
|
132
|
+
active_form=active_form or None,
|
|
133
|
+
blocks=list(blocks or []),
|
|
134
|
+
blocked_by=list(blocked_by or []),
|
|
135
|
+
)
|
|
136
|
+
return {"task": task.model_dump(mode="json")}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _list(store: TaskStore, status: TaskStatusFilter | str, owner: str) -> TaskOutput:
|
|
140
|
+
status_enum = _status_enum(status) if status and status != TaskStatusFilter.ANY else None
|
|
141
|
+
tasks = store.list_all(status=status_enum, owner=owner or None)
|
|
142
|
+
return {"tasks": [t.model_dump(mode="json") for t in tasks], "count": len(tasks)}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _update(
|
|
146
|
+
store: TaskStore,
|
|
147
|
+
task_id: str,
|
|
148
|
+
status: TaskStatusFilter | str,
|
|
149
|
+
owner: str,
|
|
150
|
+
result: str,
|
|
151
|
+
) -> TaskOutput:
|
|
152
|
+
updates: dict[str, Any] = {}
|
|
153
|
+
if status and status != TaskStatusFilter.ANY:
|
|
154
|
+
updates["status"] = _status_enum(status)
|
|
155
|
+
if owner:
|
|
156
|
+
updates["owner"] = owner
|
|
157
|
+
if result:
|
|
158
|
+
updates["result"] = result
|
|
159
|
+
if not updates:
|
|
160
|
+
raise InvalidInputError("updates", "provide at least one of status/owner/result")
|
|
161
|
+
task = store.update(task_id, **updates)
|
|
162
|
+
if task is None:
|
|
163
|
+
return {"found": False, "task_id": task_id}
|
|
164
|
+
return {"found": True, "task": task.model_dump(mode="json")}
|
|
@@ -55,17 +55,22 @@ def resolve_path(
|
|
|
55
55
|
# always-granted scratch dir, and any runtime grants are all legal
|
|
56
56
|
# roots. With no grants file published this reduces to exactly the
|
|
57
57
|
# old single-root check.
|
|
58
|
-
from cade_mcp_local.grants import current_grant_set
|
|
58
|
+
from cade_mcp_local.grants import current_grant_set, grants_file_path
|
|
59
59
|
|
|
60
60
|
grant_set = current_grant_set()
|
|
61
61
|
if not grant_set.contains(resolved):
|
|
62
62
|
from cade_mcp_local.errors import PathSecurityError
|
|
63
63
|
|
|
64
|
+
# Runtime grants exist only where a grants file is published (the
|
|
65
|
+
# daemon); elsewhere Local_RequestAccess is not advertised at all.
|
|
66
|
+
if grants_file_path() is not None:
|
|
67
|
+
remedy = "ask via Local_RequestAccess or have the operator run `/grant`"
|
|
68
|
+
else:
|
|
69
|
+
remedy = "ask the user to start cade from a directory that contains it"
|
|
64
70
|
raise PathSecurityError(
|
|
65
71
|
str(path_str),
|
|
66
72
|
f"path resolves outside the granted filesystem scopes "
|
|
67
|
-
f"({grant_set.describe()});
|
|
68
|
-
f"have the operator run `/grant`",
|
|
73
|
+
f"({grant_set.describe()}); {remedy}",
|
|
69
74
|
)
|
|
70
75
|
|
|
71
76
|
return resolved
|
|
@@ -131,7 +131,7 @@ DATA_DIRECTORY: {runtime.context_data_dir}
|
|
|
131
131
|
└─ Database: {db_path} — do not read or modify directly
|
|
132
132
|
└─ Logs: {log_path} — diagnostic only; do not alter
|
|
133
133
|
└─ Tool-managed writable areas (use only Local_* tools, not ad-hoc file edits):
|
|
134
|
-
tasks/ —
|
|
134
|
+
tasks/ — Local_Task
|
|
135
135
|
|
|
136
136
|
CONFIG_DIRECTORY: {runtime.paths.config_dir}
|
|
137
137
|
└─ Contains Cade settings. Secrets are referenced, not stored inline by default.
|
|
@@ -201,7 +201,7 @@ Signals: "[TASK_COMPLETE]" | "[CONTINUE]" | "[NEED_USER_INPUT]"
|
|
|
201
201
|
- Match existing code patterns when editing.
|
|
202
202
|
|
|
203
203
|
=== LOCAL COORDINATION ===
|
|
204
|
-
- Durable work across turns:
|
|
204
|
+
- Durable work across turns: Local_Task (action=create|list|get|update|delete).
|
|
205
205
|
- User-visible nudge on the next tool boundary: Local_PushNotification.
|
|
206
206
|
|
|
207
207
|
Tools:
|