Adds .pi/extensions/slack/ with Socket Mode bridge, allowlist gating, and 4 LLM-callable tools (slack_post/reply/react/upload). Inbound messages bridge into pi.sendUserMessage() with ctx.isIdle() guard; turn_end posts the assistant's reply back to Slack via SlackContext. session_shutdown tears down the Socket Mode connection. Customizations preserved from portfolio-advisor reference: - threadTs field on event types (7 sites in events.ts) - threadParent routing in createSlackContext (context.ts:45) - exported parseEventContent + buildSyntheticEvent Tests: 27 new (3 files); 78 pre-existing tests still green; 114 total. Plan: .claude/plans/context-study-https-github-com-ryaneggz-binary-hartmanis.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36 lines
1.3 KiB
TypeScript
36 lines
1.3 KiB
TypeScript
// Fail-safe channel/user allowlist: denies all events if both env vars are unset; grants access only to listed IDs when set.
|
|
|
|
// Fail-safe: if BOTH SLACK_ALLOW_CHANNELS and SLACK_ALLOW_USERS are unset, isAllowed returns false for everything.
|
|
// If at least one is set, a request passes when (channel matches OR allow-channels-unset) AND (user matches OR allow-users-unset).
|
|
|
|
export interface Allowlist {
|
|
isAllowed(channelId: string, userId: string): boolean;
|
|
}
|
|
|
|
export function createAllowlist(): Allowlist {
|
|
const rawChannels = process.env.SLACK_ALLOW_CHANNELS;
|
|
const rawUsers = process.env.SLACK_ALLOW_USERS;
|
|
|
|
// Both unset → deny everything (fail-safe default)
|
|
const bothUnset = rawChannels === undefined && rawUsers === undefined;
|
|
|
|
const allowedChannels = rawChannels
|
|
? new Set(rawChannels.split(",").map((s) => s.trim()).filter(Boolean))
|
|
: null; // null means "no channel filter" (allow any channel)
|
|
|
|
const allowedUsers = rawUsers
|
|
? new Set(rawUsers.split(",").map((s) => s.trim()).filter(Boolean))
|
|
: null; // null means "no user filter" (allow any user)
|
|
|
|
return {
|
|
isAllowed(channelId: string, userId: string): boolean {
|
|
if (bothUnset) return false;
|
|
|
|
const channelOk = allowedChannels === null || allowedChannels.has(channelId);
|
|
const userOk = allowedUsers === null || allowedUsers.has(userId);
|
|
|
|
return channelOk && userOk;
|
|
},
|
|
};
|
|
}
|