Skip to content

feat(twenty-slack): interactivity endpoint + native feedback buttons on assistant answers - #24107

Open
abdulrahmancodes wants to merge 13 commits into
mainfrom
feat/slack-interactivity-and-feedback-buttons
Open

feat(twenty-slack): interactivity endpoint + native feedback buttons on assistant answers#24107
abdulrahmancodes wants to merge 13 commits into
mainfrom
feat/slack-interactivity-and-feedback-buttons

Conversation

@abdulrahmancodes

@abdulrahmancodes abdulrahmancodes commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Why

Assistant answers were one-way: a markdown block and a duration footer, with no way for the person who asked to say whether the answer was any good. The app also had no Slack interactivity surface at all — no request URL, no block_actions handling — so nothing interactive could be added to any message it posts.

Closes twentyhq/core-team-issues#2767.

What it does

Interactivity endpoint. A new slack-interactivity-resolver server route (/webhooks/server/fd756b00-50a2-4816-a919-a1a959a2ed9a) receives Slack interactivity callbacks. It verifies the signature with SLACK_WEBHOOK_SECRET over the raw body, exactly like the events resolver, then parses the form-encoded payload field and validates its shape with a type guard. Interactions it does not handle are acknowledged with a 200 so Slack neither retries nor shows the user a warning; feedback actions resolve the target workspace from the server-scoped team claim and dispatch to the handler there. The manifest enables interactivity with the new request URL.

Feedback buttons. build-slack-assistant-answer-blocks.ts appends a context_actions block with Slack's native feedback_buttons element (thumbs up / thumbs down — @slack/web-api 7.18 ships the FeedbackButtons and ContextActionsBlock types). The block's block_id carries the slackAssistantRequest record id, so the interaction maps back to the record without any lookup by message timestamp. Long answers that already fall back to a plain markdown message (no blocks) keep that behavior and simply carry no buttons.

Rating storage. A new slack-assistant-feedback function runs in the resolved workspace, maps the positive_feedback / negative_feedback button values to a new feedbackRating select field (Positive / Negative) on the slackAssistantRequest object, and updates the record. Repeated clicks overwrite — last click wins. Payloads without the feedback action, without a record id, or with an unknown value are skipped with a reason instead of erroring.

Server dispatch ack is now 200 (was 202). Slack's interactivity docs require a plain HTTP 200 OK acknowledgment, and a dispatching resolver has no way to set the response status, so enqueueTargetFunction in server-route-trigger.service.ts now acks with 200. Every other webhook sender (including Slack's Events API, which accepts any 2xx) is unaffected beyond the code change; the integration spec is updated accordingly.

resolveTargetWorkspaceId now takes the team id string instead of the whole events body, so both resolvers share it.

New files follow one-export-per-file: the resolver and feedback entry files only default-export defineLogicFunction, with their handlers in the existing handlers/ directory.

Version

Bumped to 0.5.0 — 0.4.0 is claimed by #23985 and 0.3.1 by #24097, so the three compose in any merge order.

Existing Slack apps must enable Interactivity & Shortcuts with the new request URL (apps created from the updated manifest get it automatically), otherwise the buttons show a warning when clicked. No new scopes, so no re-authorization is needed. SETUP.md documents the step.

Screenshots

Screenshot 2026-08-14 at 2 17 49 PM

…s on assistant answers

Add a slack-interactivity-resolver server route that verifies Slack's
request signature, acknowledges unhandled interactions, and dispatches
feedback block_actions to the resolved workspace. Assistant answers now
end with Slack's native feedback_buttons element carrying the request
record id, and a new slack-assistant-feedback function stores the
Positive/Negative rating on a new feedbackRating select field of the
slackAssistantRequest object. The app manifest enables interactivity
with the new request URL.
@twenty-ci-bot-public

Copy link
Copy Markdown

👋 Thanks for contributing to Twenty!

Your PR has been set to draft while you work on it. Once you're done, mark it as Ready for review and our automated checks will run.

Looking forward to your contribution!

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

Changes were made to package.json, but not to yarn.lock - Perhaps you need to run yarn install?

Generated by 🚫 dangerJS against 30a0f6b

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds Slack-native feedback controls to assistant answers and an authenticated interactivity route that stores the selected rating.

  • Adds signature-verified Slack interactivity parsing and workspace dispatch.
  • Adds positive and negative feedback buttons linked to assistant request records.
  • Persists feedback in a new select field and documents the required Slack configuration.
  • Changes queued server-route acknowledgments from 202 to 200.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/twenty-apps/public/slack/src/logic-functions/tests/slack-assistant-feedback.test.ts The previous review issue is fixed by asserting exactly one update call for both positive and negative feedback, with mock history cleared before each test.
packages/twenty-apps/public/slack/src/logic-functions/slack-interactivity-resolver.ts Adds signature verification, payload routing, workspace resolution, and acknowledgment for Slack interaction callbacks.
packages/twenty-apps/public/slack/src/logic-functions/slack-assistant-feedback.ts Maps supported feedback actions to ratings and updates the referenced assistant request in the resolved workspace.
packages/twenty-apps/public/slack/src/logic-functions/utils/build-slack-assistant-answer-blocks.ts Appends native Slack feedback buttons carrying the assistant request identifier.
packages/twenty-server/src/engine/core-modules/server-route-trigger/server-route-trigger.service.ts Changes successful queued-dispatch acknowledgments from HTTP 202 to HTTP 200.

Sequence Diagram

sequenceDiagram
  participant User as Slack user
  participant Slack
  participant Resolver as Interactivity resolver
  participant Queue as Server-route queue
  participant Handler as Feedback handler
  participant CRM as Workspace record API
  User->>Slack: Click feedback button
  Slack->>Resolver: Signed block_actions payload
  Resolver->>Resolver: Verify signature and resolve workspace
  Resolver->>Queue: Dispatch feedback payload
  Resolver-->>Slack: HTTP 200
  Queue->>Handler: Execute in target workspace
  Handler->>CRM: Update assistant request rating
Loading

Reviews (2): Last reviewed commit: "fix(server): ack server route dispatches..." | Re-trigger Greptile

@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 13, 2026

Copy link
Copy Markdown

✅ Standard review · no findings

Safe to merge — no findings; all prior issues are resolved or author-adjudicated

High-level — Cohesive sub-1000-line additive Slack feedback-buttons feature that reuses existing seams (extracted shared webhook-verification util, teamId-narrowed resolveTargetWorkspaceId) and adds only an UPPER_CASE SELECT field with no migration, flag, or breaking change.
Low-level — Constants/types/guards follow the package's isDefined/isNonEmptyString conventions, the (client, {...}) data-fn signature matches existing data functions, the throwing parser carries the OrThrow suffix, and the added comments state genuine external/cross-file constraints.


Reviewed against the pr-review standard — high-level then low-level. Advisory; human review still required. Run details.

…oad path

Validate the parsed interactivity payload with a type guard instead of
an as cast, use isDefined for the feedback handler's nullish checks,
drop a redundant === true in the resolver, and pin the feedback update
mock to exactly one call in the handler tests.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/twenty-apps/public/slack/README.md">

<violation number="1" location="packages/twenty-apps/public/slack/README.md:13">
P3: The README states "Answers carry native thumbs up / thumbs down feedback buttons", but the assistant only attaches the feedback `context_actions` block when the answer fits within the markdown block limit. In the worker, `messageBlocks` is set to `undefined` (no buttons) whenever `responseText.length > SLACK_MARKDOWN_BLOCK_MAX_LENGTH` (12,000). Very long answers still deliver as plain markdown with no feedback UI, so the documentation overstates the guarantee. Qualify the claim, e.g. note that long answers keep plain text without buttons.</violation>
</file>

<file name="packages/twenty-apps/public/slack/src/logic-functions/__tests__/slack-assistant-feedback.test.ts">

<violation number="1" location="packages/twenty-apps/public/slack/src/logic-functions/__tests__/slack-assistant-feedback.test.ts:52">
P3: The `toHaveBeenCalledWith` assertion passes even if `updateSlackAssistantRequestFeedback` is called multiple times with matching args, so a regression causing duplicate writes wouldn't be caught. Add a `toHaveBeenCalledTimes(1)` assertion alongside `toHaveBeenCalledWith` for both the positive and negative rating tests.</violation>
</file>

<file name="packages/twenty-apps/public/slack/src/logic-functions/slack-interactivity-resolver.ts">

<violation number="1" location="packages/twenty-apps/public/slack/src/logic-functions/slack-interactivity-resolver.ts:50">
P2: When the interactivity body lacks a `payload` field or its JSON cannot be parsed, `parseSlackInteractivityPayload` throws and the handler surfaces a 5xx, which makes Slack retry the delivery. This contradicts the acknowledgement strategy used elsewhere in the same handler (200 `{ ok: true }` for unhandled actions) and the stated goal of not retrying junk payloads. Catch parse failures and return `new Response({ ok: true })` instead of throwing.</violation>

<violation number="2" location="packages/twenty-apps/public/slack/src/logic-functions/slack-interactivity-resolver.ts:64">
P1: When a user clicks a feedback button, this dispatch path responds with HTTP 202 instead of Slack’s required 200 acknowledgment. Slack can show an interaction failure and retry the action even though the feedback job was queued; make the interactivity route acknowledge with 200 after enqueueing.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

return {
workspaceId: await resolveTargetWorkspaceId(payload.team?.id),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a user clicks a feedback button, this dispatch path responds with HTTP 202 instead of Slack’s required 200 acknowledgment. Slack can show an interaction failure and retry the action even though the feedback job was queued; make the interactivity route acknowledge with 200 after enqueueing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-apps/public/slack/src/logic-functions/slack-interactivity-resolver.ts, line 64:

<comment>When a user clicks a feedback button, this dispatch path responds with HTTP 202 instead of Slack’s required 200 acknowledgment. Slack can show an interaction failure and retry the action even though the feedback job was queued; make the interactivity route acknowledge with 200 after enqueueing.</comment>

<file context>
@@ -0,0 +1,81 @@
+  }
+
+  return {
+    workspaceId: await resolveTargetWorkspaceId(payload.team?.id),
+    targetLogicFunctionUniversalIdentifier:
+      SLACK_ASSISTANT_FEEDBACK_UNIVERSAL_IDENTIFIER,
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e6286cc, but at the platform level rather than here: a dispatching resolver has no way to set the response status — the 202 came from enqueueTargetFunction in server-route-trigger.service.ts. That ack is now 200, which Slack's interactivity docs require and every other webhook sender accepts. One nuance to the finding: Slack does not retry interaction payloads (retries are an Events API behavior), so the failure mode was a possible user-facing warning, not a duplicate action.


Generated by Claude Code

throw new Error('Invalid Slack signature');
}

const payload = parseSlackInteractivityPayload(routePayload.body);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the interactivity body lacks a payload field or its JSON cannot be parsed, parseSlackInteractivityPayload throws and the handler surfaces a 5xx, which makes Slack retry the delivery. This contradicts the acknowledgement strategy used elsewhere in the same handler (200 { ok: true } for unhandled actions) and the stated goal of not retrying junk payloads. Catch parse failures and return new Response({ ok: true }) instead of throwing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-apps/public/slack/src/logic-functions/slack-interactivity-resolver.ts, line 50:

<comment>When the interactivity body lacks a `payload` field or its JSON cannot be parsed, `parseSlackInteractivityPayload` throws and the handler surfaces a 5xx, which makes Slack retry the delivery. This contradicts the acknowledgement strategy used elsewhere in the same handler (200 `{ ok: true }` for unhandled actions) and the stated goal of not retrying junk payloads. Catch parse failures and return `new Response({ ok: true })` instead of throwing.</comment>

<file context>
@@ -0,0 +1,81 @@
+    throw new Error('Invalid Slack signature');
+  }
+
+  const payload = parseSlackInteractivityPayload(routePayload.body);
+
+  const hasAssistantFeedbackAction =
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the throw, deliberately. Slack does not retry interaction payloads on failure — the retry cycle is an Events API behavior — so a 5xx here cannot cause redelivery. And this branch is only reachable after signature verification: a signed request with a missing or unparseable payload means Slack's contract changed or something is wrong upstream, which should surface as an error rather than be swallowed by a 200. The ok: true ack is reserved for well-formed interactions we intentionally don't handle; the events resolver draws the same line by throwing on an empty verified body.


Generated by Claude Code

Comment thread packages/twenty-apps/public/slack/README.md Outdated
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 13, 2026

Copy link
Copy Markdown

🔍 Automated Pre-Review

No issues detected - This PR is ready for human review.


View details

Automated pre-review — human approval still required.

…vity

Slack requires a plain HTTP 200 OK to acknowledge interactivity
callbacks, and every webhook sender accepts 200, so the dispatch ack
moves from 202 to 200. Also dedupe the SlackAssistantFeedbackRating
type alias next to its constant and qualify the README feedback-buttons
claim for long answers.
@twenty-eng-sync twenty-eng-sync Bot closed this Aug 14, 2026
@twenty-eng-sync twenty-eng-sync Bot added the -PR: stale auto-closed Closed by the stale-draft auto-close cron (no update in 24h) label Aug 14, 2026
@twenty-eng-sync

Copy link
Copy Markdown

Auto-closed: this PR is draft and has had no update in over 24 hours. Please reopen it once you have the bandwidth to take it forward.

@abdulrahmancodes abdulrahmancodes removed the -PR: stale auto-closed Closed by the stale-draft auto-close cron (no update in 24h) label Aug 14, 2026
Move the feedback and interactivity resolver handlers into the existing
handlers/ directory, extract SlackAssistantFeedbackRating into its own
type file, and make SlackInteractionAction a local type so every new
file has a single export.
parse-slack-assistant-feedback-rating and parse-slack-interactivity-payload
are simple pure mappings whose behavior is already covered through the
feedback handler tests; testing them directly added no signal.
abdulrahmancodes and others added 4 commits August 14, 2026 09:00
…rim the interactivity guard

Extract verifySlackWebhookRequestOrThrow so the events and interactivity
resolvers verify requests through one seam, and narrow the interactivity
payload type and guard to the fields the handlers actually consume.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(twenty-slack): interactivity endpoint + native feedback buttons on assistant answers

1 participant