diff --git a/apps/slack/slack-app-manifest.json b/apps/slack/slack-app-manifest.json index 1afcaaaff..7db47df91 100644 --- a/apps/slack/slack-app-manifest.json +++ b/apps/slack/slack-app-manifest.json @@ -74,6 +74,7 @@ "settings": { "event_subscriptions": { "bot_events": [ + "app_home_opened", "app_mention", "assistant_thread_context_changed", "assistant_thread_started", diff --git a/apps/slack/src/slack/app-home.test.ts b/apps/slack/src/slack/app-home.test.ts new file mode 100644 index 000000000..6e65ab161 --- /dev/null +++ b/apps/slack/src/slack/app-home.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "bun:test"; +import { buildAppHomeView } from "@/slack/app-home"; +import { SLACK_SUGGESTED_PROMPTS } from "@/slack/messages"; + +describe("buildAppHomeView", () => { + it("builds a home view with a header and blocks", () => { + const view = buildAppHomeView(); + expect(view.type).toBe("home"); + const blocks = view.blocks as Array>; + expect(blocks[0]).toMatchObject({ type: "header" }); + expect((blocks[0].text as { text: string }).text).toBe("Databuddy"); + expect(blocks.length).toBeGreaterThan(3); + }); + + it("lists the suggested prompts", () => { + const view = buildAppHomeView(); + const text = JSON.stringify(view); + for (const prompt of SLACK_SUGGESTED_PROMPTS) { + expect(text).toContain(prompt.message); + } + }); +}); diff --git a/apps/slack/src/slack/app-home.ts b/apps/slack/src/slack/app-home.ts new file mode 100644 index 000000000..c940176c1 --- /dev/null +++ b/apps/slack/src/slack/app-home.ts @@ -0,0 +1,43 @@ +import { SLACK_SUGGESTED_PROMPTS } from "@/slack/messages"; + +export function buildAppHomeView(): Record { + const prompts = SLACK_SUGGESTED_PROMPTS.map( + (prompt) => `• ${prompt.message}` + ).join("\n"); + + return { + type: "home", + blocks: [ + { + type: "header", + text: { type: "plain_text", text: "Databuddy" }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: "Ask about your analytics right here in Slack — traffic, pages, conversions, campaigns, errors, and product usage. Mention *@Databuddy*, send a direct message, or use the assistant.", + }, + }, + { type: "divider" }, + { + type: "section", + text: { type: "mrkdwn", text: "*Try asking*" }, + }, + { + type: "section", + text: { type: "mrkdwn", text: prompts }, + }, + { type: "divider" }, + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: "Commands: `/databuddy-status` `/databuddy-help` `/databuddy-bind`", + }, + ], + }, + ], + }; +} diff --git a/apps/slack/src/slack/listeners.ts b/apps/slack/src/slack/listeners.ts index 98614a7ce..391f3af3f 100644 --- a/apps/slack/src/slack/listeners.ts +++ b/apps/slack/src/slack/listeners.ts @@ -8,6 +8,7 @@ import { import { createRPCContext } from "@databuddy/rpc"; import { appendInvestigationReply } from "@databuddy/rpc/insights"; import type { DatabuddyAgentClient, SlackAgentRun } from "@/agent/agent-client"; +import { buildAppHomeView } from "@/slack/app-home"; import { createSlackEventLog } from "@/lib/evlog-slack"; import { abortSlackActiveRun } from "@/slack/active-runs"; import { getSlackChannelMentionPolicy } from "@/slack/channel-policy"; @@ -156,6 +157,22 @@ export function registerSlackListeners( app.assistant(createDatabuddyAssistant({ agent, dedupe, threadQueue })); + app.event("app_home_opened", async ({ client, event, logger }) => { + if (event.tab !== "home") { + return; + } + try { + await client.views.publish({ + user_id: event.user, + view: buildAppHomeView() as unknown as Parameters< + typeof client.views.publish + >[0]["view"], + }); + } catch (error) { + logger.warn("Failed to publish Slack App Home", error); + } + }); + app.event( "app_mention", async ({ body, client, context, event, logger, say }) => { diff --git a/apps/slack/src/slack/types.ts b/apps/slack/src/slack/types.ts index 833de5fd7..c25219277 100644 --- a/apps/slack/src/slack/types.ts +++ b/apps/slack/src/slack/types.ts @@ -25,6 +25,7 @@ export interface SlackAgentClient { "history" | "info" | "replies" >; reactions: Pick; + views: Pick; } export type SlackSay = (