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.
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/CHANGELOG.md +21 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/PKG-INFO +1 -1
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/a2a-agent/config.yaml +2 -3
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/a2a.md +66 -18
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/llms-full.txt +66 -18
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/pyproject.toml +1 -1
- python_slack_agents-0.9.1/src/slack_agents/a2a/agent.py +461 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/client.py +46 -3
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/delivery.py +35 -3
- python_slack_agents-0.9.1/src/slack_agents/a2a/oauth.py +75 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/config.py +20 -3
- python_slack_agents-0.9.1/src/slack_agents/oauth/discovery.py +149 -0
- python_slack_agents-0.9.1/src/slack_agents/oauth/errors.py +250 -0
- python_slack_agents-0.9.1/src/slack_agents/oauth/flow.py +370 -0
- python_slack_agents-0.9.1/src/slack_agents/oauth/scopes.py +53 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/agent.py +15 -3
- python_slack_agents-0.9.1/src/slack_agents/tools/mcp_http_oauth.py +394 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_agent_async.py +1 -2
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_agent_sync.py +12 -4
- python_slack_agents-0.9.1/tests/a2a/test_client_auth.py +63 -0
- python_slack_agents-0.9.1/tests/a2a/test_delivery_oauth.py +95 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_end_to_end.py +1 -1
- python_slack_agents-0.9.1/tests/a2a/test_oauth.py +372 -0
- python_slack_agents-0.9.1/tests/a2a/test_oauth_adapter.py +71 -0
- python_slack_agents-0.9.1/tests/a2a/test_push_preferred.py +58 -0
- python_slack_agents-0.9.1/tests/test_ingress_startup.py +106 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_mcp_http_oauth.py +25 -24
- python_slack_agents-0.9.1/tests/test_oauth_discovery.py +98 -0
- python_slack_agents-0.9.1/tests/test_oauth_errors.py +47 -0
- python_slack_agents-0.9.1/tests/test_oauth_flow.py +109 -0
- python_slack_agents-0.9.1/tests/test_oauth_flow_noninteractive.py +46 -0
- python_slack_agents-0.9.1/tests/test_oauth_flow_required_scopes.py +68 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_integration.py +1 -1
- python_slack_agents-0.9.1/tests/test_oauth_scopes.py +39 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_validation.py +52 -1
- python_slack_agents-0.9.0/src/slack_agents/a2a/agent.py +0 -228
- python_slack_agents-0.9.0/src/slack_agents/tools/mcp_http_oauth.py +0 -1098
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.dockerignore +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.env.example +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.github/workflows/ci.yml +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.github/workflows/publish.yml +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.gitignore +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/.pre-commit-config.yaml +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/AGENTS.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/CODE_OF_CONDUCT.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/CONTRIBUTING.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/LICENSE +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/README.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/SECURITY.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/README.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/a2a-agent/system_prompt.txt +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/docs-assistant/config.yaml +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/docs-assistant/system_prompt.txt +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/hello-world/config.yaml +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/hello-world/system_prompt.txt +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/kitchen-sink/config.yaml +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/agents/kitchen-sink/system_prompt.txt +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/access-control.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/agents.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/canvas.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/cli.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/deployment.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/llm.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/media/demo.gif +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/oauth.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/observability.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/private-repo.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/setup.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/slack-app-manifest.json +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/storage.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/tools.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/docs/user-context.md +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/llms.txt +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/Dockerfile +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/proxy.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/a2a/push.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/allow_all.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/allow_list.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/access/base.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/agent_loop.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/build_docker.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_conversations.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_conversations_html.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_usage.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/export_usage_csv.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/healthcheck.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/init.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/cli/run.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/conversations.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/files.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/anthropic.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/base.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/llm/openai.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/main.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/crypto.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/prompts.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/server.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/state.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/oauth/storage.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/observability.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/py.typed +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/scripts/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/scripts/download_fonts.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/scripts/generate_llms_full.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/actions.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/canvas_auth.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/canvases.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/files.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/format.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/streaming.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/streaming_formatter.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/slack/tool_blocks.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/base.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/postgres.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/postgres.sql +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/sqlite.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/storage/sqlite.sql +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/base.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/canvas.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/canvas_importer.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/file_exporter.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/file_importer.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/mcp_http.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/src/slack_agents/tools/user_context.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/__init__.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_client.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_client_files.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_delivery.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_framework_delivery.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_integration.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_proxy.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/a2a/test_push.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_access.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_agent_loop.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_canvas_auth.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_canvas_importer.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_cli.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_config.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_conversations.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_cost.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_export_documents.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_export_usage.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_file_extractors.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_format.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_init.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_llm_error_classification.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_llm_factory.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_load_plugin.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_mcp_client.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_crypto.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_prompts.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_server.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_state.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_oauth_storage.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_openai_convert.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_overlay_integration.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_storage_oauth.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_tool_blocks.py +0 -0
- {python_slack_agents-0.9.0 → python_slack_agents-0.9.1}/tests/test_tool_errors.py +0 -0
- {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.
|
|
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
|
|
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
|
|
71
|
-
|
|
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
|
|
86
|
-
|
|
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": "<
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
162
|
-
|
|
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
|
|
1727
|
-
|
|
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
|
|
1742
|
-
|
|
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": "<
|
|
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
|
-
|
|
1804
|
-
|
|
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
|
|
1818
|
-
|
|
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.
|
|
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"
|