python-slack-agents 0.8.1__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.
Files changed (154) hide show
  1. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/.gitignore +2 -0
  2. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/.pre-commit-config.yaml +5 -0
  3. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/CHANGELOG.md +21 -3
  4. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/PKG-INFO +4 -3
  5. python_slack_agents-0.9.0/SECURITY.md +13 -0
  6. python_slack_agents-0.9.0/agents/a2a-agent/config.yaml +67 -0
  7. python_slack_agents-0.9.0/agents/a2a-agent/system_prompt.txt +1 -0
  8. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/docs-assistant/system_prompt.txt +1 -1
  9. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/hello-world/system_prompt.txt +0 -1
  10. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/kitchen-sink/config.yaml +1 -1
  11. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/kitchen-sink/system_prompt.txt +1 -1
  12. python_slack_agents-0.9.0/docs/a2a.md +336 -0
  13. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/oauth.md +6 -6
  14. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/private-repo.md +47 -0
  15. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/tools.md +1 -1
  16. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/llms-full.txt +682 -1
  17. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/llms.txt +3 -2
  18. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/pyproject.toml +4 -3
  19. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/__init__.py +7 -0
  20. python_slack_agents-0.9.0/src/slack_agents/a2a/agent.py +228 -0
  21. python_slack_agents-0.9.0/src/slack_agents/a2a/client.py +276 -0
  22. python_slack_agents-0.9.0/src/slack_agents/a2a/delivery.py +99 -0
  23. python_slack_agents-0.9.0/src/slack_agents/a2a/proxy.py +113 -0
  24. python_slack_agents-0.9.0/src/slack_agents/a2a/push.py +198 -0
  25. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/init.py +15 -0
  26. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/config.py +67 -16
  27. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/main.py +5 -5
  28. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/scripts/generate_llms_full.py +2 -0
  29. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/agent.py +140 -28
  30. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/mcp_http_oauth.py +2 -2
  31. python_slack_agents-0.9.0/tests/__init__.py +0 -0
  32. python_slack_agents-0.9.0/tests/a2a/__init__.py +0 -0
  33. python_slack_agents-0.9.0/tests/a2a/test_agent_async.py +70 -0
  34. python_slack_agents-0.9.0/tests/a2a/test_agent_sync.py +140 -0
  35. python_slack_agents-0.9.0/tests/a2a/test_client.py +22 -0
  36. python_slack_agents-0.9.0/tests/a2a/test_client_files.py +46 -0
  37. python_slack_agents-0.9.0/tests/a2a/test_delivery.py +99 -0
  38. python_slack_agents-0.9.0/tests/a2a/test_end_to_end.py +65 -0
  39. python_slack_agents-0.9.0/tests/a2a/test_framework_delivery.py +44 -0
  40. python_slack_agents-0.9.0/tests/a2a/test_integration.py +58 -0
  41. python_slack_agents-0.9.0/tests/a2a/test_proxy.py +83 -0
  42. python_slack_agents-0.9.0/tests/a2a/test_push.py +173 -0
  43. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_access.py +3 -0
  44. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_oauth_integration.py +1 -1
  45. python_slack_agents-0.9.0/tests/test_oauth_validation.py +91 -0
  46. python_slack_agents-0.9.0/uv.lock +2335 -0
  47. python_slack_agents-0.8.1/SECURITY.md +0 -9
  48. python_slack_agents-0.8.1/tests/test_oauth_validation.py +0 -77
  49. python_slack_agents-0.8.1/uv.lock +0 -2108
  50. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/.dockerignore +0 -0
  51. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/.env.example +0 -0
  52. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/.github/workflows/ci.yml +0 -0
  53. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/.github/workflows/publish.yml +0 -0
  54. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/AGENTS.md +0 -0
  55. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/CODE_OF_CONDUCT.md +0 -0
  56. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/CONTRIBUTING.md +0 -0
  57. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/LICENSE +0 -0
  58. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/README.md +0 -0
  59. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/README.md +0 -0
  60. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/docs-assistant/config.yaml +0 -0
  61. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/agents/hello-world/config.yaml +0 -0
  62. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/access-control.md +0 -0
  63. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/agents.md +0 -0
  64. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/canvas.md +0 -0
  65. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/cli.md +0 -0
  66. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/deployment.md +0 -0
  67. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/llm.md +0 -0
  68. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/media/demo.gif +0 -0
  69. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/observability.md +0 -0
  70. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/setup.md +0 -0
  71. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/slack-app-manifest.json +0 -0
  72. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/storage.md +0 -0
  73. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/docs/user-context.md +0 -0
  74. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/Dockerfile +0 -0
  75. {python_slack_agents-0.8.1/src/slack_agents/access → python_slack_agents-0.9.0/src/slack_agents/a2a}/__init__.py +0 -0
  76. {python_slack_agents-0.8.1/src/slack_agents/scripts → python_slack_agents-0.9.0/src/slack_agents/access}/__init__.py +0 -0
  77. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/access/allow_all.py +0 -0
  78. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/access/allow_list.py +0 -0
  79. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/access/base.py +0 -0
  80. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/agent_loop.py +0 -0
  81. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/__init__.py +0 -0
  82. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/build_docker.py +0 -0
  83. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_conversations.py +0 -0
  84. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_conversations_html.py +0 -0
  85. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_usage.py +0 -0
  86. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/export_usage_csv.py +0 -0
  87. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/healthcheck.py +0 -0
  88. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/cli/run.py +0 -0
  89. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/conversations.py +0 -0
  90. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/files.py +0 -0
  91. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/llm/__init__.py +0 -0
  92. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/llm/anthropic.py +0 -0
  93. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/llm/base.py +0 -0
  94. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/llm/openai.py +0 -0
  95. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/oauth/__init__.py +0 -0
  96. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/oauth/crypto.py +0 -0
  97. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/oauth/prompts.py +0 -0
  98. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/oauth/server.py +0 -0
  99. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/oauth/state.py +0 -0
  100. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/oauth/storage.py +0 -0
  101. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/observability.py +0 -0
  102. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/py.typed +0 -0
  103. {python_slack_agents-0.8.1/src/slack_agents/slack → python_slack_agents-0.9.0/src/slack_agents/scripts}/__init__.py +0 -0
  104. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/scripts/download_fonts.py +0 -0
  105. {python_slack_agents-0.8.1/src/slack_agents/storage → python_slack_agents-0.9.0/src/slack_agents/slack}/__init__.py +0 -0
  106. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/actions.py +0 -0
  107. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/canvas_auth.py +0 -0
  108. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/canvases.py +0 -0
  109. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/files.py +0 -0
  110. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/format.py +0 -0
  111. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/streaming.py +0 -0
  112. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/streaming_formatter.py +0 -0
  113. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/slack/tool_blocks.py +0 -0
  114. {python_slack_agents-0.8.1/src/slack_agents/tools → python_slack_agents-0.9.0/src/slack_agents/storage}/__init__.py +0 -0
  115. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/storage/base.py +0 -0
  116. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/storage/postgres.py +0 -0
  117. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/storage/postgres.sql +0 -0
  118. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/storage/sqlite.py +0 -0
  119. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/storage/sqlite.sql +0 -0
  120. {python_slack_agents-0.8.1/tests → python_slack_agents-0.9.0/src/slack_agents/tools}/__init__.py +0 -0
  121. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/base.py +0 -0
  122. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/canvas.py +0 -0
  123. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/canvas_importer.py +0 -0
  124. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/file_exporter.py +0 -0
  125. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/file_importer.py +0 -0
  126. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/mcp_http.py +0 -0
  127. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/src/slack_agents/tools/user_context.py +0 -0
  128. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_agent_loop.py +0 -0
  129. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_canvas_auth.py +0 -0
  130. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_canvas_importer.py +0 -0
  131. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_cli.py +0 -0
  132. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_config.py +0 -0
  133. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_conversations.py +0 -0
  134. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_cost.py +0 -0
  135. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_export_documents.py +0 -0
  136. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_export_usage.py +0 -0
  137. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_file_extractors.py +0 -0
  138. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_format.py +0 -0
  139. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_init.py +0 -0
  140. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_llm_error_classification.py +0 -0
  141. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_llm_factory.py +0 -0
  142. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_load_plugin.py +0 -0
  143. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_mcp_client.py +0 -0
  144. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_mcp_http_oauth.py +0 -0
  145. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_oauth_crypto.py +0 -0
  146. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_oauth_prompts.py +0 -0
  147. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_oauth_server.py +0 -0
  148. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_oauth_state.py +0 -0
  149. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_oauth_storage.py +0 -0
  150. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_openai_convert.py +0 -0
  151. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_overlay_integration.py +0 -0
  152. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_storage_oauth.py +0 -0
  153. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_tool_blocks.py +0 -0
  154. {python_slack_agents-0.8.1 → python_slack_agents-0.9.0}/tests/test_tool_errors.py +0 -0
@@ -34,6 +34,8 @@ fonts/
34
34
  Thumbs.db
35
35
 
36
36
  agents-local/
37
+ # Local-only agent variants (e.g. agents/a2a-agent-local/)
38
+ *-local/
37
39
 
38
40
  # Certificates
39
41
  *.pem
@@ -24,3 +24,8 @@ repos:
24
24
  - id: check-yaml
25
25
  - id: check-added-large-files
26
26
  - id: check-merge-conflict
27
+
28
+ - repo: https://github.com/gitleaks/gitleaks
29
+ rev: v8.30.1
30
+ hooks:
31
+ - id: gitleaks
@@ -1,4 +1,4 @@
1
- we don# Changelog
1
+ # Changelog
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
@@ -6,6 +6,24 @@ 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
+
9
27
  ## [0.8.1] - 2026-05-07
10
28
 
11
29
  ### Fixed
@@ -101,7 +119,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
101
119
 
102
120
  ### Added
103
121
 
104
- - `slack-agents init` now generates `.gitignore`
122
+ - `slack-agents init` now generates `.gitignore`
105
123
  - `.env.example` template includes comments explaining where to get each token and links to setup guide
106
124
  - `build-docker` lists required environment variables after build completes
107
125
  - `build-docker` errors if `req*.txt` files are found (dependencies must be in `pyproject.toml`)
@@ -109,7 +127,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
109
127
 
110
128
  ### Changed
111
129
 
112
- - `pyproject.toml` template uses `python-slack-agents<2` (no minimum pin)
130
+ - `pyproject.toml` template uses `python-slack-agents<2` (no minimum pin)
113
131
  - Setup flow uses venv-first approach: create venv, install package, then `slack-agents init`
114
132
  - Updated README, docs/setup.md, and docs/private-repo.md with new setup flow
115
133
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-slack-agents
3
- Version: 0.8.1
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<46,>=42
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<9,>=8.0; extra == 'dev'
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.
@@ -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.
@@ -1,3 +1,2 @@
1
1
  You are a helpful assistant. Be concise and friendly.
2
2
  Ask people if they want to know the secret but only tell them if they ask: "yellow submarine".
3
-
@@ -146,4 +146,4 @@ tools:
146
146
  # model: "langfuse.observation.model.name"
147
147
  # input_tokens: "gen_ai.usage.input_tokens"
148
148
  # output_tokens: "gen_ai.usage.output_tokens"
149
- # usage: "langfuse.observation.usage_details"
149
+ # usage: "langfuse.observation.usage_details"
@@ -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
- | `OAUTH_PUBLIC_URL` | yes | — | Externally reachable base URL of this agent process. Must be `https://`, or `http://` with a loopback host (`localhost`, `127.0.0.1`, `[::1]`) for local dev. |
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
- | `OAUTH_BIND_HOST` | no | `0.0.0.0` | Interface the in-process callback listener binds to. |
41
- | `OAUTH_BIND_PORT` | no | `8080` | TCP port for the callback listener. |
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 OAUTH_PUBLIC_URL=https://abcd-1234.ngrok-free.app
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
- `OAUTH_PUBLIC_URL=http://localhost:8080` — the validator allows loopback
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: ... OAUTH_PUBLIC_URL is not set"** — set the env vars
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: `OAUTH_PUBLIC_URL`,
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