python-slack-agents 0.9.0__tar.gz → 0.9.1__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 (170) hide show
  1. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/CHANGELOG.md +21 -0
  2. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/PKG-INFO +1 -1
  3. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/a2a-agent/config.yaml +2 -3
  4. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/a2a.md +66 -18
  5. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/llms-full.txt +66 -18
  6. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/pyproject.toml +1 -1
  7. python_slack_agents-0.9.1/src/slack_agents/a2a/agent.py +461 -0
  8. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/client.py +46 -3
  9. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/delivery.py +35 -3
  10. python_slack_agents-0.9.1/src/slack_agents/a2a/oauth.py +75 -0
  11. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/config.py +20 -3
  12. python_slack_agents-0.9.1/src/slack_agents/oauth/discovery.py +149 -0
  13. python_slack_agents-0.9.1/src/slack_agents/oauth/errors.py +250 -0
  14. python_slack_agents-0.9.1/src/slack_agents/oauth/flow.py +370 -0
  15. python_slack_agents-0.9.1/src/slack_agents/oauth/scopes.py +53 -0
  16. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/agent.py +15 -3
  17. python_slack_agents-0.9.1/src/slack_agents/tools/mcp_http_oauth.py +394 -0
  18. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_agent_async.py +1 -2
  19. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_agent_sync.py +12 -4
  20. python_slack_agents-0.9.1/tests/a2a/test_client_auth.py +63 -0
  21. python_slack_agents-0.9.1/tests/a2a/test_delivery_oauth.py +95 -0
  22. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_end_to_end.py +1 -1
  23. python_slack_agents-0.9.1/tests/a2a/test_oauth.py +372 -0
  24. python_slack_agents-0.9.1/tests/a2a/test_oauth_adapter.py +71 -0
  25. python_slack_agents-0.9.1/tests/a2a/test_push_preferred.py +58 -0
  26. python_slack_agents-0.9.1/tests/test_ingress_startup.py +106 -0
  27. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_mcp_http_oauth.py +25 -24
  28. python_slack_agents-0.9.1/tests/test_oauth_discovery.py +98 -0
  29. python_slack_agents-0.9.1/tests/test_oauth_errors.py +47 -0
  30. python_slack_agents-0.9.1/tests/test_oauth_flow.py +109 -0
  31. python_slack_agents-0.9.1/tests/test_oauth_flow_noninteractive.py +46 -0
  32. python_slack_agents-0.9.1/tests/test_oauth_flow_required_scopes.py +68 -0
  33. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_integration.py +1 -1
  34. python_slack_agents-0.9.1/tests/test_oauth_scopes.py +39 -0
  35. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_validation.py +52 -1
  36. python_slack_agents-0.9.0/src/slack_agents/a2a/agent.py +0 -228
  37. python_slack_agents-0.9.0/src/slack_agents/tools/mcp_http_oauth.py +0 -1098
  38. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.dockerignore +0 -0
  39. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.env.example +0 -0
  40. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.github/workflows/ci.yml +0 -0
  41. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.github/workflows/publish.yml +0 -0
  42. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.gitignore +0 -0
  43. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.pre-commit-config.yaml +0 -0
  44. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/AGENTS.md +0 -0
  45. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/CODE_OF_CONDUCT.md +0 -0
  46. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/CONTRIBUTING.md +0 -0
  47. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/LICENSE +0 -0
  48. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/README.md +0 -0
  49. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/SECURITY.md +0 -0
  50. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/README.md +0 -0
  51. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/a2a-agent/system_prompt.txt +0 -0
  52. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/docs-assistant/config.yaml +0 -0
  53. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/docs-assistant/system_prompt.txt +0 -0
  54. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/hello-world/config.yaml +0 -0
  55. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/hello-world/system_prompt.txt +0 -0
  56. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/kitchen-sink/config.yaml +0 -0
  57. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/kitchen-sink/system_prompt.txt +0 -0
  58. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/access-control.md +0 -0
  59. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/agents.md +0 -0
  60. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/canvas.md +0 -0
  61. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/cli.md +0 -0
  62. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/deployment.md +0 -0
  63. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/llm.md +0 -0
  64. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/media/demo.gif +0 -0
  65. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/oauth.md +0 -0
  66. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/observability.md +0 -0
  67. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/private-repo.md +0 -0
  68. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/setup.md +0 -0
  69. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/slack-app-manifest.json +0 -0
  70. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/storage.md +0 -0
  71. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/tools.md +0 -0
  72. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/user-context.md +0 -0
  73. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/llms.txt +0 -0
  74. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/Dockerfile +0 -0
  75. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/__init__.py +0 -0
  76. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/__init__.py +0 -0
  77. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/proxy.py +0 -0
  78. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/push.py +0 -0
  79. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/__init__.py +0 -0
  80. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/allow_all.py +0 -0
  81. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/allow_list.py +0 -0
  82. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/base.py +0 -0
  83. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/agent_loop.py +0 -0
  84. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/__init__.py +0 -0
  85. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/build_docker.py +0 -0
  86. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_conversations.py +0 -0
  87. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_conversations_html.py +0 -0
  88. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_usage.py +0 -0
  89. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_usage_csv.py +0 -0
  90. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/healthcheck.py +0 -0
  91. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/init.py +0 -0
  92. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/run.py +0 -0
  93. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/conversations.py +0 -0
  94. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/files.py +0 -0
  95. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/__init__.py +0 -0
  96. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/anthropic.py +0 -0
  97. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/base.py +0 -0
  98. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/openai.py +0 -0
  99. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/main.py +0 -0
  100. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/__init__.py +0 -0
  101. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/crypto.py +0 -0
  102. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/prompts.py +0 -0
  103. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/server.py +0 -0
  104. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/state.py +0 -0
  105. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/storage.py +0 -0
  106. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/observability.py +0 -0
  107. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/py.typed +0 -0
  108. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/scripts/__init__.py +0 -0
  109. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/scripts/download_fonts.py +0 -0
  110. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/scripts/generate_llms_full.py +0 -0
  111. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/__init__.py +0 -0
  112. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/actions.py +0 -0
  113. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/canvas_auth.py +0 -0
  114. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/canvases.py +0 -0
  115. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/files.py +0 -0
  116. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/format.py +0 -0
  117. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/streaming.py +0 -0
  118. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/streaming_formatter.py +0 -0
  119. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/tool_blocks.py +0 -0
  120. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/__init__.py +0 -0
  121. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/base.py +0 -0
  122. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/postgres.py +0 -0
  123. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/postgres.sql +0 -0
  124. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/sqlite.py +0 -0
  125. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/sqlite.sql +0 -0
  126. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/__init__.py +0 -0
  127. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/base.py +0 -0
  128. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/canvas.py +0 -0
  129. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/canvas_importer.py +0 -0
  130. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/file_exporter.py +0 -0
  131. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/file_importer.py +0 -0
  132. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/mcp_http.py +0 -0
  133. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/user_context.py +0 -0
  134. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/__init__.py +0 -0
  135. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/__init__.py +0 -0
  136. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_client.py +0 -0
  137. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_client_files.py +0 -0
  138. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_delivery.py +0 -0
  139. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_framework_delivery.py +0 -0
  140. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_integration.py +0 -0
  141. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_proxy.py +0 -0
  142. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_push.py +0 -0
  143. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_access.py +0 -0
  144. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_agent_loop.py +0 -0
  145. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_canvas_auth.py +0 -0
  146. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_canvas_importer.py +0 -0
  147. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_cli.py +0 -0
  148. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_config.py +0 -0
  149. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_conversations.py +0 -0
  150. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_cost.py +0 -0
  151. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_export_documents.py +0 -0
  152. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_export_usage.py +0 -0
  153. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_file_extractors.py +0 -0
  154. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_format.py +0 -0
  155. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_init.py +0 -0
  156. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_llm_error_classification.py +0 -0
  157. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_llm_factory.py +0 -0
  158. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_load_plugin.py +0 -0
  159. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_mcp_client.py +0 -0
  160. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_crypto.py +0 -0
  161. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_prompts.py +0 -0
  162. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_server.py +0 -0
  163. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_state.py +0 -0
  164. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_storage.py +0 -0
  165. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_openai_convert.py +0 -0
  166. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_overlay_integration.py +0 -0
  167. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_storage_oauth.py +0 -0
  168. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_tool_blocks.py +0 -0
  169. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_tool_errors.py +0 -0
  170. {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/uv.lock +0 -0
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.1] - 2026-06-09
10
+
11
+ ### Added
12
+
13
+ - **A2A per-user OAuth** (`auth: { type: oauth2 }`). Remote A2A agents can now require per-Slack-user OAuth 2.1: each user authenticates separately and the agent calls on their behalf. Auth metadata is discovered from the Agent Card's `securitySchemes.oauth2` (no `client_id`/`scopes` in YAML); the flow uses Dynamic Client Registration + authorization-code + PKCE + refresh, the same ephemeral "Authenticate" button, the shared `/oauth/*` ingress, and the same `PUBLIC_URL` / `OAUTH_SECRET_KEY` env contract as `mcp_http_oauth`. Long-running tasks polled in the background use the user's stored token non-interactively (silent refresh; a "session expired — please re-ask" notice if it can't be refreshed). When push is registered for a task, the token-dependent poller is skipped (push delivers regardless of later token state). See `docs/a2a.md`.
14
+ - **A2A API-key auth** — `auth: { type: apiKey, name, value }` (sends the raw value as the named header, matching the Agent Card's `apiKey` scheme), alongside the existing `bearer` / `header` / `none` forms.
15
+ - **OAuth user-info logging** (MCP and A2A). On first authentication the user's OIDC identity (`sub` / `preferred_username` / `name` / `email`) is fetched from the userinfo endpoint and logged. The `profile` scope is now requested and registered alongside `openid` / `offline_access`.
16
+
17
+ ### Changed
18
+
19
+ - The provider-agnostic OAuth flow was extracted from `tools/mcp_http_oauth.py` into the `oauth/` package (`scopes`, `errors`, `discovery`, `flow`), parameterized by a discovery strategy — RFC 9728 PRM for MCP, Agent Card for A2A. No behavior change for MCP-OAuth.
20
+ - DCR client registration now requests the OIDC baseline (`openid`, `offline_access`, `profile`) explicitly, so strict authorization-server registration policies (e.g. Keycloak's "Allowed Client Scopes") grant the client those scopes rather than rejecting the later authorize with `invalid_scope`.
21
+ - **BREAKING (A2A):** removed the `name` field from `slack_agents.a2a.agent`. The single tool is now named after the provider's key under `tools:` (slugified to satisfy LLM tool-name rules); rename the key to rename the tool. Keys are unique within `tools:`, so two agents never collide.
22
+ - **BREAKING (A2A):** removed `allowed_functions` from `slack_agents.a2a.agent` — an A2A agent exposes exactly one opaque tool, so there was nothing to filter. A stray `allowed_functions` on an A2A config is ignored at load with a warning.
23
+
24
+ ### Fixed
25
+
26
+ - The in-process OAuth ingress crashed at startup with `TypeError: argument should be a bytes-like object … not 'bool'` whenever `OAUTH_SECRET_KEY` was set — `base64.b64decode(key, True)` passed `True` as the positional `altchars` argument instead of the keyword `validate=`. This was a latent bug in the shared ingress that affected MCP-OAuth and A2A-OAuth agents alike; it had no test coverage and is now exercised by `tests/test_ingress_startup.py`.
27
+ - A2A `call_tool` now maps OAuth-specific failures to actionable results — a user-level authorization denial becomes a permission-denied error naming the missing scope, and an IdP `redirect_uri` rejection clears the stale client registration so the next attempt self-heals — instead of a generic "contact support".
28
+ - The per-user A2A httpx client is closed if Agent Card resolution fails after construction, avoiding a connection-pool leak on the background poller's out-of-band re-auth path.
29
+
9
30
  ## [0.9.0] - 2026-06-07
10
31
 
11
32
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-slack-agents
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: A Python framework for deploying AI agents as Slack bots
5
5
  Project-URL: Homepage, https://github.com/CompareNetworks/python-slack-agents
6
6
  Project-URL: Repository, https://github.com/CompareNetworks/python-slack-agents
@@ -52,13 +52,12 @@ storage:
52
52
  path: ":memory:"
53
53
 
54
54
  tools:
55
- # The remote A2A agent, exposed to the LLM as one free-text tool named "mya2a".
55
+ # The remote A2A agent, exposed to the LLM as one free-text tool named after this
56
+ # key ("mya2a"). Rename the key to rename the tool.
56
57
  mya2a:
57
58
  type: slack_agents.a2a.agent
58
- name: "mya2a"
59
59
  url: "http://127.0.0.1:9999"
60
60
  auth: {}
61
- allowed_functions: [".*"]
62
61
 
63
62
  # Lets the framework ACCEPT Slack file uploads; the a2a tool then forwards the
64
63
  # raw bytes to the agent. (With Option A the LLM also sees the extracted text.)
@@ -44,7 +44,6 @@ tools:
44
44
  auth: { type: bearer, token: "{RESEARCH_A2A_TOKEN}" }
45
45
  poll_interval: 5
46
46
  max_task_lifetime: 3600
47
- allowed_functions: [".*"]
48
47
  ```
49
48
 
50
49
  ### Option B — dumb frontend
@@ -59,7 +58,6 @@ tools:
59
58
  type: slack_agents.a2a.agent
60
59
  url: "https://remote-agent.example.com"
61
60
  auth: { type: bearer, token: "{A2A_API_KEY}" }
62
- allowed_functions: [".*"]
63
61
  ```
64
62
 
65
63
  The system prompt is ignored in Option B; it is used normally in Option A.
@@ -67,27 +65,27 @@ The system prompt is ignored in Option B; it is used normally in Option A.
67
65
  ## `a2a.agent` — tool provider
68
66
 
69
67
  `slack_agents.a2a.agent` is a `BaseToolProvider`. At startup it fetches the
70
- remote agent's Agent Card, builds a single tool definition from the card's
71
- name and description, and registers it like any other tool.
68
+ remote agent's Agent Card and registers a single tool named after the
69
+ provider's key under `tools:` using the card's description.
72
70
 
73
71
  | Field | Type | Default | Description |
74
72
  |-------|------|---------|-------------|
75
73
  | `url` | `str` | required | Base URL of the remote agent. The framework tries `<url>/.well-known/agent-card.json` automatically. |
76
74
  | `auth` | `dict` | none | Auth credentials. See [Auth](#auth) below. |
77
- | `name` | `str` | — | Override the tool name derived from the Agent Card. Use this when the card's name would produce an inconvenient slug, or when two agents would otherwise collide. |
78
- | `allowed_functions` | `list[str]` | required | Regex filters. Because every A2A provider exposes exactly one tool, `[".*"]` is almost always correct. |
79
75
  | `timeout` | `float` | `300` | HTTP timeout in seconds for individual A2A requests. |
80
76
  | `poll_interval` | `float` | `5` | Seconds between polling attempts for long-running tasks. |
81
77
  | `max_task_lifetime` | `float` | `3600` | Maximum seconds to poll before abandoning a task and delivering a timeout message. |
82
78
 
83
79
  **One tool per agent.** A2A is a single opaque channel — it has no mechanism to
84
80
  advertise a menu of typed tools the way MCP does. Each `a2a.agent` provider
85
- exposes exactly one free-text tool named after the agent (or overridden by
86
- `name`):
81
+ exposes exactly one free-text tool, named after its key under `tools:`
82
+ (slugified to satisfy the LLM tool-name rules — letters, digits, `_`, `-`).
83
+ Rename the key to rename the tool; the key is also unique within `tools:`, so
84
+ two agents never collide:
87
85
 
88
86
  ```json
89
87
  {
90
- "name": "<name>",
88
+ "name": "<tools: key, slugified>",
91
89
  "description": "<from Agent Card>",
92
90
  "input_schema": {
93
91
  "type": "object",
@@ -125,7 +123,6 @@ tools:
125
123
  mya2a:
126
124
  type: slack_agents.a2a.agent
127
125
  url: "https://remote-agent.example.com"
128
- allowed_functions: [".*"]
129
126
  import-files:
130
127
  type: slack_agents.tools.file_importer
131
128
  allowed_functions: [".*"]
@@ -144,8 +141,10 @@ calling an LLM it routes directly to the configured A2A tool.
144
141
 
145
142
  ## Auth
146
143
 
147
- Phase 1 supports static credentials only, resolved from environment variables
148
- at startup via the standard `{ENV_VAR}` interpolation.
144
+ ### Static / API-key auth (service-level)
145
+
146
+ Credentials are resolved from environment variables at startup via the standard
147
+ `{ENV_VAR}` interpolation. These are shared across all users.
149
148
 
150
149
  ```yaml
151
150
  # Bearer token — Authorization: Bearer <token>
@@ -154,12 +153,46 @@ auth: { type: bearer, token: "{A2A_API_KEY}" }
154
153
  # Arbitrary header
155
154
  auth: { type: header, name: "X-API-Key", value: "{A2A_API_KEY}" }
156
155
 
156
+ # API-key scheme — sends the raw value as the named header
157
+ # (matches an Agent Card that advertises apiKey security)
158
+ auth: { type: apiKey, name: "Authorization", value: "{A2A_API_KEY}" }
159
+
157
160
  # No auth (default when omitted)
158
161
  auth: { type: none }
159
162
  ```
160
163
 
161
- Per-user OAuth auth (reusing the `oauth/` package) is planned for a future
162
- release.
164
+ ### Per-user OAuth (`auth: { type: oauth2 }`)
165
+
166
+ Each Slack user authenticates separately to the remote agent. The framework
167
+ uses that user's access token on subsequent calls, exactly as it does for
168
+ OAuth-protected MCP servers (see [docs/oauth.md](oauth.md)).
169
+
170
+ ```yaml
171
+ tools:
172
+ research-agent:
173
+ type: slack_agents.a2a.agent
174
+ url: "https://research.example.com"
175
+ auth: { type: oauth2 }
176
+ ```
177
+
178
+ There is intentionally no `client_id`/`scopes` field. The provider discovers
179
+ OAuth metadata from the remote agent's **Agent Card** (`securitySchemes.oauth2`
180
+ → `oauth2MetadataUrl`), performs Dynamic Client Registration, and uses the
181
+ auth-code + PKCE flow. This means a server whose RFC 9728 protected-resource-metadata
182
+ endpoint is missing or internal still works as long as its Agent Card advertises
183
+ the oauth2 scheme and `oauth2MetadataUrl`.
184
+
185
+ **User experience.** The first time a user invokes an OAuth-protected A2A agent,
186
+ the framework posts an ephemeral "Authenticate" button in Slack. Clicking it
187
+ opens the remote agent's auth server in the user's browser. After consent the
188
+ browser redirects to the shared callback URL and the original request is
189
+ completed automatically.
190
+
191
+ **Required environment variables.** Per-user OAuth needs the shared ingress
192
+ (same as MCP OAuth) — configure `PUBLIC_URL` and `OAUTH_SECRET_KEY` exactly
193
+ as described in [docs/oauth.md](oauth.md#required-environment-variables). These
194
+ are validated at startup; the agent refuses to start if they are missing or
195
+ malformed.
163
196
 
164
197
  ## Long-running tasks
165
198
 
@@ -200,6 +233,25 @@ needed. The Socket Mode deploy-anywhere property is preserved.
200
233
  backend. If the agent process restarts while tasks are in flight, they are
201
234
  re-discovered at startup and pollers are resumed automatically.
202
235
 
236
+ **OAuth and async delivery.** The two async paths behave differently with
237
+ respect to the user's token, and the framework is **push-preferred**:
238
+
239
+ - **Push (webhook).** Delivery is *inbound* — the agent POSTs updates to the
240
+ ingress, which relays them to Slack without ever calling the agent back. No
241
+ token is involved at delivery time, so a task started while the user was
242
+ authenticated **still delivers even if their OAuth token later expires or is
243
+ revoked.** When a push webhook is registered for a task, the framework does
244
+ **not** also run the poller for it (avoiding redundant — potentially double —
245
+ delivery).
246
+ - **Polling (fallback, no push).** Only used when push is not registered (the
247
+ agent doesn't support it, or no `PUBLIC_URL`). The poller calls `tasks/get`
248
+ *outbound*, which requires the user's token: it builds a fresh per-user client
249
+ from the stored token and refreshes silently — no Slack interaction happens
250
+ out-of-band. If the token cannot be refreshed (revoked, or the refresh
251
+ expired), the bot posts "Your session for this task has expired — please ask
252
+ again and re-authenticate when prompted." to the thread, instead of prompting
253
+ out-of-band where there is no safe way to do so.
254
+
203
255
  ## What a Slack user sees
204
256
 
205
257
  **For a fast response (synchronous path):**
@@ -272,7 +324,6 @@ tools:
272
324
  type: slack_agents.a2a.agent
273
325
  url: "https://remote-agent.example.com"
274
326
  push_notifications: true
275
- allowed_functions: [".*"]
276
327
  ```
277
328
 
278
329
  **Ingress.** Push needs the in-process HTTP listener (shared with OAuth) and a
@@ -312,9 +363,6 @@ The following are not yet implemented. They are planned for future releases.
312
363
  path (a long-running task delivered out-of-band carries its text, not files).
313
364
  - **Atomic responses.** Responses are collected in full before being posted.
314
365
  Token-by-token streaming to Slack is not supported yet.
315
- - **Static auth only.** Auth credentials are fixed at startup from environment
316
- variables. Per-Slack-user OAuth (each user authenticates separately to the
317
- remote agent) is not yet supported.
318
366
  - **No A2A server mode.** This framework cannot be addressed as an A2A agent
319
367
  by other agents. It is a client only.
320
368
 
@@ -1700,7 +1700,6 @@ tools:
1700
1700
  auth: { type: bearer, token: "{RESEARCH_A2A_TOKEN}" }
1701
1701
  poll_interval: 5
1702
1702
  max_task_lifetime: 3600
1703
- allowed_functions: [".*"]
1704
1703
  ```
1705
1704
 
1706
1705
  ### Option B — dumb frontend
@@ -1715,7 +1714,6 @@ tools:
1715
1714
  type: slack_agents.a2a.agent
1716
1715
  url: "https://remote-agent.example.com"
1717
1716
  auth: { type: bearer, token: "{A2A_API_KEY}" }
1718
- allowed_functions: [".*"]
1719
1717
  ```
1720
1718
 
1721
1719
  The system prompt is ignored in Option B; it is used normally in Option A.
@@ -1723,27 +1721,27 @@ The system prompt is ignored in Option B; it is used normally in Option A.
1723
1721
  ## `a2a.agent` — tool provider
1724
1722
 
1725
1723
  `slack_agents.a2a.agent` is a `BaseToolProvider`. At startup it fetches the
1726
- remote agent's Agent Card, builds a single tool definition from the card's
1727
- name and description, and registers it like any other tool.
1724
+ remote agent's Agent Card and registers a single tool named after the
1725
+ provider's key under `tools:` using the card's description.
1728
1726
 
1729
1727
  | Field | Type | Default | Description |
1730
1728
  |-------|------|---------|-------------|
1731
1729
  | `url` | `str` | required | Base URL of the remote agent. The framework tries `<url>/.well-known/agent-card.json` automatically. |
1732
1730
  | `auth` | `dict` | none | Auth credentials. See [Auth](#auth) below. |
1733
- | `name` | `str` | — | Override the tool name derived from the Agent Card. Use this when the card's name would produce an inconvenient slug, or when two agents would otherwise collide. |
1734
- | `allowed_functions` | `list[str]` | required | Regex filters. Because every A2A provider exposes exactly one tool, `[".*"]` is almost always correct. |
1735
1731
  | `timeout` | `float` | `300` | HTTP timeout in seconds for individual A2A requests. |
1736
1732
  | `poll_interval` | `float` | `5` | Seconds between polling attempts for long-running tasks. |
1737
1733
  | `max_task_lifetime` | `float` | `3600` | Maximum seconds to poll before abandoning a task and delivering a timeout message. |
1738
1734
 
1739
1735
  **One tool per agent.** A2A is a single opaque channel — it has no mechanism to
1740
1736
  advertise a menu of typed tools the way MCP does. Each `a2a.agent` provider
1741
- exposes exactly one free-text tool named after the agent (or overridden by
1742
- `name`):
1737
+ exposes exactly one free-text tool, named after its key under `tools:`
1738
+ (slugified to satisfy the LLM tool-name rules — letters, digits, `_`, `-`).
1739
+ Rename the key to rename the tool; the key is also unique within `tools:`, so
1740
+ two agents never collide:
1743
1741
 
1744
1742
  ```json
1745
1743
  {
1746
- "name": "<name>",
1744
+ "name": "<tools: key, slugified>",
1747
1745
  "description": "<from Agent Card>",
1748
1746
  "input_schema": {
1749
1747
  "type": "object",
@@ -1781,7 +1779,6 @@ tools:
1781
1779
  mya2a:
1782
1780
  type: slack_agents.a2a.agent
1783
1781
  url: "https://remote-agent.example.com"
1784
- allowed_functions: [".*"]
1785
1782
  import-files:
1786
1783
  type: slack_agents.tools.file_importer
1787
1784
  allowed_functions: [".*"]
@@ -1800,8 +1797,10 @@ calling an LLM it routes directly to the configured A2A tool.
1800
1797
 
1801
1798
  ## Auth
1802
1799
 
1803
- Phase 1 supports static credentials only, resolved from environment variables
1804
- at startup via the standard `{ENV_VAR}` interpolation.
1800
+ ### Static / API-key auth (service-level)
1801
+
1802
+ Credentials are resolved from environment variables at startup via the standard
1803
+ `{ENV_VAR}` interpolation. These are shared across all users.
1805
1804
 
1806
1805
  ```yaml
1807
1806
  # Bearer token — Authorization: Bearer <token>
@@ -1810,12 +1809,46 @@ auth: { type: bearer, token: "{A2A_API_KEY}" }
1810
1809
  # Arbitrary header
1811
1810
  auth: { type: header, name: "X-API-Key", value: "{A2A_API_KEY}" }
1812
1811
 
1812
+ # API-key scheme — sends the raw value as the named header
1813
+ # (matches an Agent Card that advertises apiKey security)
1814
+ auth: { type: apiKey, name: "Authorization", value: "{A2A_API_KEY}" }
1815
+
1813
1816
  # No auth (default when omitted)
1814
1817
  auth: { type: none }
1815
1818
  ```
1816
1819
 
1817
- Per-user OAuth auth (reusing the `oauth/` package) is planned for a future
1818
- release.
1820
+ ### Per-user OAuth (`auth: { type: oauth2 }`)
1821
+
1822
+ Each Slack user authenticates separately to the remote agent. The framework
1823
+ uses that user's access token on subsequent calls, exactly as it does for
1824
+ OAuth-protected MCP servers (see [docs/oauth.md](oauth.md)).
1825
+
1826
+ ```yaml
1827
+ tools:
1828
+ research-agent:
1829
+ type: slack_agents.a2a.agent
1830
+ url: "https://research.example.com"
1831
+ auth: { type: oauth2 }
1832
+ ```
1833
+
1834
+ There is intentionally no `client_id`/`scopes` field. The provider discovers
1835
+ OAuth metadata from the remote agent's **Agent Card** (`securitySchemes.oauth2`
1836
+ → `oauth2MetadataUrl`), performs Dynamic Client Registration, and uses the
1837
+ auth-code + PKCE flow. This means a server whose RFC 9728 protected-resource-metadata
1838
+ endpoint is missing or internal still works as long as its Agent Card advertises
1839
+ the oauth2 scheme and `oauth2MetadataUrl`.
1840
+
1841
+ **User experience.** The first time a user invokes an OAuth-protected A2A agent,
1842
+ the framework posts an ephemeral "Authenticate" button in Slack. Clicking it
1843
+ opens the remote agent's auth server in the user's browser. After consent the
1844
+ browser redirects to the shared callback URL and the original request is
1845
+ completed automatically.
1846
+
1847
+ **Required environment variables.** Per-user OAuth needs the shared ingress
1848
+ (same as MCP OAuth) — configure `PUBLIC_URL` and `OAUTH_SECRET_KEY` exactly
1849
+ as described in [docs/oauth.md](oauth.md#required-environment-variables). These
1850
+ are validated at startup; the agent refuses to start if they are missing or
1851
+ malformed.
1819
1852
 
1820
1853
  ## Long-running tasks
1821
1854
 
@@ -1856,6 +1889,25 @@ needed. The Socket Mode deploy-anywhere property is preserved.
1856
1889
  backend. If the agent process restarts while tasks are in flight, they are
1857
1890
  re-discovered at startup and pollers are resumed automatically.
1858
1891
 
1892
+ **OAuth and async delivery.** The two async paths behave differently with
1893
+ respect to the user's token, and the framework is **push-preferred**:
1894
+
1895
+ - **Push (webhook).** Delivery is *inbound* — the agent POSTs updates to the
1896
+ ingress, which relays them to Slack without ever calling the agent back. No
1897
+ token is involved at delivery time, so a task started while the user was
1898
+ authenticated **still delivers even if their OAuth token later expires or is
1899
+ revoked.** When a push webhook is registered for a task, the framework does
1900
+ **not** also run the poller for it (avoiding redundant — potentially double —
1901
+ delivery).
1902
+ - **Polling (fallback, no push).** Only used when push is not registered (the
1903
+ agent doesn't support it, or no `PUBLIC_URL`). The poller calls `tasks/get`
1904
+ *outbound*, which requires the user's token: it builds a fresh per-user client
1905
+ from the stored token and refreshes silently — no Slack interaction happens
1906
+ out-of-band. If the token cannot be refreshed (revoked, or the refresh
1907
+ expired), the bot posts "Your session for this task has expired — please ask
1908
+ again and re-authenticate when prompted." to the thread, instead of prompting
1909
+ out-of-band where there is no safe way to do so.
1910
+
1859
1911
  ## What a Slack user sees
1860
1912
 
1861
1913
  **For a fast response (synchronous path):**
@@ -1928,7 +1980,6 @@ tools:
1928
1980
  type: slack_agents.a2a.agent
1929
1981
  url: "https://remote-agent.example.com"
1930
1982
  push_notifications: true
1931
- allowed_functions: [".*"]
1932
1983
  ```
1933
1984
 
1934
1985
  **Ingress.** Push needs the in-process HTTP listener (shared with OAuth) and a
@@ -1968,9 +2019,6 @@ The following are not yet implemented. They are planned for future releases.
1968
2019
  path (a long-running task delivered out-of-band carries its text, not files).
1969
2020
  - **Atomic responses.** Responses are collected in full before being posted.
1970
2021
  Token-by-token streaming to Slack is not supported yet.
1971
- - **Static auth only.** Auth credentials are fixed at startup from environment
1972
- variables. Per-Slack-user OAuth (each user authenticates separately to the
1973
- remote agent) is not yet supported.
1974
2022
  - **No A2A server mode.** This framework cannot be addressed as an A2A agent
1975
2023
  by other agents. It is a client only.
1976
2024
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-slack-agents"
7
- version = "0.9.0"
7
+ version = "0.9.1"
8
8
  description = "A Python framework for deploying AI agents as Slack bots"
9
9
  authors = [{name = "Eric Pichon", email = "epichon@comparenetworks.com"}]
10
10
  readme = "README.md"