Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.schema
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT=
# @sensitive=false @type=string
SLACK_BROKER_ACCESS_TOKEN_SCOPES=

# Optional comma-separated GitHub logins ignored by broker GitHub event forwarding.
# "baudbot-agent" is always ignored to prevent bot loops.
# @sensitive=false @type=string
# @example="dependabot[bot],renovate[bot]"
GITHUB_IGNORED_USERS=

# Optional agent version override used in broker observability metadata.
# If unset, broker bridge falls back to ~/.pi/agent/baudbot-version.json (if present).
# @sensitive=false @type=string
Expand Down
3 changes: 3 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Set by `sudo baudbot broker register` when using brokered Slack OAuth flow.
| `SLACK_BROKER_ACCESS_TOKEN` | Broker-issued bearer token for broker API auth (required for broker pull mode runtime) |
| `SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT` | ISO timestamp for broker token expiry (recommended; runtime exits if expired) |
| `SLACK_BROKER_ACCESS_TOKEN_SCOPES` | Comma-separated broker token scopes |
| `GITHUB_IGNORED_USERS` | Optional comma-separated GitHub logins to ignore when forwarding broker GitHub events (`baudbot-agent` is always ignored) |
| `SLACK_BROKER_POLL_INTERVAL_MS` | Inbox poll interval in milliseconds (default: `3000`) |
| `SLACK_BROKER_MAX_MESSAGES` | Max leased messages per poll request (default: `10`) |
| `SLACK_BROKER_WAIT_SECONDS` | Long-poll wait window for `/api/inbox/pull` (default: `20`, set `0` for immediate short-poll, max `25`) |
Expand Down Expand Up @@ -218,6 +219,8 @@ SLACK_BROKER_WORKSPACE_ID=T0123ABCD
# SLACK_BROKER_ACCESS_TOKEN=...
# SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT=2026-02-22T22:15:00.000Z
# SLACK_BROKER_ACCESS_TOKEN_SCOPES=slack.send,inbox.pull,inbox.ack
# Optional GitHub bot/user filters for broker-delivered GitHub webhook events
# GITHUB_IGNORED_USERS=dependabot[bot],renovate[bot]
SLACK_BROKER_POLL_INTERVAL_MS=3000
SLACK_BROKER_MAX_MESSAGES=10
SLACK_BROKER_WAIT_SECONDS=20
Expand Down
50 changes: 50 additions & 0 deletions slack-bridge/broker-bridge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import {
createRateLimiter,
sanitizeOutboundText,
} from "./security.mjs";
import {
formatGitHubEvent,
shouldSkipEvent,
parseIgnoredUsers,
extractActor,
} from "./github-events.mjs";
import {
canonicalizeEnvelope,
canonicalizeProtocolRequest,
Expand Down Expand Up @@ -135,6 +141,8 @@ if (ALLOWED_USERS.length === 0) {
logWarn("⚠️ SLACK_ALLOWED_USERS not set — all workspace members can interact");
}

const GITHUB_IGNORED_USERS = parseIgnoredUsers(process.env.GITHUB_IGNORED_USERS);

const slackRateLimiter = createRateLimiter({ maxRequests: 5, windowMs: 60_000 });
const apiRateLimiter = createRateLimiter({ maxRequests: 30, windowMs: 60_000 });

Expand Down Expand Up @@ -864,6 +872,46 @@ async function handleSlackPayload(slackEventEnvelopePayload) {
return true;
}

async function handleGitHubEvent(type, payload) {
const actor = extractActor(type, payload);
const repo = payload?.repository?.full_name || "unknown/repo";
logInfo(`🐙 github event: ${type} (action: ${payload?.action || "n/a"}) repo: ${repo} actor: ${actor || "n/a"}`);

// Filtering: skip noisy or self-generated events
const skipReason = shouldSkipEvent(type, payload, GITHUB_IGNORED_USERS);
if (skipReason) {
logInfo(` ↳ skipping: ${skipReason}`);
return true;
}

const { message, isPing, isUnknown } = formatGitHubEvent(type, payload);

if (isPing) {
logInfo(" ↳ ping event — webhook configured successfully");
return true;
}

if (isUnknown) {
logWarn(` ↳ unhandled github event type: ${type} — forwarding minimal summary`);
}

if (!message) {
logWarn(` ↳ formatter returned no message for ${type} — skipping`);
return true;
}

refreshSocket();
const currentSocket = socketPath;
if (!currentSocket) {
logError("🔌 no pi socket found for github event — agent may not be running");
return true;
}

await enqueue(() => sendToAgent(currentSocket, message));
logInfo(` ↳ forwarded to agent`);
return true;
}

async function handleDashboardEvent(type, payload) {
logInfo(`📊 dashboard event: ${type}`, JSON.stringify(payload).slice(0, 200));
// TODO: implement dashboard event handling (env updates, config changes)
Expand Down Expand Up @@ -896,6 +944,8 @@ async function processPulledMessage(message) {
switch (payload.source) {
case "slack":
return handleSlackPayload(payload.payload);
case "github":
return handleGitHubEvent(payload.type, payload.payload);
case "dashboard":
return handleDashboardEvent(payload.type, payload.payload);
case "system":
Expand Down
Loading