Skip to content

Commit 11b44d9

Browse files
baudbot-agentBaudbotbenvinegar
authored
feat(bridge): GitHub webhook event dispatch and formatting (#181)
Co-authored-by: Baudbot <hornet@agentmail.to> Co-authored-by: Ben Vinegar <2153+benvinegar@users.noreply.github.com>
1 parent caa669f commit 11b44d9

9 files changed

Lines changed: 1020 additions & 15 deletions

.env.schema

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT=
152152
# @sensitive=false @type=string
153153
SLACK_BROKER_ACCESS_TOKEN_SCOPES=
154154

155+
# Optional comma-separated GitHub logins ignored by broker GitHub event forwarding.
156+
# "baudbot-agent" is always ignored to prevent bot loops.
157+
# @sensitive=false @type=string
158+
# @example="dependabot[bot],renovate[bot]"
159+
GITHUB_IGNORED_USERS=
160+
155161
# Optional agent version override used in broker observability metadata.
156162
# If unset, broker bridge falls back to ~/.pi/agent/baudbot-version.json (if present).
157163
# @sensitive=false @type=string

CONFIGURATION.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Set by `sudo baudbot broker register` when using brokered Slack OAuth flow.
107107
| `SLACK_BROKER_ACCESS_TOKEN` | Broker-issued bearer token for broker API auth (required for broker pull mode runtime) |
108108
| `SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT` | ISO timestamp for broker token expiry (recommended; runtime exits if expired) |
109109
| `SLACK_BROKER_ACCESS_TOKEN_SCOPES` | Comma-separated broker token scopes |
110+
| `GITHUB_IGNORED_USERS` | Optional comma-separated GitHub logins to ignore when forwarding broker GitHub events (`baudbot-agent` is always ignored) |
110111
| `SLACK_BROKER_POLL_INTERVAL_MS` | Inbox poll interval in milliseconds (default: `3000`) |
111112
| `SLACK_BROKER_MAX_MESSAGES` | Max leased messages per poll request (default: `10`) |
112113
| `SLACK_BROKER_WAIT_SECONDS` | Long-poll wait window for `/api/inbox/pull` (default: `20`, set `0` for immediate short-poll, max `25`) |
@@ -218,6 +219,8 @@ SLACK_BROKER_WORKSPACE_ID=T0123ABCD
218219
# SLACK_BROKER_ACCESS_TOKEN=...
219220
# SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT=2026-02-22T22:15:00.000Z
220221
# SLACK_BROKER_ACCESS_TOKEN_SCOPES=slack.send,inbox.pull,inbox.ack
222+
# Optional GitHub bot/user filters for broker-delivered GitHub webhook events
223+
# GITHUB_IGNORED_USERS=dependabot[bot],renovate[bot]
221224
SLACK_BROKER_POLL_INTERVAL_MS=3000
222225
SLACK_BROKER_MAX_MESSAGES=10
223226
SLACK_BROKER_WAIT_SECONDS=20

slack-bridge/broker-bridge.mjs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import {
2323
createRateLimiter,
2424
sanitizeOutboundText,
2525
} from "./security.mjs";
26+
import {
27+
formatGitHubEvent,
28+
shouldSkipEvent,
29+
parseIgnoredUsers,
30+
extractActor,
31+
} from "./github-events.mjs";
2632
import {
2733
canonicalizeEnvelope,
2834
canonicalizeProtocolRequest,
@@ -135,6 +141,8 @@ if (ALLOWED_USERS.length === 0) {
135141
logWarn("⚠️ SLACK_ALLOWED_USERS not set — all workspace members can interact");
136142
}
137143

144+
const GITHUB_IGNORED_USERS = parseIgnoredUsers(process.env.GITHUB_IGNORED_USERS);
145+
138146
const slackRateLimiter = createRateLimiter({ maxRequests: 5, windowMs: 60_000 });
139147
const apiRateLimiter = createRateLimiter({ maxRequests: 30, windowMs: 60_000 });
140148

@@ -864,6 +872,46 @@ async function handleSlackPayload(slackEventEnvelopePayload) {
864872
return true;
865873
}
866874

875+
async function handleGitHubEvent(type, payload) {
876+
const actor = extractActor(type, payload);
877+
const repo = payload?.repository?.full_name || "unknown/repo";
878+
logInfo(`🐙 github event: ${type} (action: ${payload?.action || "n/a"}) repo: ${repo} actor: ${actor || "n/a"}`);
879+
880+
// Filtering: skip noisy or self-generated events
881+
const skipReason = shouldSkipEvent(type, payload, GITHUB_IGNORED_USERS);
882+
if (skipReason) {
883+
logInfo(` ↳ skipping: ${skipReason}`);
884+
return true;
885+
}
886+
887+
const { message, isPing, isUnknown } = formatGitHubEvent(type, payload);
888+
889+
if (isPing) {
890+
logInfo(" ↳ ping event — webhook configured successfully");
891+
return true;
892+
}
893+
894+
if (isUnknown) {
895+
logWarn(` ↳ unhandled github event type: ${type} — forwarding minimal summary`);
896+
}
897+
898+
if (!message) {
899+
logWarn(` ↳ formatter returned no message for ${type} — skipping`);
900+
return true;
901+
}
902+
903+
refreshSocket();
904+
const currentSocket = socketPath;
905+
if (!currentSocket) {
906+
logError("🔌 no pi socket found for github event — agent may not be running");
907+
return true;
908+
}
909+
910+
await enqueue(() => sendToAgent(currentSocket, message));
911+
logInfo(` ↳ forwarded to agent`);
912+
return true;
913+
}
914+
867915
async function handleDashboardEvent(type, payload) {
868916
logInfo(`📊 dashboard event: ${type}`, JSON.stringify(payload).slice(0, 200));
869917
// TODO: implement dashboard event handling (env updates, config changes)
@@ -896,6 +944,8 @@ async function processPulledMessage(message) {
896944
switch (payload.source) {
897945
case "slack":
898946
return handleSlackPayload(payload.payload);
947+
case "github":
948+
return handleGitHubEvent(payload.type, payload.payload);
899949
case "dashboard":
900950
return handleDashboardEvent(payload.type, payload.payload);
901951
case "system":

0 commit comments

Comments
 (0)