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.
Files changed (227) hide show
  1. {cade_cli-0.20.7 → cade_cli-0.21.0}/PKG-INFO +1 -1
  2. {cade_cli-0.20.7 → cade_cli-0.21.0}/pyproject.toml +1 -1
  3. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/_metadata.py +14 -0
  4. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/sandbox.py +25 -0
  5. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/server.py +12 -22
  6. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/__init__.py +4 -14
  7. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/agents.py +65 -60
  8. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/shell.py +3 -13
  9. cade_cli-0.21.0/src/cade_mcp_local/tools/tasks.py +164 -0
  10. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/utils.py +8 -3
  11. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ai/prompts.py +2 -2
  12. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/agents.py +18 -2
  13. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/tools.py +80 -16
  14. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/agent.py +3 -1
  15. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/spawn.py +3 -3
  16. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/allowlist.py +52 -8
  17. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/config.py +4 -1
  18. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/message_pump.py +1 -4
  19. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/orchestrator.py +24 -0
  20. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/serve/serve.toml.example +3 -0
  21. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/composite.py +313 -27
  22. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/mcp.py +1 -1
  23. cade_cli-0.21.0/src/cadecoder/tools/manager/surface.py +353 -0
  24. cade_cli-0.21.0/src/cadecoder/tools/merged.py +60 -0
  25. cade_cli-0.21.0/src/cadecoder/tools/profiles.py +74 -0
  26. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/session.py +5 -3
  27. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/session.py +15 -0
  28. cade_cli-0.20.7/src/cade_mcp_local/tools/tasks.py +0 -167
  29. {cade_cli-0.20.7 → cade_cli-0.21.0}/.gitignore +0 -0
  30. {cade_cli-0.20.7 → cade_cli-0.21.0}/LICENSE +0 -0
  31. {cade_cli-0.20.7 → cade_cli-0.21.0}/README.md +0 -0
  32. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/__init__.py +0 -0
  33. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/__main__.py +0 -0
  34. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/config.py +0 -0
  35. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/errors.py +0 -0
  36. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/grants.py +0 -0
  37. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/resources.py +0 -0
  38. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  39. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/access.py +0 -0
  40. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/filesystem.py +0 -0
  41. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/git.py +0 -0
  42. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/notifications.py +0 -0
  43. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/questions.py +0 -0
  44. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/results.py +0 -0
  45. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/schemas.py +0 -0
  46. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/search.py +0 -0
  47. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  48. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/tools/web_search.py +0 -0
  49. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/__init__.py +0 -0
  50. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/arcade_client.py +0 -0
  51. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/cache.py +0 -0
  52. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/extract.py +0 -0
  53. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/markdown.py +0 -0
  54. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/preapproved.py +0 -0
  55. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/prompts.py +0 -0
  56. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/scrape.py +0 -0
  57. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/__init__.py +0 -0
  58. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/arcade.py +0 -0
  59. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/base.py +0 -0
  60. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cade_mcp_local/web/search/http.py +0 -0
  61. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/__init__.py +0 -0
  62. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ai/__init__.py +0 -0
  63. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/__init__.py +0 -0
  64. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/app.py +0 -0
  65. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/auth.py +0 -0
  66. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/__init__.py +0 -0
  67. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/account.py +0 -0
  68. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/agent_group.py +0 -0
  69. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/auth.py +0 -0
  70. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/chat.py +0 -0
  71. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/cron.py +0 -0
  72. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/daemon.py +0 -0
  73. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/grant.py +0 -0
  74. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/hooks.py +0 -0
  75. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/mcp.py +0 -0
  76. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/mem.py +0 -0
  77. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/tasks.py +0 -0
  78. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/commands/thread.py +0 -0
  79. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/mcp_hosts.py +0 -0
  80. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/onboarding.py +0 -0
  81. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/cli/ui.py +0 -0
  82. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/__init__.py +0 -0
  83. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/binary.py +0 -0
  84. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/comms.py +0 -0
  85. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/config.py +0 -0
  86. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/constants.py +0 -0
  87. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/errors.py +0 -0
  88. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/frozen.py +0 -0
  89. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/git.py +0 -0
  90. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/inbox.py +0 -0
  91. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/logging.py +0 -0
  92. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/memory.py +0 -0
  93. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/model_specs.py +0 -0
  94. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/names.py +0 -0
  95. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/paths.py +0 -0
  96. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/runtime.py +0 -0
  97. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/secrets.py +0 -0
  98. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/text.py +0 -0
  99. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/core/types.py +0 -0
  100. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/__init__.py +0 -0
  101. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/__init__.py +0 -0
  102. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/base.py +0 -0
  103. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/factory.py +0 -0
  104. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/imessage.py +0 -0
  105. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/registry.py +0 -0
  106. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/stub.py +0 -0
  107. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/adapters/telegram.py +0 -0
  108. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/attach.py +0 -0
  109. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/chunker.py +0 -0
  110. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/client.py +0 -0
  111. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/commands.py +0 -0
  112. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/control.py +0 -0
  113. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/cron.py +0 -0
  114. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/elicitation.py +0 -0
  115. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/ensure.py +0 -0
  116. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/format.py +0 -0
  117. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/__init__.py +0 -0
  118. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/app.py +0 -0
  119. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/auth.py +0 -0
  120. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/constants.py +0 -0
  121. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/elicitation.py +0 -0
  122. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/exposure.py +0 -0
  123. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/host_agent.py +0 -0
  124. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/limits.py +0 -0
  125. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/middleware.py +0 -0
  126. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/principal.py +0 -0
  127. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/resources.py +0 -0
  128. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/subscriptions.py +0 -0
  129. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/gateway/turn_bridge.py +0 -0
  130. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/grants.py +0 -0
  131. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/inbox.py +0 -0
  132. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/inbox_cron.py +0 -0
  133. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/install.py +0 -0
  134. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/lock.py +0 -0
  135. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/mesh.py +0 -0
  136. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/observability.py +0 -0
  137. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/owner.py +0 -0
  138. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/progress_sink.py +0 -0
  139. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/registry.py +0 -0
  140. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/router.py +0 -0
  141. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/runner.py +0 -0
  142. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/runner_main.py +0 -0
  143. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/secrets.py +0 -0
  144. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/secrets_broker.py +0 -0
  145. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/server.py +0 -0
  146. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/services.py +0 -0
  147. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/sinks.py +0 -0
  148. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/supervisor.py +0 -0
  149. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/tokens.py +0 -0
  150. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/traffic.py +0 -0
  151. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/turns.py +0 -0
  152. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/daemon/wake.py +0 -0
  153. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/__init__.py +0 -0
  154. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/context_window.py +0 -0
  155. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  156. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/parallel.py +0 -0
  157. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/tool_result_store.py +0 -0
  158. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  159. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/__init__.py +0 -0
  160. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/config.py +0 -0
  161. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/engine.py +0 -0
  162. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/__init__.py +0 -0
  163. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/callback.py +0 -0
  164. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/command.py +0 -0
  165. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/http.py +0 -0
  166. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/prompt.py +0 -0
  167. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  168. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/matchers.py +0 -0
  169. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/registry.py +0 -0
  170. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/hooks/types.py +0 -0
  171. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/__init__.py +0 -0
  172. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.claude-plugin/marketplace.json +0 -0
  173. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.claude-plugin/plugin.json +0 -0
  174. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/marketplace.json +0 -0
  175. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/.cursor-plugin/plugin.json +0 -0
  176. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/agents/operator.md +0 -0
  177. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/commands/ask.md +0 -0
  178. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/commands/memory.md +0 -0
  179. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/commands/tasks.md +0 -0
  180. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/mcp.json +0 -0
  181. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/rules/gateway.mdc +0 -0
  182. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/skills/ask/SKILL.md +0 -0
  183. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/skills/memory/SKILL.md +0 -0
  184. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/assets/skills/tasks/SKILL.md +0 -0
  185. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/doctor.py +0 -0
  186. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/install.py +0 -0
  187. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/paths.py +0 -0
  188. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/host_plugin/state.py +0 -0
  189. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/__init__.py +0 -0
  190. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/anthropic.py +0 -0
  191. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/base.py +0 -0
  192. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/http_client.py +0 -0
  193. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/ollama.py +0 -0
  194. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/providers/openai.py +0 -0
  195. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/storage/__init__.py +0 -0
  196. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/storage/threads.py +0 -0
  197. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/__init__.py +0 -0
  198. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/scheduler.py +0 -0
  199. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/store.py +0 -0
  200. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tasks/types.py +0 -0
  201. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/telemetry/__init__.py +0 -0
  202. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/telemetry/otel.py +0 -0
  203. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/login_failed.html +0 -0
  204. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/login_success.html +0 -0
  205. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  206. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  207. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/templates/styles.css +0 -0
  208. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/__init__.py +0 -0
  209. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/__init__.py +0 -0
  210. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  211. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/base.py +0 -0
  212. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/manager/config.py +0 -0
  213. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/__init__.py +0 -0
  214. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/discovered.py +0 -0
  215. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/scoring.py +0 -0
  216. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/tools/search/service.py +0 -0
  217. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/__init__.py +0 -0
  218. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/completion.py +0 -0
  219. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/display.py +0 -0
  220. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/elicitation.py +0 -0
  221. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/input.py +0 -0
  222. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/ui/slash.py +0 -0
  223. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/__init__.py +0 -0
  224. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/audio.py +0 -0
  225. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/cleanup.py +0 -0
  226. {cade_cli-0.20.7 → cade_cli-0.21.0}/src/cadecoder/voice/stt.py +0 -0
  227. {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.20.7
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.20.7"
3
+ version = "0.21.0"
4
4
  description = "Cade - The CLI Agent from Arcade.dev (install: curl -fsSL https://arcadeagent.dev/install | sh)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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
- agent_list_tool,
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
- task_create_tool,
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 (TaskCreate, TaskList,
56
- TaskUpdate, TaskDelete) — the host registers them as Local_* names
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
- - AgentSend / AgentList to coordinate with other agents the user has linked to this
60
- one (ACL-gated; delivered to the target agent's inbox)
61
- - AgentSpawn to delegate a self-contained sub-task to a fresh child agent and get
62
- back just its result (requires this agent to allow spawning)
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(task_create_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; delivers to another agent's inbox)
144
- app.add_tool(agent_send_tool)
145
- app.add_tool(agent_list_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 agent_list_tool, agent_send_tool, agent_spawn_tool
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
- "task_create_tool",
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
- "agent_send_tool",
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 tools for cross-agent coordination.
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 CREATE_META, READ_META
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 AgentSendOutput(TypedDict):
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
- class AgentSpawnOutput(TypedDict):
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="AgentSend",
80
+ name="Agent",
69
81
  desc=(
70
- "Send a message to another agent's inbox. Only works for agents a user "
71
- "has linked to this one (via `cade agent link` or a shared channel); otherwise "
72
- "the send is refused. The message is delivered at the target agent's next "
73
- "turn. Use AgentList first to see who you can reach."
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=CREATE_META,
88
+ metadata=READ_OR_CREATE_META,
76
89
  )
77
- async def agent_send_tool(
90
+ async def agent_tool(
78
91
  context: Context,
79
- target: Annotated[str, "Target agent name, id, or unique id prefix."],
80
- message: Annotated[str, "Message to deliver to the target agent."],
81
- ) -> Annotated[AgentSendOutput, "Delivery result."]:
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
- @tool(
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()}); ask via Local_RequestAccess or "
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/ — Local_TaskCreate, Local_TaskList, Local_TaskUpdate, Local_TaskDelete
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: Local_TaskCreate, Local_TaskList (filter by id for single fetch), Local_TaskUpdate, Local_TaskDelete.
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: