This repository has been archived on 2026-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
orchestrator/agent-team/slack
Adam Moussa 9bfb5f1534 feat(agent-team): 👍-acknowledge received Slack answers (reactions:write)
WS Slack-UX Feature 2. When the inbound listener acts on an answer in a task
thread, it adds a 👍 reaction to that reply so the human sees the machine
received it.

- SlackListener gains an optional reactor seam; handle_event reacts to the
  inbound reply message (channel + event ts) AFTER the AUTHZ-01 owner check
  passes — a non-owner message is rejected and never reacted to. Best-effort:
  any reaction failure (notably a missing scope) is swallowed and never breaks
  handle_event or the listen loop.
- /new-task is NOT reacted to (a slash command has no reactable message); its
  "📥 Task received" root post is the acknowledgement.
- build_slack_reactor wraps WebClient.reactions_add(name="thumbsup"); the
  default listener factory wires it best-effort from SLACK_BOT_TOKEN.
- Adds reactions:write to the bot scopes in agent-team-manifest.json.

NOTE: the new reactions:write scope requires Adam to re-apply the manifest to
app A0BCC7TTU66 and reinstall the app. Until then reactions.add returns
missing_scope, which the listener swallows (the reaction silently no-ops) —
answer handling is unaffected.

AUTHZ-01 and the first-answer-wins CAS remain unchanged.
2026-06-23 15:49:40 -04:00
..
agent-team-manifest.json feat(agent-team): 👍-acknowledge received Slack answers (reactions:write) 2026-06-23 15:49:40 -04:00
README.md docs(agent-team): slack app is now workspace-level A0BCC7TTU66 (org app deleted) (#27) 2026-06-22 15:47:24 -04:00

agent-team Slack app

Dedicated Slack app backing the Plane-2 clarifier human-gate (Socket Mode). Kept separate from the webhook-only Tech Notifications app (A0ARYQZU3KJ) that the nightly secrev sweep uses, to isolate the two-way bot's trust surface.

Field Value
App name Sea Haven agent-team
App ID A0BCC7TTU66
Bot agent-team (handle @agentteam2) · user id U0BCFSJ7SUC
Scope Workspace-level app, installed to Sea Haven Industries (T0A46CP6QR3)
Manifest agent-team-manifest.json (source of truth)
Settings https://api.slack.com/apps/A0BCC7TTU66

⚠️ Must be a WORKSPACE-LEVEL app (hard lesson, 2026-06-22)

Socket Mode event delivery only works for workspace-level apps. An org-owned app (one created via the Enterprise org/config token / apps.manifest.create, even when workspace-granted with --org-workspace-grant) connects the socket but receives ZERO workspace Events API events — outbound chat.postMessage works, but inbound message/app_mention are never delivered, so the clarifier never hears answers. The first build hit exactly this with the now-deleted org app A0BC7AT8NUD. Create this app from the dashboard (api.slack.com/apps → Create New App → From manifest) and pick the workspace "Sea Haven Industries" as the dev workspace, NOT the Enterprise org. Then a normal Install to Workspace works (no org-grant dance). Do not recreate it via the org config token.

Why these scopes (verified against the code)

agent_team/transport/slack_listener.py subscribes over Socket Mode to message, app_mention, and Block Kit block_actions; slack_live.py posts questions via chat.postMessage. Inbound message/app_mention arrive as the Events API envelope {"type":"event_callback","event":{...}} and a free-text thread reply is matched to its question by thread_ts == channel_ref (see find_open_question_by_channel_ref).

Capability Scope / setting Why
Post clarifier questions chat:write slack_live.py chat.postMessage
Hear thread replies channels:history / groups:history + message.channels/message.groups @app.event("message")
Hear DM replies im:history + message.im DM answer path
Hear @mentions app_mentions:read + app_mention @app.event("app_mention")
Block Kit answers interactivity.is_enabled @app.action(...)
Inbound WebSocket socket_mode_enabled + app-level token w/ connections:write VPN-only box, no public HTTPS endpoint

Inbound auth is NOT scope-based: AUTHZ-01 (AGENT_TEAM_SLACK_OWNER_IDS) gates the sender and fails closed. Socket membership alone is never authorization.

Setup (operator, in browser — secrets never echoed)

  1. Create the app — api.slack.com/apps → Create New App → From an app manifest → pick workspace "Sea Haven Industries" → paste agent-team-manifest.json.
  2. Install to Workspace → copy the Bot User OAuth Token (xoxb-) → SLACK_BOT_TOKEN.
  3. Basic Information → App-Level Tokens → Generate with scope connections:write → SLACK_APP_TOKEN.
  4. Channel — /invite @agentteam2 into the clarifier channel; its C0… id → SLACK_CHANNEL_ID.
  5. Owner allowlist — AGENT_TEAM_SLACK_OWNER_IDS = Adam's Slack user id (U0A3SC48T47), comma-separated if more than one. Fails closed if empty.

All five land in ~/secrev.env on the box (mode 600), never shell history.

Updating the app from the manifest

Edit agent-team-manifest.json, then in the dashboard (App Manifest tab) paste the updated manifest, or use apps.manifest.update with an app config token scoped to the workspace app (the org config token can read/manage it as org owner, but keep the app workspace-level — do not re-create it org-owned).