From 72322a4d722ce9ba8dd26cb5925d467572671450 Mon Sep 17 00:00:00 2001
From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com>
Date: Thu, 30 Jul 2026 15:06:38 +0530
Subject: [PATCH] feat: Slack conversational assistant (#22984)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Lets workspace members talk to the Twenty CRM agent from Slack —
`@mention` the bot in a channel or DM it, and it answers in-thread using
the `slack-assistant` agent and its assigned role.
## How it works
Slack Events webhook → app route verifies signature → **ack in <3s** and
enqueue a `slackAssistantRequest` → worker posts a placeholder
immediately, then fetches recent thread/DM history (excluding the
current message and placeholder), runs `runAgent`, and updates the
placeholder with the answer. After a successful reply, the thread stays
subscribed (24h TTL, renewed on each reply) so follow-ups work without
re-mentioning.
## App-owned orchestration
Protocol + orchestration live in `twenty-apps/public/twenty-slack`
(events resolver, enqueue, worker, team claim KV, thread subscription).
The server provides shared primitives (app routes, `runAgent`, app KV,
connection OAuth).
## Notes
- Agent role is bound via `roleUniversalIdentifier` on install. Default
**Slack Assistant** role: read/create/update/soft-delete on people,
companies, opportunities, notes, and tasks; **workspace members stay
read-only**; hard destroy stays off. Admins can tighten the role in
Settings.
- Setup (signing secret, event subscriptions, scopes) is in the app
README.
- Long-lived Slack bot tokens (no refresh token) are treated as
non-expiring.
- Multi-turn: recent Slack thread/DM messages are prepended into the
agent prompt.
- Replies are non-streaming for now (placeholder + final `chat.update`);
progressive streaming is a follow-up.
## Follow-ups
- **Streaming replies** — progressive edits while the agent runs.
- **Per-user / per-channel permissions** — Slack→Twenty user mapping and
optional channel rules (open by default; admins can narrow).
- **Other platforms** — Discord/Teams can reuse the same patterns; only
Slack protocol is in this PR.
## Screenshots
https://github.com/user-attachments/assets/3a72770a-93fa-411d-b4aa-2f741afbcee1
---
.../public/{twenty-slack => slack}/.gitignore | 0
.../public/{twenty-slack => slack}/.nvmrc | 0
.../{twenty-slack => slack}/.oxlintrc.json | 0
.../{twenty-slack => slack}/.yarnrc.yml | 0
packages/twenty-apps/public/slack/README.md | 39 ++++
packages/twenty-apps/public/slack/SETUP.md | 92 ++++++++
.../{twenty-slack => slack}/package.json | 8 +-
.../public/twenty-slack.svg | 0
.../src/__tests__/global-setup.ts | 0
.../src/__tests__/slack.integration-test.ts} | 0
.../slack/src/agents/slack-assistant.agent.ts | 19 ++
.../src/application.config.ts | 14 +-
.../send-slack-message.command-menu-item.ts | 0
.../send-message-form.front-component.tsx | 0
.../connection-providers/slack-connection.ts | 13 +-
.../default-slack-assistant-prompt.ts | 10 +
.../src/constants/universal-identifiers.ts | 101 +++++++++
...k-assistant-request-slack-message.index.ts | 32 +++
.../constants/slack-assistant-failure-text.ts | 2 +
.../slack-assistant-placeholder-text.ts | 1 +
.../slack-assistant-progress-steps.ts | 10 +
.../slack-assistant-request-status.ts | 6 +
...slack-assistant-thinking-reaction-emoji.ts | 1 +
.../slack-assistant-transient-texts.ts | 7 +
.../slack-assistant-worker-timeout-seconds.ts | 1 +
.../data/create-slack-assistant-request.ts | 30 +++
...lack-assistant-request-by-slack-message.ts | 24 ++
.../data/update-slack-assistant-request.ts | 35 +++
.../slack-post-message-handler.test.ts | 0
.../handlers/slack-add-reaction-handler.ts | 11 +
.../handlers/slack-delete-message-handler.ts | 0
.../handlers/slack-list-channels-handler.ts | 0
.../slack-post-ephemeral-message-handler.ts | 0
.../handlers/slack-post-message-handler.ts | 0
.../handlers/slack-team-claim-handler.ts | 7 +
.../handlers/slack-update-message-handler.ts | 28 ++-
.../slack-add-reaction-input.schema.ts | 0
.../slack-delete-message-input.schema.ts | 0
.../slack-list-channels-input.schema.ts | 0
...ack-post-ephemeral-message-input.schema.ts | 0
.../slack-post-message-input.schema.ts | 0
.../slack-update-message-input.schema.ts | 0
.../src/logic-functions/slack-add-reaction.ts | 0
.../logic-functions/slack-assistant-worker.ts | 206 ++++++++++++++++++
.../logic-functions/slack-delete-message.ts | 0
.../logic-functions/slack-events-enqueue.ts | 13 ++
.../logic-functions/slack-events-resolver.ts | 76 +++++++
.../slack-list-channels-route.ts | 0
.../logic-functions/slack-list-channels.ts | 0
.../slack-post-ephemeral-message.ts | 0
.../slack-post-message-route.ts | 0
.../src/logic-functions/slack-post-message.ts | 0
.../src/logic-functions/slack-team-claim.ts | 13 ++
.../logic-functions/slack-update-message.ts | 0
.../types/slack-add-reaction-input.type.ts | 0
.../slack-assistant-progress-step.type.ts | 4 +
.../slack-assistant-request-draft.type.ts | 9 +
.../slack-assistant-request-record.type.ts | 10 +
.../types/slack-delete-message-input.type.ts | 0
.../types/slack-events-request-body.type.ts | 19 ++
.../types/slack-list-channels-input.type.ts | 0
.../types/slack-list-channels-result.type.ts | 0
.../types/slack-message-body-format.type.ts | 0
...slack-post-ephemeral-message-input.type.ts | 0
.../types/slack-post-message-input.type.ts | 0
.../types/slack-team-claim-payload.type.ts | 5 +
.../types/slack-thread-reference.type.ts | 4 +
.../types/slack-thread-subscription.type.ts | 3 +
.../types/slack-tool-result.type.ts | 0
.../types/slack-update-message-input.type.ts | 0
...get-slack-chat-message-body-fields.test.ts | 0
.../__tests__/get-slack-connection.test.ts | 0
.../parse-slack-assistant-request.test.ts | 160 ++++++++++++++
.../__tests__/slack-tool-failure.test.ts | 0
.../verify-slack-request-signature.test.ts | 77 +++++++
.../build-slack-assistant-answer-text.ts | 10 +
.../utils/build-slack-assistant-prompt.ts | 31 +++
.../build-slack-assistant-request-name.ts | 11 +
.../logic-functions/utils/claim-slack-team.ts | 38 ++++
...clear-slack-assistant-thinking-reaction.ts | 17 ++
.../utils/enqueue-slack-assistant-request.ts | 62 ++++++
.../utils/extract-agent-response-text.ts | 28 +++
.../utils/fetch-slack-assistant-context.ts | 45 ++++
.../utils/fetch-slack-conversation-context.ts | 105 +++++++++
.../utils/fetch-slack-requester-name.ts | 28 +++
...sh-slack-assistant-request-with-failure.ts | 46 ++++
.../utils/format-slack-assistant-duration.ts | 24 ++
.../utils/get-slack-api-error-code.ts | 15 ++
...lack-assistant-parent-message-timestamp.ts | 17 ++
.../get-slack-chat-message-body-fields.ts | 0
.../logic-functions/utils/get-slack-client.ts | 0
.../utils/get-slack-connection.ts | 0
.../utils/get-slack-team-kv-key.ts | 2 +
.../utils/get-slack-thread-kv-key.ts | 7 +
.../utils/get-slack-webhook-secret.ts | 19 ++
.../utils/is-duplicate-record-error.ts | 7 +
.../utils/is-slack-markdown-format-error.ts | 17 ++
.../utils/is-slack-thread-active.ts | 30 +++
.../utils/json-schema-to-input-schema.ts | 0
.../utils/parse-slack-assistant-request.ts | 82 +++++++
.../utils/resolve-target-workspace-id.ts | 29 +++
...run-slack-assistant-agent-with-progress.ts | 28 +++
.../utils/run-slack-reaction.ts | 51 +++++
.../utils/slack-tool-failure.ts | 0
.../start-slack-assistant-progress-updates.ts | 34 +++
.../utils/subscribe-slack-thread.ts | 26 +++
.../utils/verify-slack-request-signature.ts | 46 ++++
.../objects/slack-assistant-request.object.ts | 159 ++++++++++++++
.../slack/src/roles/default-function.role.ts | 33 +++
.../slack/src/roles/slack-assistant.role.ts | 55 +++++
.../{twenty-slack => slack}/tsconfig.json | 0
.../tsconfig.spec.json | 0
.../{twenty-slack => slack}/vitest.config.ts | 0
.../vitest.unit.config.ts | 0
.../public/{twenty-slack => slack}/yarn.lock | 26 +--
.../twenty-apps/public/twenty-slack/README.md | 110 ----------
.../src/constants/universal-identifiers.ts | 38 ----
.../slack-add-reaction-handler.test.ts | 79 -------
.../handlers/slack-add-reaction-handler.ts | 37 ----
.../src/roles/default-function.role.ts | 21 --
...onnected-account-refresh-tokens.service.ts | 8 +
121 files changed, 2196 insertions(+), 315 deletions(-)
rename packages/twenty-apps/public/{twenty-slack => slack}/.gitignore (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/.nvmrc (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/.oxlintrc.json (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/.yarnrc.yml (100%)
create mode 100644 packages/twenty-apps/public/slack/README.md
create mode 100644 packages/twenty-apps/public/slack/SETUP.md
rename packages/twenty-apps/public/{twenty-slack => slack}/package.json (84%)
rename packages/twenty-apps/public/{twenty-slack => slack}/public/twenty-slack.svg (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/__tests__/global-setup.ts (100%)
rename packages/twenty-apps/public/{twenty-slack/src/__tests__/twenty-slack.integration-test.ts => slack/src/__tests__/slack.integration-test.ts} (100%)
create mode 100644 packages/twenty-apps/public/slack/src/agents/slack-assistant.agent.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/application.config.ts (59%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/command-menu-items/send-slack-message.command-menu-item.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/components/send-message-form.front-component.tsx (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/connection-providers/slack-connection.ts (64%)
create mode 100644 packages/twenty-apps/public/slack/src/constants/default-slack-assistant-prompt.ts
create mode 100644 packages/twenty-apps/public/slack/src/constants/universal-identifiers.ts
create mode 100644 packages/twenty-apps/public/slack/src/indexes/slack-assistant-request-slack-message.index.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-failure-text.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-placeholder-text.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-progress-steps.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-request-status.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-thinking-reaction-emoji.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-transient-texts.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-worker-timeout-seconds.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/data/create-slack-assistant-request.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/data/find-slack-assistant-request-by-slack-message.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/data/update-slack-assistant-request.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/handlers/__tests__/slack-post-message-handler.test.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-add-reaction-handler.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/handlers/slack-delete-message-handler.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/handlers/slack-list-channels-handler.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/handlers/slack-post-ephemeral-message-handler.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/handlers/slack-post-message-handler.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-team-claim-handler.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/handlers/slack-update-message-handler.ts (67%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/schemas/slack-add-reaction-input.schema.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/schemas/slack-delete-message-input.schema.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/schemas/slack-list-channels-input.schema.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/schemas/slack-post-ephemeral-message-input.schema.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/schemas/slack-post-message-input.schema.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/schemas/slack-update-message-input.schema.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-add-reaction.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/slack-assistant-worker.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-delete-message.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/slack-events-enqueue.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/slack-events-resolver.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-list-channels-route.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-list-channels.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-post-ephemeral-message.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-post-message-route.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-post-message.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/slack-team-claim.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/slack-update-message.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-add-reaction-input.type.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-assistant-progress-step.type.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-assistant-request-draft.type.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-assistant-request-record.type.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-delete-message-input.type.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-events-request-body.type.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-list-channels-input.type.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-list-channels-result.type.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-message-body-format.type.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-post-ephemeral-message-input.type.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-post-message-input.type.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-team-claim-payload.type.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-thread-reference.type.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/types/slack-thread-subscription.type.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-tool-result.type.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/types/slack-update-message-input.type.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/__tests__/get-slack-chat-message-body-fields.test.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/__tests__/get-slack-connection.test.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/__tests__/parse-slack-assistant-request.test.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/__tests__/slack-tool-failure.test.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/__tests__/verify-slack-request-signature.test.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/build-slack-assistant-answer-text.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/build-slack-assistant-prompt.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/build-slack-assistant-request-name.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/claim-slack-team.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/clear-slack-assistant-thinking-reaction.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/enqueue-slack-assistant-request.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/extract-agent-response-text.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/fetch-slack-assistant-context.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/fetch-slack-conversation-context.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/fetch-slack-requester-name.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/finish-slack-assistant-request-with-failure.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/format-slack-assistant-duration.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/get-slack-api-error-code.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/get-slack-assistant-parent-message-timestamp.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/get-slack-chat-message-body-fields.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/get-slack-client.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/get-slack-connection.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/get-slack-team-kv-key.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/get-slack-thread-kv-key.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/get-slack-webhook-secret.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/is-duplicate-record-error.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/is-slack-markdown-format-error.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/is-slack-thread-active.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/json-schema-to-input-schema.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/parse-slack-assistant-request.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/resolve-target-workspace-id.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/run-slack-assistant-agent-with-progress.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/run-slack-reaction.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/src/logic-functions/utils/slack-tool-failure.ts (100%)
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/start-slack-assistant-progress-updates.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/subscribe-slack-thread.ts
create mode 100644 packages/twenty-apps/public/slack/src/logic-functions/utils/verify-slack-request-signature.ts
create mode 100644 packages/twenty-apps/public/slack/src/objects/slack-assistant-request.object.ts
create mode 100644 packages/twenty-apps/public/slack/src/roles/default-function.role.ts
create mode 100644 packages/twenty-apps/public/slack/src/roles/slack-assistant.role.ts
rename packages/twenty-apps/public/{twenty-slack => slack}/tsconfig.json (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/tsconfig.spec.json (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/vitest.config.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/vitest.unit.config.ts (100%)
rename packages/twenty-apps/public/{twenty-slack => slack}/yarn.lock (99%)
delete mode 100644 packages/twenty-apps/public/twenty-slack/README.md
delete mode 100644 packages/twenty-apps/public/twenty-slack/src/constants/universal-identifiers.ts
delete mode 100644 packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/__tests__/slack-add-reaction-handler.test.ts
delete mode 100644 packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-add-reaction-handler.ts
delete mode 100644 packages/twenty-apps/public/twenty-slack/src/roles/default-function.role.ts
diff --git a/packages/twenty-apps/public/twenty-slack/.gitignore b/packages/twenty-apps/public/slack/.gitignore
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/.gitignore
rename to packages/twenty-apps/public/slack/.gitignore
diff --git a/packages/twenty-apps/public/twenty-slack/.nvmrc b/packages/twenty-apps/public/slack/.nvmrc
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/.nvmrc
rename to packages/twenty-apps/public/slack/.nvmrc
diff --git a/packages/twenty-apps/public/twenty-slack/.oxlintrc.json b/packages/twenty-apps/public/slack/.oxlintrc.json
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/.oxlintrc.json
rename to packages/twenty-apps/public/slack/.oxlintrc.json
diff --git a/packages/twenty-apps/public/twenty-slack/.yarnrc.yml b/packages/twenty-apps/public/slack/.yarnrc.yml
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/.yarnrc.yml
rename to packages/twenty-apps/public/slack/.yarnrc.yml
diff --git a/packages/twenty-apps/public/slack/README.md b/packages/twenty-apps/public/slack/README.md
new file mode 100644
index 0000000000..0b608ce3f3
--- /dev/null
+++ b/packages/twenty-apps/public/slack/README.md
@@ -0,0 +1,39 @@
+# Slack
+
+**Your CRM, in the conversation — ask Twenty anything from Slack and post back to any channel.**
+
+## ✨ What you get
+
+- **A CRM assistant in Slack** — `@twenty how many open opportunities do we have?` or `@twenty create a company called ACME`. It answers in-thread, remembers the thread, and can read, create, update and soft-delete records
+- **Follow-ups without re-mentioning** — once it has replied in a thread you can keep talking to it for 24 hours
+- **Slack steps for your workflows** — post, update or delete messages, send ephemerals, add reactions, list channels
+- **Send from anywhere in Twenty** — the **Send Slack message** command opens a side panel to pick a channel and post
+
+## 🤖 The assistant
+
+Mention the bot in a channel or DM it. It replies in the thread with your CRM data, using the recent conversation as context.
+
+Anyone who can message the bot acts with the **Slack Assistant** role, which by default can read, create, update and soft-delete people, companies, opportunities, notes and tasks. Workspace members stay read-only and hard delete is off. Tighten the role in **Settings → Roles** if you want a narrower bot.
+
+One Slack workspace answers into one Twenty workspace.
+
+## 🧰 The workflow steps
+
+| Step | Slack API |
+|------|-----------|
+| `slack-post-message` | `chat.postMessage` |
+| `slack-post-ephemeral-message` | `chat.postEphemeral` |
+| `slack-update-message` | `chat.update` |
+| `slack-delete-message` | `chat.delete` |
+| `slack-add-reaction` | `reactions.add` |
+| `slack-list-channels` | `conversations.list` |
+
+Pick a **workspace shared** or **just for me** Slack connection; steps run with that token.
+
+## 💳 Billing
+
+**Free** — no credits, no metering.
+
+## 📌 Heads up
+
+You need to create a Slack app and connect it — see [SETUP.md](./SETUP.md). The assistant needs a few extra steps (signing secret and event subscriptions) on top of the base connection.
diff --git a/packages/twenty-apps/public/slack/SETUP.md b/packages/twenty-apps/public/slack/SETUP.md
new file mode 100644
index 0000000000..b84b4ea6b3
--- /dev/null
+++ b/packages/twenty-apps/public/slack/SETUP.md
@@ -0,0 +1,92 @@
+# Setup
+
+Two parts: a **Slack app** you create, and the **Twenty side** where you paste its credentials and connect. The conversational assistant needs a third part on top.
+
+## 1. Slack app
+
+1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps). Use a dedicated app — do not reuse one across Twenty apps.
+
+2. **OAuth & Permissions → Bot Token Scopes.** Twenty uses Slack's bot OAuth (`oauth/v2/authorize` with `scope=…`), so scopes must be added here and not only under **User Token Scopes**, otherwise Slack refuses the install with *"doesn't have a bot user to install"*.
+
+ The scopes requested at connect time must all appear under **Bot Token Scopes** (Slack validates the set):
+
+ | Scope | Used for |
+ |-------|----------|
+ | `channels:read` | `conversations.list` and the channel picker (public channels) |
+ | `chat:write` | post, update, delete, ephemeral |
+ | `chat:write.public` | post to public channels without the bot joining |
+ | `groups:read` | list private channels the bot is in |
+ | `reactions:write` | add reactions |
+ | `app_mentions:read` | assistant: mentions of the bot |
+ | `channels:history` | assistant: thread follow-ups in public channels |
+ | `groups:history` | assistant: thread follow-ups in private channels |
+ | `im:history` | assistant: direct messages |
+
+ Adding or removing scopes later means existing installs must re-authorize: disconnect and **Add connection** again.
+
+3. **Redirect URL.** Set it to `/auth/apps/callback` — the origin your Twenty **server** uses for API routes, not the SPA. Local monorepo dev is usually `http://localhost:3000` (confirm the port `twenty-server` / `SERVER_URL` actually uses).
+
+ **PKCE and `localhost`:** if you enable **PKCE** on the Slack app, Slack treats `http://localhost…` as a *desktop* redirect, and desktop redirects cannot request bot scopes — OAuth will fail. For local dev either leave Slack's PKCE opt-in disabled, or use an `https://` redirect (ngrok, Cloudflare Tunnel), register it in the Slack app, and point `SERVER_URL` at the same base URL. See Slack's [Using PKCE](https://docs.slack.dev/authentication/using-pkce) docs. This is separate from Twenty sending a PKCE challenge on the authorize request.
+
+4. Copy the **Client ID** and **Client Secret**.
+
+## 2. Twenty
+
+1. Install this app (`slack`) on your Twenty server.
+2. **Settings → Applications → Twenty Slack → Application registration** (admin only), set `SLACK_CLIENT_ID` and `SLACK_CLIENT_SECRET`.
+3. **Connections → Add connection**, choose **Just for me** or **Workspace shared**, complete the Slack sign-in.
+
+Workflow steps then use that connection's access token: a workspace connection is preferred when present, otherwise the first connection returned for the Slack provider.
+
+For posting, either invite the bot to the channel or rely on `chat:write.public` for public channels. Private channels always require membership.
+
+## 3. Conversational assistant
+
+The assistant reuses the same Slack connection — no second bot identity.
+
+1. **Signing secret.** In **Application registration**, set `SLACK_WEBHOOK_SECRET` from your Slack app (**Basic Information → App Credentials**). The server verifies every Slack Events request with it.
+
+2. **Event subscriptions.** On the Slack app, enable **Event Subscriptions** and set the Request URL to:
+
+ ```text
+ /webhooks/server/9ad6fa20-dff5-4d3f-ad5f-084f3c8b0b09
+ ```
+
+ That ID is the `slack-events-resolver` logic function. Slack signs the handshake, so `SLACK_WEBHOOK_SECRET` must be set first or Slack reports *"didn't respond with the value of the challenge parameter."*
+
+ Under **Subscribe to bot events**, add:
+
+ - `app_mention` — mentions of the bot in a channel
+ - `message.im` — direct messages to the bot
+ - `message.channels` — replies in public-channel threads, for un-mentioned follow-ups
+ - `message.groups` — same, for private channels the bot is in
+
+ Invite the bot to any channel where it should follow threads. Slack may ask you to reinstall after changing subscriptions.
+
+3. **Reconnect** so the token picks up the assistant scopes.
+
+4. **Role.** The `slack-assistant` agent binds to the app's **Slack Assistant** role automatically on install and upgrade. Anyone who can message the bot acts with that role — Slack users are not mapped to individual Twenty members yet, so keep the role scoped to what you're comfortable exposing.
+
+## Behaviour notes
+
+- **Thread memory.** After a successful reply the bot stays active in that thread, so follow-ups need no mention. Channel threads stay active for 24 hours after the last reply (each reply renews it); DM threads never expire.
+- **One Slack workspace per Twenty workspace.** Connecting Slack claims that Slack team for the connecting Twenty workspace. On the same server, a second Twenty workspace connecting the same Slack team is rejected. The claim is not released on disconnect yet, so moving a Slack workspace needs a server admin.
+
+## Workflow field names (for step authors)
+
+Fields use camelCase in the step UI:
+
+- `slackChannelId` — channel or DM, name or ID
+- `messageText`, `newMessageText` — body to post / the replacement on update
+- `messageTimestamp` — Slack's per-message id, same value as the tool output `slackTs` when chaining steps
+- `parentMessageTimestamp` — thread replies only
+- `messageFormat` — `markdown` sends the body as Slack `markdown_text` (`**bold**`), `plain` sends `text` with markup disabled, omitted uses Slack's default for `text`
+- `recipientSlackUserId` — ephemeral steps
+- `emojiName` — Slack shortcode, for example `white_check_mark`
+
+## HTTP routes
+
+The **Send Slack message** command menu item is backed by two app routes, both requiring an authenticated Twenty user and using the same Slack connection as the workflow steps:
+
+- `GET /slack/channels` — lists channels visible to the bot
+- `POST /slack/messages` — posts a message
diff --git a/packages/twenty-apps/public/twenty-slack/package.json b/packages/twenty-apps/public/slack/package.json
similarity index 84%
rename from packages/twenty-apps/public/twenty-slack/package.json
rename to packages/twenty-apps/public/slack/package.json
index 1a7ebd26a7..5401829d8c 100644
--- a/packages/twenty-apps/public/twenty-slack/package.json
+++ b/packages/twenty-apps/public/slack/package.json
@@ -1,7 +1,7 @@
{
- "name": "@twentyhq/twenty-slack",
+ "name": "@twentyhq/slack",
"version": "0.1.0",
- "description": "Slack workflow connector for Twenty",
+ "description": "Slack assistant and workflow steps for Twenty",
"license": "MIT",
"engines": {
"node": "^24.5.0",
@@ -31,8 +31,8 @@
"oxlint": "^0.16.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "twenty-client-sdk": "^2.16.0",
- "twenty-sdk": "^2.16.0",
+ "twenty-client-sdk": "^2.25.0",
+ "twenty-sdk": "^2.25.0",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^4.0.0"
diff --git a/packages/twenty-apps/public/twenty-slack/public/twenty-slack.svg b/packages/twenty-apps/public/slack/public/twenty-slack.svg
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/public/twenty-slack.svg
rename to packages/twenty-apps/public/slack/public/twenty-slack.svg
diff --git a/packages/twenty-apps/public/twenty-slack/src/__tests__/global-setup.ts b/packages/twenty-apps/public/slack/src/__tests__/global-setup.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/__tests__/global-setup.ts
rename to packages/twenty-apps/public/slack/src/__tests__/global-setup.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/__tests__/twenty-slack.integration-test.ts b/packages/twenty-apps/public/slack/src/__tests__/slack.integration-test.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/__tests__/twenty-slack.integration-test.ts
rename to packages/twenty-apps/public/slack/src/__tests__/slack.integration-test.ts
diff --git a/packages/twenty-apps/public/slack/src/agents/slack-assistant.agent.ts b/packages/twenty-apps/public/slack/src/agents/slack-assistant.agent.ts
new file mode 100644
index 0000000000..32741f57e2
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/agents/slack-assistant.agent.ts
@@ -0,0 +1,19 @@
+import { defineAgent } from 'twenty-sdk/define';
+
+import { DEFAULT_SLACK_ASSISTANT_PROMPT } from 'src/constants/default-slack-assistant-prompt';
+import {
+ SLACK_ASSISTANT_AGENT_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_ROLE_UNIVERSAL_IDENTIFIER,
+} from 'src/constants/universal-identifiers';
+
+export default defineAgent({
+ universalIdentifier: SLACK_ASSISTANT_AGENT_UNIVERSAL_IDENTIFIER,
+ name: 'slack-assistant',
+ label: 'Slack Assistant',
+ icon: 'IconBrandSlack',
+ description:
+ 'Conversational CRM assistant reached from Slack. Answers questions and acts on workspace data using the Slack Assistant role.',
+ prompt: DEFAULT_SLACK_ASSISTANT_PROMPT,
+ responseFormat: { type: 'text' },
+ roleUniversalIdentifier: SLACK_ASSISTANT_ROLE_UNIVERSAL_IDENTIFIER,
+});
diff --git a/packages/twenty-apps/public/twenty-slack/src/application.config.ts b/packages/twenty-apps/public/slack/src/application.config.ts
similarity index 59%
rename from packages/twenty-apps/public/twenty-slack/src/application.config.ts
rename to packages/twenty-apps/public/slack/src/application.config.ts
index b565ea4f5a..6964710e46 100644
--- a/packages/twenty-apps/public/twenty-slack/src/application.config.ts
+++ b/packages/twenty-apps/public/slack/src/application.config.ts
@@ -1,15 +1,12 @@
import { defineApplication } from 'twenty-sdk/define';
-import {
- APPLICATION_UNIVERSAL_IDENTIFIER,
- DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
-} from 'src/constants/universal-identifiers';
+import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Twenty Slack',
description:
- 'Connect Slack to Twenty. Each workspace member (or a shared workspace connection) can authenticate Slack; workflow steps then post messages, ephemerals, updates, deletes, and reactions on behalf of that connection.',
+ 'Your CRM, in the conversation. Mention the bot or DM it to ask about your records and create, update or soft-delete them without leaving Slack, and use the Slack steps to post messages, ephemerals, updates, deletes and reactions from your workflows.',
logoUrl: 'public/twenty-slack.svg',
author: 'Twenty',
category: 'Communication',
@@ -17,7 +14,6 @@ export default defineApplication({
termsUrl: 'https://www.twenty.com/terms',
emailSupport: 'contact@twenty.com',
issueReportUrl: 'https://github.com/twentyhq/twenty/issues',
- defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
serverVariables: {
SLACK_CLIENT_ID: {
description:
@@ -31,5 +27,11 @@ export default defineApplication({
isSecret: true,
isRequired: true,
},
+ SLACK_WEBHOOK_SECRET: {
+ description:
+ 'Signing secret from your Slack app (Basic Information → App Credentials). Used to verify Slack Events API requests for the assistant. Only required if you enable the conversational assistant.',
+ isSecret: true,
+ isRequired: false,
+ },
},
});
diff --git a/packages/twenty-apps/public/twenty-slack/src/command-menu-items/send-slack-message.command-menu-item.ts b/packages/twenty-apps/public/slack/src/command-menu-items/send-slack-message.command-menu-item.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/command-menu-items/send-slack-message.command-menu-item.ts
rename to packages/twenty-apps/public/slack/src/command-menu-items/send-slack-message.command-menu-item.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/components/send-message-form.front-component.tsx b/packages/twenty-apps/public/slack/src/components/send-message-form.front-component.tsx
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/components/send-message-form.front-component.tsx
rename to packages/twenty-apps/public/slack/src/components/send-message-form.front-component.tsx
diff --git a/packages/twenty-apps/public/twenty-slack/src/connection-providers/slack-connection.ts b/packages/twenty-apps/public/slack/src/connection-providers/slack-connection.ts
similarity index 64%
rename from packages/twenty-apps/public/twenty-slack/src/connection-providers/slack-connection.ts
rename to packages/twenty-apps/public/slack/src/connection-providers/slack-connection.ts
index 7f0863840a..99d666f331 100644
--- a/packages/twenty-apps/public/twenty-slack/src/connection-providers/slack-connection.ts
+++ b/packages/twenty-apps/public/slack/src/connection-providers/slack-connection.ts
@@ -1,12 +1,18 @@
import { defineConnectionProvider } from 'twenty-sdk/define';
-import { SLACK_CONNECTION_PROVIDER_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
+import {
+ SLACK_CONNECTION_PROVIDER_UNIVERSAL_IDENTIFIER,
+ SLACK_TEAM_CLAIM_UNIVERSAL_IDENTIFIER,
+} from 'src/constants/universal-identifiers';
export default defineConnectionProvider({
universalIdentifier: SLACK_CONNECTION_PROVIDER_UNIVERSAL_IDENTIFIER,
name: 'slack',
displayName: 'Slack',
type: 'oauth',
+ onConnectLogicFunction: {
+ universalIdentifier: SLACK_TEAM_CLAIM_UNIVERSAL_IDENTIFIER,
+ },
oauth: {
authorizationEndpoint: 'https://slack.com/oauth/v2/authorize',
tokenEndpoint: 'https://slack.com/api/oauth.v2.access',
@@ -17,6 +23,11 @@ export default defineConnectionProvider({
'chat:write.public',
'groups:read',
'reactions:write',
+ // Inbound scopes, only used by the conversational assistant
+ 'app_mentions:read',
+ 'channels:history',
+ 'groups:history',
+ 'im:history',
],
clientIdVariable: 'SLACK_CLIENT_ID',
clientSecretVariable: 'SLACK_CLIENT_SECRET',
diff --git a/packages/twenty-apps/public/slack/src/constants/default-slack-assistant-prompt.ts b/packages/twenty-apps/public/slack/src/constants/default-slack-assistant-prompt.ts
new file mode 100644
index 0000000000..a35dc5540a
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/constants/default-slack-assistant-prompt.ts
@@ -0,0 +1,10 @@
+export const DEFAULT_SLACK_ASSISTANT_PROMPT = `You are Twenty's CRM assistant in Slack. Members @mention you in a channel or message you in a DM.
+
+Slack reply style:
+- Keep replies concise
+- Write standard Markdown, not Slack's legacy mrkdwn: **bold** renders bold while *bold* renders italic, and list items start with -
+- Lead with the answer; do not restate the request or add sign-offs
+- If the request is ambiguous, ask one short clarifying question before acting
+- Always finish with a short text reply the member can read in the thread — never end on a tool call alone
+- When a tool fails, explain the error briefly and ask for any missing fields, then retry when possible
+- When you change data, briefly confirm what changed and name the affected records`;
diff --git a/packages/twenty-apps/public/slack/src/constants/universal-identifiers.ts b/packages/twenty-apps/public/slack/src/constants/universal-identifiers.ts
new file mode 100644
index 0000000000..4518f72ace
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/constants/universal-identifiers.ts
@@ -0,0 +1,101 @@
+export const APPLICATION_UNIVERSAL_IDENTIFIER =
+ 'a8c47f21-3b9e-4d2a-8f61-9c0e7d4a2b51';
+
+export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
+ 'b7d36e10-2a8d-4c1b-9e50-8bfd6c3a1940';
+
+export const SLACK_CONNECTION_PROVIDER_UNIVERSAL_IDENTIFIER =
+ '8b6c6fd9-8d61-4b6f-9f25-3d92a0f2cc5b';
+
+export const SLACK_POST_MESSAGE_UNIVERSAL_IDENTIFIER =
+ 'c6f25d09-1b7c-4e3f-ad42-7aec5b29830f';
+
+export const SLACK_POST_EPHEMERAL_MESSAGE_UNIVERSAL_IDENTIFIER =
+ 'd5e14c98-0a6b-4e2e-ac31-69db4a18720e';
+
+export const SLACK_UPDATE_MESSAGE_UNIVERSAL_IDENTIFIER =
+ 'e4d03b87-9a5b-4f1d-8b20-58ca3917620d';
+
+export const SLACK_DELETE_MESSAGE_UNIVERSAL_IDENTIFIER =
+ 'f3c92a76-8b4a-4a09-ba19-47b9280651c9';
+
+export const SLACK_ADD_REACTION_UNIVERSAL_IDENTIFIER =
+ '2a8c7f91-4d3e-5b6f-a7c8-9d0e1f2a3b4c';
+
+export const SLACK_LIST_CHANNELS_UNIVERSAL_IDENTIFIER =
+ '3b7d8a92-5e4f-4c7a-b8d9-0e1f2a3b4c5d';
+
+export const SLACK_LIST_CHANNELS_ROUTE_UNIVERSAL_IDENTIFIER =
+ '6e0c3d5f-9a4f-4f62-bc0e-3d5a7f9b4c6e';
+
+export const SLACK_POST_MESSAGE_ROUTE_UNIVERSAL_IDENTIFIER =
+ '7f1d4e60-ab50-4273-9d1f-4e6b8a0c5d7f';
+
+export const SEND_MESSAGE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
+ '4c8a1b3f-7e2d-4f50-9a8c-1b3e5d7f2a4c';
+
+export const SEND_SLACK_MESSAGE_COMMAND_UNIVERSAL_IDENTIFIER =
+ '5d9b2c4e-8f3e-4f50-ab9d-2c4f6e8a3b5d';
+
+export const SLACK_ASSISTANT_AGENT_UNIVERSAL_IDENTIFIER =
+ '9f3a1c72-6b4e-4d8a-9c1f-2e5b7a8d3c60';
+
+export const SLACK_ASSISTANT_ROLE_UNIVERSAL_IDENTIFIER =
+ 'a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
+
+export const SLACK_EVENTS_ROUTE_UNIVERSAL_IDENTIFIER =
+ '9ad6fa20-dff5-4d3f-ad5f-084f3c8b0b09';
+
+export const SLACK_EVENTS_ENQUEUE_UNIVERSAL_IDENTIFIER =
+ '8f2e1d3c-4b5a-4c6d-9e7f-0a1b2c3d4e5f';
+
+export const SLACK_ASSISTANT_WORKER_UNIVERSAL_IDENTIFIER =
+ '4b92a49f-d674-46ea-a3d9-e8d658ae3a17';
+
+export const SLACK_TEAM_CLAIM_UNIVERSAL_IDENTIFIER =
+ 'a29ae15d-dd16-4b99-bb6c-079842da55ab';
+
+export const SLACK_ASSISTANT_REQUEST_OBJECT_UNIVERSAL_IDENTIFIER =
+ '4dfdd6c7-9042-4278-8d3e-8172a8a5e15f';
+
+export const SLACK_ASSISTANT_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER =
+ '6e5d36df-0e1a-4a59-beb0-f4772d75721b';
+
+export const SLACK_ASSISTANT_REQUEST_EVENT_ID_FIELD_UNIVERSAL_IDENTIFIER =
+ '924f9d52-f0b2-41c7-9c1e-148cf8204468';
+
+export const SLACK_ASSISTANT_REQUEST_CHANNEL_ID_FIELD_UNIVERSAL_IDENTIFIER =
+ '7c3d07d7-f3ce-436a-80ba-fa8419165e99';
+
+export const SLACK_ASSISTANT_REQUEST_CHANNEL_TYPE_FIELD_UNIVERSAL_IDENTIFIER =
+ '3dc880f3-459d-4848-85aa-c294da7031c2';
+
+export const SLACK_ASSISTANT_REQUEST_THREAD_TS_FIELD_UNIVERSAL_IDENTIFIER =
+ '70658c28-e0f3-4222-bbe0-ee0f52cefcf5';
+
+export const SLACK_ASSISTANT_REQUEST_MESSAGE_TS_FIELD_UNIVERSAL_IDENTIFIER =
+ '10754b04-7bc1-4dc0-ad36-bcbbc0faa05a';
+
+export const SLACK_ASSISTANT_REQUEST_USER_ID_FIELD_UNIVERSAL_IDENTIFIER =
+ '856b356b-6322-45d7-b364-e8d5bea1111c';
+
+export const SLACK_ASSISTANT_REQUEST_TEXT_FIELD_UNIVERSAL_IDENTIFIER =
+ '19bdef71-db46-4749-b1ec-ca10d0126568';
+
+export const SLACK_ASSISTANT_REQUEST_RESPONSE_FIELD_UNIVERSAL_IDENTIFIER =
+ 'a08de293-a5cd-4928-ae08-54edea424269';
+
+export const SLACK_ASSISTANT_REQUEST_STATUS_FIELD_UNIVERSAL_IDENTIFIER =
+ '0a3aed23-b69d-4fd4-9147-e0111eba8a90';
+
+export const SLACK_ASSISTANT_REQUEST_ERROR_FIELD_UNIVERSAL_IDENTIFIER =
+ '13fa7926-c37b-464a-a6a7-d30e3a5f33ce';
+
+export const SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_UNIVERSAL_IDENTIFIER =
+ '486c87fe-c557-4dd1-a7ec-133e4e0324ae';
+
+export const SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_CHANNEL_ID_FIELD_UNIVERSAL_IDENTIFIER =
+ 'a8b30563-af6e-4522-83fb-7ddce50f35fd';
+
+export const SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_MESSAGE_TS_FIELD_UNIVERSAL_IDENTIFIER =
+ '6cd5d276-1c12-486c-aa6d-c9967cd6b7dd';
diff --git a/packages/twenty-apps/public/slack/src/indexes/slack-assistant-request-slack-message.index.ts b/packages/twenty-apps/public/slack/src/indexes/slack-assistant-request-slack-message.index.ts
new file mode 100644
index 0000000000..82ea8fd967
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/indexes/slack-assistant-request-slack-message.index.ts
@@ -0,0 +1,32 @@
+import { defineIndex } from 'twenty-sdk/define';
+
+import {
+ SLACK_ASSISTANT_REQUEST_CHANNEL_ID_FIELD_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_REQUEST_MESSAGE_TS_FIELD_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_REQUEST_OBJECT_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_CHANNEL_ID_FIELD_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_MESSAGE_TS_FIELD_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_UNIVERSAL_IDENTIFIER,
+} from 'src/constants/universal-identifiers';
+
+export default defineIndex({
+ universalIdentifier:
+ SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_UNIVERSAL_IDENTIFIER,
+ objectUniversalIdentifier:
+ SLACK_ASSISTANT_REQUEST_OBJECT_UNIVERSAL_IDENTIFIER,
+ isUnique: true,
+ fields: [
+ {
+ universalIdentifier:
+ SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_CHANNEL_ID_FIELD_UNIVERSAL_IDENTIFIER,
+ fieldUniversalIdentifier:
+ SLACK_ASSISTANT_REQUEST_CHANNEL_ID_FIELD_UNIVERSAL_IDENTIFIER,
+ },
+ {
+ universalIdentifier:
+ SLACK_ASSISTANT_REQUEST_SLACK_MESSAGE_INDEX_MESSAGE_TS_FIELD_UNIVERSAL_IDENTIFIER,
+ fieldUniversalIdentifier:
+ SLACK_ASSISTANT_REQUEST_MESSAGE_TS_FIELD_UNIVERSAL_IDENTIFIER,
+ },
+ ],
+});
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-failure-text.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-failure-text.ts
new file mode 100644
index 0000000000..b1d244a0a4
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-failure-text.ts
@@ -0,0 +1,2 @@
+export const SLACK_ASSISTANT_FAILURE_TEXT =
+ 'Sorry, I could not complete that request. An admin can check the Slack Assistant Request record in Twenty for details.';
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-placeholder-text.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-placeholder-text.ts
new file mode 100644
index 0000000000..79d8ed8bd0
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-placeholder-text.ts
@@ -0,0 +1 @@
+export const SLACK_ASSISTANT_PLACEHOLDER_TEXT = '_Looking into it…_';
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-progress-steps.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-progress-steps.ts
new file mode 100644
index 0000000000..bc0f36a3dd
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-progress-steps.ts
@@ -0,0 +1,10 @@
+import { type SlackAssistantProgressStep } from 'src/logic-functions/types/slack-assistant-progress-step.type';
+
+export const SLACK_ASSISTANT_PROGRESS_STEPS: SlackAssistantProgressStep[] = [
+ { afterSeconds: 5, text: '_Exploring…_' },
+ { afterSeconds: 10, text: '_Checking results…_' },
+ { afterSeconds: 30, text: '_Still digging through your CRM…_' },
+ { afterSeconds: 60, text: '_This takes a while, but still here…_' },
+ { afterSeconds: 120, text: '_Still on it, thanks for waiting…_' },
+ { afterSeconds: 180, text: '_Almost out of time, wrapping up…_' },
+];
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-request-status.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-request-status.ts
new file mode 100644
index 0000000000..22469dee5b
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-request-status.ts
@@ -0,0 +1,6 @@
+export const SLACK_ASSISTANT_REQUEST_STATUS = {
+ PENDING: 'PENDING',
+ PROCESSING: 'PROCESSING',
+ DONE: 'DONE',
+ FAILED: 'FAILED',
+} as const;
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-thinking-reaction-emoji.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-thinking-reaction-emoji.ts
new file mode 100644
index 0000000000..a88610886d
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-thinking-reaction-emoji.ts
@@ -0,0 +1 @@
+export const SLACK_ASSISTANT_THINKING_REACTION_EMOJI = 'eyes';
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-transient-texts.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-transient-texts.ts
new file mode 100644
index 0000000000..ba170afeab
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-transient-texts.ts
@@ -0,0 +1,7 @@
+import { SLACK_ASSISTANT_PLACEHOLDER_TEXT } from 'src/logic-functions/constants/slack-assistant-placeholder-text';
+import { SLACK_ASSISTANT_PROGRESS_STEPS } from 'src/logic-functions/constants/slack-assistant-progress-steps';
+
+export const SLACK_ASSISTANT_TRANSIENT_TEXTS = [
+ SLACK_ASSISTANT_PLACEHOLDER_TEXT,
+ ...SLACK_ASSISTANT_PROGRESS_STEPS.map((step) => step.text),
+];
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-worker-timeout-seconds.ts b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-worker-timeout-seconds.ts
new file mode 100644
index 0000000000..a0b1a619da
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/constants/slack-assistant-worker-timeout-seconds.ts
@@ -0,0 +1 @@
+export const SLACK_ASSISTANT_WORKER_TIMEOUT_SECONDS = 60 * 4;
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/data/create-slack-assistant-request.ts b/packages/twenty-apps/public/slack/src/logic-functions/data/create-slack-assistant-request.ts
new file mode 100644
index 0000000000..79f860450e
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/data/create-slack-assistant-request.ts
@@ -0,0 +1,30 @@
+import { isNonEmptyString } from '@sniptt/guards';
+import { type CoreApiClient } from 'twenty-client-sdk/core';
+
+import { type SlackAssistantRequestDraft } from 'src/logic-functions/types/slack-assistant-request-draft.type';
+import { buildSlackAssistantRequestName } from 'src/logic-functions/utils/build-slack-assistant-request-name';
+
+export const createSlackAssistantRequest = async (
+ client: CoreApiClient,
+ draft: SlackAssistantRequestDraft,
+): Promise => {
+ const mutationResult = await client.mutation({
+ createSlackAssistantRequest: {
+ __args: {
+ data: {
+ ...draft,
+ name: buildSlackAssistantRequestName(draft.requestText),
+ },
+ },
+ id: true,
+ },
+ });
+
+ const requestId = mutationResult.createSlackAssistantRequest?.id;
+
+ if (!isNonEmptyString(requestId)) {
+ throw new Error('createSlackAssistantRequest did not return an id');
+ }
+
+ return requestId;
+};
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/data/find-slack-assistant-request-by-slack-message.ts b/packages/twenty-apps/public/slack/src/logic-functions/data/find-slack-assistant-request-by-slack-message.ts
new file mode 100644
index 0000000000..f90a6ca87e
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/data/find-slack-assistant-request-by-slack-message.ts
@@ -0,0 +1,24 @@
+import { type CoreApiClient } from 'twenty-client-sdk/core';
+
+export const findSlackAssistantRequestBySlackMessage = async (
+ client: CoreApiClient,
+ {
+ slackChannelId,
+ slackMessageTimestamp,
+ }: { slackChannelId: string; slackMessageTimestamp: string },
+): Promise => {
+ const queryResult = await client.query({
+ slackAssistantRequests: {
+ __args: {
+ filter: {
+ slackChannelId: { eq: slackChannelId },
+ slackMessageTimestamp: { eq: slackMessageTimestamp },
+ },
+ first: 1,
+ },
+ edges: { node: { id: true } },
+ },
+ });
+
+ return queryResult.slackAssistantRequests?.edges?.[0]?.node?.id ?? undefined;
+};
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/data/update-slack-assistant-request.ts b/packages/twenty-apps/public/slack/src/logic-functions/data/update-slack-assistant-request.ts
new file mode 100644
index 0000000000..382530447d
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/data/update-slack-assistant-request.ts
@@ -0,0 +1,35 @@
+import { type CoreApiClient } from 'twenty-client-sdk/core';
+
+import { type SLACK_ASSISTANT_REQUEST_STATUS } from 'src/logic-functions/constants/slack-assistant-request-status';
+
+type SlackAssistantRequestStatus =
+ (typeof SLACK_ASSISTANT_REQUEST_STATUS)[keyof typeof SLACK_ASSISTANT_REQUEST_STATUS];
+
+export const updateSlackAssistantRequest = async (
+ client: CoreApiClient,
+ {
+ id,
+ status,
+ responseText,
+ errorMessage,
+ }: {
+ id: string;
+ status: SlackAssistantRequestStatus;
+ responseText?: string;
+ errorMessage?: string;
+ },
+): Promise => {
+ await client.mutation({
+ updateSlackAssistantRequest: {
+ __args: {
+ id,
+ data: {
+ status,
+ ...(responseText !== undefined ? { responseText } : {}),
+ ...(errorMessage !== undefined ? { errorMessage } : {}),
+ },
+ },
+ id: true,
+ },
+ });
+};
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/__tests__/slack-post-message-handler.test.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/__tests__/slack-post-message-handler.test.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/__tests__/slack-post-message-handler.test.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/handlers/__tests__/slack-post-message-handler.test.ts
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-add-reaction-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-add-reaction-handler.ts
new file mode 100644
index 0000000000..1085abb303
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-add-reaction-handler.ts
@@ -0,0 +1,11 @@
+import { type SlackAddReactionInput } from 'src/logic-functions/types/slack-add-reaction-input.type';
+import { type SlackToolResult } from 'src/logic-functions/types/slack-tool-result.type';
+import { runSlackReaction } from 'src/logic-functions/utils/run-slack-reaction';
+
+export const slackAddReactionHandler = (
+ parameters: SlackAddReactionInput,
+): Promise =>
+ runSlackReaction({
+ operation: 'add',
+ ...parameters,
+ });
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-delete-message-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-delete-message-handler.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-delete-message-handler.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-delete-message-handler.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-list-channels-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-list-channels-handler.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-list-channels-handler.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-list-channels-handler.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-post-ephemeral-message-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-post-ephemeral-message-handler.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-post-ephemeral-message-handler.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-post-ephemeral-message-handler.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-post-message-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-post-message-handler.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-post-message-handler.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-post-message-handler.ts
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-team-claim-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-team-claim-handler.ts
new file mode 100644
index 0000000000..6d5d6d90bb
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-team-claim-handler.ts
@@ -0,0 +1,7 @@
+import { type SlackTeamClaimPayload } from 'src/logic-functions/types/slack-team-claim-payload.type';
+import { claimSlackTeam } from 'src/logic-functions/utils/claim-slack-team';
+
+export const slackTeamClaimHandler = (payload: SlackTeamClaimPayload) =>
+ claimSlackTeam({
+ connectedAccountId: payload.connectedAccountId,
+ });
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-update-message-handler.ts b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-update-message-handler.ts
similarity index 67%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-update-message-handler.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-update-message-handler.ts
index 732a155ae0..8229ae2d94 100644
--- a/packages/twenty-apps/public/twenty-slack/src/logic-functions/handlers/slack-update-message-handler.ts
+++ b/packages/twenty-apps/public/slack/src/logic-functions/handlers/slack-update-message-handler.ts
@@ -2,6 +2,7 @@ import { type SlackToolResult } from 'src/logic-functions/types/slack-tool-resul
import { type SlackUpdateMessageInput } from 'src/logic-functions/types/slack-update-message-input.type';
import { getSlackChatMessageBodyFields } from 'src/logic-functions/utils/get-slack-chat-message-body-fields';
import { getSlackClient } from 'src/logic-functions/utils/get-slack-client';
+import { isSlackMarkdownFormatError } from 'src/logic-functions/utils/is-slack-markdown-format-error';
import { slackToolFailure } from 'src/logic-functions/utils/slack-tool-failure';
export const slackUpdateMessageHandler = async (
@@ -19,19 +20,19 @@ export const slackUpdateMessageHandler = async (
const { client } = slackClientResult;
- try {
+ const updateWithFormat = async (
+ messageFormat: SlackUpdateMessageInput['messageFormat'],
+ ): Promise => {
const bodyFields = getSlackChatMessageBodyFields(
parameters.newMessageText,
- parameters.messageFormat,
+ messageFormat,
);
- const updatePayload = {
+ const data = await client.chat.update({
channel: parameters.slackChannelId,
ts: parameters.messageTimestamp,
...bodyFields,
- };
-
- const data = await client.chat.update(updatePayload);
+ });
return {
success: true,
@@ -39,6 +40,21 @@ export const slackUpdateMessageHandler = async (
slackTs: data.ts,
channel: parameters.slackChannelId,
};
+ };
+
+ try {
+ return await updateWithFormat(parameters.messageFormat);
+ } catch (error) {
+ if (
+ parameters.messageFormat !== 'markdown' ||
+ !isSlackMarkdownFormatError(error)
+ ) {
+ return slackToolFailure('Failed to update Slack message', error);
+ }
+ }
+
+ try {
+ return await updateWithFormat('plain');
} catch (error) {
return slackToolFailure('Failed to update Slack message', error);
}
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-add-reaction-input.schema.ts b/packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-add-reaction-input.schema.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-add-reaction-input.schema.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-add-reaction-input.schema.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-delete-message-input.schema.ts b/packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-delete-message-input.schema.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-delete-message-input.schema.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-delete-message-input.schema.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-list-channels-input.schema.ts b/packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-list-channels-input.schema.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-list-channels-input.schema.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-list-channels-input.schema.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-post-ephemeral-message-input.schema.ts b/packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-post-ephemeral-message-input.schema.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-post-ephemeral-message-input.schema.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-post-ephemeral-message-input.schema.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-post-message-input.schema.ts b/packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-post-message-input.schema.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-post-message-input.schema.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-post-message-input.schema.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-update-message-input.schema.ts b/packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-update-message-input.schema.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/schemas/slack-update-message-input.schema.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/schemas/slack-update-message-input.schema.ts
diff --git a/packages/twenty-apps/public/twenty-slack/src/logic-functions/slack-add-reaction.ts b/packages/twenty-apps/public/slack/src/logic-functions/slack-add-reaction.ts
similarity index 100%
rename from packages/twenty-apps/public/twenty-slack/src/logic-functions/slack-add-reaction.ts
rename to packages/twenty-apps/public/slack/src/logic-functions/slack-add-reaction.ts
diff --git a/packages/twenty-apps/public/slack/src/logic-functions/slack-assistant-worker.ts b/packages/twenty-apps/public/slack/src/logic-functions/slack-assistant-worker.ts
new file mode 100644
index 0000000000..5090f3dd62
--- /dev/null
+++ b/packages/twenty-apps/public/slack/src/logic-functions/slack-assistant-worker.ts
@@ -0,0 +1,206 @@
+import { isNonEmptyString } from '@sniptt/guards';
+import { CoreApiClient } from 'twenty-client-sdk/core';
+import {
+ type DatabaseEventPayload,
+ defineLogicFunction,
+ type ObjectRecordCreateEvent,
+} from 'twenty-sdk/define';
+
+import {
+ SLACK_ASSISTANT_AGENT_UNIVERSAL_IDENTIFIER,
+ SLACK_ASSISTANT_WORKER_UNIVERSAL_IDENTIFIER,
+} from 'src/constants/universal-identifiers';
+import { SLACK_ASSISTANT_PLACEHOLDER_TEXT } from 'src/logic-functions/constants/slack-assistant-placeholder-text';
+import { SLACK_ASSISTANT_REQUEST_STATUS } from 'src/logic-functions/constants/slack-assistant-request-status';
+import { SLACK_ASSISTANT_THINKING_REACTION_EMOJI } from 'src/logic-functions/constants/slack-assistant-thinking-reaction-emoji';
+import { SLACK_ASSISTANT_WORKER_TIMEOUT_SECONDS } from 'src/logic-functions/constants/slack-assistant-worker-timeout-seconds';
+import { updateSlackAssistantRequest } from 'src/logic-functions/data/update-slack-assistant-request';
+import { slackPostMessageHandler } from 'src/logic-functions/handlers/slack-post-message-handler';
+import { slackUpdateMessageHandler } from 'src/logic-functions/handlers/slack-update-message-handler';
+import { type SlackAssistantRequestRecord } from 'src/logic-functions/types/slack-assistant-request-record.type';
+import { buildSlackAssistantAnswerText } from 'src/logic-functions/utils/build-slack-assistant-answer-text';
+import { buildSlackAssistantPrompt } from 'src/logic-functions/utils/build-slack-assistant-prompt';
+import { clearSlackAssistantThinkingReaction } from 'src/logic-functions/utils/clear-slack-assistant-thinking-reaction';
+import { extractAgentResponseText } from 'src/logic-functions/utils/extract-agent-response-text';
+import { fetchSlackAssistantContext } from 'src/logic-functions/utils/fetch-slack-assistant-context';
+import { finishSlackAssistantRequestWithFailure } from 'src/logic-functions/utils/finish-slack-assistant-request-with-failure';
+import { getSlackAssistantParentMessageTimestamp } from 'src/logic-functions/utils/get-slack-assistant-parent-message-timestamp';
+import { runSlackAssistantAgentWithProgress } from 'src/logic-functions/utils/run-slack-assistant-agent-with-progress';
+import { runSlackReaction } from 'src/logic-functions/utils/run-slack-reaction';
+import { subscribeSlackThread } from 'src/logic-functions/utils/subscribe-slack-thread';
+
+const SLACK_ASSISTANT_REQUEST_OBJECT_NAME = 'slackAssistantRequest';
+
+type SlackAssistantRequestCreatedEvent = DatabaseEventPayload<
+ ObjectRecordCreateEvent
+>;
+
+export const slackAssistantWorkerHandler = async (
+ event: SlackAssistantRequestCreatedEvent,
+): Promise