Skip to content

feat(api-service,framework): agent onReaction event fixes NV-7370#10733

Merged
ChmaraX merged 3 commits intonextfrom
nv/agent-on-reaction
Apr 15, 2026
Merged

feat(api-service,framework): agent onReaction event fixes NV-7370#10733
ChmaraX merged 3 commits intonextfrom
nv/agent-on-reaction

Conversation

@ChmaraX
Copy link
Copy Markdown
Contributor

@ChmaraX ChmaraX commented Apr 15, 2026

Summary

  • Fire an ON_REACTION bridge event when a user adds or removes an emoji reaction on any message in an agent conversation thread
  • API: register chat.onReaction(), new handleReaction() on inbound handler (ephemeral — no activity persistence, no ack reaction, no typing indicator), extend bridge executor with mapReaction() reusing mapMessage() for real author data
  • Framework SDK: required onReaction handler on AgentHandlers, AgentContext.reaction: AgentReaction | null, uniform map-based dispatch in runAgentHandler(), validation in agent() factory

How it works

sequenceDiagram
    participant Slack as Slack (reaction_added/removed)
    participant ChatSDK as Chat SDK
    participant Inbound as AgentInboundHandler
    participant Bridge as BridgeExecutorService
    participant Framework as Framework SDK
    participant Handler as onReaction handler

    Slack->>ChatSDK: reaction event
    ChatSDK->>Inbound: handleReaction(event)
    Inbound->>Inbound: lookup conversation by thread
    Inbound->>Inbound: resolve subscriber
    Inbound->>Bridge: execute(ON_REACTION, reaction)
    Bridge->>Bridge: mapReaction() + mapMessage()
    Bridge->>Framework: POST agent-event?event=onReaction
    Framework->>Handler: handlers.onReaction(ctx)
    Handler->>Framework: ctx.reply() / ctx.resolve() / etc.
Loading

Test plan

  • All 21 framework agent tests pass (18 existing updated + 3 new)
  • Verify Slack reaction_added/removed events dispatch to onReaction handler
  • Verify reaction on non-agent thread is silently ignored
  • Verify ctx.reaction is null on non-reaction events
  • Verify ctx.reaction.message contains real author data when available

Fixes NV-7370

Made with Cursor

What changed

The PR adds ON_REACTION event handling to the agent system, enabling agents to respond when users add or remove emoji reactions on messages in conversation threads. When a reaction occurs on a message, the Chat SDK forwards the event through the inbound handler, which resolves the conversation and subscriber, builds a bridge payload with reaction metadata, and triggers the framework SDK to invoke the agent's onReaction handler. Reaction handling is ephemeral—no activity is persisted, no acknowledgment reaction is sent, and no typing indicator is shown.

Affected areas

  • api: Added AgentEventEnum.ON_REACTION, created AgentInboundHandler.handleReaction() to resolve conversations and subscribers from reaction events, registered chat.onReaction() handler to dispatch reactions, and extended BridgeExecutorService to map reaction payloads with optional message context.
  • framework: Added AgentContext.reaction and AgentBridgeRequest.reaction fields, introduced AgentReaction type to represent reaction metadata, added optional onReaction handler to AgentHandlers, and refactored runAgentHandler() to use a map-based dispatch pattern supporting all agent event types.

Key technical decisions

  • Reaction events use event.user (the reactor) rather than event.message.author to correctly identify the subscriber performing the action.
  • messageId is explicitly included in reaction payloads so consumers can identify the reacted message even when the message body is null.
  • Handler dispatch uses hasOwnProperty checks to avoid prototype traversal risks when looking up handlers by event type.
  • Handler invocation is conditional on the handler being defined, making onReaction optional without requiring explicit null checks in agent implementations.

Testing

21 framework agent tests total (18 existing updated + 3 new) verify the feature; all framework agent tests pass. E2E tests validate that reactions correctly dispatch bridge events with proper payload mapping, skip processing for non-agent threads, and do not persist conversation activity. Mock agent handler updated to demonstrate onReaction implementation with emoji logging and replies. Manual verification checklist covers Slack dispatch behavior, non-agent-thread filtering, null reaction context on non-reaction events, and real author data mapping when available.

@linear
Copy link
Copy Markdown

linear Bot commented Apr 15, 2026

@cursor
Copy link
Copy Markdown
Contributor

cursor Bot commented Apr 15, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 15, 2026

Deploy Preview for dashboard-v2-novu-staging canceled.

Name Link
🔨 Latest commit 1f725c4
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69dfb72b01c2e60009f3af9f

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 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

Adds emoji reaction support across the agent stack: new ON_REACTION event, inbound reaction handler, bridge reaction payloads, chat SDK listener, framework dispatch updates, context/type extensions, and corresponding unit and e2e tests.

Changes

Cohort / File(s) Summary
Event Types & Public Types
apps/api/src/app/agents/dtos/agent-event.enum.ts, packages/framework/src/resources/agent/agent.types.ts, packages/framework/src/resources/agent/index.ts, packages/framework/src/index.ts
Added ON_REACTION to AgentEventEnum; introduced AgentReaction type; extended AgentContext, AgentHandlers, and AgentBridgeRequest to carry `reaction: AgentReaction
Inbound Handling (API)
apps/api/src/app/agents/services/agent-inbound-handler.service.ts
Added exported InboundReactionEvent and AgentInboundHandler.handleReaction(...) to validate thread, optionally resolve subscriber, load conversation/history, build BridgeReaction, and invoke BridgeExecutor with event: AgentEventEnum.ON_REACTION.
Bridge Execution
apps/api/src/app/agents/services/bridge-executor.service.ts
Added exported BridgeReaction and internal BridgeReactionPayload; threaded optional reaction through BridgeExecutorParams and AgentBridgeRequest; updated payload mapping and deliveryId logic when reaction-only deliveries occur.
Chat SDK Listener
apps/api/src/app/agents/services/chat-sdk.service.ts
Registered chat.onReaction listener that forwards reaction payload (emoji, added, messageId, message, thread, user) to inboundHandler.handleReaction(...) with try/catch error logging.
Framework Dispatch Logic
packages/framework/src/handler.ts
Refactored event dispatch to a handlerMap keyed by AgentEventEnum, validate unknown events, call mapped handler when present (no-op if missing), and support registeredAgent.handlers.onReaction; ctx.flush() remains unconditional.
Agent Context Implementation
packages/framework/src/resources/agent/agent.context.ts
Added public readonly `reaction: AgentReaction
Framework Tests
packages/framework/src/resources/agent/agent.test.ts
Updated mock AgentBridgeRequest to include reaction: null; added tests for onReaction dispatch, ctx.reaction population (including null message), reply behavior, and ACK when handler absent.
E2E & Mock Agent
apps/api/src/app/agents/e2e/agent-webhook.e2e.ts, apps/api/src/app/agents/e2e/mock-agent-handler.ts
Added e2e reaction tests and invokeReaction() helper; added onReaction handler to mock agent; assertions for bridge execution, payload contents (including sourceMessage), and no activity count change.

Sequence Diagram(s)

sequenceDiagram
  participant ChatSDK as Chat SDK
  participant API as Agent API (InboundHandler)
  participant DB as Conversations DB
  participant Bridge as BridgeExecutor
  participant Agent as Agent Runtime

  ChatSDK->>API: onReaction(event: { emoji, added, messageId, message?, thread?, user? })
  API->>DB: findConversationByThread(thread.id)
  DB-->>API: conversation or null
  alt conversation found
    API->>API: resolve subscriber (if event.user?.userId)
    API->>DB: loadConversationHistory(conversation.id)
    API->>Bridge: execute({ event: ON_REACTION, reaction: {...}, platformContext })
    Bridge->>Agent: deliver AgentBridgeRequest (reaction payload)
    Agent-->>Bridge: handler executes (may call ctx.reply())
    Bridge->>API: send outbound reply request (if any)
  else conversation not found
    API-->>ChatSDK: no bridge execution (silent/no-op)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: introducing agent onReaction event handling across API and framework services, and properly references the Linear ticket NV-7370.

✏️ 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.

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

🤖 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 159-175: The code uses event.message?.author?.userId to identify
the reacting user; change it to use event.user?.userId instead (i.e., set
platformUserId = event.user?.userId) and keep the existing logic that calls
this.subscriberResolver.resolve(...) to build subscriberId (or returns null on
catch); ensure subscriberId resolution and the logger.warn still reference
agentId and handle the case where event.user is undefined by leaving
subscriberId null.

In `@packages/framework/src/handler.ts`:
- Around line 300-307: The current validation uses "event in handlerMap" which
checks inherited properties and can allow prototype keys through; change the
check to use Object.prototype.hasOwnProperty.call(handlerMap, event) (or
handlerMap.hasOwnProperty via Object.prototype) to ensure only own properties
are accepted, and if absent throw the same InvalidActionError; update the
validation around handlerMap / event / AgentEventEnum usage so only
registeredAgent.handlers entries are dispatchable and prototype properties like
"constructor" or "toString" cannot bypass the check.

In `@packages/framework/src/resources/agent/agent.resource.ts`:
- Around line 18-20: The change made the onReaction handler mandatory and throws
if missing, which is a breaking SDK change; instead keep handlers.onReaction
optional by replacing the throw with a noop fallback: if handlers?.onReaction is
missing, set handlers.onReaction = () => {} (or an async no-op) so existing
agents implementing only onMessage continue to work; update the initialization
around handlers and agentId where the check occurs (look for the onReaction
check referencing agentId and handlers) to assign the default no-op handler
rather than throwing, and reserve making it required for a major version bump.

In `@packages/framework/src/resources/agent/agent.types.ts`:
- Around line 106-110: AgentReaction currently drops the message identifier, so
add a messageId?: string (or string | null) field to the AgentReaction interface
and update any related types to carry it through the bridge; specifically update
BridgeReactionPayload to include messageId and modify the mapReaction() function
to copy inbound.messageId (or ctx.reaction.messageId) into the AgentReaction
output so the reaction retains the reacted message identifier even when message
is null. Ensure all call sites that construct or consume
AgentReaction/BridgeReactionPayload are updated to pass the messageId through.
- Around line 132-135: The review flags that making onReaction mandatory in the
AgentHandlers interface is a breaking change; change the AgentHandlers interface
to make onReaction optional again (onReaction?: (ctx: AgentContext) =>
Promise<void>) and audit call sites that invoke handler.onReaction to guard the
call (e.g., if (handler.onReaction) await handler.onReaction(ctx)) or invoke a
default no-op async function when undefined; ensure any code expecting
onReaction handles the absent case to maintain backward compatibility with
existing agent implementations.
🪄 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: 46359d12-5aaf-42d6-9e77-fcc6ed01ce6b

📥 Commits

Reviewing files that changed from the base of the PR and between 6e7bfa5 and dacdfc2.

📒 Files selected for processing (11)
  • apps/api/src/app/agents/dtos/agent-event.enum.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • packages/framework/src/handler.ts
  • packages/framework/src/index.ts
  • packages/framework/src/resources/agent/agent.context.ts
  • packages/framework/src/resources/agent/agent.resource.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
  • packages/framework/src/resources/agent/index.ts

Comment thread apps/api/src/app/agents/services/agent-inbound-handler.service.ts Outdated
Comment thread packages/framework/src/handler.ts Outdated
Comment on lines +18 to +20
if (!handlers?.onReaction) {
throw new Error(`agent('${agentId}') requires an onReaction handler`);
}
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

Making onReaction mandatory is a breaking SDK change.

Existing agents that only implement onMessage will now fail type-checks and can also throw at runtime here. Keep onReaction optional with a noop fallback until the next major, or version this as a major release.

As per coding guidelines, packages/**/*: “Treat all exported symbols in packages as public API; follow semver conventions with breaking changes requiring major bumps, new exports as minor versions, and fixes as patches”.

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

In `@packages/framework/src/resources/agent/agent.resource.ts` around lines 18 -
20, The change made the onReaction handler mandatory and throws if missing,
which is a breaking SDK change; instead keep handlers.onReaction optional by
replacing the throw with a noop fallback: if handlers?.onReaction is missing,
set handlers.onReaction = () => {} (or an async no-op) so existing agents
implementing only onMessage continue to work; update the initialization around
handlers and agentId where the check occurs (look for the onReaction check
referencing agentId and handlers) to assign the default no-op handler rather
than throwing, and reserve making it required for a major version bump.

Comment on lines +106 to +110
export interface AgentReaction {
emoji: { name: string };
added: boolean;
message: AgentMessage | null;
}
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

Preserve messageId on AgentReaction.

InboundReactionEvent.message is optional upstream, but the API layer already has messageId. Dropping it here means ctx.reaction cannot identify the reacted message whenever reaction.message is null.

💡 Suggested API shape
 export interface AgentReaction {
+  messageId: string;
   emoji: { name: string };
   added: boolean;
   message: AgentMessage | null;
 }

Wire the same field through BridgeReactionPayload and mapReaction() so the identifier survives the bridge hop.

📝 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
export interface AgentReaction {
emoji: { name: string };
added: boolean;
message: AgentMessage | null;
}
export interface AgentReaction {
messageId: string;
emoji: { name: string };
added: boolean;
message: AgentMessage | null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/framework/src/resources/agent/agent.types.ts` around lines 106 -
110, AgentReaction currently drops the message identifier, so add a messageId?:
string (or string | null) field to the AgentReaction interface and update any
related types to carry it through the bridge; specifically update
BridgeReactionPayload to include messageId and modify the mapReaction() function
to copy inbound.messageId (or ctx.reaction.messageId) into the AgentReaction
output so the reaction retains the reacted message identifier even when message
is null. Ensure all call sites that construct or consume
AgentReaction/BridgeReactionPayload are updated to pass the messageId through.

Comment on lines 132 to 135
export interface AgentHandlers {
onMessage: (ctx: AgentContext) => Promise<void>;
onReaction: (ctx: AgentContext) => Promise<void>;
onAction?: (ctx: AgentContext) => Promise<void>;
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

Making onReaction mandatory is a breaking SDK change.

This turns every existing agent definition into a compile/runtime break even if the agent does not care about reactions. Unless this release is a major, keep onReaction optional and no-op when it is absent.

💡 Minor-compatible alternative
 export interface AgentHandlers {
   onMessage: (ctx: AgentContext) => Promise<void>;
-  onReaction: (ctx: AgentContext) => Promise<void>;
+  onReaction?: (ctx: AgentContext) => Promise<void>;
   onAction?: (ctx: AgentContext) => Promise<void>;
   onResolve?: (ctx: AgentContext) => Promise<void>;
 }

As per coding guidelines, packages/**/*: Treat all exported symbols in packages as public API; follow semver conventions with breaking changes requiring major bumps, new exports as minor versions, and fixes as patches.

📝 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
export interface AgentHandlers {
onMessage: (ctx: AgentContext) => Promise<void>;
onReaction: (ctx: AgentContext) => Promise<void>;
onAction?: (ctx: AgentContext) => Promise<void>;
export interface AgentHandlers {
onMessage: (ctx: AgentContext) => Promise<void>;
onReaction?: (ctx: AgentContext) => Promise<void>;
onAction?: (ctx: AgentContext) => Promise<void>;
onResolve?: (ctx: AgentContext) => Promise<void>;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/framework/src/resources/agent/agent.types.ts` around lines 132 -
135, The review flags that making onReaction mandatory in the AgentHandlers
interface is a breaking change; change the AgentHandlers interface to make
onReaction optional again (onReaction?: (ctx: AgentContext) => Promise<void>)
and audit call sites that invoke handler.onReaction to guard the call (e.g., if
(handler.onReaction) await handler.onReaction(ctx)) or invoke a default no-op
async function when undefined; ensure any code expecting onReaction handles the
absent case to maintain backward compatibility with existing agent
implementations.

@ChmaraX ChmaraX force-pushed the nv/agent-on-reaction branch from d6622b1 to 328fa73 Compare April 15, 2026 15:34
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.

🧹 Nitpick comments (1)
apps/api/src/app/agents/e2e/mock-agent-handler.ts (1)

131-141: Add an early guard for missing ctx.reaction in onReaction.

If ctx.reaction is unexpectedly null, this currently emits a misleading reply (unknown / unreaction) instead of surfacing the wiring issue.

Suggested defensive tweak
   onReaction: async (ctx) => {
+    if (!ctx.reaction) {
+      console.warn(`[${ctx.event}] missing reaction payload`);
+
+      return;
+    }
+
     console.log('\n─────────────────────────────────────────');
-    console.log(`[${ctx.event}] reaction: ${ctx.reaction?.emoji.name} (${ctx.reaction?.added ? 'added' : 'removed'})`);
-    console.log(`Reacted message: ${ctx.reaction?.message?.text ?? '(unavailable)'}`);
+    console.log(`[${ctx.event}] reaction: ${ctx.reaction.emoji.name} (${ctx.reaction.added ? 'added' : 'removed'})`);
+    console.log(`Reacted message: ${ctx.reaction.message?.text ?? '(unavailable)'}`);
     console.log('─────────────────────────────────────────');
 
-    const emoji = ctx.reaction?.emoji.name ?? 'unknown';
-    const added = ctx.reaction?.added ?? false;
+    const emoji = ctx.reaction.emoji.name;
+    const added = ctx.reaction.added;
 
     await ctx.reply(`Got ${added ? '' : 'un'}reaction: :${emoji}:`);
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/e2e/mock-agent-handler.ts` around lines 131 - 141,
onReaction currently proceeds when ctx.reaction is null and sends a misleading
reply; add an early guard at the top of the onReaction handler (function
onReaction) to detect missing ctx.reaction and surface the wiring problem
instead of continuing: log an error via console.error or process logger and
either return early (no reply) or call ctx.reply with a clear failure message
indicating missing reaction context; ensure you reference ctx.reaction in that
guard so the rest of the handler can safely assume it is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/src/app/agents/e2e/mock-agent-handler.ts`:
- Around line 131-141: onReaction currently proceeds when ctx.reaction is null
and sends a misleading reply; add an early guard at the top of the onReaction
handler (function onReaction) to detect missing ctx.reaction and surface the
wiring problem instead of continuing: log an error via console.error or process
logger and either return early (no reply) or call ctx.reply with a clear failure
message indicating missing reaction context; ensure you reference ctx.reaction
in that guard so the rest of the handler can safely assume it is present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32c16050-e0c3-4b71-83d3-f6ff6da53a5b

📥 Commits

Reviewing files that changed from the base of the PR and between dacdfc2 and d6622b1.

📒 Files selected for processing (4)
  • apps/api/src/app/agents/e2e/agent-webhook.e2e.ts
  • apps/api/src/app/agents/e2e/mock-agent-handler.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/framework/src/resources/agent/agent.types.ts
  • packages/framework/src/resources/agent/agent.test.ts

ChmaraX added 2 commits April 15, 2026 18:38
…nt on emoji reaction add/remove fixes NV-7370

When a user adds or removes an emoji reaction on any message in an agent
conversation thread, fire an ON_REACTION bridge event so the agent handler
can respond.

API layer:
- Add ON_REACTION to AgentEventEnum
- Register chat.onReaction() in ChatSdkService
- New handleReaction() on AgentInboundHandler — looks up conversation by
  thread, resolves subscriber, builds bridge payload (no activity
  persistence, no ack reaction, no typing indicator)
- Extend BridgeExecutorService with reaction field and mapReaction()
  that reuses mapMessage() for real author data

Framework SDK:
- AgentHandlers.onReaction (required, same as onMessage)
- AgentContext.reaction: AgentReaction | null
- AgentBridgeRequest.reaction: AgentReaction | null
- agent() factory validates onReaction presence
- Uniform map-based handler dispatch in runAgentHandler()

Made-with: Cursor
onReaction is now optional in AgentHandlers, consistent with onAction
and onResolve — most agents won't need to respond to emoji reactions.

- Remove onReaction validation from agent() factory
- Remove no-op onReaction stubs from all non-reaction tests
- Add "silently skip onReaction when no handler" framework test
- Add "batch signals with reply in onReaction" framework test
- Add E2E tests: subscriber resolution, null subscriber, platformContext
  shape, sourceMessage propagation, no activity persistence for reactions
- Add onReaction handler to mock-agent-handler for manual testing

Made-with: Cursor
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)
packages/framework/src/resources/agent/agent.types.ts (1)

106-110: ⚠️ Potential issue | 🟠 Major

Preserve reacted message identity even when message is null.

AgentReaction currently loses the target message reference whenever message is absent, which makes ctx.reaction ambiguous for handlers.

💡 Suggested type adjustment
 export interface AgentReaction {
+  messageId: string;
   emoji: { name: string };
   added: boolean;
   message: AgentMessage | null;
 }

Then carry messageId through the bridge payload mapping so it survives API → framework hops.

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

In `@packages/framework/src/resources/agent/agent.types.ts` around lines 106 -
110, AgentReaction currently drops the identity of the reacted message when
message is null, making ctx.reaction ambiguous; update the AgentReaction
interface to include a messageId: string | null (in addition to message:
AgentMessage | null) and ensure all places that construct or map reactions (the
bridge payload mapping code that transforms API reaction payloads into framework
AgentReaction instances) populate messageId from the incoming payload so the
message identity survives API→framework hops and handlers can rely on
ctx.reaction.messageId even when ctx.reaction.message is null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/framework/src/resources/agent/agent.types.ts`:
- Around line 106-110: AgentReaction currently drops the identity of the reacted
message when message is null, making ctx.reaction ambiguous; update the
AgentReaction interface to include a messageId: string | null (in addition to
message: AgentMessage | null) and ensure all places that construct or map
reactions (the bridge payload mapping code that transforms API reaction payloads
into framework AgentReaction instances) populate messageId from the incoming
payload so the message identity survives API→framework hops and handlers can
rely on ctx.reaction.messageId even when ctx.reaction.message is null.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f0da1ea-4cb7-4e8f-b5d8-cc0f36624bcf

📥 Commits

Reviewing files that changed from the base of the PR and between d6622b1 and 328fa73.

📒 Files selected for processing (4)
  • apps/api/src/app/agents/e2e/agent-webhook.e2e.ts
  • apps/api/src/app/agents/e2e/mock-agent-handler.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/src/app/agents/e2e/agent-webhook.e2e.ts
  • packages/framework/src/resources/agent/agent.test.ts

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: 1

♻️ Duplicate comments (2)
packages/framework/src/handler.ts (1)

300-314: ⚠️ Potential issue | 🟠 Major

Harden event dispatch key validation (prototype-chain bypass).

Line 307 uses in, which accepts inherited keys. Because event is query-derived, values like constructor can bypass the guard and resolve to non-agent handlers.

Suggested fix
-    const handlerMap: Record<string, ((ctx: AgentContextImpl) => Promise<void>) | undefined> = {
+    const handlerMap: Partial<Record<AgentEventEnum, (ctx: AgentContextImpl) => Promise<void>>> = {
       [AgentEventEnum.ON_MESSAGE]: registeredAgent.handlers.onMessage,
       [AgentEventEnum.ON_REACTION]: registeredAgent.handlers.onReaction,
       [AgentEventEnum.ON_ACTION]: registeredAgent.handlers.onAction,
       [AgentEventEnum.ON_RESOLVE]: registeredAgent.handlers.onResolve,
     };
 
-    if (!(event in handlerMap)) {
+    if (!Object.prototype.hasOwnProperty.call(handlerMap, event)) {
       throw new InvalidActionError(event, AgentEventEnum);
     }
 
-    const handler = handlerMap[event];
+    const handler = handlerMap[event as AgentEventEnum];
     if (handler) {
       await handler(ctx);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/framework/src/handler.ts` around lines 300 - 314, The current guard
"if (!(event in handlerMap))" is vulnerable to prototype-chain keys; replace it
with a direct own-key check using
Object.prototype.hasOwnProperty.call(handlerMap, event) (or alternatively check
handlerMap[event] === undefined) before throwing InvalidActionError so only own
keys of handlerMap (the AgentEventEnum entries) are accepted; update the code
around handlerMap, the event validation, and the subsequent retrieval of handler
to use this safe check.
apps/api/src/app/agents/services/agent-inbound-handler.service.ts (1)

17-23: ⚠️ Potential issue | 🟠 Major

Resolve reactor identity from the reaction actor field, not the reacted message author.

Line 187 currently derives platformUserId from event.message?.author?.userId, which can attribute the reaction to the message author instead of the user who reacted.

Proposed fix
 export interface InboundReactionEvent {
+  user?: { userId?: string };
   emoji: { name: string };
   added: boolean;
   messageId: string;
   message?: Message;
   thread?: Thread;
 }
@@
-    const platformUserId = event.message?.author?.userId;
+    const platformUserId = event.user?.userId;
For the npm package "chat", in the `chat.onReaction()` callback payload (`ReactionEvent`), which field identifies the user who added/removed the reaction: `event.user` or `event.message.author`?

Also applies to: 187-197

🤖 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 17 - 23, The handler currently attributes reactions to the message author;
update the InboundReactionEvent shape to include the reactor field (e.g., add a
user or actor property with the platform user id) and change the code that
computes platformUserId to read from event.user (or event.actor) instead of
event.message?.author; keep a safe fallback to event.message?.author only if
event.user is undefined. Specifically, modify the InboundReactionEvent interface
to include a user/actor identifier and update the platformUserId assignment (the
variable referenced as platformUserId in the reaction handling code) to prefer
event.user?.id or event.user?.userId (matching the external ReactionEvent
payload) then fallback to event.message?.author?.userId.
🤖 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 183-184: In the if (!conversation) guard inside
AgentInboundHandlerService (the method handling inbound messages), add a single
blank line immediately before the return statement to satisfy the repository
style rule requiring a blank line before every return; locate the block that
checks "if (!conversation) { return; }" and change it so there's an empty line
between the opening brace and the return.

---

Duplicate comments:
In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts`:
- Around line 17-23: The handler currently attributes reactions to the message
author; update the InboundReactionEvent shape to include the reactor field
(e.g., add a user or actor property with the platform user id) and change the
code that computes platformUserId to read from event.user (or event.actor)
instead of event.message?.author; keep a safe fallback to event.message?.author
only if event.user is undefined. Specifically, modify the InboundReactionEvent
interface to include a user/actor identifier and update the platformUserId
assignment (the variable referenced as platformUserId in the reaction handling
code) to prefer event.user?.id or event.user?.userId (matching the external
ReactionEvent payload) then fallback to event.message?.author?.userId.

In `@packages/framework/src/handler.ts`:
- Around line 300-314: The current guard "if (!(event in handlerMap))" is
vulnerable to prototype-chain keys; replace it with a direct own-key check using
Object.prototype.hasOwnProperty.call(handlerMap, event) (or alternatively check
handlerMap[event] === undefined) before throwing InvalidActionError so only own
keys of handlerMap (the AgentEventEnum entries) are accepted; update the code
around handlerMap, the event validation, and the subsequent retrieval of handler
to use this safe check.
🪄 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: ddfd14fb-071e-403e-9b62-381b19698c28

📥 Commits

Reviewing files that changed from the base of the PR and between 328fa73 and 3b95369.

📒 Files selected for processing (12)
  • apps/api/src/app/agents/dtos/agent-event.enum.ts
  • apps/api/src/app/agents/e2e/agent-webhook.e2e.ts
  • apps/api/src/app/agents/e2e/mock-agent-handler.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • packages/framework/src/handler.ts
  • packages/framework/src/index.ts
  • packages/framework/src/resources/agent/agent.context.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
  • packages/framework/src/resources/agent/index.ts
✅ Files skipped from review due to trivial changes (5)
  • apps/api/src/app/agents/dtos/agent-event.enum.ts
  • packages/framework/src/resources/agent/index.ts
  • packages/framework/src/index.ts
  • apps/api/src/app/agents/e2e/agent-webhook.e2e.ts
  • packages/framework/src/resources/agent/agent.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/framework/src/resources/agent/agent.context.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • packages/framework/src/resources/agent/agent.types.ts

Comment on lines +183 to +184
if (!conversation) {
return;
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

Add a blank line before return in this block.

This block violates the TS/JS style rule used in this repo.

Proposed fix
     if (!conversation) {
+
       return;
     }

As per coding guidelines: **/*.{ts,tsx,js,jsx} — “Include a blank line before every return statement”.

📝 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 (!conversation) {
return;
if (!conversation) {
return;
}
🤖 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 183 - 184, In the if (!conversation) guard inside
AgentInboundHandlerService (the method handling inbound messages), add a single
blank line immediately before the return statement to satisfy the repository
style rule requiring a blank line before every return; locate the block that
checks "if (!conversation) { return; }" and change it so there's an empty line
between the opening brace and the return.

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: 1

🧹 Nitpick comments (1)
apps/api/src/app/agents/services/bridge-executor.service.ts (1)

238-275: Guard ON_REACTION payload construction.

buildPayload() still allows event === AgentEventEnum.ON_REACTION with reaction == null, which would serialize an impossible bridge request and hand the framework an onReaction event with ctx.reaction === null. Add an assertion here, or tighten BridgeExecutorParams into an event-discriminated union.

Suggested guard
   private buildPayload(params: BridgeExecutorParams): AgentBridgeRequest {
     const { event, config, conversation, subscriber, history, message, platformContext, action, reaction } = params;
     const agentIdentifier = config.agentIdentifier;
+
+    if (event === AgentEventEnum.ON_REACTION && !reaction) {
+      throw new Error('Missing reaction payload for onReaction bridge request');
+    }
 
     const apiRootUrl = process.env.API_ROOT_URL || 'http://localhost:3000';
     const replyUrl = `${apiRootUrl}/v1/agents/${agentIdentifier}/reply`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/bridge-executor.service.ts` around lines 238
- 275, The buildPayload function can produce an ON_REACTION event with reaction
=== null; update buildPayload (or BridgeExecutorParams) to prevent constructing
impossible payloads by asserting reaction is present when event ===
AgentEventEnum.ON_REACTION: inside buildPayload, add a guard that throws or
returns an error if event === AgentEventEnum.ON_REACTION && !reaction (or
tighten BridgeExecutorParams into a discriminated union so ON_REACTION requires
reaction) and ensure subsequent code uses reaction safely (e.g., rely on
reaction after the guard and mapReaction only when non-null).
🤖 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/chat-sdk.service.ts`:
- Around line 299-308: The chat.onReaction handler currently forwards
event.thread and event.user without validation causing runtime errors for
partial/malformed reactions; update the chat.onReaction callback to validate
presence and shape of event.thread and event.user (and any other required fields
like event.messageId, event.emoji) and skip/return early for malformed events
instead of calling this.inboundHandler.handleReaction(agentId, config, ...);
ensure the guard logic mirrors the action-path checks (validate thread and user
exist and are well-formed) before invoking inboundHandler.handleReaction so only
complete reaction events are forwarded.

---

Nitpick comments:
In `@apps/api/src/app/agents/services/bridge-executor.service.ts`:
- Around line 238-275: The buildPayload function can produce an ON_REACTION
event with reaction === null; update buildPayload (or BridgeExecutorParams) to
prevent constructing impossible payloads by asserting reaction is present when
event === AgentEventEnum.ON_REACTION: inside buildPayload, add a guard that
throws or returns an error if event === AgentEventEnum.ON_REACTION && !reaction
(or tighten BridgeExecutorParams into a discriminated union so ON_REACTION
requires reaction) and ensure subsequent code uses reaction safely (e.g., rely
on reaction after the guard and mapReaction only when non-null).
🪄 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: 2f386f35-093c-4f9f-bb53-acde9eee6f6a

📥 Commits

Reviewing files that changed from the base of the PR and between 3b95369 and 269f567.

📒 Files selected for processing (6)
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • packages/framework/src/handler.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/framework/src/handler.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts

Comment on lines +299 to +308
chat.onReaction(async (event: any) => {
try {
await this.inboundHandler.handleReaction(agentId, config, {
emoji: event.emoji,
added: event.added,
messageId: event.messageId,
message: event.message,
thread: event.thread,
user: event.user,
});
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

Skip malformed reaction events before forwarding.

Unlike the action path on Line 284, this branch forwards thread and user without checking them first. If an adapter emits a partial reaction event, this becomes a logged runtime error instead of a clean skip.

Suggested guard
     chat.onReaction(async (event: any) => {
       try {
+        if (!event.thread || !event.user) {
+          this.logger.warn(`[agent:${agentId}] Reaction received without complete context, skipping`);
+
+          return;
+        }
+
         await this.inboundHandler.handleReaction(agentId, config, {
           emoji: event.emoji,
           added: event.added,
           messageId: event.messageId,
           message: event.message,

As per coding guidelines, apps/api/**: Review with focus on security, authentication, and authorization. Check for proper error handling and input validation.

📝 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
chat.onReaction(async (event: any) => {
try {
await this.inboundHandler.handleReaction(agentId, config, {
emoji: event.emoji,
added: event.added,
messageId: event.messageId,
message: event.message,
thread: event.thread,
user: event.user,
});
chat.onReaction(async (event: any) => {
try {
if (!event.thread || !event.user) {
this.logger.warn(`[agent:${agentId}] Reaction received without complete context, skipping`);
return;
}
await this.inboundHandler.handleReaction(agentId, config, {
emoji: event.emoji,
added: event.added,
messageId: event.messageId,
message: event.message,
thread: event.thread,
user: event.user,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/chat-sdk.service.ts` around lines 299 - 308,
The chat.onReaction handler currently forwards event.thread and event.user
without validation causing runtime errors for partial/malformed reactions;
update the chat.onReaction callback to validate presence and shape of
event.thread and event.user (and any other required fields like event.messageId,
event.emoji) and skip/return early for malformed events instead of calling
this.inboundHandler.handleReaction(agentId, config, ...); ensure the guard logic
mirrors the action-path checks (validate thread and user exist and are
well-formed) before invoking inboundHandler.handleReaction so only complete
reaction events are forwarded.

…ity, handler safety, messageId

- Use event.user (reactor) instead of event.message.author (message author) for subscriber resolution
- Use hasOwnProperty check instead of `in` operator for handler dispatch to prevent prototype traversal
- Add messageId to AgentReaction and BridgeReactionPayload so consumers can identify the reacted message even when message body is null

Made-with: Cursor
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