Skip to content

refactor(dashboard, api-service): ack flow for conversational#10788

Merged
scopsy merged 8 commits intonextfrom
fix-acknoelwgde-flow
Apr 19, 2026
Merged

refactor(dashboard, api-service): ack flow for conversational#10788
scopsy merged 8 commits intonextfrom
fix-acknoelwgde-flow

Conversation

@scopsy
Copy link
Copy Markdown
Contributor

@scopsy scopsy commented Apr 19, 2026

What changed? Why was the change needed?

What changed

Refactors conversational acknowledgement: replaces the typing/reaction model (thinkingIndicatorEnabled + nested reactions) with two flat behavior fields—acknowledgeOnReceived (boolean) that controls whether the agent shows typing or sends an acknowledgement on first inbound messages, and reactionOnResolved (emoji | null) for a post-resolution reaction. Platform logic was inverted to an explicit whitelist (PLATFORMS_WITH_TYPING_INDICATOR) so Slack/Teams show typing and other platforms fall back to a single 'eyes' emoji acknowledgement. The dashboard exposes controlled UI to toggle acknowledgement and pick/disable the resolved-reaction emoji.

Affected areas

api: DTOs, config resolver, inbound handler, chat SDK comments, usecases (update-agent, handle-agent-reply) and services now use acknowledgeOnReceived and reactionOnResolved; platform constant renamed/inverted; first-message flow prefers typing where supported, otherwise adds an 'eyes' reaction.

dashboard: Adds AgentBehavior types, wires AgentBehaviorSection to accept agent props, introduces ResolvedEmojiPicker and behavior toggles that call updateAgent mutations with query invalidation and error toasts.

dal: Agent entity and Mongoose schema updated—removed thinkingIndicatorEnabled and nested reactions, added acknowledgeOnReceived (Boolean) and reactionOnResolved (String | null).

tests: Agent e2e tests updated to verify PATCH/GET behavior for acknowledgeOnReceived and reactionOnResolved.

Key technical decisions

  • Flattened behavior shape (removed nested reactions) to simplify storage and updates and make updates atomic.
  • Switched typing-indicator logic from a blacklist to an explicit whitelist to make supported platforms explicit.
  • Enforced reactionOnResolved as a String in the schema (previously Mixed), which may require migration/cleanup of existing non-string records.

Testing

E2E tests were updated to cover the new behavior fields (enable/disable ack and set/clear resolved emoji); UI changes rely on these tests and should be manually verified for picker behavior and mutation UX.

Screenshots

Expand for optional sections

Related enterprise PR

Special notes for your reviewer

scopsy added 2 commits April 19, 2026 12:33
Replace the previous thinkingIndicatorEnabled + reactions object with a simplified behavior model: acknowledgeOnReceived (boolean, default true) and reactionOnResolved (emoji | null). Update DTOs, schema, repository entity, update-agent usecase, and agent config resolver to read the new fields. Inbound handling now shows a typing indicator when supported or falls back to adding an "eyes" reaction for first messages on platforms without typing (SLACK and WHATSAPP); handle-agent-reply removes that fallback reaction on reply. Dashboard types and UI, e2e tests, and chat SDK comments were adjusted to reflect the new behavior fields.
Replace the negative PLATFORMS_WITHOUT_TYPING_INDICATOR with PLATFORMS_WITH_TYPING_INDICATOR and update the set to include Slack and Teams (leaving WhatsApp out). Update agent inbound logic to check the positive set when deciding to start a typing indicator, and adjust the API DTO and dashboard tooltip text to reflect the corrected platform behavior and wording.
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 19, 2026

Deploy Preview for dashboard-v2-novu-staging ready!

Name Link
🔨 Latest commit 50af8d9
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69e4ada0e5c9970008ff7003
😎 Deploy Preview https://deploy-preview-10788.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces nested reaction and typing-indicator behavior with a simplified behavior shape: boolean acknowledgeOnReceived and single reactionOnResolved emoji; updates DTOs, DAL schema, resolver, inbound/reply flows, update usecase, e2e tests, and dashboard UI to use the new fields and adjusted typing/acknowledgement control flow.

Changes

Cohort / File(s) Summary
API DTOs & Enums
apps/api/src/app/agents/dtos/agent-behavior.dto.ts, apps/api/src/app/agents/dtos/agent-platform.enum.ts
Renamed DTO to AgentBehaviorDto; removed nested reactions and thinkingIndicatorEnabled; added acknowledgeOnReceived?: boolean and `reactionOnResolved?: string
Backend Services & Usecases
apps/api/src/app/agents/services/agent-config-resolver.service.ts, apps/api/src/app/agents/services/agent-inbound-handler.service.ts, apps/api/src/app/agents/services/chat-sdk.service.ts, apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts, apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
Resolved config now exposes acknowledgeOnReceived; removed thinking-indicator and per-message reaction defaults; inbound handler shows typing for supported platforms or falls back to hardcoded 'eyes' ack and persists firstPlatformMessageId only for first messages; ack removal and update usecase updated to new fields and paths; chat-sdk inline comment updated.
DAL Types & Schema
libs/dal/src/repositories/agent/agent.entity.ts, libs/dal/src/repositories/agent/agent.schema.ts
Removed AgentReactionSettings and thinkingIndicatorEnabled from AgentBehavior; added acknowledgeOnReceived?: boolean and `reactionOnResolved?: string
E2E Tests
apps/api/src/app/agents/e2e/agents.e2e.ts
Adjusted tests to patch/assert acknowledgeOnReceived and reactionOnResolved instead of previous thinkingIndicatorEnabled and nested reactions fields.
Dashboard Types & UI
apps/dashboard/src/api/agents.ts, apps/dashboard/src/components/agents/agent-behavior-section.tsx, apps/dashboard/src/components/agents/agent-connected-overview.tsx, apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx, apps/dashboard/src/components/agents/slack-setup-guide.tsx
Added AgentBehavior type and included behavior in request/response types; AgentBehaviorSection now receives agent prop and exposes controlled toggle for acknowledgeOnReceived plus ResolvedEmojiPicker (including Disabled/null); parent passes agent; WhatsApp/Slack setup guides and step logic/copy updated.
Minor docs/comment
apps/api/src/app/agents/services/chat-sdk.service.ts
Updated inline comment to list acknowledgeOnReceived and reactionOnResolved as cached config fields.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant InboundHandler as AgentInboundHandler
  participant Platform
  participant DB as Channel/Cache

  Client->>InboundHandler: Incoming first message (message, platform)
  InboundHandler->>DB: Read cached ResolvedAgentConfig
  DB-->>InboundHandler: ResolvedAgentConfig (acknowledgeOnReceived, reactionOnResolved, platform)
  alt Platform supports typing (PLATFORMS_WITH_TYPING_INDICATOR.has(platform))
    InboundHandler->>Platform: Show typing indicator
  else Platform does not support typing
    opt acknowledgeOnReceived
      InboundHandler->>Platform: Add reaction "eyes"
      InboundHandler->>DB: Persist firstPlatformMessageId
    end
  end
  Platform-->>InboundHandler: Ack/Reaction created
  InboundHandler-->>Client: Continue processing inbound event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #10726 — Refactors agent behavior/reaction settings across DTOs, services, and schema; closely overlaps with these changes.
  • #10764 — Modifies emoji/reaction validation and emoji listing consumed by the updated reactionOnResolved control.
  • #10721 — Introduced prior thinkingIndicatorEnabled and related flow that this PR replaces.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title uses the correct Conventional Commits format with type 'refactor' and valid scopes 'dashboard, api-service', but the description 'ack flow for conversational' is unclear and incomplete, making the intent difficult to understand without additional context. Revise the description to be more specific and descriptive; consider 'refactor(dashboard, api-service): restructure agent acknowledgment behavior configuration' or similar to clearly convey the main change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: refactor(dashboard, api-service): ack flow for conversational

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

Replace static emoji button with an interactive ResolvedEmojiPicker and wire up behavior updates via a react-query mutation. AgentBehaviorSection now accepts an agent prop, reads current behavior values, and uses updateAgent (with requireEnvironment) to persist changes; on success it invalidates the agent detail query and on error shows a toast. UI changes include a Popover picker with a Disabled option, disabled state while mutation is pending, and updated imports. Also pass the agent prop from AgentConnectedOverview.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/src/app/agents/dtos/agent-behavior.dto.ts (1)

18-27: ⚠️ Potential issue | 🟡 Minor

Mark reactionOnResolved as nullable in Swagger.

The DTO accepts null to disable resolved reactions, but the OpenAPI metadata only marks the field optional. Generated clients may not know null is valid.

Proposed fix
   `@ApiPropertyOptional`({
     description:
       'Cross-platform emoji name for resolved conversations (e.g. "check", "star"). ' +
       'Set to null to disable. Default: "check"',
     default: 'check',
+    nullable: true,
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/dtos/agent-behavior.dto.ts` around lines 18 - 27, The
OpenAPI metadata for the DTO property reactionOnResolved is missing
nullable=true, so update the `@ApiPropertyOptional` decorator on the
reactionOnResolved property to include nullable: true (e.g.
`@ApiPropertyOptional`({ ..., default: 'check', nullable: true })) so generated
clients know null is an accepted value while keeping the existing
`@IsOptional/`@ValidateIf/@IsWellKnownEmoji usage intact.
apps/dashboard/src/components/agents/agent-behavior-section.tsx (1)

23-25: ⚠️ Potential issue | 🟠 Major

Include the environment ID in the emoji query key.

The listAgentEmoji() function requires currentEnvironment to fetch the correct emoji list per environment, but getAgentEmojiQueryKey() is static. When switching environments, React Query returns the cached data from the previous environment instead of refetching, violating React Query's requirement that query keys include all dependencies that affect the returned data.

Proposed fix
-    queryKey: getAgentEmojiQueryKey(),
+    queryKey: getAgentEmojiQueryKey(currentEnvironment?._id),

And update the API helper:

export function getAgentEmojiQueryKey(environmentId: string | undefined) {
  return [AGENT_EMOJI_QUERY_KEY, environmentId] as const;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx` around lines
23 - 25, The query key must include the environment dependency so React Query
refetches when environment changes: update the call in agent-behavior-section to
pass the environment into the key (e.g., useQuery({ queryKey:
getAgentEmojiQueryKey(currentEnvironment!), queryFn: ({ signal }) =>
listAgentEmoji(currentEnvironment!, signal) })) and change the helper signature
getAgentEmojiQueryKey(environmentId) to return [AGENT_EMOJI_QUERY_KEY,
environmentId] as const; ensure the helper and all callers accept and forward
the environmentId parameter.
🧹 Nitpick comments (1)
apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts (1)

343-359: Avoid hardcoding 'eyes' — reuse the shared fallback-emoji constant.

The inbound handler adds this reaction via ACKNOWLEDGE_FALLBACK_EMOJI (see agent-inbound-handler.service.ts), but here the removal references the literal 'eyes'. If the constant is ever changed, acks will silently stop being cleared on reply. Keep add/remove in lock-step by importing the same constant.

♻️ Proposed change
-import { AgentConfigResolver, ResolvedAgentConfig } from '../../services/agent-config-resolver.service';
+import { ACKNOWLEDGE_FALLBACK_EMOJI } from '../../services/agent-inbound-handler.service';
+import { AgentConfigResolver, ResolvedAgentConfig } from '../../services/agent-config-resolver.service';
     await this.chatSdkService.removeReaction(
       conversation._agentId,
       config.integrationIdentifier,
       channel.platform,
       channel.platformThreadId,
       firstMessageId,
-      'eyes'
+      ACKNOWLEDGE_FALLBACK_EMOJI
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`
around lines 343 - 359, The removeAckReaction method currently hardcodes the
emoji string 'eyes' which can drift from the inbound handler; instead import and
use the shared ACKNOWLEDGE_FALLBACK_EMOJI constant (the same one used in the
inbound handler) and pass that to chatSdkService.removeReaction; update the
imports at the top of the file to include ACKNOWLEDGE_FALLBACK_EMOJI and replace
the literal in removeAckReaction so add/remove remain in lock-step.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts`:
- Around line 124-125: The call to thread.startTyping inside the supportsTyping
branch is an external platform call and must be best-effort so it cannot abort
the inbound handler; change the code around supportsTyping/thread.startTyping to
make the typing ack non-blocking and swallow transient errors (e.g. call
thread.startTyping('Thinking...') without awaiting it and attach a .catch(...)
that logs a warning via the service logger, or wrap the await in try/catch and
log the error) so failures do not prevent the rest of
agent-inbound-handler.service (the handler that executes the bridge and
processes the message) from running.
- Around line 126-145: The two side-effect calls in the isFirstMessage branch
(thread.createSentMessageFromMessage(...).addReaction(ACKNOWLEDGE_FALLBACK_EMOJI)
and conversationRepository.setFirstPlatformMessageId(...)) must be awaited
before continuing to bridge execution; change the fire-and-forget pattern to
await both results (e.g., capture the Promise from
thread.createSentMessageFromMessage(...).addReaction(...) and the Promise from
conversationRepository.setFirstPlatformMessageId(...) and use Promise.all or
sequential awaits inside a try/catch), log any errors with this.logger.warn but
ensure the awaits complete so the ack reaction and firstPlatformMessageId are
persisted before the bridge reply runs.

In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx`:
- Around line 169-171: The label and tooltip for the ToggleRow controlling
resolved-reaction are inconsistent (label: "React to the final message when a
conversation is resolved" vs tooltip: "Add an emoji reaction to the first
message in the thread when the conversation is resolved."); update one so both
describe the same target message. Locate the ToggleRow instance in
agent-behavior-section.tsx (the component rendering label and tooltip) and
change either the label text or the tooltip text so they match—e.g., make both
say "final message" or both say "first message" depending on intended
behavior—and ensure the wording clearly states when the reaction is applied.
- Around line 70-83: The trigger currently shows "Off" when unicodeMap lacks an
entry for currentEmoji because displayUnicode falls back to '' — change the
logic so displayUnicode is undefined when the emoji key is missing (e.g., set
displayUnicode = currentEmoji ? unicodeMap.get(currentEmoji) : null) and update
the render branch in the PopoverTrigger/button to: show the unicode when
displayUnicode is a string, show a loading/placeholder (or nothing) when
currentEmoji is present but displayUnicode is undefined, and only render "Off"
when currentEmoji is null; update references to displayUnicode, currentEmoji and
unicodeMap in the agent behavior component accordingly.

In `@libs/dal/src/repositories/agent/agent.schema.ts`:
- Around line 21-24: The schema change replaced old behavior fields and
introduced behavior.acknowledgeOnReceived and behavior.reactionOnResolved but
left legacy fields (behavior.thinkingIndicatorEnabled and
behavior.reactions.onMessageReceived/onResolved) and used Schema.Types.Mixed for
reactionOnResolved; update the schema to use Schema.Types.String (allowing null)
for reactionOnResolved, and add a data migration script to unset the legacy
fields and optionally backfill the new fields from the old values (map
thinkingIndicatorEnabled -> acknowledgeOnReceived and
reactions.{onMessageReceived,onResolved} -> reactionOnResolved as appropriate)
so no orphaned fields persist and Mongoose change-tracking works correctly for
nested updates.

---

Outside diff comments:
In `@apps/api/src/app/agents/dtos/agent-behavior.dto.ts`:
- Around line 18-27: The OpenAPI metadata for the DTO property
reactionOnResolved is missing nullable=true, so update the `@ApiPropertyOptional`
decorator on the reactionOnResolved property to include nullable: true (e.g.
`@ApiPropertyOptional`({ ..., default: 'check', nullable: true })) so generated
clients know null is an accepted value while keeping the existing
`@IsOptional/`@ValidateIf/@IsWellKnownEmoji usage intact.

In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx`:
- Around line 23-25: The query key must include the environment dependency so
React Query refetches when environment changes: update the call in
agent-behavior-section to pass the environment into the key (e.g., useQuery({
queryKey: getAgentEmojiQueryKey(currentEnvironment!), queryFn: ({ signal }) =>
listAgentEmoji(currentEnvironment!, signal) })) and change the helper signature
getAgentEmojiQueryKey(environmentId) to return [AGENT_EMOJI_QUERY_KEY,
environmentId] as const; ensure the helper and all callers accept and forward
the environmentId parameter.

---

Nitpick comments:
In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`:
- Around line 343-359: The removeAckReaction method currently hardcodes the
emoji string 'eyes' which can drift from the inbound handler; instead import and
use the shared ACKNOWLEDGE_FALLBACK_EMOJI constant (the same one used in the
inbound handler) and pass that to chatSdkService.removeReaction; update the
imports at the top of the file to include ACKNOWLEDGE_FALLBACK_EMOJI and replace
the literal in removeAckReaction so add/remove remain in lock-step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 685dec7f-365c-4f3a-9bf1-e2dfdfc2c5c2

📥 Commits

Reviewing files that changed from the base of the PR and between 1c6540a and 2f2abd6.

📒 Files selected for processing (13)
  • apps/api/src/app/agents/dtos/agent-behavior.dto.ts
  • apps/api/src/app/agents/dtos/agent-platform.enum.ts
  • apps/api/src/app/agents/e2e/agents.e2e.ts
  • apps/api/src/app/agents/services/agent-config-resolver.service.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/components/agents/agent-behavior-section.tsx
  • apps/dashboard/src/components/agents/agent-connected-overview.tsx
  • libs/dal/src/repositories/agent/agent.entity.ts
  • libs/dal/src/repositories/agent/agent.schema.ts

Comment on lines +124 to +125
if (supportsTyping) {
await thread.startTyping('Thinking...');
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.

⚠️ Potential issue | 🟠 Major

Keep typing acknowledgements best-effort.

thread.startTyping() is an external platform call. If Slack/Teams returns a transient error, this aborts the inbound handler before the bridge executes, so the agent can miss the message.

Proposed fix
       if (supportsTyping) {
-        await thread.startTyping('Thinking...');
+        await thread.startTyping('Thinking...').catch((err) => {
+          this.logger.warn(err, `[agent:${agentId}] Failed to start typing indicator`);
+        });
       } else if (isFirstMessage && message.id) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (supportsTyping) {
await thread.startTyping('Thinking...');
if (supportsTyping) {
await thread.startTyping('Thinking...').catch((err) => {
this.logger.warn(err, `[agent:${agentId}] Failed to start typing indicator`);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts` around
lines 124 - 125, The call to thread.startTyping inside the supportsTyping branch
is an external platform call and must be best-effort so it cannot abort the
inbound handler; change the code around supportsTyping/thread.startTyping to
make the typing ack non-blocking and swallow transient errors (e.g. call
thread.startTyping('Thinking...') without awaiting it and attach a .catch(...)
that logs a warning via the service logger, or wrap the await in try/catch and
log the error) so failures do not prevent the rest of
agent-inbound-handler.service (the handler that executes the bridge and
processes the message) from running.

Comment thread apps/api/src/app/agents/services/agent-inbound-handler.service.ts
Comment thread apps/dashboard/src/components/agents/agent-behavior-section.tsx Outdated
Comment thread apps/dashboard/src/components/agents/agent-behavior-section.tsx Outdated
Comment on lines 21 to 24
behavior: {
thinkingIndicatorEnabled: Schema.Types.Boolean,
reactions: {
onMessageReceived: Schema.Types.Mixed,
onResolved: Schema.Types.Mixed,
},
acknowledgeOnReceived: Schema.Types.Boolean,
reactionOnResolved: Schema.Types.Mixed,
},
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.

⚠️ Potential issue | 🟡 Minor

Schema rename needs a data migration; consider String over Mixed for reactionOnResolved.

Two concerns on this behavior subdocument:

  1. Migration for stale fields. This rename silently leaves existing agent documents with orphaned behavior.thinkingIndicatorEnabled and behavior.reactions.{onMessageReceived,onResolved} fields. They won't be read anymore, but they'll persist indefinitely and may leak through if any code ever does a $set: { behavior: {...} } wholesale replace, or confuse future debugging. Plan a migration to $unset the old fields (and optionally backfill acknowledgeOnReceived / reactionOnResolved from the old values to preserve user intent).

  2. Schema.Types.Mixed for reactionOnResolved. The entity types it as string | null; Schema.Types.String accepts null and gives you field-level casting/validation. Mixed opts out of that and also disables Mongoose change-tracking on nested mutations, which can bite later.

♻️ Proposed type tightening
     behavior: {
       acknowledgeOnReceived: Schema.Types.Boolean,
-      reactionOnResolved: Schema.Types.Mixed,
+      reactionOnResolved: Schema.Types.String,
     },

As per coding guidelines: "Flag any schema changes that need migrations."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
behavior: {
thinkingIndicatorEnabled: Schema.Types.Boolean,
reactions: {
onMessageReceived: Schema.Types.Mixed,
onResolved: Schema.Types.Mixed,
},
acknowledgeOnReceived: Schema.Types.Boolean,
reactionOnResolved: Schema.Types.Mixed,
},
behavior: {
acknowledgeOnReceived: Schema.Types.Boolean,
reactionOnResolved: Schema.Types.String,
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/dal/src/repositories/agent/agent.schema.ts` around lines 21 - 24, The
schema change replaced old behavior fields and introduced
behavior.acknowledgeOnReceived and behavior.reactionOnResolved but left legacy
fields (behavior.thinkingIndicatorEnabled and
behavior.reactions.onMessageReceived/onResolved) and used Schema.Types.Mixed for
reactionOnResolved; update the schema to use Schema.Types.String (allowing null)
for reactionOnResolved, and add a data migration script to unset the legacy
fields and optionally backfill the new fields from the old values (map
thinkingIndicatorEnabled -> acknowledgeOnReceived and
reactions.{onMessageReceived,onResolved} -> reactionOnResolved as appropriate)
so no orphaned fields persist and Mongoose change-tracking works correctly for
nested updates.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/dashboard/src/components/agents/agent-behavior-section.tsx (1)

70-83: ⚠️ Potential issue | 🟡 Minor

Trigger still flashes "Off" before emojiList loads.

The default reactionOnResolved is 'check', but until emojiList resolves, unicodeMap.get('check') returns undefined, the ?? '' collapses to an empty string, and the falsy check on line 79 then renders the Off branch — even though reactions are enabled. Distinguish "unknown/loading" from "explicitly disabled" (null).

Proposed fix
-  const displayUnicode = currentEmoji ? (unicodeMap.get(currentEmoji) ?? '') : null;
+  const resolvedUnicode = currentEmoji ? unicodeMap.get(currentEmoji) : null;
@@
-          {displayUnicode ? (
-            <span className="text-label-sm leading-5">{displayUnicode}</span>
-          ) : (
-            <span className="text-text-soft text-label-sm leading-5">Off</span>
-          )}
+          {currentEmoji === null ? (
+            <span className="text-text-soft text-label-sm leading-5">Off</span>
+          ) : (
+            <span className="text-label-sm leading-5">{resolvedUnicode ?? ''}</span>
+          )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx` around lines
70 - 83, The UI shows "Off" while emoji data is loading because displayUnicode
collapses missing entries to '' — change the display logic so there are three
states: null = explicitly disabled (show "Off"), undefined = loading/unknown
(show a loading placeholder or nothing), and string = found emoji (show it).
Concretely, compute displayUnicode from currentEmoji and unicodeMap (e.g., if
currentEmoji == null => null; else if unicodeMap.has(currentEmoji) =>
unicodeMap.get(currentEmoji) ?? '' ; else => undefined) and update the JSX
branch in AgentBehaviorSection (the button render that currently checks
displayUnicode) to test displayUnicode === null for the "Off" branch,
displayUnicode === undefined for a loading state, and otherwise render the emoji
string.
🧹 Nitpick comments (1)
apps/dashboard/src/components/agents/agent-behavior-section.tsx (1)

136-180: Shared isPending cross-disables unrelated controls.

A single useMutation is shared by both the acknowledge Switch (line 164) and the ResolvedEmojiPicker (line 177). Toggling one control disables the other for the duration of the in-flight request, which is surprising for independent settings. Consider tracking pending state per field (e.g., the variables from useMutation, or two separate mutations) so each control only disables itself.

Sketch
-  const { mutate, isPending } = useMutation({ ... });
+  const mutation = useMutation({ ... });
+  const pendingAck = mutation.isPending && mutation.variables?.acknowledgeOnReceived !== undefined;
+  const pendingReaction = mutation.isPending && mutation.variables?.reactionOnResolved !== undefined;
...
-              disabled={isPending}
+              disabled={pendingAck}
...
-              disabled={isPending}
+              disabled={pendingReaction}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx` around lines
136 - 180, The current single useMutation (symbols: useMutation, mutate,
isPending) is shared by both the acknowledge Switch and the ResolvedEmojiPicker
so toggling one disables the other; split this into two independent mutations
(e.g., create mutateAcknowledge and isPendingAcknowledge for
acknowledgeOnReceived, and mutateReaction and isPendingReaction for
reactionOnResolved) or otherwise derive per-field pending state from the
mutation variables so each control (Switch and ResolvedEmojiPicker) only
disables itself when its own mutation is in-flight; update the mutationFn calls
to pass the correct body ({ acknowledgeOnReceived } vs { reactionOnResolved })
and wire each control to its corresponding mutate and isPending flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx`:
- Around line 70-83: The UI shows "Off" while emoji data is loading because
displayUnicode collapses missing entries to '' — change the display logic so
there are three states: null = explicitly disabled (show "Off"), undefined =
loading/unknown (show a loading placeholder or nothing), and string = found
emoji (show it). Concretely, compute displayUnicode from currentEmoji and
unicodeMap (e.g., if currentEmoji == null => null; else if
unicodeMap.has(currentEmoji) => unicodeMap.get(currentEmoji) ?? '' ; else =>
undefined) and update the JSX branch in AgentBehaviorSection (the button render
that currently checks displayUnicode) to test displayUnicode === null for the
"Off" branch, displayUnicode === undefined for a loading state, and otherwise
render the emoji string.

---

Nitpick comments:
In `@apps/dashboard/src/components/agents/agent-behavior-section.tsx`:
- Around line 136-180: The current single useMutation (symbols: useMutation,
mutate, isPending) is shared by both the acknowledge Switch and the
ResolvedEmojiPicker so toggling one disables the other; split this into two
independent mutations (e.g., create mutateAcknowledge and isPendingAcknowledge
for acknowledgeOnReceived, and mutateReaction and isPendingReaction for
reactionOnResolved) or otherwise derive per-field pending state from the
mutation variables so each control (Switch and ResolvedEmojiPicker) only
disables itself when its own mutation is in-flight; update the mutationFn calls
to pass the correct body ({ acknowledgeOnReceived } vs { reactionOnResolved })
and wire each control to its corresponding mutate and isPending flag.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1461cf2a-b81d-4622-80e0-88a93c764f83

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2abd6 and 0e98359.

📒 Files selected for processing (1)
  • apps/dashboard/src/components/agents/agent-behavior-section.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx (1)

59-96: ⚠️ Potential issue | 🟠 Major

Reset credential progress when the setup target changes.

isCredentialsSaved is local state, but only isConnected resets when integrationId changes. After saving credentials for one integration, switching to another can mark “Save credentials in Novu” as completed and jump straight to the webhook step.

🐛 Proposed fix
   // biome-ignore lint/correctness/useExhaustiveDependencies: reset when the watched integration changes
   useEffect(() => {
     setIsConnected(false);
+    setIsCredentialsSaved(false);
+    setIsCredentialsSidebarOpen(false);
-  }, [integrationId]);
+  }, [integrationId, agent._id]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx` around lines
59 - 96, The effect that resets progress when the watched integration changes
currently only calls setIsConnected(false); also reset the local credential
state so saved credentials don't carry over to a different integration: update
the useEffect that watches integrationId (the block with the biome-ignore
comment) to call setIsCredentialsSaved(false) alongside setIsConnected(false) so
the component recalculates firstIncompleteStep correctly when integrationId
changes; ensure you reference the existing isCredentialsSaved state setter and
keep integrationId in the dependency array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx`:
- Around line 59-96: The effect that resets progress when the watched
integration changes currently only calls setIsConnected(false); also reset the
local credential state so saved credentials don't carry over to a different
integration: update the useEffect that watches integrationId (the block with the
biome-ignore comment) to call setIsCredentialsSaved(false) alongside
setIsConnected(false) so the component recalculates firstIncompleteStep
correctly when integrationId changes; ensure you reference the existing
isCredentialsSaved state setter and keep integrationId in the dependency array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2cdb8496-457f-4785-8cf6-163d1cca3288

📥 Commits

Reviewing files that changed from the base of the PR and between 0e98359 and 59fbb4d.

📒 Files selected for processing (1)
  • apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx

UI: Adjust ResolvedEmojiPicker logic to distinguish an explicit "off" (currentEmoji === null) from unmapped emoji values. displayUnicode now returns undefined when no mapping exists; the picker shows "Off" for null, the mapped unicode when available, and falls back to the raw emoji string otherwise. Also update the toggle label and tooltip to clarify it targets the first message.

Schema: Change agent.behavior.reactionOnResolved from Mixed to String to enforce string values for stored reactions. Note: existing records using non-string values may require migration or cleanup.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dashboard/src/components/agents/slack-setup-guide.tsx (1)

148-151: ⚠️ Potential issue | 🟡 Minor

Make the Slack completion callback idempotent.

Line 269 adds a second source for handleSlackWorkspaceConnected, while ListeningStatus still calls the same handler on Line 282. This can invoke onStepsCompleted twice for one install flow.

🐛 Proposed fix
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@
   const [isCredentialsSidebarOpen, setIsCredentialsSidebarOpen] = useState(false);
   const [isCredentialsSaved, setIsCredentialsSaved] = useState(false);
   const [isSlackWorkspaceConnected, setIsSlackWorkspaceConnected] = useState(false);
+  const hasReportedSlackWorkspaceConnectedRef = useRef(false);
@@
   useEffect(() => {
     setIsSlackWorkspaceConnected(false);
+    hasReportedSlackWorkspaceConnectedRef.current = false;
   }, [integrationId]);
 
   const handleSlackWorkspaceConnected = useCallback(() => {
     setIsSlackWorkspaceConnected(true);
+
+    if (hasReportedSlackWorkspaceConnectedRef.current) {
+      return;
+    }
+
+    hasReportedSlackWorkspaceConnectedRef.current = true;
     onStepsCompleted?.();
   }, [onStepsCompleted]);

Also applies to: 263-269, 278-283

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/agents/slack-setup-guide.tsx` around lines 148
- 151, The handler handleSlackWorkspaceConnected currently sets state and calls
onStepsCompleted and can be invoked twice; make it idempotent by returning early
if the workspace is already marked connected (or if a ref/flag like
isSlackWorkspaceConnectedRef is true) so onStepsCompleted is only called once;
update handleSlackWorkspaceConnected (and any other callers that may invoke it)
to check the flag before calling setIsSlackWorkspaceConnected and before
invoking onStepsCompleted, or update the invocation to guard with the same flag
to ensure a single invocation per install flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/dashboard/src/components/agents/slack-setup-guide.tsx`:
- Around line 148-151: The handler handleSlackWorkspaceConnected currently sets
state and calls onStepsCompleted and can be invoked twice; make it idempotent by
returning early if the workspace is already marked connected (or if a ref/flag
like isSlackWorkspaceConnectedRef is true) so onStepsCompleted is only called
once; update handleSlackWorkspaceConnected (and any other callers that may
invoke it) to check the flag before calling setIsSlackWorkspaceConnected and
before invoking onStepsCompleted, or update the invocation to guard with the
same flag to ensure a single invocation per install flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e101227a-1d2d-4081-a45a-561a89997fec

📥 Commits

Reviewing files that changed from the base of the PR and between 59fbb4d and 0a7f75a.

📒 Files selected for processing (1)
  • apps/dashboard/src/components/agents/slack-setup-guide.tsx

@scopsy scopsy merged commit 8389bc4 into next Apr 19, 2026
35 of 36 checks passed
@scopsy scopsy deleted the fix-acknoelwgde-flow branch April 19, 2026 10:34
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.

1 participant