Skip to content

feat(dashboard, api-service): Slack quickstart flow#10728

Merged
scopsy merged 30 commits intonextfrom
agent-overview-page-impl
Apr 15, 2026
Merged

feat(dashboard, api-service): Slack quickstart flow#10728
scopsy merged 30 commits intonextfrom
agent-overview-page-impl

Conversation

@scopsy
Copy link
Copy Markdown
Contributor

@scopsy scopsy commented Apr 15, 2026

What changed? Why was the change needed?

What changed

This PR adds a Slack quickstart flow for agents: UI components and API/schema support to guide workspace installation, detect when a Slack integration is connected, and allow toggling agent active state. It also changes routing to target specific integration identifiers and adds error-handling to recover when a delivery bridge URL is missing.

Affected areas

api: Added agent active: boolean (default true) and connectedAt on agent-integration links; updated DTOs, mappers, commands, and usecases to persist/serialize these fields; added NoBridgeUrlError and improved bridge executor/inbound handler error flow to send an onboarding reply when no bridge URL exists.

dashboard: New quickstart UI — AgentOverviewTab, AgentSidebarWidget, AgentSetupGuide, SlackSetupGuide, ProviderDropdown, and setup primitives; switched integrations routing/props from providerIdintegrationIdentifier; added agent PATCH client, updated types to include active and connectedAt, and adjusted integration creation payload typing.

dal: Persisted active on Agent schema/entity with default true and added optional connectedAt Date|null on AgentIntegration schema/entity.

shared: Added conversational-providers metadata (CONVERSATIONAL_PROVIDERS and IDs) to list conversational provider options (Slack enabled, others marked coming soon).

Key technical decisions

  • Used NestJS forwardRef/@Inject to resolve circular DI between inbound handler and chat SDK services.
  • Introduced connectedAt to track first successful integration connection, enabling polling-based workspace detection in the UI.
  • Replaced URL segment providerId with integrationIdentifier to scope guides to a specific integration instance.
  • Added a specific NoBridgeUrlError to distinguish bridge-missing cases and trigger a user-facing onboarding reply.

Testing

No automated tests were added; manual verification is required for the Slack setup flow, the polling-based connection detection, agent update (activate/deactivate) behavior, and the new bridge error recovery path.

Screenshots

Expand for optional sections

Related enterprise PR

Special notes for your reviewer

scopsy added 25 commits April 14, 2026 13:08
Introduce a new ProviderDropdown component (apps/dashboard/src/components/agents/provider-dropdown.tsx) that renders a searchable, grouped provider picker (uses novu providers, existing integrations, Popover + Command UI). Replace the inline ProviderDropdown in AgentSetupGuide with the new component, add selectedProviderId state and pass value/onSelect to handle selection. The new picker groups by channel, shows existing integrations (or an add icon), and closes on select.
Make integration identifier optional in CreateIntegrationData and enhance Slack agent setup flow: generate a safe YAML manifest (escape strings), build a webhook URL that includes the integration identifier, and expand the Slack manifest with updated features, scopes, settings, and event subscriptions. Wire integrations state into AgentSetupGuide (useFetchIntegrations) to allow selecting an existing integration or showing a placeholder identifier when none is selected. Replace the simple provider selector with a more capable ProviderDropdown: support listing existing integrations, creating new integrations, linking them to an agent (mutations + query invalidation), show loading state and toasts, require an environment, and disable UI while operations are pending. Overall this enables creating/linking Slack integrations directly from the dropdown and produces a more complete Slack app manifest for agents.
Pass a Set of linked integration IDs from AgentSetupGuide to ProviderDropdown and use it to skip the addAgentIntegrationMutation when a user selects an integration that's already linked to the agent. Adds a memoized linkedIntegrationIds in AgentSetupGuide, a new optional prop (with comment) on ProviderDropdown, and a check to prevent duplicate API calls/toasts while still invoking onSelect and closing the dropdown.
Expose an 'active' boolean for agents across backend and frontend. API: add active to DTOs, commands, mappers and usecases (create defaults to true; update accepts active and requires at least one of name, description or active). Controller payloads updated. DAL agent entity/schema updated accordingly.

Dashboard: propagate active into types and UI — sidebar shows Active/Inactive badge and a Switch to toggle active state. Agent integration flows were refactored to use integrationIdentifier (route & params) instead of providerId, adjust router path, and update resolve/guide components. Integrations sidebar now uses a ProviderDropdown (with excludeLinked and renderTrigger options) instead of the old sheet; it auto-redirects to the first linked integration unless skipped. Webhook URL generation now includes the integration identifier. Miscellaneous UI/UX and code cleanup (manifest section formatting, copy tweaks, memoization/useEffect improvements, and small component reorganizations).
agent-setup-guide: Simplify polling logic for agent integration status by removing refs and extra state, using local intervalId and confettiFired flags, and ensuring proper cleanup. Move confetti rendering into a createPortal with explicit dimensions and increased z-index, and remove focus-based celebration handling.

provider-dropdown: Add detection for 409 "already linked" errors and gracefully handle race conditions when linking integrations by ignoring duplicate-link errors or selecting the existing integration. Centralize cache invalidation (using environmentId) and improve query refetch/invalidate flow to keep UI consistent after link operations.
Throw a specific NoBridgeUrlError when no bridge URL is configured and send an onboarding reply instead of silently returning. Added NoBridgeUrlError and changed BridgeExecutorService to throw it; ensure it is rethrown for non-bridge cases. AgentInboundHandler now catches NoBridgeUrlError and invokes HandleAgentReply to post an onboarding message (markdown) to the conversation. Introduced forwardRef/Inject wiring between ChatSdkService, AgentInboundHandler, and HandleAgentReply to resolve circular dependencies and adjusted imports and error handling accordingly.
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 15, 2026

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

Name Link
🔨 Latest commit 5a82d83
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69df945d7d9c7900086e7f3b
😎 Deploy Preview https://deploy-preview-10728.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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 15, 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: feat(dashboard, api-service): Slack quickstart flow

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 15, 2026

Open in StackBlitz

npm i https://pkg.pr.new/novuhq/novu@10728
npm i https://pkg.pr.new/novuhq/novu/@novu/providers@10728
npm i https://pkg.pr.new/novuhq/novu/@novu/shared@10728

commit: 5a82d83

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

Adds agent active status and agent-integration connectedAt; persists and surfaces these fields across API, DAL, mappers, use-cases, and dashboard UI. Introduces provider-selection and Slack setup UI, updates routing to use integrationIdentifier, and improves bridge error handling with a recovery reply path.

Changes

Cohort / File(s) Summary
API DTOs & Controller
apps/api/src/app/agents/dtos/*, apps/api/src/app/agents/agents.controller.ts
Added active to agent DTOs and optional connectedAt to integration DTO; controller now forwards body.active to commands and had minor formatting/import reorder.
API Commands & Usecases
apps/api/src/app/agents/usecases/.../create-agent.*, .../update-agent.*, .../handle-agent-reply/*
Added optional active to Create/Update commands; Create defaults active→true; Update includes active in validation and conditional $set. Adjusted DI wiring for HandleAgentReply/ChatSdkService.
API Services (bridge/inbound/config)
apps/api/src/app/agents/services/bridge-executor.service.ts, agent-inbound-handler.service.ts, chat-sdk.service.ts, agent-config-resolver.service.ts
Introduced exported NoBridgeUrlError; BridgeExecutor throws it when no bridge URL. AgentInboundHandler catches it and dispatches a system onboarding reply. AgentConfigResolver persists connectedAt when first resolving; several DI params updated to use forwardRef/Inject.
DAL (entities & schemas)
libs/dal/src/repositories/agent/*, .../agent-integration/*
Added active: boolean to Agent entity/schema (default true). Added optional `connectedAt?: string
Mappers
apps/api/src/app/agents/mappers/agent-response.mapper.ts
Mapper now includes active on agent responses and connectedAt on integration responses.
Dashboard API client
apps/dashboard/src/api/agents.ts, apps/dashboard/src/api/integrations.ts
AgentResponse now requires active; CreateAgentBody/UpdateAgentBody updated; added updateAgent() (PATCH). Integration create payload identifier made optional.
Dashboard routing & pages
apps/dashboard/src/utils/routes.ts, apps/dashboard/src/main.tsx, apps/dashboard/src/pages/agent-details.tsx
Renamed route from :providerId:integrationIdentifier, updated route constant, and changed page to decode integrationIdentifier and render overview/integrations accordingly.
Dashboard integration guide / webhook
apps/dashboard/src/components/agents/agent-integration-guides/*
buildWebhookUrl now requires integrationIdentifier; ResolveAgentIntegrationGuide uses required integrationLink and early-returns to SlackSetupGuide when Slack link is not yet connected.
Dashboard new/updated agent UI
apps/dashboard/src/components/agents/*
Added AgentOverviewTab, AgentSidebarWidget (status toggle, editors, update mutation), AgentSetupGuide, ProviderDropdown, SlackSetupGuide (3-step flow, polling, confetti), setup primitives, and step-utils. AgentIntegrationsTab refactored to integrationIdentifier routing and ProviderDropdown-based linking.
Shared constants
packages/shared/src/consts/providers/*
Added CONVERSATIONAL_PROVIDERS metadata and re-export to list conversational provider IDs and display names.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Dashboard UI
    participant ProvDD as ProviderDropdown
    participant API as Dashboard API client
    participant Backend as Agent API
    participant Bridge as BridgeExecutor / ChatSdk
    participant Inbound as AgentInboundHandler

    User->>UI: Open Agent Setup
    UI->>API: listIntegrations()
    API-->>UI: integrations list

    User->>ProvDD: Select provider/integration
    ProvDD->>API: createIntegration? / addAgentIntegration
    API->>Backend: POST /integrations or PATCH /agents/:id
    Backend-->>API: integration created/linked
    API-->>ProvDD: success
    ProvDD-->>UI: selection confirmed

    alt Slack selected and not connected
        UI->>API: poll listAgentIntegrations (1s)
        API->>Backend: GET /agents/:id/integrations
        Backend-->>API: shows connectedAt when workspace connected
        API-->>UI: connectedAt set
        UI->>UI: render Connected + confetti
    end

    User->>UI: Trigger message delivery (via agent)
    UI->>Backend: send message to agent -> BridgeExecutor
    Backend->>Bridge: resolveBridgeUrl -> returns none
    Bridge-->>Backend: throws NoBridgeUrlError
    Backend->>Inbound: catch NoBridgeUrlError, call HandleAgentReply
    Inbound->>Backend: persist system onboarding reply / respond to user
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • LetItRock
  • djabarovgeorge
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.22% 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 follows Conventional Commits format with valid type (feat) and valid scopes (dashboard, api-service), using lowercase imperative description.

✏️ 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: 4

🧹 Nitpick comments (7)
packages/shared/src/consts/providers/conversational-providers.ts (1)

3-20: Harden shared exports as readonly public API.

CONVERSATIONAL_PROVIDERS and CONVERSATIONAL_PROVIDER_IDS are mutable exports; consumers can mutate them globally at runtime. Consider exposing readonly shapes.

♻️ Suggested refactor
-export type ConversationalProvider = {
+export type ConversationalProvider = Readonly<{
   providerId: string;
   displayName: string;
   comingSoon?: boolean;
-};
+}>;

-export const CONVERSATIONAL_PROVIDERS: ConversationalProvider[] = [
+export const CONVERSATIONAL_PROVIDERS = [
   { providerId: ChatProviderIdEnum.Slack, displayName: 'Slack' },
   { providerId: ChatProviderIdEnum.MsTeams, displayName: 'MS Teams', comingSoon: true },
   { providerId: ChatProviderIdEnum.WhatsAppBusiness, displayName: 'WhatsApp Business', comingSoon: true },
   { providerId: 'telegram', displayName: 'Telegram', comingSoon: true },
   { providerId: 'google-chat', displayName: 'Google Chat', comingSoon: true },
   { providerId: 'linear', displayName: 'Linear', comingSoon: true },
   { providerId: 'zoom', displayName: 'Zoom', comingSoon: true },
   { providerId: 'imessages', displayName: 'iMessages', comingSoon: true },
-];
+] as const satisfies readonly ConversationalProvider[];

-export const CONVERSATIONAL_PROVIDER_IDS = new Set(CONVERSATIONAL_PROVIDERS.map((p) => p.providerId));
+export const CONVERSATIONAL_PROVIDER_IDS: ReadonlySet<string> = new Set(
+  CONVERSATIONAL_PROVIDERS.map((p) => p.providerId)
+);

As per coding guidelines, packages/shared exports are public API and should avoid mutable cross-runtime side effects.

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

In `@packages/shared/src/consts/providers/conversational-providers.ts` around
lines 3 - 20, Change the mutable public exports to readonly/immutable shapes:
make the ConversationalProvider type properties readonly (e.g., readonly
providerId; readonly displayName; readonly comingSoon?), export
CONVERSATIONAL_PROVIDERS as a ReadonlyArray<ConversationalProvider> and
create/freeze the array and each provider object (e.g., Object.freeze([...]) /
Object.freeze(provider) for each entry) so consumers cannot mutate entries at
runtime, and export CONVERSATIONAL_PROVIDER_IDS as a ReadonlySet<string> (create
with new Set(...) then Object.freeze(...) to prevent reassignment) so both the
list and id set are exposed as immutable public API; update declarations for
CONVERSATIONAL_PROVIDERS and CONVERSATIONAL_PROVIDER_IDS accordingly and keep
provider identifiers (e.g., ChatProviderIdEnum.Slack) unchanged.
apps/api/src/app/agents/services/agent-credential.service.ts (1)

80-89: Side-effect in resolve() lacks error handling and may surprise callers.

The resolve() method was previously a read-only lookup but now mutates state by setting connectedAt. Two concerns:

  1. Unhandled error: If updateOne fails, the exception propagates and aborts the entire resolve flow, which could break webhook handling unexpectedly.
  2. Method semantics: Callers (e.g., handleWebhook, sendMessage) may not expect resolve() to perform writes.

Consider wrapping the update in a try-catch to log failures without blocking resolution, or document the side-effect clearly:

♻️ Proposed defensive handling
     if (agentIntegration.connectedAt == null) {
-      await this.agentIntegrationRepository.updateOne(
-        {
-          _id: agentIntegration._id,
-          _environmentId: environmentId,
-          _organizationId: organizationId,
-        },
-        { $set: { connectedAt: new Date() } }
-      );
+      try {
+        await this.agentIntegrationRepository.updateOne(
+          {
+            _id: agentIntegration._id,
+            _environmentId: environmentId,
+            _organizationId: organizationId,
+          },
+          { $set: { connectedAt: new Date() } }
+        );
+      } catch (err) {
+        // Non-critical: log and continue — connectedAt can be set on next call
+        // Consider adding a logger injection to track these failures
+      }
     }
🤖 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-credential.service.ts` around lines 80
- 89, The resolve() method now mutates agentIntegration.connectedAt via
agentIntegrationRepository.updateOne which can throw and unexpectedly break
callers like handleWebhook or sendMessage; wrap the updateOne call that sets
connectedAt in a try-catch, log the error (including
agentIntegration._id/organizationId/environmentId) and swallow the exception so
resolve() remains a non-blocking read operation, or alternatively expose a flag
parameter on resolve() to toggle the side-effect so callers can opt-in; ensure
you reference resolve(), connectedAt and agentIntegrationRepository.updateOne
when making the change.
apps/dashboard/src/components/agents/agent-sidebar-widget.tsx (1)

334-338: Consider using addSuffix: true to simplify the last updated display.

The current approach manually concatenates "ago" after calling formatDistanceToNow with addSuffix: false. Using addSuffix: true would produce the same result with less code.

♻️ Suggested simplification
-      <p className="text-label-xs font-medium">
-        <span className="text-text-soft">Last updated </span>
-        <span className="text-text-sub">{formatDistanceToNow(new Date(agent.updatedAt), { addSuffix: false })}</span>
-        <span className="text-text-sub"> ago</span>
-      </p>
+      <p className="text-label-xs font-medium">
+        <span className="text-text-soft">Last updated </span>
+        <span className="text-text-sub">{formatDistanceToNow(new Date(agent.updatedAt), { addSuffix: true })}</span>
+      </p>

Note: addSuffix: true produces "X minutes ago" directly. If you need the specific styling split between the time value and "ago", the current approach is intentional.

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

In `@apps/dashboard/src/components/agents/agent-sidebar-widget.tsx` around lines
334 - 338, The JSX manually appends "ago" after calling formatDistanceToNow(new
Date(agent.updatedAt), { addSuffix: false }); change the call to
formatDistanceToNow(new Date(agent.updatedAt), { addSuffix: true }) and remove
the separate " ago" span so the rendered string already includes the suffix;
update the spans around the time value in the component (the element using
formatDistanceToNow and the surrounding <p> / <span> with className
"text-text-sub") accordingly.
apps/dashboard/src/components/agents/provider-dropdown.tsx (1)

270-275: Consider removing redundant refetchQueries before invalidateAgentLinkQueries.

Line 270 calls refetchQueries for fetchIntegrations, then line 275 calls invalidateAgentLinkQueries which also invalidates fetchIntegrations (line 234). The invalidation will trigger a refetch anyway, making the explicit refetchQueries call redundant.

♻️ Suggested simplification
         const created = await createIntegrationMutation.mutateAsync({
           providerId: item.providerId,
           name: uniqueName,
         });
         await addAgentIntegrationMutation.mutateAsync(created.identifier);
         showSuccessToast('Integration linked', `${created.name} was added to this agent.`);
-        await queryClient.refetchQueries({ queryKey: [QueryKeys.fetchIntegrations, environmentId] });
         onSelect(item.providerId, created);
         setOpen(false);
+        await invalidateAgentLinkQueries();
+      } else {
+        // existing integration branch continues...
       }
-
-      await invalidateAgentLinkQueries();

Alternatively, keep both if the synchronous refetch before onSelect is intentional for immediate UI update.

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

In `@apps/dashboard/src/components/agents/provider-dropdown.tsx` around lines 270
- 275, The explicit await queryClient.refetchQueries({ queryKey:
[QueryKeys.fetchIntegrations, environmentId] }) is redundant because
invalidateAgentLinkQueries already invalidates the same key (see
invalidateAgentLinkQueries) and will trigger a refetch; remove that
refetchQueries call, keep the onSelect(item.providerId, created) and
setOpen(false) flow, and ensure any required immediate synchronous refresh is
handled (only keep refetchQueries if you intentionally need the refetch to
complete before calling onSelect).
apps/dashboard/src/components/agents/slack-setup-guide.tsx (2)

108-109: Consider increasing the polling interval to reduce server load.

A 1-second polling interval may be aggressive, especially if multiple users are on the setup page simultaneously. Consider using 2-3 seconds, or implementing exponential backoff.

♻️ Suggested adjustment
     void tick();
-    intervalId = setInterval(() => void tick(), 1000);
+    intervalId = setInterval(() => void tick(), 3000);

Alternatively, consider using React Query's refetchInterval option if converting to a proper query, which also handles background tab throttling.

🤖 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 108
- 109, The current polling setup calls void tick() immediately and sets
intervalId = setInterval(() => void tick(), 1000), which uses a 1-second
interval; change this to a less aggressive interval (e.g., 2000–3000 ms) or
implement exponential backoff on the tick function so it increases delay after
repeated unchanged responses; update the setInterval call (and any intervalId
management/clear logic) to use the new interval variable or backoff mechanism,
and optionally consider replacing this manual polling with React Query's
refetchInterval on the query that tick() drives to benefit from built-in
throttling and background-tab handling.

217-219: YAML request_url values should be quoted for safety.

The request_url fields (lines 219, 231) are not wrapped in quotes, unlike other URL fields. While typically safe, if the webhook URL contains special YAML characters (like #), it could cause parsing issues.

♻️ Suggested fix
 settings:
   event_subscriptions:
-    request_url: ${webhookHandlerUrl}
+    request_url: "${escapeYamlDoubleQuoted(webhookHandlerUrl)}"
     bot_events:
       # ...
   interactivity:
     is_enabled: true
-    request_url: ${webhookHandlerUrl}
+    request_url: "${escapeYamlDoubleQuoted(webhookHandlerUrl)}"
🤖 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 217
- 219, The YAML snippet sets request_url using an unquoted interpolation
(${webhookHandlerUrl}), which can break if the URL contains YAML-special
characters; update the YAML in slack-setup-guide.tsx so the request_url fields
use quoted values (e.g., wrap ${webhookHandlerUrl} in double quotes) for all
occurrences of request_url in that YAML block to ensure safe parsing.
apps/dashboard/src/components/agents/setup-guide-primitives.tsx (1)

19-24: Consider using Tailwind color tokens instead of hardcoded hex values.

The completed step indicator uses hardcoded hex colors (#5ec269, #77db89). Per coding guidelines, prefer Tailwind utility classes. If these are design-system colors, consider adding them to the Tailwind config.

♻️ Example using existing tokens
-      <div className="flex size-5 shrink-0 items-center justify-center rounded-full border border-[`#5ec269`] bg-[`#77db89`] shadow-[0px_0px_0px_1px_#FFF,0px_0px_0px_2px_#E1E4EA]">
+      <div className="flex size-5 shrink-0 items-center justify-center rounded-full border border-success-base bg-success-base shadow-[0px_0px_0px_1px_#FFF,0px_0px_0px_2px_#E1E4EA]">

If the specific green shades are intentional design choices, keeping the hex values is acceptable.

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

In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx` around lines
19 - 24, The completed-step indicator is using hardcoded hex colors in the JSX
element with className 'flex size-5 shrink-0 items-center justify-center
rounded-full border border-[`#5ec269`] bg-[`#77db89`] shadow[...]'—replace those hex
values with Tailwind utility color classes (or with a new design-system token
you add to tailwind.config.js and then use its utility class) so the component
uses theme colors instead of literals; update the div's className to use the
appropriate border-<token> and bg-<token> classes (and keep the svg stroke as
text-white or equivalent) and add the new color tokens to theme.extend.colors if
they don’t already exist.
🤖 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/usecases/update-agent/update-agent.command.ts`:
- Around line 20-22: The UpdateAgentCommand allows null to bypass boolean
validation because `@IsOptional`() skips `@IsBoolean`() for null; replace
`@IsOptional`() on the active property with `@ValidateIf`((_, value) => value !==
undefined) so null values are validated by `@IsBoolean`(), and keep the existing
usecase check that tests command.active !== undefined unchanged; update the
decorator on the active property in UpdateAgentCommand to use ValidateIf to
ensure only undefined means "not provided".

In `@apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts`:
- Around line 12-18: The validation error message in update-agent use case is
out of sync with the conditional: update the thrown BadRequestException in the
update logic (the block that checks command.name, command.description,
command.behavior, command.active) so the message includes "active" as an allowed
field (e.g., "...name, description, behavior, or active must be provided.");
locate the check in update-agent.usecase.ts (the function/method handling the
update command) and replace the old message text accordingly.

In `@apps/dashboard/src/components/agents/slack-setup-guide.tsx`:
- Around line 88-93: The setTimeout started when confetti is shown can fire
after the component unmounts; capture the timer id returned by window.setTimeout
(e.g., const timeoutId = window.setTimeout(...)) and store it in a ref (e.g.,
confettiTimeoutRef) or a variable in a useEffect scope, then clear it with
clearTimeout(timeoutId) in a cleanup function (useEffect return) and also clear
any existing timeout before creating a new one; update the code around
confettiFired, setShowConfetti, and the window.setTimeout call to use the
ref/timeout id and clear it on unmount to prevent calling setShowConfetti on an
unmounted component.

In `@libs/dal/src/repositories/agent/agent.schema.ts`:
- Around line 17-20: The Agent schema's active field (agent.schema.ts: property
"active") currently only sets default: true which won't protect existing
documents or prevent null-ish writes; update the schema to make active required
(e.g., add required: true) while keeping default: true to enforce validation on
future writes, and add a one-time backfill migration that finds Agent documents
with active === null or active not set and updates them to true (include the
migration in libs/dal with a clear id so ops can run it). Ensure the change is
accompanied by tests or a small script that verifies no documents remain without
active and that the schema change is communicated as a migration-required
change.

---

Nitpick comments:
In `@apps/api/src/app/agents/services/agent-credential.service.ts`:
- Around line 80-89: The resolve() method now mutates
agentIntegration.connectedAt via agentIntegrationRepository.updateOne which can
throw and unexpectedly break callers like handleWebhook or sendMessage; wrap the
updateOne call that sets connectedAt in a try-catch, log the error (including
agentIntegration._id/organizationId/environmentId) and swallow the exception so
resolve() remains a non-blocking read operation, or alternatively expose a flag
parameter on resolve() to toggle the side-effect so callers can opt-in; ensure
you reference resolve(), connectedAt and agentIntegrationRepository.updateOne
when making the change.

In `@apps/dashboard/src/components/agents/agent-sidebar-widget.tsx`:
- Around line 334-338: The JSX manually appends "ago" after calling
formatDistanceToNow(new Date(agent.updatedAt), { addSuffix: false }); change the
call to formatDistanceToNow(new Date(agent.updatedAt), { addSuffix: true }) and
remove the separate " ago" span so the rendered string already includes the
suffix; update the spans around the time value in the component (the element
using formatDistanceToNow and the surrounding <p> / <span> with className
"text-text-sub") accordingly.

In `@apps/dashboard/src/components/agents/provider-dropdown.tsx`:
- Around line 270-275: The explicit await queryClient.refetchQueries({ queryKey:
[QueryKeys.fetchIntegrations, environmentId] }) is redundant because
invalidateAgentLinkQueries already invalidates the same key (see
invalidateAgentLinkQueries) and will trigger a refetch; remove that
refetchQueries call, keep the onSelect(item.providerId, created) and
setOpen(false) flow, and ensure any required immediate synchronous refresh is
handled (only keep refetchQueries if you intentionally need the refetch to
complete before calling onSelect).

In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx`:
- Around line 19-24: The completed-step indicator is using hardcoded hex colors
in the JSX element with className 'flex size-5 shrink-0 items-center
justify-center rounded-full border border-[`#5ec269`] bg-[`#77db89`]
shadow[...]'—replace those hex values with Tailwind utility color classes (or
with a new design-system token you add to tailwind.config.js and then use its
utility class) so the component uses theme colors instead of literals; update
the div's className to use the appropriate border-<token> and bg-<token> classes
(and keep the svg stroke as text-white or equivalent) and add the new color
tokens to theme.extend.colors if they don’t already exist.

In `@apps/dashboard/src/components/agents/slack-setup-guide.tsx`:
- Around line 108-109: The current polling setup calls void tick() immediately
and sets intervalId = setInterval(() => void tick(), 1000), which uses a
1-second interval; change this to a less aggressive interval (e.g., 2000–3000
ms) or implement exponential backoff on the tick function so it increases delay
after repeated unchanged responses; update the setInterval call (and any
intervalId management/clear logic) to use the new interval variable or backoff
mechanism, and optionally consider replacing this manual polling with React
Query's refetchInterval on the query that tick() drives to benefit from built-in
throttling and background-tab handling.
- Around line 217-219: The YAML snippet sets request_url using an unquoted
interpolation (${webhookHandlerUrl}), which can break if the URL contains
YAML-special characters; update the YAML in slack-setup-guide.tsx so the
request_url fields use quoted values (e.g., wrap ${webhookHandlerUrl} in double
quotes) for all occurrences of request_url in that YAML block to ensure safe
parsing.

In `@packages/shared/src/consts/providers/conversational-providers.ts`:
- Around line 3-20: Change the mutable public exports to readonly/immutable
shapes: make the ConversationalProvider type properties readonly (e.g., readonly
providerId; readonly displayName; readonly comingSoon?), export
CONVERSATIONAL_PROVIDERS as a ReadonlyArray<ConversationalProvider> and
create/freeze the array and each provider object (e.g., Object.freeze([...]) /
Object.freeze(provider) for each entry) so consumers cannot mutate entries at
runtime, and export CONVERSATIONAL_PROVIDER_IDS as a ReadonlySet<string> (create
with new Set(...) then Object.freeze(...) to prevent reassignment) so both the
list and id set are exposed as immutable public API; update declarations for
CONVERSATIONAL_PROVIDERS and CONVERSATIONAL_PROVIDER_IDS accordingly and keep
provider identifiers (e.g., ChatProviderIdEnum.Slack) unchanged.
🪄 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: faac3439-0400-4c3b-b93f-b839bf5d94ca

📥 Commits

Reviewing files that changed from the base of the PR and between 719f407 and f445381.

⛔ Files ignored due to path filters (5)
  • apps/dashboard/public/images/providers/light/square/google-chat.svg is excluded by !**/*.svg
  • apps/dashboard/public/images/providers/light/square/imessages.svg is excluded by !**/*.svg
  • apps/dashboard/public/images/providers/light/square/linear.svg is excluded by !**/*.svg
  • apps/dashboard/public/images/providers/light/square/telegram.svg is excluded by !**/*.svg
  • apps/dashboard/public/images/providers/light/square/zoom.svg is excluded by !**/*.svg
📒 Files selected for processing (36)
  • apps/api/src/app/agents/agents.controller.ts
  • apps/api/src/app/agents/dtos/agent-integration-response.dto.ts
  • apps/api/src/app/agents/dtos/agent-response.dto.ts
  • apps/api/src/app/agents/dtos/create-agent-request.dto.ts
  • apps/api/src/app/agents/dtos/update-agent-request.dto.ts
  • apps/api/src/app/agents/mappers/agent-response.mapper.ts
  • apps/api/src/app/agents/services/agent-credential.service.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
  • apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts
  • apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.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.command.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/api/integrations.ts
  • apps/dashboard/src/components/agents/agent-integration-guides/agent-integration-guide-layout.tsx
  • apps/dashboard/src/components/agents/agent-integration-guides/resolve-agent-integration-guide.tsx
  • apps/dashboard/src/components/agents/agent-integrations-tab.tsx
  • apps/dashboard/src/components/agents/agent-overview-tab.tsx
  • apps/dashboard/src/components/agents/agent-setup-guide.tsx
  • apps/dashboard/src/components/agents/agent-sidebar-widget.tsx
  • apps/dashboard/src/components/agents/provider-dropdown.tsx
  • apps/dashboard/src/components/agents/setup-guide-primitives.tsx
  • apps/dashboard/src/components/agents/setup-guide-step-utils.ts
  • apps/dashboard/src/components/agents/slack-setup-guide.tsx
  • apps/dashboard/src/main.tsx
  • apps/dashboard/src/pages/agent-details.tsx
  • apps/dashboard/src/utils/routes.ts
  • libs/dal/src/repositories/agent-integration/agent-integration.entity.ts
  • libs/dal/src/repositories/agent-integration/agent-integration.schema.ts
  • libs/dal/src/repositories/agent/agent.entity.ts
  • libs/dal/src/repositories/agent/agent.schema.ts
  • packages/shared/src/consts/providers/conversational-providers.ts
  • packages/shared/src/consts/providers/index.ts

Comment thread apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts Outdated
Comment thread apps/dashboard/src/components/agents/slack-setup-guide.tsx
Comment thread libs/dal/src/repositories/agent/agent.schema.ts
scopsy added 2 commits April 15, 2026 16:27
Multiple fixes and refinements across API and dashboard:

- API: Inject PinoLogger into AgentCredentialService for logging. Update UpdateAgentCommand to validate optional `active` explicitly and include `active` in the usecase's required-fields error message.
- Dashboard (agents): Show relative updated time with suffix (e.g. "3 minutes ago").
- Provider dropdown: tighten types to readonly for conversational providers and remove an unnecessary query refetch after creating an integration.
- Slack setup guide: manage confetti timeout via a ref to avoid multiple timers/leaks, ensure webhook URL is YAML-escaped and quoted in the generated Slack manifest, and add missing imports (useRef).
- Shared: make conversational providers immutable (freeze objects and arrays) and export a frozen set of provider IDs.

These changes improve robustness (timers, logging), correctness (validation, manifest quoting), and immutability for shared provider constants.
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: 3

🧹 Nitpick comments (1)
apps/dashboard/src/components/agents/setup-guide-primitives.tsx (1)

17-24: Match the repo’s blank-line-before-return rule.

A few new return statements still skip the required blank line (StepIndicator, SetupStep, SetupButton, and the early exits in IntegrationCredentialsSidebar). As per coding guidelines "Include a blank line before every return statement".

Also applies to: 51-52, 103-104, 132-132, 156-156

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

In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx` around lines
17 - 24, Add the required blank line before every return in this file: insert an
empty line immediately above the early return in the status === 'completed'
branch shown and do the same for the other components mentioned (StepIndicator,
SetupStep, SetupButton) and the early exits in IntegrationCredentialsSidebar so
each return statement is preceded by a blank line to comply with the repo rule;
ensure you update the returns at the noted locations (around lines currently
near 51-52, 103-104, 132, and 156) consistently.
🤖 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-credential.service.ts`:
- Around line 81-90: The current pre-validation write to
agentIntegration.connectedAt via agentIntegrationRepository.updateOne can
incorrectly mark success on transient DB failures and races; move the update to
after successful validation/decryption and perform an atomic conditional update
(filtering for { _id: agentIntegration._id, connectedAt: null, _environmentId:
environmentId, _organizationId: organizationId } and $set: { connectedAt: new
Date() }) so only the first resolver wins, and wrap the updateOne call in a
try/catch that logs errors but does not propagate them (so credential resolution
still succeeds on transient write failures). Ensure you modify the code paths
around agentIntegration.connectedAt and the callsite that currently performs the
update before validation/decryption.

In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx`:
- Around line 124-156: The component currently returns null when integration or
provider is missing, which hides loading vs not-found states; update the logic
around useFetchIntegrations() and integration/provider resolution so you handle
loading and error states instead of silently returning: use the loading/error
flags from useFetchIntegrations (e.g., isLoading/isError) and, when the sidebar
is open, render a loader while isLoading, render a clear "integration not found"
or provider error UI when !integration or !provider, or alternatively accept the
resolved integration as a prop rather than refetching here; update the
conditional that now does "if (!integration || !provider) return null" to show
loading/error UI and keep onSubmit/integrationId logic unchanged (functions:
useFetchIntegrations, integrationId, integration, provider, onSubmit).
- Around line 86-107: When href is present avoid nesting <Button /> inside an
<a>; instead render a single anchor element styled like the button. Replace the
branch that returns <a><Button/></a> by returning an <a> that contains the same
children/leadingIcon/RiArrowRightUpLine markup used in content, and move
className/size/variant styling onto the <a>. Ensure disabled behavior by not
setting the href when disabled (or set aria-disabled="true" and tabIndex={-1}
and add a pointer-events-none/opacity class) and preserve onClick only for the
non-href case; adjust the code around the content variable, Button component
usage, href, onClick, disabled and RiArrowRightUpLine accordingly.

---

Nitpick comments:
In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx`:
- Around line 17-24: Add the required blank line before every return in this
file: insert an empty line immediately above the early return in the status ===
'completed' branch shown and do the same for the other components mentioned
(StepIndicator, SetupStep, SetupButton) and the early exits in
IntegrationCredentialsSidebar so each return statement is preceded by a blank
line to comply with the repo rule; ensure you update the returns at the noted
locations (around lines currently near 51-52, 103-104, 132, and 156)
consistently.
🪄 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: 2c0e5a0f-1a1c-4f85-874b-d9278547a9c4

📥 Commits

Reviewing files that changed from the base of the PR and between f445381 and 6c4f377.

📒 Files selected for processing (7)
  • apps/api/src/app/agents/services/agent-credential.service.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.command.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
  • apps/dashboard/src/components/agents/agent-sidebar-widget.tsx
  • apps/dashboard/src/components/agents/provider-dropdown.tsx
  • apps/dashboard/src/components/agents/setup-guide-primitives.tsx
  • apps/dashboard/src/components/agents/slack-setup-guide.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/api/src/app/agents/usecases/update-agent/update-agent.command.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
  • apps/dashboard/src/components/agents/agent-sidebar-widget.tsx
  • apps/dashboard/src/components/agents/provider-dropdown.tsx
  • apps/dashboard/src/components/agents/slack-setup-guide.tsx

Comment thread apps/api/src/app/agents/services/agent-credential.service.ts Outdated
Comment thread apps/dashboard/src/components/agents/setup-guide-primitives.tsx Outdated
Comment thread apps/dashboard/src/components/agents/setup-guide-primitives.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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/api/src/app/agents/services/agent-inbound-handler.service.ts (1)

215-228: ⚠️ Potential issue | 🟠 Major

Missing NoBridgeUrlError handling in handleAction().

The handleAction() method calls bridgeExecutor.execute() without catching NoBridgeUrlError, unlike the handle() method. With the new throwing behavior in BridgeExecutorService, this will cause unhandled exceptions when actions are triggered without a configured bridge URL.

Either wrap this call in the same try/catch pattern, or document why actions should fail differently than messages.

🐛 Proposed fix to add consistent error handling
     const [subscriber, history] = await Promise.all([
       subscriberId
         ? this.subscriberRepository.findBySubscriberId(config.environmentId, subscriberId)
         : Promise.resolve(null),
       this.conversationService.getHistory(config.environmentId, conversation._id),
     ]);
 
-    await this.bridgeExecutor.execute({
-      event: AgentEventEnum.ON_ACTION,
-      config,
-      conversation,
-      subscriber,
-      history,
-      message: null,
-      platformContext: {
-        threadId: thread.id,
-        channelId: thread.channelId,
-        isDM: thread.isDM,
-      },
-      action,
-    });
+    try {
+      await this.bridgeExecutor.execute({
+        event: AgentEventEnum.ON_ACTION,
+        config,
+        conversation,
+        subscriber,
+        history,
+        message: null,
+        platformContext: {
+          threadId: thread.id,
+          channelId: thread.channelId,
+          isDM: thread.isDM,
+        },
+        action,
+      });
+    } catch (err) {
+      if (err instanceof NoBridgeUrlError) {
+        this.logger.warn(`[agent:${agentId}] No bridge URL configured for action ${action.actionId}, skipping`);
+
+        return;
+      }
+
+      throw err;
+    }
   }
 }
🤖 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 215 - 228, handleAction() currently calls bridgeExecutor.execute({ event:
AgentEventEnum.ON_ACTION, ... }) without catching NoBridgeUrlError, causing
unhandled exceptions; modify handleAction() to wrap the
bridgeExecutor.execute(...) call in a try/catch that specifically catches
NoBridgeUrlError (and reuses the same handling logic used in handle(), e.g.,
logging and returning/short-circuiting), so actions triggered without a bridge
URL are handled consistently with message handling.
🧹 Nitpick comments (1)
apps/api/src/app/agents/services/agent-inbound-handler.service.ts (1)

13-15: Consider extracting onboarding messages to a shared constants file.

The Slack-specific markdown formatting (*bold*) is appropriate for the current use case. If more onboarding messages are added, consider extracting them to a dedicated constants module for maintainability.

🤖 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 13 - 15, The ONBOARDING_NO_BRIDGE_REPLY_MARKDOWN constant contains
Slack-specific onboarding text and should be moved into a shared constants
module (e.g., an onboarding/messages or agents/onboarding constants file) so
future onboarding messages are centralized; extract the constant (keep the same
identifier ONBOARDING_NO_BRIDGE_REPLY_MARKDOWN), export it from the new module,
import and use it from agent-inbound-handler.service.ts, and update any
references to pull from the shared constants instead of being defined inline.
🤖 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-config-resolver.service.ts`:
- Around line 93-102: The current read-then-write for setting connectedAt causes
a race and can block/ fail resolve(); change it to an atomic update with a
conditional filter and make it non-blocking: replace the two-step check+update
with a single atomic agentIntegrationRepository.updateOne call that includes
connectedAt: null in the query (or use $setOnInsert) to set connectedAt = new
Date() only when it is not already set, and do not await it inside
resolve()—instead fire-and-forget the Promise and catch/log any errors from
agentIntegrationRepository.updateOne so resolution proceeds even if the
persistence fails.

---

Outside diff comments:
In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts`:
- Around line 215-228: handleAction() currently calls bridgeExecutor.execute({
event: AgentEventEnum.ON_ACTION, ... }) without catching NoBridgeUrlError,
causing unhandled exceptions; modify handleAction() to wrap the
bridgeExecutor.execute(...) call in a try/catch that specifically catches
NoBridgeUrlError (and reuses the same handling logic used in handle(), e.g.,
logging and returning/short-circuiting), so actions triggered without a bridge
URL are handled consistently with message handling.

---

Nitpick comments:
In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts`:
- Around line 13-15: The ONBOARDING_NO_BRIDGE_REPLY_MARKDOWN constant contains
Slack-specific onboarding text and should be moved into a shared constants
module (e.g., an onboarding/messages or agents/onboarding constants file) so
future onboarding messages are centralized; extract the constant (keep the same
identifier ONBOARDING_NO_BRIDGE_REPLY_MARKDOWN), export it from the new module,
import and use it from agent-inbound-handler.service.ts, and update any
references to pull from the shared constants instead of being defined inline.
🪄 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: 1e13f552-6d67-4a6f-afed-93b6403413d1

📥 Commits

Reviewing files that changed from the base of the PR and between 6c4f377 and 3e0cb33.

📒 Files selected for processing (8)
  • 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/bridge-executor.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
  • libs/dal/src/repositories/agent/agent.entity.ts
  • libs/dal/src/repositories/agent/agent.schema.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • libs/dal/src/repositories/agent/agent.schema.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/dal/src/repositories/agent/agent.entity.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

Comment thread apps/api/src/app/agents/services/agent-config-resolver.service.ts Outdated
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: 2

♻️ Duplicate comments (1)
apps/dashboard/src/components/agents/setup-guide-primitives.tsx (1)

138-170: ⚠️ Potential issue | 🟠 Major

Handle loading/error/not-found states explicitly instead of returning null.

Line 170 still silently returns nothing when the query hasn’t resolved (or fails), so opening the sidebar can appear broken.

Suggested patch
-  const { integrations } = useFetchIntegrations();
+  const { integrations, isLoading, isError } = useFetchIntegrations();
@@
-  if (!integration || !provider) return null;
+  if (isOpen && isLoading) {
+    return <div className="text-text-soft p-4 text-sm">Loading integration settings…</div>;
+  }
+
+  if (isOpen && (isError || !integration || !provider)) {
+    return <div className="text-text-soft p-4 text-sm">Could not load integration settings.</div>;
+  }
+
+  if (!integration || !provider) return null;

As per coding guidelines "Check for proper loading/error states."

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

In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx` around lines
138 - 170, The component silently returns null when integration or provider is
missing; update the render logic around the
useFetchIntegrations/useUpdateIntegration results (and the integration/provider
lookups) to explicitly handle loading, error, and not-found states: check the
hooks' isLoading/isFetching/isError (or equivalent) and render a loading
indicator while fetching, an error message when the query fails, and a clear
"integration not found" or fallback UI when the integration/provider lookup
yields no match, only rendering the existing form and calling onSubmit when data
is present; reference the integration and provider variables, the
useFetchIntegrations/useUpdateIntegration hooks, and the onSubmit handler when
adding these conditional branches.
🤖 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/usecases/update-agent/update-agent.usecase.ts`:
- Around line 12-20: The guard in update-agent.usecase.ts allows requests where
command.behavior is an empty object, producing an empty Mongo $set and a no-op
update; after you build the update document (the object passed to updateOne)
check the constructed $set's size/keys and if there are no keys throw
BadRequestException (same message or a new one like "At least one of name,
description, behavior, or active must be provided.") before calling updateOne;
ensure this check references the same update object you pass to updateOne so
empty behavior-only payloads are rejected.

In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx`:
- Around line 16-31: The StepIndicator component currently renders visual-only
markers; update StepIndicator (props: status, index) to expose the state to
assistive tech by adding an accessible label or screen-reader text: for the
completed branch include an aria-label (or visually-hidden <span>) that reads
e.g. "Step {index} completed", and for the non-completed branch include "Step
{index} {status}" (or "incomplete"/"current" as appropriate); ensure the wrapper
element has an appropriate role/aria-live if you want dynamic announcements
(e.g., role="status" or aria-live="polite") so screen readers announce changes.

---

Duplicate comments:
In `@apps/dashboard/src/components/agents/setup-guide-primitives.tsx`:
- Around line 138-170: The component silently returns null when integration or
provider is missing; update the render logic around the
useFetchIntegrations/useUpdateIntegration results (and the integration/provider
lookups) to explicitly handle loading, error, and not-found states: check the
hooks' isLoading/isFetching/isError (or equivalent) and render a loading
indicator while fetching, an error message when the query fails, and a clear
"integration not found" or fallback UI when the integration/provider lookup
yields no match, only rendering the existing form and calling onSubmit when data
is present; reference the integration and provider variables, the
useFetchIntegrations/useUpdateIntegration hooks, and the onSubmit handler when
adding these conditional branches.
🪄 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: 3a2739b5-ee5d-4e3e-bc52-9f2ff2407459

📥 Commits

Reviewing files that changed from the base of the PR and between 3e0cb33 and 5a82d83.

📒 Files selected for processing (3)
  • apps/api/src/app/agents/services/agent-config-resolver.service.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
  • apps/dashboard/src/components/agents/setup-guide-primitives.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/app/agents/services/agent-config-resolver.service.ts

Comment thread apps/dashboard/src/components/agents/setup-guide-primitives.tsx
@scopsy scopsy merged commit 6e7bfa5 into next Apr 15, 2026
39 checks passed
@scopsy scopsy deleted the agent-overview-page-impl branch April 15, 2026 14:38
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