python-slack-agents 0.8.0__tar.gz → 0.9.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.
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/.gitignore +2 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/.pre-commit-config.yaml +5 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/CHANGELOG.md +31 -2
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/PKG-INFO +4 -3
- python_slack_agents-0.9.0/SECURITY.md +13 -0
- python_slack_agents-0.9.0/agents/a2a-agent/config.yaml +67 -0
- python_slack_agents-0.9.0/agents/a2a-agent/system_prompt.txt +1 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/docs-assistant/system_prompt.txt +1 -1
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/hello-world/system_prompt.txt +0 -1
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/kitchen-sink/config.yaml +1 -1
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/kitchen-sink/system_prompt.txt +1 -1
- python_slack_agents-0.9.0/docs/a2a.md +336 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/oauth.md +6 -6
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/private-repo.md +47 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/tools.md +1 -1
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/llms-full.txt +682 -1
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/llms.txt +3 -2
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/pyproject.toml +4 -3
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/__init__.py +7 -0
- python_slack_agents-0.9.0/src/slack_agents/a2a/agent.py +228 -0
- python_slack_agents-0.9.0/src/slack_agents/a2a/client.py +276 -0
- python_slack_agents-0.9.0/src/slack_agents/a2a/delivery.py +99 -0
- python_slack_agents-0.9.0/src/slack_agents/a2a/proxy.py +113 -0
- python_slack_agents-0.9.0/src/slack_agents/a2a/push.py +198 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/init.py +15 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/config.py +67 -16
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/main.py +5 -5
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/oauth/storage.py +5 -3
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/scripts/generate_llms_full.py +2 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/agent.py +140 -28
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/storage/base.py +10 -4
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/storage/postgres.py +19 -7
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/storage/postgres.sql +4 -2
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/storage/sqlite.py +17 -7
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/storage/sqlite.sql +4 -2
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/mcp_http_oauth.py +60 -6
- python_slack_agents-0.9.0/tests/__init__.py +0 -0
- python_slack_agents-0.9.0/tests/a2a/__init__.py +0 -0
- python_slack_agents-0.9.0/tests/a2a/test_agent_async.py +70 -0
- python_slack_agents-0.9.0/tests/a2a/test_agent_sync.py +140 -0
- python_slack_agents-0.9.0/tests/a2a/test_client.py +22 -0
- python_slack_agents-0.9.0/tests/a2a/test_client_files.py +46 -0
- python_slack_agents-0.9.0/tests/a2a/test_delivery.py +99 -0
- python_slack_agents-0.9.0/tests/a2a/test_end_to_end.py +65 -0
- python_slack_agents-0.9.0/tests/a2a/test_framework_delivery.py +44 -0
- python_slack_agents-0.9.0/tests/a2a/test_integration.py +58 -0
- python_slack_agents-0.9.0/tests/a2a/test_proxy.py +83 -0
- python_slack_agents-0.9.0/tests/a2a/test_push.py +173 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_access.py +3 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_mcp_http_oauth.py +155 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_oauth_integration.py +1 -1
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_oauth_storage.py +76 -8
- python_slack_agents-0.9.0/tests/test_oauth_validation.py +91 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_storage_oauth.py +38 -4
- python_slack_agents-0.9.0/uv.lock +2335 -0
- python_slack_agents-0.8.0/SECURITY.md +0 -9
- python_slack_agents-0.8.0/tests/test_oauth_validation.py +0 -77
- python_slack_agents-0.8.0/uv.lock +0 -2108
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/.dockerignore +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/.env.example +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/.github/workflows/ci.yml +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/.github/workflows/publish.yml +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/AGENTS.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/CODE_OF_CONDUCT.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/CONTRIBUTING.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/LICENSE +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/README.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/README.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/docs-assistant/config.yaml +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/hello-world/config.yaml +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/access-control.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/agents.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/canvas.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/cli.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/deployment.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/llm.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/media/demo.gif +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/observability.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/setup.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/slack-app-manifest.json +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/storage.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/docs/user-context.md +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/Dockerfile +0 -0
- {python_slack_agents-0.8.0/src/slack_agents/access → python_slack_agents-0.9.0/src/slack_agents/a2a}/__init__.py +0 -0
- {python_slack_agents-0.8.0/src/slack_agents/scripts → python_slack_agents-0.9.0/src/slack_agents/access}/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/access/allow_all.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/access/allow_list.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/access/base.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/agent_loop.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/build_docker.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_conversations.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_conversations_html.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_usage.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_usage_csv.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/healthcheck.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/cli/run.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/conversations.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/files.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/llm/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/llm/anthropic.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/llm/base.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/llm/openai.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/oauth/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/oauth/crypto.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/oauth/prompts.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/oauth/server.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/oauth/state.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/observability.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/py.typed +0 -0
- {python_slack_agents-0.8.0/src/slack_agents/slack → python_slack_agents-0.9.0/src/slack_agents/scripts}/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/scripts/download_fonts.py +0 -0
- {python_slack_agents-0.8.0/src/slack_agents/storage → python_slack_agents-0.9.0/src/slack_agents/slack}/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/actions.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/canvas_auth.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/canvases.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/files.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/format.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/streaming.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/streaming_formatter.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/slack/tool_blocks.py +0 -0
- {python_slack_agents-0.8.0/src/slack_agents/tools → python_slack_agents-0.9.0/src/slack_agents/storage}/__init__.py +0 -0
- {python_slack_agents-0.8.0/tests → python_slack_agents-0.9.0/src/slack_agents/tools}/__init__.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/base.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/canvas.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/canvas_importer.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/file_exporter.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/file_importer.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/mcp_http.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/src/slack_agents/tools/user_context.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_agent_loop.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_canvas_auth.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_canvas_importer.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_cli.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_config.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_conversations.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_cost.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_export_documents.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_export_usage.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_file_extractors.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_format.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_init.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_llm_error_classification.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_llm_factory.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_load_plugin.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_mcp_client.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_oauth_crypto.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_oauth_prompts.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_oauth_server.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_oauth_state.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_openai_convert.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_overlay_integration.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_tool_blocks.py +0 -0
- {python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/tests/test_tool_errors.py +0 -0
|
@@ -6,6 +6,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-06-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **A2A push notifications (receiving).** A push-capable agent (`capabilities.pushNotifications`) can deliver task updates — status messages and file artifacts — to the Slack thread out-of-band, including reports that arrive after a synchronous reply. Opt in per agent with `push_notifications: true`. The framework registers the webhook inline on the first send with a random per-task token, validates the `X-A2A-Notification-Token` header, correlates by `taskId`, and de-duplicates by message/artifact id (the server re-pushes the immediate reply). The OAuth HTTP sidecar is generalized into a shared ingress (one listener starting for OAuth or push), configured by the new `PUBLIC_URL` / `HTTP_BIND_HOST` / `HTTP_BIND_PORT` env vars (see Changed). Verified end-to-end against a live agent. See `docs/a2a.md`.
|
|
14
|
+
- **Agent2Agent (A2A) protocol integration** over the official `a2a-sdk` (1.x, protobuf), isolated behind `slack_agents.a2a.client`. Two topologies: `slack_agents.a2a.agent` exposes a remote A2A agent as a single free-text tool a real LLM delegates to (Option A, smart routing), and `slack_agents.a2a.proxy` turns Slack into a dumb frontend for one agent (Option B, dev/debugging). Features: per-thread `contextId` + `taskId` threading for correct multi-turn (`input-required`) conversations; synchronous replies plus detached background polling with out-of-band delivery for long-running (`working`) tasks (real LLMs re-process the result, the proxy posts it raw); bidirectional file attachments (Slack upload → A2A `raw` part, file artifact → Slack upload); static bearer/header auth. Includes an env-gated live integration test (`A2A_TEST_URL`). See `docs/a2a.md`.
|
|
15
|
+
- `docs/private-repo.md` — "Protecting secrets in your overlay" section covering GitHub push protection (server-side block that survives `--no-verify`), a gitleaks pre-commit hook, and a one-time trufflehog history sweep. Aimed at overlay maintainers whose configs reference Slack tokens, LLM API keys, and OAuth client secrets via `{ENV_VAR}` placeholders.
|
|
16
|
+
- `slack-agents init` now prints a visible "SECURITY: protect your secrets before pushing" banner at the end of scaffolding, linking to the new docs section.
|
|
17
|
+
- `SECURITY.md` — pointer for overlay maintainers to the overlay security guidance.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **BREAKING (OAuth):** the in-process HTTP listener is now a shared ingress for OAuth callbacks and A2A push. Its env vars were renamed — `OAUTH_PUBLIC_URL` → `PUBLIC_URL`, `OAUTH_BIND_HOST` → `HTTP_BIND_HOST`, `OAUTH_BIND_PORT` → `HTTP_BIND_PORT` — with **no aliases**. (`OAUTH_SECRET_KEY` is unchanged.) Agents using OAuth must rename these in their environment. The startup validator fails fast with a clear message naming `PUBLIC_URL` when OAuth or push is configured but the var is missing.
|
|
22
|
+
|
|
23
|
+
### Security
|
|
24
|
+
|
|
25
|
+
- Dependency security updates clearing all open advisories. Lifted the speculative `cryptography` upper cap (`<46`) that had blocked the patch (the fix shipped in the next major) and raised the floor to `>=46.0.7`; refreshed the lockfile to current releases: `cryptography` 48, `aiohttp` 3.14.1, `pillow` 12.2.0, `lxml` 6.1.1, `urllib3` 2.7.0, `python-multipart` 0.0.32, `starlette` 1.2.1, `anthropic` 0.107.1, `pygments` 2.20.0.
|
|
26
|
+
|
|
27
|
+
## [0.8.1] - 2026-05-07
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- `oauth_clients` cache is now keyed by `(server_id, redirect_uri)` instead of `server_id` alone. Two `mcp_http_oauth` providers pointing at the same MCP server but with different `OAUTH_PUBLIC_URL` values (e.g. agents sharing a database, or a single agent whose tunnel hostname rotates) used to collide on the cached client registration; the second one would reuse a row whose `redirect_uri` the IdP no longer accepted, producing `Invalid parameter: redirect_uri` from the IdP.
|
|
32
|
+
- `mcp_http_oauth.Provider.call_tool` now detects an IdP `Invalid parameter: redirect_uri` (or `redirect_uri_mismatch`) rejection, deletes the stale cached client registration and the user's cached tokens, and surfaces a structured `system_error` with `code="redirect_uri_mismatch"` so the LLM can explain the situation. The next call re-registers a fresh client and prompts for re-auth.
|
|
33
|
+
|
|
34
|
+
### Migration
|
|
35
|
+
|
|
36
|
+
- The `oauth_clients` table primary key changed from `(server_id)` to `(server_id, redirect_uri)`. There is no automatic migration: any rows from 0.8.0 will be re-created on demand the first time each `(server_id, redirect_uri)` pair is used, leaving harmless orphan rows behind. Operators who want a clean slate can `DELETE FROM oauth_clients;` before upgrading.
|
|
37
|
+
|
|
9
38
|
## [0.8.0] - 2026-05-06
|
|
10
39
|
|
|
11
40
|
### Added
|
|
@@ -90,7 +119,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
|
|
|
90
119
|
|
|
91
120
|
### Added
|
|
92
121
|
|
|
93
|
-
- `slack-agents init` now generates `.gitignore`
|
|
122
|
+
- `slack-agents init` now generates `.gitignore`
|
|
94
123
|
- `.env.example` template includes comments explaining where to get each token and links to setup guide
|
|
95
124
|
- `build-docker` lists required environment variables after build completes
|
|
96
125
|
- `build-docker` errors if `req*.txt` files are found (dependencies must be in `pyproject.toml`)
|
|
@@ -98,7 +127,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
|
|
|
98
127
|
|
|
99
128
|
### Changed
|
|
100
129
|
|
|
101
|
-
- `pyproject.toml` template uses `python-slack-agents<2` (no minimum pin)
|
|
130
|
+
- `pyproject.toml` template uses `python-slack-agents<2` (no minimum pin)
|
|
102
131
|
- Setup flow uses venv-first approach: create venv, install package, then `slack-agents init`
|
|
103
132
|
- Updated README, docs/setup.md, and docs/private-repo.md with new setup flow
|
|
104
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-slack-agents
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
@@ -17,11 +17,12 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
17
|
Classifier: Topic :: Communications :: Chat
|
|
18
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
19
|
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: a2a-sdk<2,>=1.0
|
|
20
21
|
Requires-Dist: aiohttp<4,>=3.9
|
|
21
22
|
Requires-Dist: aiosqlite<1,>=0.20
|
|
22
23
|
Requires-Dist: anthropic<1,>=0.40
|
|
23
24
|
Requires-Dist: asyncpg<1,>=0.30
|
|
24
|
-
Requires-Dist: cryptography
|
|
25
|
+
Requires-Dist: cryptography>=46.0.7
|
|
25
26
|
Requires-Dist: fpdf2<3,>=2.8
|
|
26
27
|
Requires-Dist: httpx<1,>=0.27
|
|
27
28
|
Requires-Dist: mcp<2,>=1.0
|
|
@@ -41,7 +42,7 @@ Requires-Dist: slack-sdk[socket-mode-handlers]<4,>=3.33
|
|
|
41
42
|
Provides-Extra: dev
|
|
42
43
|
Requires-Dist: pre-commit<5,>=4.0; extra == 'dev'
|
|
43
44
|
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
|
|
44
|
-
Requires-Dist: pytest<
|
|
45
|
+
Requires-Dist: pytest<10,>=8.0; extra == 'dev'
|
|
45
46
|
Requires-Dist: ruff<1,>=0.8; extra == 'dev'
|
|
46
47
|
Description-Content-Type: text/markdown
|
|
47
48
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Please report security vulnerabilities privately using [GitHub's security advisory feature](https://github.com/CompareNetworks/python-slack-agents/security/advisories/new).
|
|
6
|
+
|
|
7
|
+
Do **not** open public issues for security concerns.
|
|
8
|
+
|
|
9
|
+
We will acknowledge reports within 72 hours and aim to release fixes promptly.
|
|
10
|
+
|
|
11
|
+
## For overlay maintainers
|
|
12
|
+
|
|
13
|
+
If you operate an overlay repository (your own `agents/`, `src/`, and configs built on top of this framework), see [Protecting secrets in your overlay](docs/private-repo.md#protecting-secrets-in-your-overlay) for the recommended setup: GitHub push protection, a gitleaks pre-commit hook, and a one-time trufflehog history sweep.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Agent2Agent (A2A) example — connects to a remote A2A agent.
|
|
2
|
+
#
|
|
3
|
+
# It points at the official "helloworld" reference agent (a2a-sdk 1.x, no API
|
|
4
|
+
# key), run locally in Docker. START THE SERVER FIRST:
|
|
5
|
+
#
|
|
6
|
+
# git clone --depth 1 https://github.com/a2aproject/a2a-samples /tmp/a2a-samples
|
|
7
|
+
# cd /tmp/a2a-samples/samples/python/agents/helloworld
|
|
8
|
+
# # upstream binds 127.0.0.1 inside the container; make it bind 0.0.0.0:
|
|
9
|
+
# sed -i '' "s/host='127.0.0.1'/host='0.0.0.0'/" __main__.py # GNU sed: use sed -i
|
|
10
|
+
# docker build -f Containerfile -t helloworld-a2a-server .
|
|
11
|
+
# docker run -d -p 9999:9999 helloworld-a2a-server
|
|
12
|
+
#
|
|
13
|
+
# Then run this agent: slack-agents run agents/a2a-agent
|
|
14
|
+
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
schema: "slack-agents/v1"
|
|
17
|
+
|
|
18
|
+
slack:
|
|
19
|
+
bot_token: "{SLACK_BOT_TOKEN}"
|
|
20
|
+
app_token: "{SLACK_APP_TOKEN}"
|
|
21
|
+
|
|
22
|
+
access:
|
|
23
|
+
type: slack_agents.access.allow_all
|
|
24
|
+
|
|
25
|
+
# --- Routing: enable exactly ONE of the two llm blocks below. ---
|
|
26
|
+
|
|
27
|
+
# Option A (default) — smart routing: a real LLM delegates to the A2A agent as a
|
|
28
|
+
# tool, decides when to call it, and synthesizes the reply.
|
|
29
|
+
|
|
30
|
+
# # Try in Slack: "ask the a2a agent to say hello to Jim" -> it sends "Hello Jim!" to the agent.
|
|
31
|
+
|
|
32
|
+
llm:
|
|
33
|
+
type: slack_agents.llm.anthropic
|
|
34
|
+
model: claude-sonnet-4-6
|
|
35
|
+
api_key: "{ANTHROPIC_API_KEY}"
|
|
36
|
+
max_tokens: 4096
|
|
37
|
+
max_input_tokens: 200000
|
|
38
|
+
|
|
39
|
+
# Option B — dumb frontend (dev/debugging): no local LLM. Every user message is
|
|
40
|
+
# forwarded straight to the single A2A agent and its reply relayed back. To use
|
|
41
|
+
# it, comment out the Option A block above and uncomment this one. `model` is the
|
|
42
|
+
# a2a tool's key under `tools:` below.
|
|
43
|
+
|
|
44
|
+
# Try in Slack: "Jim" -> it echoes your request.
|
|
45
|
+
|
|
46
|
+
#llm:
|
|
47
|
+
# type: slack_agents.a2a.proxy
|
|
48
|
+
# model: "mya2a"
|
|
49
|
+
|
|
50
|
+
storage:
|
|
51
|
+
type: slack_agents.storage.sqlite
|
|
52
|
+
path: ":memory:"
|
|
53
|
+
|
|
54
|
+
tools:
|
|
55
|
+
# The remote A2A agent, exposed to the LLM as one free-text tool named "mya2a".
|
|
56
|
+
mya2a:
|
|
57
|
+
type: slack_agents.a2a.agent
|
|
58
|
+
name: "mya2a"
|
|
59
|
+
url: "http://127.0.0.1:9999"
|
|
60
|
+
auth: {}
|
|
61
|
+
allowed_functions: [".*"]
|
|
62
|
+
|
|
63
|
+
# Lets the framework ACCEPT Slack file uploads; the a2a tool then forwards the
|
|
64
|
+
# raw bytes to the agent. (With Option A the LLM also sees the extracted text.)
|
|
65
|
+
import-files:
|
|
66
|
+
type: slack_agents.tools.file_importer
|
|
67
|
+
allowed_functions: [".*"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
You are a helpful assistant. Be concise and friendly.
|
{python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/docs-assistant/system_prompt.txt
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
You are a documentation assistant. You can search GitHub repositories for
|
|
2
2
|
documentation using the DeepWiki tool and create PDF documents.
|
|
3
3
|
|
|
4
|
-
When asked about a project, search for its documentation first, then summarize.
|
|
4
|
+
When asked about a project, search for its documentation first, then summarize.
|
{python_slack_agents-0.8.0 → python_slack_agents-0.9.0}/agents/kitchen-sink/system_prompt.txt
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
You are a helpful assistant. This agent is configured as an example showcasing all available config options.
|
|
1
|
+
You are a helpful assistant. This agent is configured as an example showcasing all available config options.
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Agent2Agent (A2A) integration
|
|
2
|
+
|
|
3
|
+
`slack_agents.a2a.agent` and `slack_agents.a2a.proxy` connect this framework
|
|
4
|
+
to remote agents over the
|
|
5
|
+
[Agent2Agent protocol](https://a2a-protocol.org). A2A is an interaction
|
|
6
|
+
protocol — the remote agent is an opaque, possibly-conversational message
|
|
7
|
+
endpoint that describes itself via an Agent Card. The framework treats it as a
|
|
8
|
+
single free-text channel (not a menu of typed tools the way MCP does).
|
|
9
|
+
|
|
10
|
+
Built on the official [`a2a-sdk`](https://github.com/a2aproject/a2a-python)
|
|
11
|
+
(1.x, protobuf-based). All SDK-specific code is isolated in
|
|
12
|
+
`slack_agents.a2a.client`; the rest of the package depends only on a small
|
|
13
|
+
stable interface, so SDK version changes are contained to one module.
|
|
14
|
+
|
|
15
|
+
Two deployment topologies are supported from one codebase:
|
|
16
|
+
|
|
17
|
+
- **Option A — smart router.** A real local LLM (`slack_agents.llm.anthropic`,
|
|
18
|
+
etc.) orchestrates a conversation and delegates to one or more remote A2A
|
|
19
|
+
agents exposed as tools (`slack_agents.a2a.agent`). The local LLM decides
|
|
20
|
+
when and how to invoke each agent, synthesizes the replies, and continues the
|
|
21
|
+
conversation normally. Multiple A2A agents can coexist alongside other tool
|
|
22
|
+
providers.
|
|
23
|
+
- **Option B — dumb frontend.** No local reasoning at all. `slack_agents.a2a.proxy`
|
|
24
|
+
forwards every user message to a single remote A2A agent and relays the reply
|
|
25
|
+
back. Slack becomes a thin chat front-end; the intelligence lives entirely in
|
|
26
|
+
the remote agent. This is primarily a **development/debugging convenience** for
|
|
27
|
+
poking at an A2A agent directly through Slack — Option A is the production shape.
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
### Option A — smart router
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
llm:
|
|
35
|
+
type: slack_agents.llm.anthropic
|
|
36
|
+
model: claude-sonnet-4-6
|
|
37
|
+
api_key: "{ANTHROPIC_API_KEY}"
|
|
38
|
+
max_tokens: 4096
|
|
39
|
+
max_input_tokens: 200000
|
|
40
|
+
tools:
|
|
41
|
+
research-agent:
|
|
42
|
+
type: slack_agents.a2a.agent
|
|
43
|
+
url: "https://research.example.com"
|
|
44
|
+
auth: { type: bearer, token: "{RESEARCH_A2A_TOKEN}" }
|
|
45
|
+
poll_interval: 5
|
|
46
|
+
max_task_lifetime: 3600
|
|
47
|
+
allowed_functions: [".*"]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Option B — dumb frontend
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
llm:
|
|
54
|
+
type: slack_agents.a2a.proxy
|
|
55
|
+
model: "mya2a"
|
|
56
|
+
max_input_tokens: 200000
|
|
57
|
+
tools:
|
|
58
|
+
mya2a:
|
|
59
|
+
type: slack_agents.a2a.agent
|
|
60
|
+
url: "https://remote-agent.example.com"
|
|
61
|
+
auth: { type: bearer, token: "{A2A_API_KEY}" }
|
|
62
|
+
allowed_functions: [".*"]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The system prompt is ignored in Option B; it is used normally in Option A.
|
|
66
|
+
|
|
67
|
+
## `a2a.agent` — tool provider
|
|
68
|
+
|
|
69
|
+
`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.
|
|
72
|
+
|
|
73
|
+
| Field | Type | Default | Description |
|
|
74
|
+
|-------|------|---------|-------------|
|
|
75
|
+
| `url` | `str` | required | Base URL of the remote agent. The framework tries `<url>/.well-known/agent-card.json` automatically. |
|
|
76
|
+
| `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
|
+
| `timeout` | `float` | `300` | HTTP timeout in seconds for individual A2A requests. |
|
|
80
|
+
| `poll_interval` | `float` | `5` | Seconds between polling attempts for long-running tasks. |
|
|
81
|
+
| `max_task_lifetime` | `float` | `3600` | Maximum seconds to poll before abandoning a task and delivering a timeout message. |
|
|
82
|
+
|
|
83
|
+
**One tool per agent.** A2A is a single opaque channel — it has no mechanism to
|
|
84
|
+
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`):
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"name": "<name>",
|
|
91
|
+
"description": "<from Agent Card>",
|
|
92
|
+
"input_schema": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"properties": { "message": { "type": "string" } },
|
|
95
|
+
"required": ["message"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Conversation continuity.** The framework tracks the A2A `contextId` (and, for
|
|
101
|
+
multi-turn tasks, the `taskId`) per Slack thread, transparently — the LLM never
|
|
102
|
+
sees or passes these. Successive messages in the same thread are sent on the
|
|
103
|
+
same `contextId` so the remote agent can keep conversational state.
|
|
104
|
+
|
|
105
|
+
When the agent replies with an **`input-required`** (or `auth-required`) state —
|
|
106
|
+
i.e. it is mid-task and wants another message — the framework relays the agent's
|
|
107
|
+
prompt to the user and **threads the same `taskId`** on the next message, so the
|
|
108
|
+
exchange continues the *same* Task (and its server-side state). When the task
|
|
109
|
+
reaches a terminal state, the saved `taskId` is cleared and the next message
|
|
110
|
+
starts a fresh Task. This is what makes multi-turn agents (e.g. a step-by-step
|
|
111
|
+
form or a guessing game) behave correctly instead of restarting each turn.
|
|
112
|
+
|
|
113
|
+
**Files.** File attachments flow in both directions. A file a user uploads in
|
|
114
|
+
Slack is forwarded to the agent as an A2A `raw` part (alongside the text); a
|
|
115
|
+
file the agent returns as an artifact is surfaced back into the Slack thread as
|
|
116
|
+
an upload. Non-text artifacts (CSV, PDF, …) come through as files; text
|
|
117
|
+
artifacts are used as the reply.
|
|
118
|
+
|
|
119
|
+
To **send** files you must also configure a file-import handler (e.g.
|
|
120
|
+
`slack_agents.tools.file_importer`) in `tools:` — the framework only accepts
|
|
121
|
+
uploads it has a handler for, and the a2a tool then forwards the raw bytes:
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
tools:
|
|
125
|
+
mya2a:
|
|
126
|
+
type: slack_agents.a2a.agent
|
|
127
|
+
url: "https://remote-agent.example.com"
|
|
128
|
+
allowed_functions: [".*"]
|
|
129
|
+
import-files:
|
|
130
|
+
type: slack_agents.tools.file_importer
|
|
131
|
+
allowed_functions: [".*"]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## `a2a.proxy` — passthrough LLM
|
|
135
|
+
|
|
136
|
+
`slack_agents.a2a.proxy` is a `BaseLLMProvider` that does no local reasoning.
|
|
137
|
+
It drives the same conversation loop as any other LLM provider, but instead of
|
|
138
|
+
calling an LLM it routes directly to the configured A2A tool.
|
|
139
|
+
|
|
140
|
+
| Field | Type | Default | Description |
|
|
141
|
+
|-------|------|---------|-------------|
|
|
142
|
+
| `model` | `str` | — | Name of the target `a2a.agent` tool (the key under `tools:` in `config.yaml`). When set, it is always used. When omitted, the proxy auto-selects the tool **only if exactly one** is configured; with more than one tool and no `model:`, startup fails with an error. |
|
|
143
|
+
| `max_input_tokens` | `int` | `200000` | Context-window guard, passed through to the loop. |
|
|
144
|
+
|
|
145
|
+
## Auth
|
|
146
|
+
|
|
147
|
+
Phase 1 supports static credentials only, resolved from environment variables
|
|
148
|
+
at startup via the standard `{ENV_VAR}` interpolation.
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
# Bearer token — Authorization: Bearer <token>
|
|
152
|
+
auth: { type: bearer, token: "{A2A_API_KEY}" }
|
|
153
|
+
|
|
154
|
+
# Arbitrary header
|
|
155
|
+
auth: { type: header, name: "X-API-Key", value: "{A2A_API_KEY}" }
|
|
156
|
+
|
|
157
|
+
# No auth (default when omitted)
|
|
158
|
+
auth: { type: none }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Per-user OAuth auth (reusing the `oauth/` package) is planned for a future
|
|
162
|
+
release.
|
|
163
|
+
|
|
164
|
+
## Long-running tasks
|
|
165
|
+
|
|
166
|
+
The A2A protocol distinguishes between tasks that complete quickly and tasks
|
|
167
|
+
that may take minutes or hours. The framework handles both transparently.
|
|
168
|
+
|
|
169
|
+
The client sends every message with `blocking: true`, requesting that the
|
|
170
|
+
server wait for completion before responding. This is only a hint — the server
|
|
171
|
+
is free to return a non-terminal (`working`) task for long jobs.
|
|
172
|
+
|
|
173
|
+
**Synchronous path.** If the remote agent returns a terminal result
|
|
174
|
+
(`completed`, `failed`, `canceled`, or `rejected`) immediately, the result is
|
|
175
|
+
returned inline to the LLM (Option A) or relayed directly to Slack (Option B).
|
|
176
|
+
|
|
177
|
+
**Background-polling path.** If the remote agent returns a non-terminal
|
|
178
|
+
(`submitted` or `working`) task, the framework:
|
|
179
|
+
|
|
180
|
+
1. Persists an in-flight record (task ID, context, thread, channel) to the
|
|
181
|
+
storage backend.
|
|
182
|
+
2. Returns an acknowledgement to the LLM/proxy immediately
|
|
183
|
+
("Started a longer task — I'll post the result here when it's ready.").
|
|
184
|
+
3. Spawns a background poller that calls `tasks/get` every `poll_interval`
|
|
185
|
+
seconds until the task reaches a terminal state or `max_task_lifetime` is
|
|
186
|
+
exceeded.
|
|
187
|
+
4. On completion, delivers the result **out-of-band** into the original Slack
|
|
188
|
+
thread:
|
|
189
|
+
- **Option A (real LLM):** the result is injected as a synthetic inbound
|
|
190
|
+
turn and the standard loop re-runs, so the local LLM can interpret or act
|
|
191
|
+
on it before replying to the user.
|
|
192
|
+
- **Option B (proxy):** the result text is posted directly to the thread
|
|
193
|
+
without re-entering the loop (the proxy has no intelligence to add).
|
|
194
|
+
|
|
195
|
+
This is entirely outbound — the background poller calls the remote agent's
|
|
196
|
+
`tasks/get` endpoint on a timer. No public URL or inbound HTTP listener is
|
|
197
|
+
needed. The Socket Mode deploy-anywhere property is preserved.
|
|
198
|
+
|
|
199
|
+
**Crash resilience.** In-flight task records are persisted in the storage
|
|
200
|
+
backend. If the agent process restarts while tasks are in flight, they are
|
|
201
|
+
re-discovered at startup and pollers are resumed automatically.
|
|
202
|
+
|
|
203
|
+
## What a Slack user sees
|
|
204
|
+
|
|
205
|
+
**For a fast response (synchronous path):**
|
|
206
|
+
|
|
207
|
+
1. User asks the bot something.
|
|
208
|
+
2. The bot forwards the message to the remote A2A agent and replies with the
|
|
209
|
+
result, same as any other tool call.
|
|
210
|
+
|
|
211
|
+
**For a long-running task (async path):**
|
|
212
|
+
|
|
213
|
+
1. User asks the bot something.
|
|
214
|
+
2. The bot replies: "Started a longer task — I'll post the result here when
|
|
215
|
+
it's ready."
|
|
216
|
+
3. When the remote agent finishes, the result appears in the same Slack thread.
|
|
217
|
+
|
|
218
|
+
## Trying it against a reference agent
|
|
219
|
+
|
|
220
|
+
You don't need to write an A2A agent to try the integration — point it at the
|
|
221
|
+
official [`helloworld`](https://github.com/a2aproject/a2a-samples/tree/main/samples/python/agents/helloworld)
|
|
222
|
+
sample (a2a-sdk 1.x, no API key). It's a simple echo, so it exercises the core
|
|
223
|
+
path — Agent Card resolution, send, completion, artifact handling — but not
|
|
224
|
+
multi-turn or files.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git clone --depth 1 https://github.com/a2aproject/a2a-samples /tmp/a2a-samples
|
|
228
|
+
cd /tmp/a2a-samples/samples/python/agents/helloworld
|
|
229
|
+
|
|
230
|
+
# The upstream Containerfile's CMD passes --host 0.0.0.0, but __main__.py hardcodes
|
|
231
|
+
# 127.0.0.1, so it binds container-loopback and isn't reachable from the host.
|
|
232
|
+
# Make it bind 0.0.0.0 (the card's advertised url stays 127.0.0.1:9999, which is
|
|
233
|
+
# correct for a client on the host):
|
|
234
|
+
sed -i '' "s/host='127.0.0.1'/host='0.0.0.0'/" __main__.py # GNU sed: use sed -i
|
|
235
|
+
|
|
236
|
+
docker build -f Containerfile -t helloworld-a2a-server .
|
|
237
|
+
docker run -d -p 9999:9999 helloworld-a2a-server
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Set `url: "http://127.0.0.1:9999"` in your `a2a.agent` config and message the
|
|
241
|
+
bot — it replies `Hello, World! I have received your request (...)`. A ready-made
|
|
242
|
+
example lives at [`agents/a2a-agent/config.yaml`](../agents/a2a-agent/config.yaml),
|
|
243
|
+
which documents both the smart-routing and proxy topologies (proxy commented out
|
|
244
|
+
for easy switching) and repeats these server-start steps inline.
|
|
245
|
+
|
|
246
|
+
**Integration test.** `tests/a2a/test_integration.py` drives the real client
|
|
247
|
+
against a live agent and is **skipped unless `A2A_TEST_URL` is set**, so it
|
|
248
|
+
never affects CI:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
A2A_TEST_URL=http://127.0.0.1:9999 pytest tests/a2a/test_integration.py -v
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The assertions are agent-agnostic — the same test works against any conformant
|
|
255
|
+
A2A agent.
|
|
256
|
+
|
|
257
|
+
## Push notifications
|
|
258
|
+
|
|
259
|
+
When a remote agent supports push (`capabilities.pushNotifications`), the
|
|
260
|
+
framework can register a **webhook** so the agent delivers task updates —
|
|
261
|
+
status messages and file artifacts — to the Slack thread out-of-band, including
|
|
262
|
+
reports that arrive *after* a synchronous reply. This is the unified,
|
|
263
|
+
**push-preferred** async path; the background poller remains the fallback for
|
|
264
|
+
agents that don't support push.
|
|
265
|
+
|
|
266
|
+
**Enable it** with `push_notifications: true` on the `a2a.agent` config (opt-in,
|
|
267
|
+
because push requires a publicly reachable URL):
|
|
268
|
+
|
|
269
|
+
```yaml
|
|
270
|
+
tools:
|
|
271
|
+
mya2a:
|
|
272
|
+
type: slack_agents.a2a.agent
|
|
273
|
+
url: "https://remote-agent.example.com"
|
|
274
|
+
push_notifications: true
|
|
275
|
+
allowed_functions: [".*"]
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Ingress.** Push needs the in-process HTTP listener (shared with OAuth) and a
|
|
279
|
+
public URL the agent can POST to. Configure:
|
|
280
|
+
|
|
281
|
+
| Env | Default | Purpose |
|
|
282
|
+
|-----|---------|---------|
|
|
283
|
+
| `PUBLIC_URL` | — (required) | Public base URL of the ingress; the webhook is `<PUBLIC_URL>/a2a/push`. |
|
|
284
|
+
| `HTTP_BIND_HOST` | `0.0.0.0` | Listener bind host. |
|
|
285
|
+
| `HTTP_BIND_PORT` | `8080` | Listener bind port. |
|
|
286
|
+
|
|
287
|
+
This is the one A2A feature that needs **inbound** HTTP — unlike the polling
|
|
288
|
+
path, which is outbound-only. For local testing the URL can be a loopback
|
|
289
|
+
(`http://127.0.0.1:8080`) reachable by an agent on the same host.
|
|
290
|
+
|
|
291
|
+
**How it works.** On the first send the framework registers the webhook inline
|
|
292
|
+
(`SendMessageConfiguration.task_push_notification_config`) with a random
|
|
293
|
+
per-task token, and persists a `taskId → thread` record. Incoming POSTs are
|
|
294
|
+
validated against the token, correlated by `taskId`, **de-duplicated** by
|
|
295
|
+
message/artifact id (the server re-pushes the immediate reply, which we already
|
|
296
|
+
delivered synchronously), and any genuinely-new text/files are posted/uploaded
|
|
297
|
+
to the thread.
|
|
298
|
+
|
|
299
|
+
**Security & limits.** We validate the shared-secret token and only act on tasks
|
|
300
|
+
we registered. The protocol's signing (JWS) and SSRF-allowlist are server-side
|
|
301
|
+
concerns we don't yet rely on. There are no delivery retries (the agent sends a
|
|
302
|
+
single POST), and a *server* restart drops its own registration — so a
|
|
303
|
+
previously-registered task simply stops pushing.
|
|
304
|
+
|
|
305
|
+
## Current limitations
|
|
306
|
+
|
|
307
|
+
The following are not yet implemented. They are planned for future releases.
|
|
308
|
+
|
|
309
|
+
- **Files: image uploads and the async path.** *Receiving* files works for any
|
|
310
|
+
type. When *sending*, only non-image uploads (CSV, PDF, DOCX, …) are forwarded
|
|
311
|
+
— images are not yet — and files are forwarded/surfaced only on the synchronous
|
|
312
|
+
path (a long-running task delivered out-of-band carries its text, not files).
|
|
313
|
+
- **Atomic responses.** Responses are collected in full before being posted.
|
|
314
|
+
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
|
+
- **No A2A server mode.** This framework cannot be addressed as an A2A agent
|
|
319
|
+
by other agents. It is a client only.
|
|
320
|
+
|
|
321
|
+
## Troubleshooting
|
|
322
|
+
|
|
323
|
+
**"A2A agent returned a long-running task but async delivery is unavailable."**
|
|
324
|
+
— the `a2a.agent` provider was not given a `framework_ctx` (injected
|
|
325
|
+
automatically by the framework when the provider is loaded via `config.yaml`).
|
|
326
|
+
This should not happen in normal operation; it can appear in test setups that
|
|
327
|
+
construct the provider directly without the framework context.
|
|
328
|
+
|
|
329
|
+
**The background poller never delivers a result.** Check that `poll_interval`
|
|
330
|
+
and `max_task_lifetime` are appropriate for the remote agent. If the agent
|
|
331
|
+
takes longer than `max_task_lifetime`, the task is abandoned and a timeout
|
|
332
|
+
message is posted to the thread. Increase `max_task_lifetime` if needed.
|
|
333
|
+
|
|
334
|
+
**"a2a.proxy: set `model:` to the target a2a tool name"** — you have more than
|
|
335
|
+
one tool configured and the proxy doesn't know which one to route to. Set
|
|
336
|
+
`model:` under the `llm:` section to the key of the intended `a2a.agent` tool.
|
|
@@ -35,10 +35,10 @@ single consolidated error message.
|
|
|
35
35
|
|
|
36
36
|
| Variable | Required | Default | Description |
|
|
37
37
|
|---|---|---|---|
|
|
38
|
-
| `
|
|
38
|
+
| `PUBLIC_URL` | yes | — | Externally reachable base URL of this agent process (shared with A2A push). Must be `https://`, or `http://` with a loopback host (`localhost`, `127.0.0.1`, `[::1]`) for local dev. |
|
|
39
39
|
| `OAUTH_SECRET_KEY` | yes | — | Root key for HKDF; ≥32 bytes after base64 decode. Used to sign OAuth state tokens and encrypt refresh tokens at rest. |
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
40
|
+
| `HTTP_BIND_HOST` | no | `0.0.0.0` | Interface the in-process listener binds to (shared ingress). |
|
|
41
|
+
| `HTTP_BIND_PORT` | no | `8080` | TCP port for the listener (shared ingress). |
|
|
42
42
|
|
|
43
43
|
### Generating `OAUTH_SECRET_KEY`
|
|
44
44
|
|
|
@@ -67,13 +67,13 @@ ngrok http 8080
|
|
|
67
67
|
# → forwards https://abcd-1234.ngrok-free.app to localhost:8080
|
|
68
68
|
|
|
69
69
|
# Terminal 2 — set env vars and run the agent:
|
|
70
|
-
export
|
|
70
|
+
export PUBLIC_URL=https://abcd-1234.ngrok-free.app
|
|
71
71
|
export OAUTH_SECRET_KEY=$(openssl rand -base64 32)
|
|
72
72
|
slack-agents run agents/my-agent
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
If you'd rather not use a tunnel, you can run with
|
|
76
|
-
`
|
|
76
|
+
`PUBLIC_URL=http://localhost:8080` — the validator allows loopback
|
|
77
77
|
addresses over plain HTTP per RFC 8252.
|
|
78
78
|
|
|
79
79
|
## What a Slack user sees
|
|
@@ -190,7 +190,7 @@ plaintext (still in the private DB).
|
|
|
190
190
|
|
|
191
191
|
## Troubleshooting
|
|
192
192
|
|
|
193
|
-
**"Configuration error: ...
|
|
193
|
+
**"Configuration error: ... PUBLIC_URL is not set"** — set the env vars
|
|
194
194
|
listed in the message and restart.
|
|
195
195
|
|
|
196
196
|
**"Authentication timed out"** — the user didn't click the link within
|
|
@@ -79,3 +79,50 @@ No custom Dockerfile needed — `python-slack-agents` bundles one that auto-dete
|
|
|
79
79
|
slack-agents build-docker agents/my-agent
|
|
80
80
|
slack-agents build-docker agents/my-agent --push registry.example.com
|
|
81
81
|
```
|
|
82
|
+
|
|
83
|
+
## Protecting secrets in your overlay
|
|
84
|
+
|
|
85
|
+
Overlay configs reference secrets via `{ENV_VAR}` placeholders — Slack tokens, LLM API keys, and OAuth client secrets. The scaffolded `.gitignore` keeps `.env` out of git, but that's a single layer. A few minutes of setup adds defense in depth.
|
|
86
|
+
|
|
87
|
+
### 1. Enable GitHub push protection
|
|
88
|
+
|
|
89
|
+
GitHub refuses pushes that contain known provider tokens (Slack `xoxb-`/`xapp-`, Anthropic `sk-ant-`, OpenAI `sk-`, AWS, etc.) before they ever reach the remote. It cannot be bypassed by `git commit --no-verify` — the check runs server-side. Free on public repos, and included in GitHub Advanced Security on private/organisation repos.
|
|
90
|
+
|
|
91
|
+
Toggle it in **Settings → Code security → Secret scanning** (enable both *Secret scanning* and *Push protection*), or in one shot via the CLI:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
gh api -X PATCH repos/<org>/<repo> --input - <<'EOF'
|
|
95
|
+
{
|
|
96
|
+
"security_and_analysis": {
|
|
97
|
+
"secret_scanning": {"status": "enabled"},
|
|
98
|
+
"secret_scanning_push_protection": {"status": "enabled"},
|
|
99
|
+
"secret_scanning_non_provider_patterns": {"status": "enabled"}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
EOF
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 2. Add a gitleaks pre-commit hook
|
|
106
|
+
|
|
107
|
+
Catches secrets on the developer's machine before they ever reach a remote — useful as a first line of defense and as the only layer for contributors who fork the repo. Add to your overlay's `.pre-commit-config.yaml`:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
repos:
|
|
111
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
112
|
+
rev: v8.30.1 # pin to a tag; bump via `pre-commit autoupdate`
|
|
113
|
+
hooks:
|
|
114
|
+
- id: gitleaks
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then run `pre-commit install` once per clone. Pre-commit requires a pinned `rev` for reproducibility and supply-chain safety. Keep it fresh either by running `pre-commit autoupdate` periodically or by adding a `package-ecosystem: "pre-commit"` entry to `.github/dependabot.yml` so Dependabot opens hook-bump PRs.
|
|
118
|
+
|
|
119
|
+
### 3. Sweep history once
|
|
120
|
+
|
|
121
|
+
Before turning the layers above on, check whether anything already leaked. Trufflehog walks every commit in your history and reports candidate secrets:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
docker run --rm -v "$PWD:/repo" trufflesecurity/trufflehog:latest \
|
|
125
|
+
git file:///repo --no-update
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If trufflehog finds a real secret, **rotate it immediately** at the issuer (Slack, Anthropic, OpenAI, etc.). Rewriting git history with `git-filter-repo` is optional — once a token has been pushed publicly, assume it's compromised and prioritise rotation over removal.
|
|
@@ -152,7 +152,7 @@ tools:
|
|
|
152
152
|
allowed_functions: [".*"]
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
This pulls in extra runtime requirements: `
|
|
155
|
+
This pulls in extra runtime requirements: `PUBLIC_URL`,
|
|
156
156
|
`OAUTH_SECRET_KEY`, and an in-process HTTP listener for OAuth callbacks. Read
|
|
157
157
|
the OAuth doc before configuring this in production.
|
|
158
158
|
|