Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(api-service): managed-runtime mode with Claude Platform integration fixes NV-7618#11058

Merged
djabarovgeorge merged 27 commits into
nextfrom
cursor/managed-agents-claude-platform
May 12, 2026
Merged

feat(api-service): managed-runtime mode with Claude Platform integration fixes NV-7618#11058
djabarovgeorge merged 27 commits into
nextfrom
cursor/managed-agents-claude-platform

Conversation

@djabarovgeorge
Copy link
Copy Markdown
Contributor

@djabarovgeorge djabarovgeorge commented May 10, 2026

What changed? Why was the change needed?

What changed

This PR adds managed agent runtimes so Novu agents can run on Anthropic's Claude Platform instead of a self‑hosted bridge. Novu can store Anthropic API keys as Integrations, provision or adopt Claude agents upstream, and proxy live runtime configuration (model, system prompt, MCP servers, tools, skills) via a new IAgentRuntimeProvider abstraction. The change enables provider-backed provisioning/adoption, provider-aware config updates, soft-delete with async upstream cleanup, and stable error mapping for runtime errors (including rate-limit Retry-After).

Affected areas

  • api: New use cases/endpoints to provision/adopt managed agents and to GET/PATCH agent runtime config; AgentRuntimeExceptionFilter maps provider errors to HTTP codes (adds Retry-After for 429); DeleteAgent supports optional upstream deletion and soft-delete fields; create-agent flow accepts managed runtime inputs and adopt mode.
  • dashboard (react): Client APIs, hooks and UI for managed onboarding, provider chooser, AgentRuntimeBadge, config section, auth banner, drift modal, create-agent dialog adaptations, agents list updates, and delete dialog with delete-from-provider scope.
  • shared: New AGENT_RUNTIME_PROVIDERS catalog, AgentRuntimeProviderIdEnum (anthropic), managed-runtime DTOs/types (runtime, managedRuntime, AgentRuntimeConfigDto), Claude tools/MCP servers/skills lists, feature flag IS_MANAGED_AGENT_RUNTIME_ENABLED, and NotificationChannelTypeEnum to exclude AGENT_RUNTIME from user-facing channel preference keys.
  • libs/dal: Agent entity/schema extended with runtime, managedRuntime (_integrationId + externalAgentId), creationSource, and soft-delete bookkeeping (deletedAt, pendingExternalDelete).
  • libs/application-generic: IAgentRuntimeProvider interface, AnthropicAgentRuntimeProvider implementation (uses @anthropic-ai/sdk), provider factory, rich AgentRuntimeError hierarchy, and a provider capability parity unit test.

Key technical decisions

  • New dependency: @anthropic-ai/sdk (application-generic) to manage Claude environments and agents.
  • Persist only provider linkage (integration id + externalAgentId); live runtime config (MCP servers, tools, skills) is fetched from the provider and not persisted.
  • Soft-delete with background upstream cleanup to avoid blocking local deletions on provider errors.
  • Providers surface deterministic error classes; API maps them to stable HTTP statuses and honors Retry-After; providers use a single-retry-with-jitter for transient failures.
  • Introduced NotificationChannelTypeEnum to keep AGENT_RUNTIME out of end-user workflow preference keys.

Testing

Adds extensive e2e tests covering provisioning, adoption, apiKey auto-provisioning and rollback, error mappings (drift/unauthorized/bad request/rate limit), and config PATCH behavior, plus a unit test ensuring provider capability parity.

Introduces managed agents — users can now point a Novu agent at Claude Platform instead of self-hosting a bridge server. Novu stores the Anthropic API key as an Integration, provisions the Claude agent upstream, and proxies all live configuration (model, system prompt, MCP servers, tools) through a new IAgentRuntimeProvider abstraction.

Linear: https://linear.app/novu/issue/NV-7618


packages/shared

  • AGENT_RUNTIME_PROVIDERS static catalog with AgentRuntimeCapabilities per provider (drives capability-gated UI panels)
  • AgentRuntimeProviderIdEnum (anthropic) + ChannelTypeEnum.AGENT_RUNTIME
  • NotificationChannelTypeEnum (Exclude<ChannelTypeEnum, AGENT_RUNTIME>) — keeps channel-preference types correct
  • IS_MANAGED_AGENT_RUNTIME_ENABLED feature flag
  • AgentRuntime, ManagedRuntimeConfigDto, AgentRuntimeConfigDto DTOs

libs/dal

  • AgentEntity extended with runtime, managedRuntime { providerId, _integrationId, externalAgentId }, and soft-delete fields (deletedAt, pendingExternalDelete)

libs/application-generic

  • IAgentRuntimeProvider interface
  • AnthropicAgentRuntimeProvider using @anthropic-ai/sdk with error normalisation and single-retry for transient failures
  • getAgentRuntimeProvider factory
  • Full AgentRuntime*Error taxonomy (Unauthorized / Forbidden / NotFound / RateLimited / Overloaded / ServiceUnavailable / Timeout / Network / BadRequest / Unknown)
  • Capabilities-parity unit test

apps/api

  • ProvisionManagedAgent use-case — fetches integration credentials, calls provider, persists externalAgentId, rolls back on Mongo failure
  • GetAgentRuntimeConfig / UpdateAgentRuntimeConfig use-cases proxying live config to/from the provider
  • DELETE extended for soft-delete + async upstream cleanup job
  • AgentRuntimeExceptionFilter — maps error union to stable HTTP codes + Retry-After header on 429
  • GET /agents/runtime-providers — returns static catalog (gated by feature flag)
  • GET/PATCH /agents/:id/runtime/config endpoints

apps/dashboard

  • src/api/agent-runtime.tsgetAgentRuntimeProviders, getAgentRuntimeConfig, updateAgentRuntimeConfig
  • src/utils/agent-runtime-errors.ts — error-code → UI treatment map + TanStack Query retry/backoff (honours Retry-After on 429, no retry on 401/403/400/409, 2 retries on 503)
  • AgentRuntimeBadge — Self-hosted / Managed (provider name) badge
  • AgentRuntimeConfigSection — live config card with loading skeleton + section-level error panels
  • AgentRuntimeAuthBanner — sticky page banner on 401, links to integration edit
  • AgentRuntimeDriftModal — Recreate / Unlink / Cancel modal for AGENT_RUNTIME_DRIFT (409)
  • AgentOverviewTab — wires all new components; capability-driven panel visibility
  • ManagedRuntimeSetupGuide — onboarding runtime chooser + Anthropic key + model + system prompt form
  • AgentsSetupPage — runtime chooser injected before self-hosted bridge setup, gated by IS_MANAGED_AGENT_RUNTIME_ENABLED

Screenshots

Expand for optional sections

Special notes for your reviewer

  • Message-time execution (handle-agent-reply.usecase.ts) is out of scope — a TODO(managed-agents) comment marks the branch point for a future PR by the runtime team.
  • MCP servers and tools are not persisted in Novu's DB — they are fetched live from Claude Platform on every request.
  • The @anthropic-ai/sdk dependency satisfies the minimumReleaseAge: 1440 policy in pnpm-workspace.yaml.

Made with Cursor

djabarovgeorge and others added 2 commits May 10, 2026 16:14
Introduces managed agents — users can now point a Novu agent at Claude
Platform instead of self-hosting a bridge. Novu stores the Anthropic API
key as an Integration, provisions the agent upstream, and proxies all live
config (model, system prompt, MCP servers, tools) through a new
IAgentRuntimeProvider abstraction.

- packages/shared: AGENT_RUNTIME_PROVIDERS catalog, AgentRuntimeCapabilities,
  IS_MANAGED_AGENT_RUNTIME_ENABLED flag, NotificationChannelTypeEnum,
  managed-runtime DTOs, ChannelTypeEnum.AGENT_RUNTIME, anthropic providerId
- libs/dal: AgentEntity extended with runtime, managedRuntime, soft-delete fields
- libs/application-generic: IAgentRuntimeProvider interface,
  AnthropicAgentRuntimeProvider (@anthropic-ai/sdk), factory, error taxonomy
  (Unauthorized/Forbidden/NotFound/RateLimited/ServiceUnavailable/...),
  capabilities-parity test
- apps/api: ProvisionManagedAgent / GetAgentRuntimeConfig /
  UpdateAgentRuntimeConfig use-cases, soft-delete with async Claude cleanup,
  AgentRuntimeExceptionFilter (stable HTTP codes + Retry-After header),
  GET /agents/runtime-providers endpoint
- apps/dashboard: agent-runtime API client, agent-runtime-errors utility
  (UI treatment map + TanStack Query retry/backoff), runtime badge,
  runtime-config section with section-level error panels, auth banner (401),
  drift modal (409), onboarding runtime chooser + managed setup form

Co-authored-by: Cursor <[email protected]>
…okups

Follow-up fixes after introducing ChannelTypeEnum.AGENT_RUNTIME:
- Add AGENT_RUNTIME entry to all ChannelTypeEnum lookup maps
  (channels.ts, channel-preferences-form, preferences-item,
   workflow-preferences, use-integration-list, agent-integrations-tab)
- Use NotificationChannelTypeEnum in upsert-workflow.command to keep
  workflow preferences typed correctly (excludes AGENT_RUNTIME)
- Fix TanStack Query v5 keepPreviousData → placeholderData in
  agent-runtime-config-section
- Fix Button ghost variant props in drift modal and managed setup guide

Co-authored-by: Cursor <[email protected]>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 10, 2026

NV-7618

@netlify
Copy link
Copy Markdown

netlify Bot commented May 10, 2026

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

Name Link
🔨 Latest commit 29b5c99
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/6a0399e5b3f25900092e9a52

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 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 managed-agent runtime support (Anthropic provider): provider catalog, provider implementation, runtime DTOs, controller endpoints for provider discovery and runtime config, usecases for provisioning/adoption/get/update/delete with rollback, dashboard onboarding/UI, runtime error mapping, and e2e tests.

Changes

Managed Agent Runtime Support

Layer / File(s) Summary
Shared Types and Provider Catalog
packages/shared/src/consts/providers/agent-runtime-providers.ts, packages/shared/src/dto/agent/managed-runtime.dto.ts, packages/shared/src/types/channel.ts, packages/shared/src/types/feature-flags.ts, packages/shared/src/types/providers.ts, packages/shared/src/types/workflow-channel-preferences.ts
New AgentRuntimeProviderIdEnum with Anthropic value; AgentRuntime union ('self-hosted' | 'managed'); AgentRuntimeCapabilities with boolean flags for MCP servers, tools, model, system prompt, and skills; AGENT_RUNTIME_PROVIDERS catalog with Anthropic entry; NotificationChannelTypeEnum excluding AGENT_RUNTIME from workflow channels.
DTO Definitions and Exports
apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts, apps/api/src/app/agents/dtos/index.ts, packages/shared/src/dto/agent/index.ts
New DTOs for MCP servers, tools, runtime configuration responses, and patch requests; extended agent request/response DTOs for managed runtime; barrel exports of agent-runtime-config and managed-runtime modules.
Data Models and Database Schema
libs/dal/src/repositories/agent/agent.entity.ts, libs/dal/src/repositories/agent/agent.schema.ts, apps/api/src/app/agents/dtos/agent-response.dto.ts
Agent entity extended with runtime, managedRuntime, and creationSource fields; schema updated; response DTOs include runtime metadata.
Create Agent Command Extension
apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts
Extends CreateAgentCommand with optional runtime and managedRuntime fields with conditional validation for managed agents.
Runtime Provider Interface
libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts
IAgentRuntimeProvider interface defining methods for credential validation, agent creation/deletion, environment lifecycle, and configuration retrieval/update with typed I/O.
Runtime Error Hierarchy
libs/application-generic/src/agent-runtimes/errors.ts
Comprehensive error hierarchy with base AgentRuntimeError and concrete subclasses (unauthorized, forbidden, not found, rate limited with retry-after support, overloaded, service unavailable, timeout, network, bad request, unknown).
Anthropic Runtime Provider
libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts
Full implementation of Anthropic agent lifecycle, environment lifecycle, MCP server and tool synchronization, error mapping from SDK exceptions to runtime errors, request timeout, and retry/jitter logic for transient failures.
Provider Factory and Exception Filter
libs/application-generic/src/agent-runtimes/agent-runtime.factory.ts, apps/api/src/app/agents/filters/agent-runtime-exception.filter.ts
Factory to instantiate runtime providers by ID; NestJS exception filter to catch AgentRuntimeError and convert to HTTP responses with appropriate status codes, retry-after headers, and structured JSON.
Backend Module Setup
apps/api/src/app/agents/agents.module.ts, apps/api/src/app/agents/agents.controller.ts
Registers AgentRuntimeExceptionFilter and IntegrationRepository in AgentsModule; extends controller with new endpoints and service injections.
Backend Commands
apps/api/src/app/agents/usecases/get-agent-runtime-config/get-agent-runtime-config.command.ts, apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.command.ts, apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts
Command classes for getting/updating agent runtime configuration and provisioning managed agents with validated fields.
Backend Usecases
apps/api/src/app/agents/usecases/create-agent/, apps/api/src/app/agents/usecases/get-agent-runtime-config/, apps/api/src/app/agents/usecases/update-agent-runtime-config/, apps/api/src/app/agents/usecases/provision-managed-agent/, apps/api/src/app/agents/usecases/delete-agent/, apps/api/src/app/agents/usecases/index.ts
Implements provisioning, retrieval, update, and deletion of managed agents with provider calls, credential decryption, MCP/tool/skill synchronization, and rollback handling; extends create and delete for managed agents; registers new usecases.
Backend Controller Endpoints
apps/api/src/app/agents/agents.controller.ts
Exposes GET /agents/runtime-providers (catalog), GET /agents/:identifier/runtime/config (fetch config), PATCH /agents/:identifier/runtime/config (update config), all protected by permissions and exception filtering.
Agent Response Mapper
apps/api/src/app/agents/mappers/agent-response.mapper.ts
Updates toAgentResponse to map agent.runtime and managedRuntime fields into response DTOs.
Dashboard API Client
apps/dashboard/src/api/agent-runtime.ts, apps/dashboard/src/api/agents.ts
Query key helpers and async functions for fetching runtime provider catalog, getting/updating agent runtime configuration; extended agent types support managed runtime fields.
Dashboard Error and Retry Utilities
apps/dashboard/src/utils/agent-runtime-errors.ts, apps/dashboard/src/utils/channels.ts
Error code mapping to UI specs with titles, descriptions, retry countdowns; retry decision/delay logic for React Query; provider status URL resolution; channel type to string mappings.
Dashboard Runtime Badge and Auth Banner
apps/dashboard/src/components/agents/agent-runtime-badge.tsx, apps/dashboard/src/components/agents/agent-runtime-auth-banner.tsx
Badge showing runtime mode/provider; auth banner for API key expiration with optional update link.
Dashboard Runtime Configuration Section
apps/dashboard/src/components/agents/agent-runtime-config-section.tsx
Fetches and displays managed agent runtime configuration (model, system prompt, MCP servers, tools, skills) with error handling, retry logic, and callbacks for drift/auth errors.
Dashboard Drift Modal
apps/dashboard/src/components/agents/agent-runtime-drift-modal.tsx
Modal for handling provider-side agent deletion with options to recreate or unlink with loading states.
Dashboard Managed Runtime Setup Guide
apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx
Multi-step onboarding: provider choice, API key collection, optional model/system prompt configuration; integration creation and agent provisioning with telemetry.
Dashboard UI Integration
apps/dashboard/src/components/agents/agent-overview-tab.tsx, apps/dashboard/src/components/agents/agent-integrations-tab.tsx, apps/dashboard/src/components/integrations/components/hooks/use-integration-list.ts, apps/dashboard/src/pages/agents-setup-page.tsx, apps/dashboard/src/components/subscribers/preferences/*
Agent overview conditionally renders runtime config section, drift modal, auth banner for managed agents; preference and workflow UI add runtime channel labels with icon/color fallbacks; agents setup page routes to managed or self-hosted path; integration list adds agent-runtime sorting.
Workflow Type System Updates
libs/application-generic/src/dtos/workflow/workflow-preferences.dto.ts, libs/application-generic/src/usecases/preview/preview.usecase.ts, libs/application-generic/src/usecases/upsert-preferences/upsert-preferences.command.ts, libs/application-generic/src/usecases/upsert-workflow/upsert-workflow.command.ts, packages/shared/src/utils/buildWorkflowPreferences.ts
Workflow channel preferences narrowed to NotificationChannelTypeEnum, excluding AGENT_RUNTIME from preference maps; preview usecase casts types accordingly; workflow preference building filters out agent runtime channels.
Infrastructure and Tests
libs/application-generic/package.json, libs/application-generic/src/agent-runtimes/agent-runtime-capabilities-parity.spec.ts, libs/application-generic/src/agent-runtimes/index.ts, apps/api/src/app/agents/e2e/managed-agent.e2e.ts
Adds @anthropic-ai/sdk dependency; test asserts provider capabilities match catalog; barrel exports agent-runtimes module and comprehensive e2e tests for managed agents.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • novuhq/novu#11059: Modifies the same AgentsController routes and constructor injections.

Comment thread libs/application-generic/src/usecases/preview/preview.usecase.ts Fixed
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: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (3)
apps/dashboard/src/components/agents/agent-integrations-tab.tsx (1)

33-39: ⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Add AGENT_RUNTIME to CHANNEL_GROUP_ORDER to display agent-runtime integrations.

CHANNEL_GROUP_ORDER is used by groupLinksByChannel (lines 100–105) to determine which channel groups to render. Since AGENT_RUNTIME is missing from this array, any integration with channel === ChannelTypeEnum.AGENT_RUNTIME will be collected in the map but never displayed in the UI.

🔧 Proposed fix
 const CHANNEL_GROUP_ORDER: ChannelTypeEnum[] = [
   ChannelTypeEnum.IN_APP,
   ChannelTypeEnum.CHAT,
   ChannelTypeEnum.EMAIL,
   ChannelTypeEnum.PUSH,
   ChannelTypeEnum.SMS,
+  ChannelTypeEnum.AGENT_RUNTIME,
 ];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/agent-integrations-tab.tsx` around lines
33 - 39, CHANNEL_GROUP_ORDER is missing ChannelTypeEnum.AGENT_RUNTIME so
groupLinksByChannel will collect agent-runtime integrations but never render
them; update the CHANNEL_GROUP_ORDER array to include
ChannelTypeEnum.AGENT_RUNTIME (in the appropriate display order) so
agent-runtime channel groups are rendered by groupLinksByChannel.
packages/shared/src/types/channel.ts (1)

36-42: ⚠️ Potential issue | 🔴 Critical

Add null check for undefined channel in create-notification-jobs.usecase.ts or filter AGENT_RUNTIME steps.

AGENT_RUNTIME is now a valid StepType but is intentionally excluded from STEP_TYPE_TO_CHANNEL_TYPE. In create-notification-jobs.usecase.ts at line 187–188, the map result is used directly without a null check:

const channel = STEP_TYPE_TO_CHANNEL_TYPE.get(step.template.type);
const providerId = command.templateProviderIds[channel];  // unsafe if channel is undefined

If an AGENT_RUNTIME step reaches this function, channel will be undefined, causing providerId to be undefined. Either add a null check before line 188, skip AGENT_RUNTIME steps in the loop at line 79–85, or add AGENT_RUNTIME to the map.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/types/channel.ts` around lines 36 - 42, The code uses
STEP_TYPE_TO_CHANNEL_TYPE.get(step.template.type) in
create-notification-jobs.usecase.ts and then indexes command.templateProviderIds
with the resulting channel without guarding against undefined; ensure
AGENT_RUNTIME (a valid StepTypeEnum intentionally excluded from the map) cannot
reach that usage by either: add a null/undefined check after const channel =
STEP_TYPE_TO_CHANNEL_TYPE.get(step.template.type) and skip processing when
channel is undefined, or filter out steps with step.template.type ===
StepTypeEnum.AGENT_RUNTIME earlier (e.g., in the loop that iterates steps) so
the providerId lookup (command.templateProviderIds[channel]) is never executed
for AGENT_RUNTIME; reference STEP_TYPE_TO_CHANNEL_TYPE, the local variable
channel, and command.templateProviderIds when applying the fix.
apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts (1)

35-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Orphaned local agent if managed provisioning fails.

The agent row is create()d before provisioning runs. If provisionManagedAgentUsecase.execute(...) throws (network error, invalid API key, Anthropic 5xx), the local agent doc remains with runtime: undefined and the user retrying the create with the same identifier will hit the existing-agent ConflictException at line 30, leaving them stuck.

Either run create + provision inside a transaction (or compensating delete on failure), or deliberately allow the partial state and let the user repair via update — but the current behavior silently strands users on retry.

     const agent = await this.agentRepository.create({
       ...
     });

     if (command.runtime === 'managed' && command.managedRuntime) {
-      await this.provisionManagedAgentUsecase.execute(
-        Object.assign(new ProvisionManagedAgentCommand(), {
-          ...
-        })
-      );
+      try {
+        await this.provisionManagedAgentUsecase.execute(
+          Object.assign(new ProvisionManagedAgentCommand(), {
+            agentId: agent._id,
+            ...
+          })
+        );
+      } catch (err) {
+        // Compensate: remove the orphan local agent so the caller can retry
+        await this.agentRepository.delete({
+          _id: agent._id,
+          _environmentId: command.environmentId,
+          _organizationId: command.organizationId,
+        });
+        throw err;
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts` around
lines 35 - 66, The create flow currently persists the Agent via
agentRepository.create before calling provisionManagedAgentUsecase.execute,
which can orphan a local agent if provisioning fails; modify
create-agent.usecase.ts to perform compensating cleanup: after creating the
agent (agentRepository.create) and before returning, wrap the
provisionManagedAgentUsecase.execute call in a try/catch and on any error call
agentRepository.delete (scoped by _id, _environmentId, _organizationId) to
remove the partially-created agent, then rethrow the original error; ensure the
cleanup itself is awaited and failures are logged/handled so you don't swallow
the original error (references: agentRepository.create,
provisionManagedAgentUsecase.execute, ProvisionManagedAgentCommand,
agentRepository.delete, agentRepository.findOne).
🟡 Minor comments (8)
apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx-111-111 (1)

111-111: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add blank line before return statement.

As per coding guidelines, include a blank line before every return statement.

📐 Proposed fix
   if (!isManagedEnabled) {
+
     return null;
   }

As per coding guidelines: Include a blank line before every return statement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx` at
line 111, In the ManagedRuntimeSetupGuide component (in
apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx) there
is no blank line before the return statement; insert a single blank line
immediately above the "return null;" statement so the return is separated from
preceding code, preserving the file's coding guideline of a blank line before
every return.
apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx-36-36 (1)

36-36: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent null-safety handling.

Line 36 uses a non-null assertion (!), but line 268 uses optional chaining (anthropicProvider?.displayName), suggesting the provider could be undefined. Since the code already has a fallback on line 268, remove the non-null assertion to handle edge cases gracefully.

🛡️ Proposed fix
-const anthropicProvider = AGENT_RUNTIME_PROVIDERS.find((p) => p.providerId === 'anthropic')!;
+const anthropicProvider = AGENT_RUNTIME_PROVIDERS.find((p) => p.providerId === 'anthropic');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx` at
line 36, The code currently forces anthropicProvider non-null with a "!" when
finding the provider (const anthropicProvider = AGENT_RUNTIME_PROVIDERS.find((p)
=> p.providerId === 'anthropic')!), but other code (e.g.,
anthropicProvider?.displayName) already treats it as potentially undefined;
remove the non-null assertion so the variable is typed as possibly undefined and
update any usages to handle undefined safely (use optional chaining or a
fallback display string) — locate the find call for anthropicProvider and change
it to not assert non-null, then ensure all references to anthropicProvider (like
displayName) use ?. or a default value.
apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx-301-301 (1)

301-301: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add blank line before return statement.

As per coding guidelines, include a blank line before every return statement.

📐 Proposed fix
   const provider = AGENT_RUNTIME_PROVIDERS.find((p) => p.providerId === providerId);

-  if (!provider) return null;
+  if (!provider) {
+
+    return null;
+  }

As per coding guidelines: Include a blank line before every return statement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx` at
line 301, The conditional early-return in the ManagedRuntimeSetupGuide component
("if (!provider) return null;") lacks a blank line before the return; update the
conditional to place a blank line immediately before the return statement (e.g.,
convert to a block or separate the return onto its own line) so there is a blank
line preceding every return in the ManagedRuntimeSetupGuide component.
apps/dashboard/src/components/agents/agent-overview-tab.tsx-68-80 (1)

68-80: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Drift modal recreate/unlink are no-ops — users hit a UX dead-end.

Both callbacks just close the modal, so a user surfaced into this flow because of AGENT_RUNTIME_DRIFT has no path forward from this UI; the comments acknowledge the work is deferred but the buttons still appear functional. At minimum, disable/hide the recreate and unlink controls (or render a "coming soon" inline note) until the corresponding endpoints land, so users don't repeatedly trigger no-op clicks and assume the system is broken.

Want me to wire the AgentRuntimeDriftModal to render disabled CTAs with a tooltip explaining the action is pending until the recreate/unlink endpoints ship?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/agent-overview-tab.tsx` around lines 68
- 80, The recreate/unlink callbacks on AgentRuntimeDriftModal are no-ops and
leave users stuck; update the usage so the modal shows disabled CTAs or a
"coming soon" note instead of active buttons: either pass explicit props (e.g.,
recreateEnabled={false} unlinkEnabled={false} or
comingSoonMessage="Recreate/unlink pending backend support") to
AgentRuntimeDriftModal, or replace the onRecreate/onUnlink handlers with logic
that opens an inline tooltip/alert explaining the action is pending (instead of
just calling setShowDriftModal(false)). Modify the AgentRuntimeDriftModal
invocation here and ensure the modal component (AgentRuntimeDriftModal) uses
those props to render disabled buttons and a tooltip/message so users aren’t
offered functional-looking but no-op controls.
apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts-34-34 (1)

34-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Silent leak path when managedRuntime is missing on a managed agent.

isManagedAgent requires both runtime === 'managed' AND a truthy managedRuntime. If a row has runtime: 'managed' but managedRuntime is somehow absent (provisioning crashed mid-flight, partial restore, manual edit), this branch falls through to the hard-delete path and the local agent is destroyed without ever attempting the external cleanup. Consider explicitly handling that state — e.g., throw a 422/422-equivalent so the caller can retry provisioning, or log+hard-delete deliberately.

-    const isManagedAgent = agent.runtime === 'managed' && agent.managedRuntime;
-
-    if (isManagedAgent) {
+    if (agent.runtime === 'managed') {
+      if (!agent.managedRuntime) {
+        this.logger.warn(
+          { agentId: agent._id },
+          'Managed agent missing managedRuntime metadata; performing local-only delete'
+        );
+      }
+      // Soft-delete: mark as pending external deletion, then clean up provider-side async
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts` at
line 34, The current check using isManagedAgent (const isManagedAgent =
agent.runtime === 'managed' && agent.managedRuntime) silently falls through to
hard-delete when runtime === 'managed' but managedRuntime is missing; add an
explicit branch that detects agent.runtime === 'managed' &&
!agent.managedRuntime and handle it deterministically (e.g., throw a
422/unprocessable error or log and choose hard-delete) before using
isManagedAgent so callers can retry provisioning or you can record the anomalous
state; update the delete logic in delete-agent.usecase.ts to reference
agent.runtime and agent.managedRuntime directly to implement this explicit
error/decision path.
apps/api/src/app/agents/usecases/get-agent-runtime-config/get-agent-runtime-config.usecase.ts-48-49 (1)

48-49: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate apiKey before instantiating the provider.

ProvisionManagedAgent explicitly throws when apiKey is missing (lines 31-33 there), but here a missing/empty key is asserted away with ! and silently passed into getAgentRuntimeProvider, surfacing as an opaque SDK error rather than a clear 422. Mirror the validation for consistency.

-    const decryptedCredentials = decryptCredentials(integration.credentials);
-    const runtimeProvider = getAgentRuntimeProvider(providerId, decryptedCredentials.apiKey!);
+    const decryptedCredentials = decryptCredentials(integration.credentials);
+    if (!decryptedCredentials.apiKey) {
+      throw new UnprocessableEntityException(
+        `Runtime integration for agent "${command.identifier}" has no API key configured.`
+      );
+    }
+    const runtimeProvider = getAgentRuntimeProvider(providerId, decryptedCredentials.apiKey);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/get-agent-runtime-config/get-agent-runtime-config.usecase.ts`
around lines 48 - 49, The code calls getAgentRuntimeProvider(providerId,
decryptedCredentials.apiKey!) without validating the apiKey; replicate the
validation used in ProvisionManagedAgent by checking decryptedCredentials.apiKey
(and treating empty string as missing) and throw or return a 422-style error
before calling getAgentRuntimeProvider so missing keys produce a clear
validation error; update the logic around decryptCredentials and the call-site
for getAgentRuntimeProvider to validate apiKey and only pass a non-empty string.
apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts-31-33 (1)

31-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use UnprocessableEntityException (not NotFoundException) when the integration lacks an API key.

The integration was found; its configuration is invalid. Returning a 404 here misleads the dashboard's error-to-UI mapping (which already treats 404 as "not found, please re-create") and conflates two distinct failure modes.

-import { Injectable, NotFoundException } from '@nestjs/common';
+import { Injectable, NotFoundException, UnprocessableEntityException } from '@nestjs/common';
...
-    if (!apiKey) {
-      throw new NotFoundException(`Integration "${command.integrationId}" has no API key configured.`);
-    }
+    if (!apiKey) {
+      throw new UnprocessableEntityException(
+        `Integration "${command.integrationId}" has no API key configured.`
+      );
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`
around lines 31 - 33, The code currently throws NotFoundException when an
integration exists but has no API key; update the check in
provision-managed-agent.usecase.ts to throw UnprocessableEntityException instead
(keep the existing message or adjust to indicate invalid configuration), and
ensure you import UnprocessableEntityException from `@nestjs/common` and remove
NotFoundException if it becomes unused; update any related tests/handlers
expecting a 404 to expect a 422 for this validation case.
apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts-47-48 (1)

47-48: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same apiKey! non-null assertion as in GetAgentRuntimeConfig.

Apply the same validation suggested for the GET use case here so missing credentials surface as a clean 422 instead of a downstream SDK error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts`
around lines 47 - 48, The code uses a non-null assertion on
integration.credentials.apiKey when calling getAgentRuntimeProvider in
update-agent-runtime-config.usecase.ts; mirror the GET use case validation by
checking decryptCredentials(integration.credentials) and ensuring apiKey exists
before calling getAgentRuntimeProvider(providerId, apiKey). If apiKey is
missing, throw/return the same 422 validation error used in
GetAgentRuntimeConfig (i.e., produce a clear 422 response rather than letting
the SDK error surface) and remove the trailing `!` on apiKey.
🧹 Nitpick comments (8)
apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts (1)

42-44: TODO appropriately marks future work for managed agent execution.

The comment clearly defers message-time execution to a future PR. When implementing this, note that the agent entity will need to be fetched earlier in the method (currently fetched at line 149), and the fetch projection must include the runtime and managedRuntime fields (currently only fetches _id, name, identifier at line 155).

Would you like me to open a tracking issue that outlines the implementation prerequisites (early agent fetch, field inclusion, branching logic, runtime provider integration) to make this TODO more actionable for the runtime team?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`
around lines 42 - 44, Update the TODO branch so the code fetches the agent
entity earlier in handle-agent-reply.usecase.ts (before routing logic) and
include the runtime and managedRuntime fields in the query projection in
addition to _id, name, identifier; then add a branch: if (agent.runtime ===
'managed') call the runtime provider via
IAgentRuntimeProvider.runConversationTurn(externalAgentId, userMessage) (passing
the agent.managedRuntime/external id as required) and short-circuit the
self-hosted bridge path, otherwise continue current routing; ensure variable
names match existing symbols (agent, runtime, managedRuntime, externalAgentId,
userMessage, IAgentRuntimeProvider.runConversationTurn).
packages/shared/src/consts/providers/agent-runtime-providers.ts (1)

26-42: ⚡ Quick win

Prefer immutable exported provider collections.

AGENT_RUNTIME_PROVIDERS and AGENT_RUNTIME_PROVIDER_IDS are currently mutable exports. Making these readonly reduces accidental cross-module mutation risk.

Suggested refactor
-export const AGENT_RUNTIME_PROVIDERS: AgentRuntimeProvider[] = [
+export const AGENT_RUNTIME_PROVIDERS: ReadonlyArray<AgentRuntimeProvider> = [
   {
     providerId: AgentRuntimeProviderIdEnum.Anthropic,
     displayName: 'Claude (Anthropic)',
@@
-];
+];
 
-export const AGENT_RUNTIME_PROVIDER_IDS = new Set(AGENT_RUNTIME_PROVIDERS.map((p) => p.providerId));
+export const AGENT_RUNTIME_PROVIDER_IDS: ReadonlySet<AgentRuntimeProviderIdEnum> = new Set(
+  AGENT_RUNTIME_PROVIDERS.map((p) => p.providerId)
+);

As per coding guidelines: “Keep packages/shared free of runtime side-effects since it is imported by both Node.js services and browser bundles.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/consts/providers/agent-runtime-providers.ts` around lines
26 - 42, AGENT_RUNTIME_PROVIDERS and AGENT_RUNTIME_PROVIDER_IDS are exported
mutable collections; change them to readonly to prevent cross-module mutation
by: update the type of AGENT_RUNTIME_PROVIDERS to
ReadonlyArray<AgentRuntimeProvider> (or add the `readonly` modifier) and
AGENT_RUNTIME_PROVIDER_IDS to ReadonlySet<AgentRuntimeProviderIdEnum>, and make
the runtime values immutable (e.g., use Object.freeze on the array and its
objects or construct a ReadonlySet) so both the TypeScript types and the runtime
values are immutable; refer to the AGENT_RUNTIME_PROVIDERS and
AGENT_RUNTIME_PROVIDER_IDS symbols to locate and apply these changes.
libs/application-generic/src/agent-runtimes/agent-runtime-capabilities-parity.spec.ts (1)

13-19: ⚡ Quick win

Run this parity check through the real factory and compare the whole capability object.

This helper duplicates provider registration, so agent-runtime.factory.ts can drift without the test noticing. The per-field assertions also miss newly added capability flags. Reusing getAgentRuntimeProvider and asserting instance.capabilities with toEqual closes both gaps.

Suggested refactor
 import { AGENT_RUNTIME_PROVIDERS, AgentRuntimeProviderIdEnum } from '@novu/shared';
-import { createAnthropicProvider } from './anthropic/anthropic-agent-runtime.provider';
+import { getAgentRuntimeProvider } from './agent-runtime.factory';
 import type { IAgentRuntimeProvider } from './i-agent-runtime-provider';
@@
 function getProviderInstance(id: AgentRuntimeProviderIdEnum): IAgentRuntimeProvider {
-  switch (id) {
-    case AgentRuntimeProviderIdEnum.Anthropic:
-      return createAnthropicProvider('test-key');
-    default:
-      throw new Error(`No concrete provider registered for ${id}. Add it to this test.`);
-  }
+  return getAgentRuntimeProvider(id, 'test-key');
 }
@@
-      it('capabilities.mcpServers matches the catalog', () => {
-        expect(instance.capabilities.mcpServers).toBe(catalogEntry.capabilities.mcpServers);
-      });
-
-      it('capabilities.tools matches the catalog', () => {
-        expect(instance.capabilities.tools).toBe(catalogEntry.capabilities.tools);
-      });
-
-      it('capabilities.model matches the catalog', () => {
-        expect(instance.capabilities.model).toBe(catalogEntry.capabilities.model);
-      });
-
-      it('capabilities.systemPrompt matches the catalog', () => {
-        expect(instance.capabilities.systemPrompt).toBe(catalogEntry.capabilities.systemPrompt);
+      it('capabilities match the catalog', () => {
+        expect(instance.capabilities).toEqual(catalogEntry.capabilities);
       });

Also applies to: 35-49

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/agent-runtime-capabilities-parity.spec.ts`
around lines 13 - 19, The test helper getProviderInstance duplicates provider
creation and can drift from the real registry; instead call the real factory
function getAgentRuntimeProvider with AgentRuntimeProviderIdEnum.Anthropic to
obtain the provider instance, then replace the per-field capability assertions
with a single deep equality assertion comparing instance.capabilities to the
expected capability object (use toEqual) so the whole capability shape from
agent-runtime.factory.ts is validated; update the other occurrence (lines 35-49)
similarly to reuse getAgentRuntimeProvider and assert instance.capabilities with
toEqual.
apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts (1)

37-45: 💤 Low value

validateCredentials + createAgent is two upstream calls where one usually suffices.

createAgent will surface 401 just as well as validateCredentials, and you're paying double rate-limit budget for every managed-agent creation. Consider relying on createAgent and mapping its 401 to your AgentRuntimeUnauthorizedError, dropping the explicit pre-flight unless it has a meaningful side benefit (e.g., probing scopes that createAgent doesn't reveal).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`
around lines 37 - 45, Remove the redundant pre-flight call to
runtimeProvider.validateCredentials and instead rely on
runtimeProvider.createAgent to surface authorization failures; catch a
401/unauthorized response from createAgent in the provision flow and map it to
AgentRuntimeUnauthorizedError (or rethrow as that type) so we preserve behavior
without the extra upstream call; update the logic in
provision-managed-agent.usecase.ts around the runtimeProvider.createAgent call
(and any surrounding try/catch) to perform this mapping and delete the
validateCredentials invocation.
apps/dashboard/src/components/agents/agent-runtime-config-section.tsx (1)

35-36: 💤 Low value

Type-narrowing nit on the runtime error.

The RuntimeErrorPanel casts error to Record<string, unknown> | null, then accesses err?.code. Since the runtime error utility module already exports typed helpers, exposing a small getRuntimeErrorCode(error: unknown): string from @/utils/agent-runtime-errors and reusing it both here and at lines 110-114 would remove the duplicated as unknown as ladder and centralize the contract. Optional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/agent-runtime-config-section.tsx` around
lines 35 - 36, Replace the ad-hoc cast and property access in RuntimeErrorPanel
with a dedicated helper: add or re-export getRuntimeErrorCode(error: unknown):
string in the agent-runtime-errors util and call it from RuntimeErrorPanel
(instead of casting to Record<string, unknown> and reading err?.code) and also
use it where similar logic exists around lines 110-114; this centralizes the
type-narrowing and ensures you return a default code like
'AGENT_RUNTIME_UNKNOWN' from getRuntimeErrorCode when the error shape is
unexpected.
apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts (1)

59-66: 💤 Low value

Avoid the extra round-trip; have ProvisionManagedAgent return what's needed.

ProvisionManagedAgent.execute already knows the persisted runtime and managedRuntime values it just wrote. Returning them (or the updated doc) avoids this extra findOne('*'). Optional polish.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts` around
lines 59 - 66, The extra findOne('*') round-trip should be removed by having
ProvisionManagedAgent.execute return the persisted values (either the updated
agent document or at least the runtime and managedRuntime it wrote); update
ProvisionManagedAgent.execute's signature to return those values, change the
caller in create-agent.usecase.ts (the code that currently calls
agentRepository.findOne with _id and '*') to use the returned
runtime/managedRuntime or returned updatedAgent instead, and adjust any related
types/interfaces (and tests) accordingly so no additional repository fetch is
needed.
apps/dashboard/src/components/agents/agent-overview-tab.tsx (1)

21-31: ⚡ Quick win

Extract runtime mode string literal to a named constant in packages/shared.

The string 'managed' is duplicated across 9+ files (agent-overview-tab.tsx, agent-runtime-config-section.tsx, plus create-agent, delete-agent, get-agent-runtime-config, update-agent-runtime-config, and provision-managed-agent files). Introduce a named constant—either an enum like AgentRuntimeMode alongside AgentRuntimeProviderIdEnum, or a const like AGENT_RUNTIME_MODES = { MANAGED: 'managed', SELF_HOSTED: 'self-hosted' } in packages/shared—to prevent silent typos and enable searchability across the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/agent-overview-tab.tsx` around lines 21
- 31, The codebase uses the string literal 'managed' in multiple places (e.g.,
the isManagedRuntime check in agent-overview-tab.tsx) which risks typos and
duplication; add a central constant or enum in packages/shared (for example
AgentRuntimeMode or AGENT_RUNTIME_MODES with MANAGED and SELF_HOSTED values),
export it, and replace all occurrences of the literal 'managed' across
components and API modules (agent-overview-tab.tsx,
agent-runtime-config-section.tsx, create-agent, delete-agent,
get-agent-runtime-config, update-agent-runtime-config, provision-managed-agent)
to reference the new constant (update the isManagedRuntime assignment to compare
against the exported enum/const instead of the raw string).
apps/api/src/app/agents/agents.controller.ts (1)

132-134: ⚡ Quick win

Avoid unsafe cast for provider catalog response.

Line 133 uses as AgentRuntimeProviderResponseDto[], which can hide contract drift between shared catalog shape and DTO shape.

💡 Suggested fix
   listAgentRuntimeProviders(): AgentRuntimeProviderResponseDto[] {
-    return AGENT_RUNTIME_PROVIDERS as AgentRuntimeProviderResponseDto[];
+    return [...AGENT_RUNTIME_PROVIDERS];
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/agents.controller.ts` around lines 132 - 134, The
return uses an unsafe cast of AGENT_RUNTIME_PROVIDERS to
AgentRuntimeProviderResponseDto[], which can hide contract drift; update
listAgentRuntimeProviders to explicitly map/validate each entry from
AGENT_RUNTIME_PROVIDERS into an AgentRuntimeProviderResponseDto (e.g., map over
AGENT_RUNTIME_PROVIDERS and construct objects containing only the DTO fields or
run a validation/transform such as class-transformer/class-validator or a type
guard) so the function (listAgentRuntimeProviders) returns a guaranteed-correct
AgentRuntimeProviderResponseDto[] rather than relying on a raw "as" cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts`:
- Around line 38-42: The nested DTO validation for managed runtime is broken
because ManagedRuntimeDto is imported as a type-only and `@Type` uses Object;
update the import so ManagedRuntimeDto is a real runtime class import (not
type-only) and change the decorator from `@Type`(() => Object) to `@Type`(() =>
ManagedRuntimeDto) in create-agent.command.ts for proper
class-transformer/validator behavior; then update the controller action in
agents.controller (the method that constructs CreateAgentCommand) to pass
request.body.runtime and request.body.managedRuntime into the CreateAgentCommand
constructor/assignment so the CreateAgentCommand and its handler
(CreateAgentCommand handler lines that read runtime/managedRuntime) receive the
values at runtime.

In `@apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts`:
- Around line 34-55: The current fire-and-forget call to
deleteExternalAgent(agent._id, agent.managedRuntime!, command) after setting
pendingExternalDelete: true leaves external agents orphaned because there is no
retry/reconciler and the logger message incorrectly claims "will retry"; fix by
either (A) enqueueing the deletion into the existing job/queue system so it
survives restarts and gets retries/backoff, (B) adding a scheduled reconciler
that scans agentRepository for pendingExternalDelete: true and re-invokes
deleteExternalAgent, or (C) changing the flow to await deleteExternalAgent and
only hard-delete locally on success; if you cannot implement a durable retry
now, at minimum remove/replace the "will retry" text from the logger.error call
to avoid misleading on-call engineers and ensure pendingExternalDelete is
cleared or handled by one of the above approaches.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts`:
- Around line 13-15: The providerId property in ProvisionManagedAgentCommand is
only validated with `@IsNotEmpty`(); add enum validation by decorating providerId
with `@IsEnum`(AgentRuntimeProviderIdEnum) (and import IsEnum from
class-validator) so invalid provider IDs are rejected at DTO validation time;
ensure the AgentRuntimeProviderIdEnum symbol is imported/used and place the
decorator above the providerId field in provision-managed-agent.command.ts.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`:
- Around line 49-83: The update result from agentRepository.update in
provision-managed-agent.usecase.ts must be checked because it returns { matched,
modified } and a matched===0 indicates the local agent record was removed (or
not found) leaving the external agent orphaned; capture the result of
this.update call, and if result.matched === 0 perform the same best-effort
rollback as in the catch (await runtimeProvider.deleteAgent(externalAgentId)),
log the rollback failure with this.logger.error including agentId,
externalAgentId, providerId and rollbackError, and then throw an error (e.g.,
throw new Error or rethrow a descriptive error) to abort the flow so callers
know the update did not apply.

In
`@apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts`:
- Around line 50-55: The PATCH use case returns the raw provider result from
runtimeProvider.updateConfig(...) cast to AgentRuntimeConfigResponseDto, which
omits the derived capabilities that GetAgentRuntimeConfig adds from
AGENT_RUNTIME_PROVIDERS and breaks consumers; fix by creating a small shared
helper (e.g., withCapabilities(providerId, config)) that looks up
AGENT_RUNTIME_PROVIDERS[providerId] to compute and attach the capabilities, then
call that helper to enrich the result of runtimeProvider.updateConfig(...) in
update-agent-runtime-config.usecase.ts (and refactor GetAgentRuntimeConfig to
use the same withCapabilities helper) so both endpoints return the same shape
including capabilities.

In `@apps/dashboard/src/api/agent-runtime.ts`:
- Around line 28-54: Patch payload currently requires externalId because
PatchAgentRuntimeConfigBody.mcpServers and .tools reference AgentMcpServer and
AgentTool which have externalId mandatory; update the patch types so externalId
is optional by defining new types (e.g., PatchAgentMcpServer and PatchAgentTool)
that mirror AgentMcpServer and AgentTool but mark externalId as optional, then
change PatchAgentRuntimeConfigBody to use PatchAgentMcpServer[] and
PatchAgentTool[]; alternatively use Partial<...> or Omit+optional externalId for
the same effect.

In `@apps/dashboard/src/components/agents/agent-runtime-config-section.tsx`:
- Around line 109-114: The current render body in AgentRuntimeConfigSection
calls onDrift?.() and onUnauthorized?.() directly when error exists, which
triggers parent setters (setShowDriftModal/setShowAuthBanner) during render;
move this logic into a useEffect inside AgentRuntimeConfigSection that watches
the error identity (e.g., error and/or (error as Record<string,unknown>)?.code)
and, when the code equals 'AGENT_RUNTIME_DRIFT' or 'AGENT_RUNTIME_UNAUTHORIZED',
invokes onDrift() or onUnauthorized() respectively; ensure the effect only runs
when the error/code changes to avoid repeated invocations and remove the direct
calls from the render body so AgentOverviewTab updates happen from the effect.

In `@apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx`:
- Around line 108-112: The render is calling onSelfHostedSelected() directly
which can cause setState during render; remove the direct call from the JSX
branch and instead invoke onSelfHostedSelected inside a useEffect that watches
isManagedEnabled (e.g., useEffect(() => { if (!isManagedEnabled)
onSelfHostedSelected(); }, [isManagedEnabled])); keep the return null when
!isManagedEnabled in the render path so the UI still short-circuits, and ensure
the effect only runs when isManagedEnabled transitions to false to avoid
repeated calls.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 251-258: The sync logic in anthropic-agent-runtime.provider.ts
currently only creates MCP servers/tools when externalId is missing and deletes
when absent, so entries with the same externalId but changed fields (name, url,
authToken, description, type) are skipped; modify the loops that handle desired
servers (the block using (client as any).beta.agents.mcpServers.create) and the
analogous tools loop to detect if currentIds has server.externalId then fetch
the existing record and compare mutable fields, and if any differ call the
provider's update method (e.g., mcpServers.update or tools.update) with the
agentId and the changed fields instead of no-op; ensure authToken is handled
like create (auth_token key) and preserve unchanged fields when issuing the
update.
- Around line 140-143: The current .catch(() => ({ data: [] })) on calls like
(client as any).beta.agents.mcpServers.list(externalAgentId) and (client as
any).beta.agents.tools.list(externalAgentId) hides provider failures by
returning an empty list; remove those swallow-catches so errors propagate (or
replace them with a catch that logs and rethrows) so getConfig/updateConfig see
real failures and don’t diff against fabricated empty baselines. Update the same
pattern at the other occurrences you noted (the similar calls around the
getConfig/updateConfig usages) to either drop the .catch or rethrow after
logging so transient provider errors are not converted into empty data.
- Around line 50-65: The error-normalization block incorrectly uses bracket
notation on APIError.headers (a Headers object) and drops the error's request
identifier; update this to read the request id from the error first
(err.requestId or err.requestID) and fall back to err.headers.get('request-id')
if needed, and replace bracket access with err.headers.get('retry-after') when
calling parseRetryAfter; ensure the requestId value is passed into
AgentRuntimeUnauthorizedError, AgentRuntimeForbiddenError,
AgentRuntimeNotFoundError, and AgentRuntimeRateLimitedError along with
PROVIDER_ID and the parsed retryAfterMs where applicable.

In `@libs/dal/src/repositories/agent/agent.schema.ts`:
- Line 43: The agent schema's soft-delete field "deletedAt" is declared as
Schema.Types.String; change it to a Date type to preserve correct
sorting/filtering and consistent timestamp semantics by updating the "deletedAt"
field in agent.schema.ts to use Schema.Types.Date (optionally nullable/default
null and indexed/sparse if you rely on queries), ensuring any related code that
sets/reads deletedAt (e.g., softDelete methods or repository queries)
writes/reads Date objects or converts strings to Date objects accordingly.

In `@packages/shared/src/dto/agent/managed-runtime.dto.ts`:
- Around line 25-32: The AgentMcpServerDto currently exposes a raw authToken
which can leak provider secrets; remove the authToken field from this shared
read DTO (AgentMcpServerDto) and replace it with a safe read-only indicator such
as hasAuthToken: boolean (or masked metadata) to signal presence without
exposing the secret, and add a separate write/update DTO (e.g.,
AgentMcpServerUpdateDto or AgentMcpServerWriteDto) that includes authToken?:
string for incoming updates only; update any consumers/serializers to use the
read DTO for responses and the write DTO for mutations.

In `@packages/shared/src/types/workflow-channel-preferences.ts`:
- Around line 70-71: The change narrows the exported key type for
WorkflowPreferences.channels to NotificationChannelTypeEnum which is a
public-API-breaking change; restore backward compatibility by keeping
WorkflowPreferences.channels keyed by the original ChannelTypeEnum (or a
compatible union) and introduce NotificationChannelTypeEnum as a narrower,
documented/deprecated helper for consumers to migrate to; update the type
declarations around NotificationChannelTypeEnum and WorkflowPreferences.channels
to preserve the old shape while adding a deprecation comment and migration
guidance so you can ship this as a non-breaking patch (or only make the breaking
narrow change behind a major bump).

---

Outside diff comments:
In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts`:
- Around line 35-66: The create flow currently persists the Agent via
agentRepository.create before calling provisionManagedAgentUsecase.execute,
which can orphan a local agent if provisioning fails; modify
create-agent.usecase.ts to perform compensating cleanup: after creating the
agent (agentRepository.create) and before returning, wrap the
provisionManagedAgentUsecase.execute call in a try/catch and on any error call
agentRepository.delete (scoped by _id, _environmentId, _organizationId) to
remove the partially-created agent, then rethrow the original error; ensure the
cleanup itself is awaited and failures are logged/handled so you don't swallow
the original error (references: agentRepository.create,
provisionManagedAgentUsecase.execute, ProvisionManagedAgentCommand,
agentRepository.delete, agentRepository.findOne).

In `@apps/dashboard/src/components/agents/agent-integrations-tab.tsx`:
- Around line 33-39: CHANNEL_GROUP_ORDER is missing
ChannelTypeEnum.AGENT_RUNTIME so groupLinksByChannel will collect agent-runtime
integrations but never render them; update the CHANNEL_GROUP_ORDER array to
include ChannelTypeEnum.AGENT_RUNTIME (in the appropriate display order) so
agent-runtime channel groups are rendered by groupLinksByChannel.

In `@packages/shared/src/types/channel.ts`:
- Around line 36-42: The code uses
STEP_TYPE_TO_CHANNEL_TYPE.get(step.template.type) in
create-notification-jobs.usecase.ts and then indexes command.templateProviderIds
with the resulting channel without guarding against undefined; ensure
AGENT_RUNTIME (a valid StepTypeEnum intentionally excluded from the map) cannot
reach that usage by either: add a null/undefined check after const channel =
STEP_TYPE_TO_CHANNEL_TYPE.get(step.template.type) and skip processing when
channel is undefined, or filter out steps with step.template.type ===
StepTypeEnum.AGENT_RUNTIME earlier (e.g., in the loop that iterates steps) so
the providerId lookup (command.templateProviderIds[channel]) is never executed
for AGENT_RUNTIME; reference STEP_TYPE_TO_CHANNEL_TYPE, the local variable
channel, and command.templateProviderIds when applying the fix.

---

Minor comments:
In `@apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts`:
- Line 34: The current check using isManagedAgent (const isManagedAgent =
agent.runtime === 'managed' && agent.managedRuntime) silently falls through to
hard-delete when runtime === 'managed' but managedRuntime is missing; add an
explicit branch that detects agent.runtime === 'managed' &&
!agent.managedRuntime and handle it deterministically (e.g., throw a
422/unprocessable error or log and choose hard-delete) before using
isManagedAgent so callers can retry provisioning or you can record the anomalous
state; update the delete logic in delete-agent.usecase.ts to reference
agent.runtime and agent.managedRuntime directly to implement this explicit
error/decision path.

In
`@apps/api/src/app/agents/usecases/get-agent-runtime-config/get-agent-runtime-config.usecase.ts`:
- Around line 48-49: The code calls getAgentRuntimeProvider(providerId,
decryptedCredentials.apiKey!) without validating the apiKey; replicate the
validation used in ProvisionManagedAgent by checking decryptedCredentials.apiKey
(and treating empty string as missing) and throw or return a 422-style error
before calling getAgentRuntimeProvider so missing keys produce a clear
validation error; update the logic around decryptCredentials and the call-site
for getAgentRuntimeProvider to validate apiKey and only pass a non-empty string.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`:
- Around line 31-33: The code currently throws NotFoundException when an
integration exists but has no API key; update the check in
provision-managed-agent.usecase.ts to throw UnprocessableEntityException instead
(keep the existing message or adjust to indicate invalid configuration), and
ensure you import UnprocessableEntityException from `@nestjs/common` and remove
NotFoundException if it becomes unused; update any related tests/handlers
expecting a 404 to expect a 422 for this validation case.

In
`@apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts`:
- Around line 47-48: The code uses a non-null assertion on
integration.credentials.apiKey when calling getAgentRuntimeProvider in
update-agent-runtime-config.usecase.ts; mirror the GET use case validation by
checking decryptCredentials(integration.credentials) and ensuring apiKey exists
before calling getAgentRuntimeProvider(providerId, apiKey). If apiKey is
missing, throw/return the same 422 validation error used in
GetAgentRuntimeConfig (i.e., produce a clear 422 response rather than letting
the SDK error surface) and remove the trailing `!` on apiKey.

In `@apps/dashboard/src/components/agents/agent-overview-tab.tsx`:
- Around line 68-80: The recreate/unlink callbacks on AgentRuntimeDriftModal are
no-ops and leave users stuck; update the usage so the modal shows disabled CTAs
or a "coming soon" note instead of active buttons: either pass explicit props
(e.g., recreateEnabled={false} unlinkEnabled={false} or
comingSoonMessage="Recreate/unlink pending backend support") to
AgentRuntimeDriftModal, or replace the onRecreate/onUnlink handlers with logic
that opens an inline tooltip/alert explaining the action is pending (instead of
just calling setShowDriftModal(false)). Modify the AgentRuntimeDriftModal
invocation here and ensure the modal component (AgentRuntimeDriftModal) uses
those props to render disabled buttons and a tooltip/message so users aren’t
offered functional-looking but no-op controls.

In `@apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx`:
- Line 111: In the ManagedRuntimeSetupGuide component (in
apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx) there
is no blank line before the return statement; insert a single blank line
immediately above the "return null;" statement so the return is separated from
preceding code, preserving the file's coding guideline of a blank line before
every return.
- Line 36: The code currently forces anthropicProvider non-null with a "!" when
finding the provider (const anthropicProvider = AGENT_RUNTIME_PROVIDERS.find((p)
=> p.providerId === 'anthropic')!), but other code (e.g.,
anthropicProvider?.displayName) already treats it as potentially undefined;
remove the non-null assertion so the variable is typed as possibly undefined and
update any usages to handle undefined safely (use optional chaining or a
fallback display string) — locate the find call for anthropicProvider and change
it to not assert non-null, then ensure all references to anthropicProvider (like
displayName) use ?. or a default value.
- Line 301: The conditional early-return in the ManagedRuntimeSetupGuide
component ("if (!provider) return null;") lacks a blank line before the return;
update the conditional to place a blank line immediately before the return
statement (e.g., convert to a block or separate the return onto its own line) so
there is a blank line preceding every return in the ManagedRuntimeSetupGuide
component.

---

Nitpick comments:
In `@apps/api/src/app/agents/agents.controller.ts`:
- Around line 132-134: The return uses an unsafe cast of AGENT_RUNTIME_PROVIDERS
to AgentRuntimeProviderResponseDto[], which can hide contract drift; update
listAgentRuntimeProviders to explicitly map/validate each entry from
AGENT_RUNTIME_PROVIDERS into an AgentRuntimeProviderResponseDto (e.g., map over
AGENT_RUNTIME_PROVIDERS and construct objects containing only the DTO fields or
run a validation/transform such as class-transformer/class-validator or a type
guard) so the function (listAgentRuntimeProviders) returns a guaranteed-correct
AgentRuntimeProviderResponseDto[] rather than relying on a raw "as" cast.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts`:
- Around line 59-66: The extra findOne('*') round-trip should be removed by
having ProvisionManagedAgent.execute return the persisted values (either the
updated agent document or at least the runtime and managedRuntime it wrote);
update ProvisionManagedAgent.execute's signature to return those values, change
the caller in create-agent.usecase.ts (the code that currently calls
agentRepository.findOne with _id and '*') to use the returned
runtime/managedRuntime or returned updatedAgent instead, and adjust any related
types/interfaces (and tests) accordingly so no additional repository fetch is
needed.

In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`:
- Around line 42-44: Update the TODO branch so the code fetches the agent entity
earlier in handle-agent-reply.usecase.ts (before routing logic) and include the
runtime and managedRuntime fields in the query projection in addition to _id,
name, identifier; then add a branch: if (agent.runtime === 'managed') call the
runtime provider via IAgentRuntimeProvider.runConversationTurn(externalAgentId,
userMessage) (passing the agent.managedRuntime/external id as required) and
short-circuit the self-hosted bridge path, otherwise continue current routing;
ensure variable names match existing symbols (agent, runtime, managedRuntime,
externalAgentId, userMessage, IAgentRuntimeProvider.runConversationTurn).

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`:
- Around line 37-45: Remove the redundant pre-flight call to
runtimeProvider.validateCredentials and instead rely on
runtimeProvider.createAgent to surface authorization failures; catch a
401/unauthorized response from createAgent in the provision flow and map it to
AgentRuntimeUnauthorizedError (or rethrow as that type) so we preserve behavior
without the extra upstream call; update the logic in
provision-managed-agent.usecase.ts around the runtimeProvider.createAgent call
(and any surrounding try/catch) to perform this mapping and delete the
validateCredentials invocation.

In `@apps/dashboard/src/components/agents/agent-overview-tab.tsx`:
- Around line 21-31: The codebase uses the string literal 'managed' in multiple
places (e.g., the isManagedRuntime check in agent-overview-tab.tsx) which risks
typos and duplication; add a central constant or enum in packages/shared (for
example AgentRuntimeMode or AGENT_RUNTIME_MODES with MANAGED and SELF_HOSTED
values), export it, and replace all occurrences of the literal 'managed' across
components and API modules (agent-overview-tab.tsx,
agent-runtime-config-section.tsx, create-agent, delete-agent,
get-agent-runtime-config, update-agent-runtime-config, provision-managed-agent)
to reference the new constant (update the isManagedRuntime assignment to compare
against the exported enum/const instead of the raw string).

In `@apps/dashboard/src/components/agents/agent-runtime-config-section.tsx`:
- Around line 35-36: Replace the ad-hoc cast and property access in
RuntimeErrorPanel with a dedicated helper: add or re-export
getRuntimeErrorCode(error: unknown): string in the agent-runtime-errors util and
call it from RuntimeErrorPanel (instead of casting to Record<string, unknown>
and reading err?.code) and also use it where similar logic exists around lines
110-114; this centralizes the type-narrowing and ensures you return a default
code like 'AGENT_RUNTIME_UNKNOWN' from getRuntimeErrorCode when the error shape
is unexpected.

In
`@libs/application-generic/src/agent-runtimes/agent-runtime-capabilities-parity.spec.ts`:
- Around line 13-19: The test helper getProviderInstance duplicates provider
creation and can drift from the real registry; instead call the real factory
function getAgentRuntimeProvider with AgentRuntimeProviderIdEnum.Anthropic to
obtain the provider instance, then replace the per-field capability assertions
with a single deep equality assertion comparing instance.capabilities to the
expected capability object (use toEqual) so the whole capability shape from
agent-runtime.factory.ts is validated; update the other occurrence (lines 35-49)
similarly to reuse getAgentRuntimeProvider and assert instance.capabilities with
toEqual.

In `@packages/shared/src/consts/providers/agent-runtime-providers.ts`:
- Around line 26-42: AGENT_RUNTIME_PROVIDERS and AGENT_RUNTIME_PROVIDER_IDS are
exported mutable collections; change them to readonly to prevent cross-module
mutation by: update the type of AGENT_RUNTIME_PROVIDERS to
ReadonlyArray<AgentRuntimeProvider> (or add the `readonly` modifier) and
AGENT_RUNTIME_PROVIDER_IDS to ReadonlySet<AgentRuntimeProviderIdEnum>, and make
the runtime values immutable (e.g., use Object.freeze on the array and its
objects or construct a ReadonlySet) so both the TypeScript types and the runtime
values are immutable; refer to the AGENT_RUNTIME_PROVIDERS and
AGENT_RUNTIME_PROVIDER_IDS symbols to locate and apply these changes.
🪄 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: 979f9196-5de8-4ee8-8a48-54bace5436ba

📥 Commits

Reviewing files that changed from the base of the PR and between 02d982f and 3fd2dc1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (59)
  • apps/api/src/app/agents/agents.controller.ts
  • apps/api/src/app/agents/agents.module.ts
  • apps/api/src/app/agents/dtos/agent-response.dto.ts
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/dtos/create-agent-request.dto.ts
  • apps/api/src/app/agents/dtos/index.ts
  • apps/api/src/app/agents/filters/agent-runtime-exception.filter.ts
  • apps/api/src/app/agents/mappers/agent-response.mapper.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/delete-agent/delete-agent.usecase.ts
  • apps/api/src/app/agents/usecases/get-agent-runtime-config/get-agent-runtime-config.command.ts
  • apps/api/src/app/agents/usecases/get-agent-runtime-config/get-agent-runtime-config.usecase.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/api/src/app/agents/usecases/index.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts
  • apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.command.ts
  • apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts
  • apps/dashboard/src/api/agent-runtime.ts
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/components/agents/agent-integrations-tab.tsx
  • apps/dashboard/src/components/agents/agent-overview-tab.tsx
  • apps/dashboard/src/components/agents/agent-runtime-auth-banner.tsx
  • apps/dashboard/src/components/agents/agent-runtime-badge.tsx
  • apps/dashboard/src/components/agents/agent-runtime-config-section.tsx
  • apps/dashboard/src/components/agents/agent-runtime-drift-modal.tsx
  • apps/dashboard/src/components/integrations/components/hooks/use-integration-list.ts
  • apps/dashboard/src/components/onboarding/managed-runtime-setup-guide.tsx
  • apps/dashboard/src/components/subscribers/preferences/preferences-item.tsx
  • apps/dashboard/src/components/subscribers/preferences/workflow-preferences.tsx
  • apps/dashboard/src/components/workflow-editor/channel-preferences-form.tsx
  • apps/dashboard/src/pages/agents-setup-page.tsx
  • apps/dashboard/src/utils/agent-runtime-errors.ts
  • apps/dashboard/src/utils/channels.ts
  • libs/application-generic/package.json
  • libs/application-generic/src/agent-runtimes/agent-runtime-capabilities-parity.spec.ts
  • libs/application-generic/src/agent-runtimes/agent-runtime.factory.ts
  • libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts
  • libs/application-generic/src/agent-runtimes/errors.ts
  • libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts
  • libs/application-generic/src/agent-runtimes/index.ts
  • libs/application-generic/src/dtos/workflow/workflow-preferences.dto.ts
  • libs/application-generic/src/index.ts
  • libs/application-generic/src/usecases/preview/preview.usecase.ts
  • libs/application-generic/src/usecases/upsert-preferences/upsert-preferences.command.ts
  • libs/application-generic/src/usecases/upsert-workflow/upsert-workflow.command.ts
  • libs/dal/src/repositories/agent/agent.entity.ts
  • libs/dal/src/repositories/agent/agent.schema.ts
  • packages/shared/src/consts/providers/agent-runtime-providers.ts
  • packages/shared/src/consts/providers/index.ts
  • packages/shared/src/dto/agent/index.ts
  • packages/shared/src/dto/agent/managed-runtime.dto.ts
  • packages/shared/src/dto/index.ts
  • packages/shared/src/types/channel.ts
  • packages/shared/src/types/feature-flags.ts
  • packages/shared/src/types/providers.ts
  • packages/shared/src/types/workflow-channel-preferences.ts
  • packages/shared/src/utils/buildWorkflowPreferences.ts

Comment thread apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts Outdated
},
externalAgentId: Schema.Types.String,
},
deletedAt: Schema.Types.String,
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a date type for soft-delete timestamps.

deletedAt is currently a string (Line 43). This should be a Date field to keep sort/filter semantics correct and avoid inconsistent timestamp parsing.

Suggested fix
-    deletedAt: Schema.Types.String,
+    deletedAt: Schema.Types.Date,

As per coding guidelines: “libs/dal/**: Review with focus on data integrity and query performance.”

📝 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
deletedAt: Schema.Types.String,
deletedAt: Schema.Types.Date,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/dal/src/repositories/agent/agent.schema.ts` at line 43, The agent
schema's soft-delete field "deletedAt" is declared as Schema.Types.String;
change it to a Date type to preserve correct sorting/filtering and consistent
timestamp semantics by updating the "deletedAt" field in agent.schema.ts to use
Schema.Types.Date (optionally nullable/default null and indexed/sparse if you
rely on queries), ensuring any related code that sets/reads deletedAt (e.g.,
softDelete methods or repository queries) writes/reads Date objects or converts
strings to Date objects accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

⚠️ Stale — deletedAt is no longer in the schema. Soft-delete fields (deletedAt, pendingExternalDelete) were removed; deletion is now synchronous and either-or (Novu only, or Novu + provider via deleteFromProvider query).

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.

@djabarovgeorge — confirmed, the current schema file no longer contains deletedAt or pendingExternalDelete. The synchronous deleteFromProvider path (calling the provider directly before local cleanup) is a cleaner design anyway — no background job, no partial state to reconcile. The review comment is stale and can be dismissed. Thanks for the clarification!


🧠 Learnings used
Learnt from: ChmaraX
Repo: novuhq/novu PR: 11003
File: packages/framework/src/handler.ts:0-0
Timestamp: 2026-05-06T13:36:38.575Z
Learning: In novuhq/novu, Biome's noUselessUndefinedInitialization rule automatically strips '= undefined' initializers in TypeScript files. When fixing TS2454 'used before assigned' errors, prefer structural fixes over adding '= undefined' (which will be removed and can cause the commit to fail). Do not initialize variables with undefined. Instead, initialize with a concrete value at declaration when possible, or refactor to ensure the value is assigned before use (e.g., via a helper function or inline initialization that yields a valid value). Use guards or explicit control flow to guarantee assignment before first use. This guidance applies broadly to TypeScript files in the repository, including packages/framework/src/handler.ts.

Learnt from: scopsy
Repo: novuhq/novu PR: 11060
File: packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsx:55-56
Timestamp: 2026-05-11T05:04:35.896Z
Learning: In PR reviews for TypeScript/TSX code, do not suggest adding blank lines before `return` statements. Follow the repository’s existing formatting conventions and avoid raising review comments about this specific whitespace change (blank line insertion before `return`).

Comment on lines +25 to +32
export type AgentMcpServerDto = {
/** Provider-assigned ID for the MCP server */
externalId: string;
name: string;
url: string;
/** Optional token used to authenticate with the MCP server */
authToken?: string;
};
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid exposing raw MCP auth tokens in shared read DTOs.

Including authToken in a cross-layer DTO makes it easy to leak provider secrets to UI/logging paths. Prefer a write-only token field for updates and a read DTO with hasAuthToken/masked metadata only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/dto/agent/managed-runtime.dto.ts` around lines 25 - 32,
The AgentMcpServerDto currently exposes a raw authToken which can leak provider
secrets; remove the authToken field from this shared read DTO
(AgentMcpServerDto) and replace it with a safe read-only indicator such as
hasAuthToken: boolean (or masked metadata) to signal presence without exposing
the secret, and add a separate write/update DTO (e.g., AgentMcpServerUpdateDto
or AgentMcpServerWriteDto) that includes authToken?: string for incoming updates
only; update any consumers/serializers to use the read DTO for responses and the
write DTO for mutations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

💬 Acknowledged — deferred. The read DTO does not currently expose authToken from the provider (Anthropic's getConfig mapping at mapMcpServer only writes externalId, name, url). The optional field on the shared DTO is reserved for future write/update flows. Tracking the split into separate read/write DTOs (hasAuthToken: boolean for reads) as a follow-up to avoid blocking this PR.

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.

@djabarovgeorge, thanks for the context — that's a meaningful clarification. Since mapMcpServer in the Anthropic getConfig path currently only populates externalId, name, and url, the optional authToken field carries no runtime exposure risk at this time. The concern becomes relevant only once a write/update flow populates it.

Would you like me to open a GitHub issue to track the follow-up work of splitting into separate read (hasAuthToken: boolean) and write (authToken?: string) DTOs? That way it won't get lost between PRs.


🧠 Learnings used
Learnt from: ChmaraX
Repo: novuhq/novu PR: 11003
File: packages/framework/src/handler.ts:0-0
Timestamp: 2026-05-06T13:36:38.575Z
Learning: In novuhq/novu, Biome's noUselessUndefinedInitialization rule automatically strips '= undefined' initializers in TypeScript files. When fixing TS2454 'used before assigned' errors, prefer structural fixes over adding '= undefined' (which will be removed and can cause the commit to fail). Do not initialize variables with undefined. Instead, initialize with a concrete value at declaration when possible, or refactor to ensure the value is assigned before use (e.g., via a helper function or inline initialization that yields a valid value). Use guards or explicit control flow to guarantee assignment before first use. This guidance applies broadly to TypeScript files in the repository, including packages/framework/src/handler.ts.

Learnt from: scopsy
Repo: novuhq/novu PR: 11060
File: packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsx:55-56
Timestamp: 2026-05-11T05:04:35.896Z
Learning: In PR reviews for TypeScript/TSX code, do not suggest adding blank lines before `return` statements. Follow the repository’s existing formatting conventions and avoid raising review comments about this specific whitespace change (blank line insertion before `return`).

Comment thread packages/shared/src/types/workflow-channel-preferences.ts Outdated
Comment thread libs/dal/src/repositories/agent/agent.schema.ts
type: Schema.Types.ObjectId,
ref: 'Integration',
},
externalAgentId: Schema.Types.String,
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.

I guess we also need here vendor specific fields? Like anthropic specific environmentId, etc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

💬 Discussion: keeping managedRuntime provider-agnostic by design. The persisted fields (providerId, _integrationId, externalAgentId) are the minimal stable linkage. Anthropic-specific data (environment id, etc.) lives on the Integration's credentials (where we already encrypt-at-rest and rotate). Live config (model, system prompt, MCP servers, tools, skills) is fetched from the provider on-demand via IAgentRuntimeProvider.getConfig — not persisted. This avoids drift and keeps the agent doc lean. If we ever need provider-specific metadata that doesn't fit credentials, we can add an opaque providerMetadata: Record<string, unknown> field then.

Comment thread libs/dal/src/repositories/agent/agent.schema.ts Outdated
- Added support for managed runtime agents, allowing integration with Claude Platform.
- Updated `CreateAgentRequestDto` to include `runtime` and `managedRuntime` fields.
- Introduced `AgentSkillInputDto` for specifying skills during agent creation.
- Enhanced `ProvisionManagedAgentCommand` to accept tools, MCP servers, and skills.
- Updated the dashboard to handle new fields in agent creation and management.

This update improves the flexibility and capabilities of agent provisioning, enabling users to leverage external skills and tools effectively.
…board integration

- Fixed bugs related to agent runtime configuration handling in the dashboard.
- Updated the agent-runtime API client to improve error handling and user experience.
- Enhanced the runtime-config section with better error panels and notifications.

These changes ensure a smoother user experience when managing agent runtimes and improve the overall stability of the dashboard.
Comment thread packages/shared/src/consts/providers/claude-skills.ts Fixed
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: 7

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/components/agents/create-agent-dialog.tsx (1)

229-265: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validation gap: existing mode skips required-field checks for both API key and external agent ID.

When runtime === 'claude' && mode === 'existing':

  • apiKey is never validated (line 229 only checks mode === 'create'), but agents-list.tsx requires body.apiKey to be truthy to create the integration. If the user leaves it blank, integration creation is silently skipped and the agent is created with managedRuntime.integrationId === '' — the backend will reject this with a 4xx and the user will see a generic toast.
  • externalAgentId is also never validated (line 263 only assigns it if .trim() is truthy). An empty value silently degenerates into the create-new-agent code path, but with body.runtime = 'managed' already set, so Claude will provision a brand-new agent instead of linking to the existing one — the opposite of what the user intended.

Both checks belong in the validation block before the payload is assembled.

🛠️ Suggested fix
-    if (runtime === 'claude' && mode === 'create' && !apiKey.trim()) {
-      nextErrors.apiKey = 'Anthropic API key is required.';
-    }
+    if (runtime === 'claude' && !apiKey.trim()) {
+      nextErrors.apiKey = 'Anthropic API key is required.';
+    }
+
+    if (runtime === 'claude' && mode === 'existing' && !externalAgentId.trim()) {
+      nextErrors.externalAgentId = 'External agent ID is required when linking an existing agent.';
+    }

(You'll also need to add externalAgentId?: string to FormErrors and surface it on the input at lines 468–479.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx` around lines
229 - 265, The validation currently only checks apiKey when runtime === 'claude'
&& mode === 'create', which lets mode === 'existing' submit without apiKey or
externalAgentId; move/extend validation into the pre-payload block so that when
runtime === 'claude' && mode === 'existing' you validate apiKey.trim() and
externalAgentId.trim() and add errors to nextErrors (e.g., nextErrors.apiKey and
nextErrors.externalAgentId) before calling setErrors/return; ensure
CreateAgentDialogSubmitBody population still sets body.apiKey and
body.externalAgentId only after validation; also add externalAgentId?: string to
the FormErrors type and surface that error on the externalAgentId input so the
UI shows the message.
♻️ Duplicate comments (2)
packages/shared/src/dto/agent/managed-runtime.dto.ts (1)

52-59: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

AgentMcpServerDto.authToken is still exposed on the read DTO.

This concern was raised previously and remains unaddressed in the current code: an authToken?: string field on a DTO that flows into both read and write paths makes it easy for MCP auth tokens to leak through API responses, logs, or analytics events. Split into a read DTO that exposes only hasAuthToken: boolean (or masked metadata) and a separate write DTO that accepts authToken?: string for updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/dto/agent/managed-runtime.dto.ts` around lines 52 - 59,
AgentMcpServerDto currently exposes authToken and must be split into separate
read/write DTOs: keep a read-only shape (e.g., AgentMcpServerReadDto) that
replaces authToken with a boolean hasAuthToken (or masked metadata) and a
write/update shape (e.g., AgentMcpServerWriteDto) that accepts authToken?:
string for incoming requests. Replace usages of AgentMcpServerDto in
response/serialization paths with AgentMcpServerReadDto and use
AgentMcpServerWriteDto for controllers/services that accept or persist
authToken; ensure any mapping code (serializers or constructors that currently
reference AgentMcpServerDto.authToken) only sets hasAuthToken on read DTOs and
only reads/writes authToken from the write DTO.
libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts (1)

55-71: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Still using bracket notation on APIError.headers — request-id and Retry-After are being dropped.

This was raised in a prior review and remains unresolved. APIError.headers in @anthropic-ai/sdk is a Headers instance, so err.headers?.['request-id'] and err.headers?.['retry-after'] always evaluate to undefined. The SDK also exposes err.requestID directly. As written:

  • The requestId arg passed into every error class is always undefined — request-id is never logged or surfaced.
  • parseRetryAfter always falls through to its 60_000 default, so 429s lose their actual Retry-After hint and the API/dashboard's Retry-After-aware retry logic gets the wrong value.
🩹 Proposed fix
     if (err instanceof APIError) {
-      const requestId = err.headers?.['request-id'] as string | undefined;
+      const requestId = err.requestID ?? err.headers?.get('request-id') ?? undefined;

       if (err.status === 401) {
         throw new AgentRuntimeUnauthorizedError(err.message, PROVIDER_ID, requestId);
       }
       if (err.status === 403) {
         throw new AgentRuntimeForbiddenError(err.message, PROVIDER_ID, requestId);
       }
       if (err.status === 404) {
         throw new AgentRuntimeNotFoundError(err.message, PROVIDER_ID, requestId);
       }
       if (err.status === 429) {
-        const retryAfterMs = parseRetryAfter(err.headers?.['retry-after'] as string | undefined);
+        const retryAfterMs = parseRetryAfter(err.headers?.get('retry-after') ?? undefined);

         throw new AgentRuntimeRateLimitedError(err.message, PROVIDER_ID, retryAfterMs, requestId);
       }
`@anthropic-ai/sdk` 0.95.1 APIError headers Headers object requestID property
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 55 - 71, The code is reading request-id and retry-after using
bracket notation on APIError.headers which is a Headers instance, so change to
read requestId from err.requestID (set requestId = err.requestID as string |
undefined) and read retry-after via the Headers.get method (call
parseRetryAfter(err.headers?.get('retry-after') as string | undefined)); then
pass that requestId into the
AgentRuntimeUnauthorizedError/AgentRuntimeForbiddenError/AgentRuntimeNotFoundError/AgentRuntimeRateLimitedError
constructors (and pass the parsed retryAfterMs to AgentRuntimeRateLimitedError)
while keeping PROVIDER_ID unchanged.
🧹 Nitpick comments (6)
libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts (1)

10-32: ⚡ Quick win

Use interface instead of type for backend type definitions.

CreateAgentInput, CreateAgentResult, and UpdateAgentRuntimeConfigInput should be declared as interface on the backend to match the rest of the contract (which already uses interface IAgentRuntimeProvider).

♻️ Proposed change
-export type CreateAgentInput = {
+export interface CreateAgentInput {
   name: string;
   model?: string;
   systemPrompt?: string;
   /** Builtin tool type strings, e.g. 'web_search', 'bash' */
   tools?: string[];
   /** MCP server catalog entries resolved to {name, url} pairs */
   mcpServers?: Array<{ name: string; url: string }>;
   /** Skills to attach to the agent at creation time. Maximum 20. */
   skills?: AgentSkillDto[];
-};
+}

-export type CreateAgentResult = {
+export interface CreateAgentResult {
   externalAgentId: string;
-};
+}

-export type UpdateAgentRuntimeConfigInput = {
+export interface UpdateAgentRuntimeConfigInput {
   model?: string;
   systemPrompt?: string;
   mcpServers?: AgentMcpServerDto[];
   tools?: AgentToolDto[];
   skills?: AgentSkillDto[];
-};
+}

As per coding guidelines: "On the backend: use interface for type definitions; on the frontend: use type for type definitions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts`
around lines 10 - 32, Replace the three backend type aliases with interfaces to
match the existing contract style: convert CreateAgentInput, CreateAgentResult,
and UpdateAgentRuntimeConfigInput from "type" to "interface" declarations,
preserving all property names, optional markers, and nested types (e.g.,
AgentSkillDto, AgentMcpServerDto, AgentToolDto) so consumers of
IAgentRuntimeProvider see consistent interface types; ensure JSDoc/comments
(like the tools and mcpServers descriptions) remain above the corresponding
interface properties.
apps/dashboard/src/components/agents/create-agent-dialog.tsx (1)

250-262: 💤 Low value

Optional: extract the hard-coded model and providerId: 'anthropic' strings.

'claude-opus-4-5' (line 253) and 'anthropic' (line 251) are duplicated in agents-list.tsx and the provider catalog. Consider pulling them from the shared AGENT_RUNTIME_PROVIDERS entry (default model) and AgentRuntimeProviderIdEnum.Anthropic so a model bump only needs to be made in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx` around lines
250 - 262, Replace hard-coded provider and model strings in the create-agent
payload with the shared constants: use AgentRuntimeProviderIdEnum.Anthropic
(instead of 'anthropic') to set providerId and pull the default model from
AGENT_RUNTIME_PROVIDERS (e.g.,
AGENT_RUNTIME_PROVIDERS[AgentRuntimeProviderIdEnum.Anthropic].defaultModel)
instead of 'claude-opus-4-5'; update the block that assigns body.managedRuntime
(and any related mapping for skills) to reference these constants so future
model/provider changes are centralized.
packages/shared/src/consts/providers/claude-skills.ts (1)

64-157: 💤 Low value

Optional: track CLAUDE_OPENSOURCE_SKILLS as a parked TODO so it doesn't quietly rot.

CLAUDE_OPENSOURCE_SKILLS is defined (~90 lines) but neither exported nor included in CLAUDE_ANTHROPIC_SKILLS, so it ships as dead bytes until the /v1/skills upload flow lands. Consider either:

  • Adding a // TODO(NV-XXXX): reference next to the comment on line 156 so the link to the follow-up work is grep-able, or
  • Stripping the constant until the upload feature is implemented and reintroducing it then.

Either approach prevents accidental drift between this list and the upstream anthropics/skills repo.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/consts/providers/claude-skills.ts` around lines 64 - 157,
CLAUDE_OPENSOURCE_SKILLS is defined but unused and will rot; either add a
grep-able TODO or remove it: locate the CLAUDE_OPENSOURCE_SKILLS constant and
either (a) add a comment like "// TODO(NV-XXXX): track open-source skills list
until /v1/skills upload is implemented" immediately above or next to the
declaration so it’s searchable, or (b) delete the CLAUDE_OPENSOURCE_SKILLS block
and reintroduce it when the upload flow exists; ensure CLAUDE_ANTHROPIC_SKILLS
(and any reference to CLAUDE_PREBUILT_SKILLS) remains unchanged.
apps/dashboard/src/components/agents/agents-list.tsx (1)

95-106: ⚡ Quick win

Hard-coded integration name collides for users with multiple managed agents.

Every managed-runtime agent in an environment creates an integration named "Anthropic (managed agents)". From the integrations list this becomes indistinguishable noise (and there's no link back to which agent the credential belongs to). Consider scoping the name with the agent identifier or name, e.g.:

-            name: 'Anthropic (managed agents)',
+            name: `Anthropic — ${body.name}`,

That keeps it human-readable and traceable. Same comment applies if/when more managed providers are added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/agents-list.tsx` around lines 95 - 106,
Integration names are being hard-coded to "Anthropic (managed agents)" which
collides when multiple managed agents exist; update the createIntegration call
(the object passed to createIntegration in agents-list.tsx) to include a unique,
human-readable identifier by incorporating the agent's name or id (e.g., append
agent.name or agent._id into the name field) so each created integration is
traceable back to its agent, and ensure the chosen field (agent.name or
agent._id) is available in scope and safely stringified/escaped before
concatenation.
libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts (1)

116-189: ⚖️ Poor tradeoff

Remove unnecessary as any casts for typed access to beta.agents API.

The provider casts client as any for create (line 116), retrieve (line 151), and update (line 189), but uses the typed surface for archive (line 139). The inconsistency indicates the casts are defensive or residual from prototyping. Since beta.agents.archive() works without casting, the typed API is accessible for all methods.

Removing the as any casts will:

  • Allow TypeScript to catch payload-shape changes at compile time rather than runtime
  • Eliminate implicit any types on method returns (e.g., agent.model, agent.skills)
  • Be consistent across all beta.agents methods

Verify that your TypeScript configuration or IDE correctly infers types for beta.agents.create, retrieve, and update in @anthropic-ai/[email protected], then drop the casts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 116 - 189, The file uses defensive casts like (client as
any).beta.agents.create/retrieve/update while archive uses the typed client;
remove the unnecessary "as any" casts and call client.beta.agents.create,
client.beta.agents.retrieve and client.beta.agents.update directly so TypeScript
can infer the correct request/response types (adjust any downstream usages that
assumed implicit any on agent.model/agent.skills to match the SDK types); ensure
buildClient returns the correctly typed client so the compiler recognizes
beta.agents methods without casting.
apps/api/src/app/agents/e2e/managed-agent.e2e.ts (1)

30-42: ⚡ Quick win

Use the runtime integration channel in this fixture.

The managed-runtime flow stores Anthropic credentials as an agent-runtime integration, but this setup inserts an IN_APP integration. That means the suite will not catch bugs in channel-specific lookup or validation.

Suggested fix
-    channel: ChannelTypeEnum.IN_APP,
+    channel: ChannelTypeEnum.AGENT_RUNTIME,

Based on PR objectives: the PR adds ChannelTypeEnum.AGENT_RUNTIME for managed runtime integrations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/e2e/managed-agent.e2e.ts` around lines 30 - 42, The
fixture creates an integration with ChannelTypeEnum.IN_APP but the
managed-runtime flow expects ChannelTypeEnum.AGENT_RUNTIME; update the
integration object in the test (the call to integrationRepository.create where
providerId is AgentRuntimeProviderIdEnum.Anthropic and name 'Anthropic Runtime
E2E' / identifier `anthropic-runtime-e2e-${Date.now()}`) to set channel:
ChannelTypeEnum.AGENT_RUNTIME so the test uses the runtime integration path and
validates channel-specific lookup/validation logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app/agents/agents.controller.ts`:
- Around line 123-134: The GET /agents/runtime-providers route is not
feature-flag gated; update the listAgentRuntimeProviders method to check the
managed-runtime feature flag before returning AGENT_RUNTIME_PROVIDERS and deny
access when the flag is disabled. Inside listAgentRuntimeProviders (referencing
the method name) call the existing feature-flag check utility (e.g.,
managedRuntimeEnabled(), featureFlagService.isEnabled('managed-runtime'), or
equivalent used elsewhere in the codebase) and if it returns false, respond with
an appropriate forbidden/not-found behavior (throw
HttpException/ForbiddenException or return an empty list) instead of returning
AGENT_RUNTIME_PROVIDERS; otherwise continue to return AGENT_RUNTIME_PROVIDERS as
AgentRuntimeProviderResponseDto[].

In `@apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts`:
- Around line 84-96: The AgentRuntimeCapabilitiesDto is missing the new "skills"
capability so the GET /agents/runtime-providers schema is out of sync; add a
boolean skills property decorated with `@ApiProperty`() to the
AgentRuntimeCapabilitiesDto class (and ensure any code that builds or maps
AgentRuntimeCapabilities objects to this DTO includes the skills flag) so
clients can detect provider skills support.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts`:
- Around line 35-90: The current isManaged check (const isManaged =
command.runtime === 'managed' && command.managedRuntime) allows a missing
managedRuntime to fall through to the self-hosted create path and silently
persist an invalid agent; change validation in the create logic to explicitly
reject when command.runtime === 'managed' but command.managedRuntime is falsy by
throwing a clear ValidationError (or similar) before calling
agentRepository.create, e.g. in the create-agent use case (around isManaged and
before any repository calls), so that provisionManagedAgentUsecase.execute and
the ProvisionManagedAgentCommand are only used when managedRuntime is present
and the invalid DTO is never persisted.

In `@apps/dashboard/src/components/agents/agents-list.tsx`:
- Around line 90-122: The current createMutation flow calls createIntegration
then createAgent and can leave orphaned integrations if createAgent fails; wrap
the "createIntegration → createAgent" sequence inside a try/catch in
createMutation: after createIntegration succeeds capture
integrationResponse.data._id, call createAgent, and if createAgent throws call
deleteIntegration(integrationResponse.data._id) (log any deletion error but do
not swallow the original error) before rethrowing so the UI shows the original
failure; alternatively, prefer moving this whole flow into a backend
transactional API so createIntegration and createAgent are atomic (references:
createMutation, createIntegration, createAgent, deleteIntegration,
requireEnvironment, integrationResponse.data._id).

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 110-205: The provider currently reads the API key via `(this as
any)._apiKey`; change the class to accept and store the apiKey via constructor
injection (e.g., add a private readonly apiKey: string field and a constructor
AnthropicAgentRuntimeProvider(apiKey: string)), update createAnthropicProvider
to instantiate the class with new AnthropicAgentRuntimeProvider(apiKey) and
remove the ad-hoc cast assignment, and update all usages in methods createAgent,
deleteAgent, getConfig, and updateConfig to call this.buildClient(this.apiKey)
(or pass this.apiKey to the existing buildClient overload) instead of `(this as
any)._apiKey`; keep validateCredentials/buildClient's apiKey parameter behavior
intact.
- Around line 110-132: createAgent is currently wrapped in withRetry which can
double-create upstream agents if the first create succeeds but the response is
lost; either stop using withRetry for createAgent or make the create call
idempotent via the SDK's idempotency key. Fix by changing createAgent to call
(client as any).beta.agents.create(...) directly (remove the withRetry wrapper
around the closure) and let normaliseError(err) propagate the failure, or
alternately pass a stable idempotencyKey to beta.agents.create (e.g., derived
from a provided stable input field or deterministic hash of
input.name+input.model) so retries via withRetry are deduped; update any related
tests and keep deleteAgent/getConfig unchanged (consider same idempotency
approach for updateConfig which uses update).

In `@packages/shared/src/consts/providers/claude-mcp-servers.ts`:
- Around line 24-412: Update the incorrect MCP endpoint URLs in the
CLAUDE_MCP_SERVERS constant: for id 'slack' (Slack) change the url from .../sse
to the official /mcp path, for id 'stripe' (Stripe) use the vendor-recommended
bare MCP domain, for id 'figma' (Figma) change .../sse to /mcp, for id
'airtable' (Airtable) change .../sse to /mcp, and for Atlassian entries (e.g.,
id 'atlassian-rovo' and other atlassian-based entries like 'confluence' and
'jira') update their urls to use the official /v1/sse path; locate and edit
these entries inside the CLAUDE_MCP_SERVERS array to match the vendor docs
before rebuilding and releasing.

---

Outside diff comments:
In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx`:
- Around line 229-265: The validation currently only checks apiKey when runtime
=== 'claude' && mode === 'create', which lets mode === 'existing' submit without
apiKey or externalAgentId; move/extend validation into the pre-payload block so
that when runtime === 'claude' && mode === 'existing' you validate apiKey.trim()
and externalAgentId.trim() and add errors to nextErrors (e.g., nextErrors.apiKey
and nextErrors.externalAgentId) before calling setErrors/return; ensure
CreateAgentDialogSubmitBody population still sets body.apiKey and
body.externalAgentId only after validation; also add externalAgentId?: string to
the FormErrors type and surface that error on the externalAgentId input so the
UI shows the message.

---

Duplicate comments:
In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 55-71: The code is reading request-id and retry-after using
bracket notation on APIError.headers which is a Headers instance, so change to
read requestId from err.requestID (set requestId = err.requestID as string |
undefined) and read retry-after via the Headers.get method (call
parseRetryAfter(err.headers?.get('retry-after') as string | undefined)); then
pass that requestId into the
AgentRuntimeUnauthorizedError/AgentRuntimeForbiddenError/AgentRuntimeNotFoundError/AgentRuntimeRateLimitedError
constructors (and pass the parsed retryAfterMs to AgentRuntimeRateLimitedError)
while keeping PROVIDER_ID unchanged.

In `@packages/shared/src/dto/agent/managed-runtime.dto.ts`:
- Around line 52-59: AgentMcpServerDto currently exposes authToken and must be
split into separate read/write DTOs: keep a read-only shape (e.g.,
AgentMcpServerReadDto) that replaces authToken with a boolean hasAuthToken (or
masked metadata) and a write/update shape (e.g., AgentMcpServerWriteDto) that
accepts authToken?: string for incoming requests. Replace usages of
AgentMcpServerDto in response/serialization paths with AgentMcpServerReadDto and
use AgentMcpServerWriteDto for controllers/services that accept or persist
authToken; ensure any mapping code (serializers or constructors that currently
reference AgentMcpServerDto.authToken) only sets hasAuthToken on read DTOs and
only reads/writes authToken from the write DTO.

---

Nitpick comments:
In `@apps/api/src/app/agents/e2e/managed-agent.e2e.ts`:
- Around line 30-42: The fixture creates an integration with
ChannelTypeEnum.IN_APP but the managed-runtime flow expects
ChannelTypeEnum.AGENT_RUNTIME; update the integration object in the test (the
call to integrationRepository.create where providerId is
AgentRuntimeProviderIdEnum.Anthropic and name 'Anthropic Runtime E2E' /
identifier `anthropic-runtime-e2e-${Date.now()}`) to set channel:
ChannelTypeEnum.AGENT_RUNTIME so the test uses the runtime integration path and
validates channel-specific lookup/validation logic.

In `@apps/dashboard/src/components/agents/agents-list.tsx`:
- Around line 95-106: Integration names are being hard-coded to "Anthropic
(managed agents)" which collides when multiple managed agents exist; update the
createIntegration call (the object passed to createIntegration in
agents-list.tsx) to include a unique, human-readable identifier by incorporating
the agent's name or id (e.g., append agent.name or agent._id into the name
field) so each created integration is traceable back to its agent, and ensure
the chosen field (agent.name or agent._id) is available in scope and safely
stringified/escaped before concatenation.

In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx`:
- Around line 250-262: Replace hard-coded provider and model strings in the
create-agent payload with the shared constants: use
AgentRuntimeProviderIdEnum.Anthropic (instead of 'anthropic') to set providerId
and pull the default model from AGENT_RUNTIME_PROVIDERS (e.g.,
AGENT_RUNTIME_PROVIDERS[AgentRuntimeProviderIdEnum.Anthropic].defaultModel)
instead of 'claude-opus-4-5'; update the block that assigns body.managedRuntime
(and any related mapping for skills) to reference these constants so future
model/provider changes are centralized.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 116-189: The file uses defensive casts like (client as
any).beta.agents.create/retrieve/update while archive uses the typed client;
remove the unnecessary "as any" casts and call client.beta.agents.create,
client.beta.agents.retrieve and client.beta.agents.update directly so TypeScript
can infer the correct request/response types (adjust any downstream usages that
assumed implicit any on agent.model/agent.skills to match the SDK types); ensure
buildClient returns the correctly typed client so the compiler recognizes
beta.agents methods without casting.

In `@libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts`:
- Around line 10-32: Replace the three backend type aliases with interfaces to
match the existing contract style: convert CreateAgentInput, CreateAgentResult,
and UpdateAgentRuntimeConfigInput from "type" to "interface" declarations,
preserving all property names, optional markers, and nested types (e.g.,
AgentSkillDto, AgentMcpServerDto, AgentToolDto) so consumers of
IAgentRuntimeProvider see consistent interface types; ensure JSDoc/comments
(like the tools and mcpServers descriptions) remain above the corresponding
interface properties.

In `@packages/shared/src/consts/providers/claude-skills.ts`:
- Around line 64-157: CLAUDE_OPENSOURCE_SKILLS is defined but unused and will
rot; either add a grep-able TODO or remove it: locate the
CLAUDE_OPENSOURCE_SKILLS constant and either (a) add a comment like "//
TODO(NV-XXXX): track open-source skills list until /v1/skills upload is
implemented" immediately above or next to the declaration so it’s searchable, or
(b) delete the CLAUDE_OPENSOURCE_SKILLS block and reintroduce it when the upload
flow exists; ensure CLAUDE_ANTHROPIC_SKILLS (and any reference to
CLAUDE_PREBUILT_SKILLS) remains 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: 76778214-727d-43fe-bbf3-8da0246b2ffb

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd2dc1 and 5b7daee.

📒 Files selected for processing (17)
  • apps/api/src/app/agents/agents.controller.ts
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/e2e/managed-agent.e2e.ts
  • apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/components/agents/agents-list.tsx
  • apps/dashboard/src/components/agents/create-agent-dialog.tsx
  • libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts
  • libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts
  • packages/shared/src/consts/providers/agent-runtime-providers.ts
  • packages/shared/src/consts/providers/claude-mcp-servers.ts
  • packages/shared/src/consts/providers/claude-skills.ts
  • packages/shared/src/consts/providers/claude-tools.ts
  • packages/shared/src/consts/providers/index.ts
  • packages/shared/src/dto/agent/managed-runtime.dto.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/shared/src/consts/providers/index.ts
  • packages/shared/src/consts/providers/agent-runtime-providers.ts
  • apps/dashboard/src/api/agents.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts

Comment thread apps/api/src/app/agents/agents.controller.ts Outdated
Comment thread apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts Outdated
Comment thread apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts Outdated
Comment thread apps/dashboard/src/components/agents/agents-list.tsx
Comment thread packages/shared/src/consts/providers/claude-mcp-servers.ts
- Added support for adopting existing agents from the provider platform, allowing users to link to an existing agent instead of creating a new one.
- Updated `CreateAgentRequestDto` and `ProvisionManagedAgentCommand` to include `externalAgentId` for adoption scenarios.
- Enhanced the `CreateAgent` use case to handle temporary placeholders for name and identifier during the adoption process.
- Improved error handling for scenarios where the external agent ID does not exist or the API key is invalid.
- Updated the dashboard components to accommodate the new adoption flow, including validation for external agent ID input.

These changes enhance the flexibility of agent management, enabling users to leverage existing agents seamlessly.
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: 7

♻️ Duplicate comments (3)
apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts (1)

43-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Defense-in-depth: still silently downgrades runtime: 'managed' to a self-hosted insert when managedRuntime is absent.

isManaged = command.runtime === 'managed' && command.managedRuntime evaluates to false when runtime === 'managed' but managedRuntime is missing, so the code falls through to the plain agentRepository.create(...) branch on line 127 and silently persists a self-hosted agent labeled as managed-in-intent. The DTO @ValidateIf should normally catch this, but until create-agent.command.ts is fixed (@Type(() => Object) and the type-only import still neuter the nested validator), this is reachable. Throw UnprocessableEntityException explicitly here.

Suggested fix
-import { ConflictException, Injectable } from '@nestjs/common';
+import { ConflictException, Injectable, UnprocessableEntityException } from '@nestjs/common';
@@
-    const isManaged = command.runtime === 'managed' && command.managedRuntime;
+    if (command.runtime === 'managed' && !command.managedRuntime) {
+      throw new UnprocessableEntityException('managedRuntime is required when runtime is "managed".');
+    }
+    const isManaged = command.runtime === 'managed';

As per coding guidelines: apps/api/**: Check for proper error handling and input validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts` around
lines 43 - 44, The current isManaged calculation can silently downgrade
runtime:'managed' when managedRuntime is missing; update the logic in
create-agent.usecase.ts to explicitly validate this: if command.runtime ===
'managed' and !command.managedRuntime, throw an UnprocessableEntityException
with a clear message instead of falling through to agentRepository.create; keep
the existing isManaged flag (e.g., const isManaged = command.runtime ===
'managed' && !!command.managedRuntime) but add the explicit guard before any
repository create calls to prevent persisting a mis-labeled agent.
apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts (1)

15-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Nested DTO validation for managedRuntime is still broken.

The class is still imported as a type-only symbol on line 15, and @Type(() => Object) on line 43 does not trigger class-transformer to instantiate ManagedRuntimeDto. As a result @ValidateNested() walks a plain object and the inner @IsEnum/@IsString/etc. decorators on ManagedRuntimeDto never run, so invalid providerId, integrationId, or externalAgentId shapes pass through unchecked.

Suggested fix
-import type { ManagedRuntimeDto } from '../../dtos/agent-runtime-config.dto';
+import { ManagedRuntimeDto } from '../../dtos/agent-runtime-config.dto';
@@
   `@ValidateIf`((o) => o.runtime === 'managed')
   `@IsObject`()
   `@ValidateNested`()
-  `@Type`(() => Object)
+  `@Type`(() => ManagedRuntimeDto)
   managedRuntime?: ManagedRuntimeDto;

Also applies to: 40-44

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts` at
line 15, The nested DTO validation fails because ManagedRuntimeDto is imported
as a type-only symbol and the property decorator uses `@Type`(() => Object), so
class-transformer never instantiates ManagedRuntimeDto and `@ValidateNested`()
walks a plain object; fix by importing ManagedRuntimeDto as a runtime value
(remove the type-only import) and change the property decorator for
managedRuntime to `@Type`(() => ManagedRuntimeDto) alongside `@ValidateNested`(), so
class-transformer will create a ManagedRuntimeDto instance and inner decorators
(e.g., `@IsEnum/`@IsString) will run; apply the same change to the other
managedRuntime occurrences around the existing decorators.
apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts (1)

1-1: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

providerId still lacks enum validation.

providerId is only decorated with @IsNotEmpty(), so any non-empty string (e.g. "foo") survives DTO validation and is forwarded to getAgentRuntimeProvider. Add @IsEnum(AgentRuntimeProviderIdEnum) and promote the import from a type-only import so the enum is available at runtime.

Suggested fix
-import type { AgentRuntimeProviderIdEnum, AgentSkillDto } from '@novu/shared';
+import { AgentRuntimeProviderIdEnum } from '@novu/shared';
+import type { AgentSkillDto } from '@novu/shared';
 import { Type } from 'class-transformer';
-import { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
+import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
@@
   `@IsNotEmpty`()
+  `@IsEnum`(AgentRuntimeProviderIdEnum)
   providerId: AgentRuntimeProviderIdEnum;

Also applies to: 22-23

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts`
at line 1, The providerId field lacks enum validation; change the type-only
import of AgentRuntimeProviderIdEnum to a runtime import (remove the "type"
modifier) so the enum is available at runtime, and add the class-validator
decorator `@IsEnum`(AgentRuntimeProviderIdEnum) to the providerId property (in the
DTO used by provision-managed-agent.command and the related DTO on lines 22-23)
instead of only `@IsNotEmpty`(), so invalid strings are rejected before calling
getAgentRuntimeProvider.
🧹 Nitpick comments (3)
libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts (2)

224-230: 💤 Low value

parseRetryAfter ignores the HTTP‑date form of Retry-After.

Per RFC 9110, Retry-After may be either delta-seconds or an HTTP-date. parseFloat will return NaN for the date form and silently fall back to 60s, which is fine as a safety net but can grossly under- or over-state the actual wait. If you want to honor a 429/503 hint accurately, parse the date branch as well (Date.parse(header) - Date.now()), clamped to a non-negative value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 224 - 230, The parseRetryAfter function currently treats the header
only as delta-seconds; update parseRetryAfter(header: string | undefined) to
also handle the HTTP-date form: if parseFloat(header) yields NaN, attempt a date
parse (const ms = Date.parse(header) - Date.now()), ensure the result is a
non-negative integer (Math.max(0, Math.round(ms))), and return that value in
milliseconds; if the date parse is invalid fall back to the existing 60_000 ms
default. Ensure you reference parseRetryAfter in
anthropic-agent-runtime.provider.ts and preserve the original return type and
fallback semantics.

306-327: 💤 Low value

buildToolsPayload always emits at least one entry, so the length > 0 guards in createAgent/updateConfig are dead code.

buildToolsPayload unconditionally pushes the agent_toolset_20260401 entry on line 315 before considering MCP servers, so its return array is never empty. The toolsPayload.length > 0 checks at lines 124 and 198 will therefore always be true. This isn't itself broken, but it's misleading and hides the side effect that every create/patch implicitly resets the builtin toolset. Either drop the dead guards or short‑circuit buildToolsPayload to return [] when both inputs are absent so the gates become meaningful.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 306 - 327, buildToolsPayload currently always pushes the
agent_toolset_20260401 entry so callers' guards (toolsPayload.length > 0) in
createAgent/updateConfig are dead; fix by short‑circuiting buildToolsPayload to
return [] when both toolTypes is null/empty and mcpServers is null/empty (i.e.
check if (!toolTypes || toolTypes.length===0) && (!mcpServers ||
mcpServers.length===0) then return []), otherwise build the payload as before
(refer to buildToolsPayload, CLAUDE_BUILTIN_TOOLS, and the callers
createAgent/updateConfig).
apps/dashboard/src/components/agents/create-agent-dialog.tsx (1)

254-263: ⚡ Quick win

integrationId: '' is sent from the dialog and must be overwritten by the caller.

Both submission branches set managedRuntime.integrationId to an empty string. The dialog itself never creates an integration, so this implicitly requires the caller (e.g., agents-list.tsx) to create the integration first and overwrite integrationId (or strip it) before forwarding to the API. This is easy to break silently if a new caller is added. Consider omitting integrationId here and letting the caller add it, so the type system catches the missing value rather than shipping a placeholder.

Also applies to: 273-287

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx` around lines
254 - 263, The dialog currently always sets managedRuntime.integrationId to an
empty string in both submission branches (see the body object built when
isExistingMode and the other branch around lines 273-287), which forces callers
to overwrite it; instead remove integrationId from the dialog payload when it's
empty and only include managedRuntime.integrationId when a non-empty value is
provided (e.g., check externalIntegrationId or a local integrationId field and
conditionally add that property), so callers must explicitly supply an
integrationId and the type system will catch missing values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app/agents/e2e/managed-agent.e2e.ts`:
- Around line 81-83: The test mutates
process.env.IS_CONVERSATIONAL_AGENTS_ENABLED in the before hook without
restoring it; capture the previous value (e.g., const previous =
process.env.IS_CONVERSATIONAL_AGENTS_ENABLED) before setting it in the before()
block and add a matching after() hook that restores
process.env.IS_CONVERSATIONAL_AGENTS_ENABLED = previous (or deletes it if
previous was undefined) to avoid leaking the env var across test files.
- Line 8: The test imports the internal build path module
(AgentRuntimeFactoryModule) which couples the test to package internals and can
prevent stubs from intercepting runtime imports; change the import to the
package public entry (import * as AgentRuntimeFactoryModule from
'@novu/application-generic' or better import { getAgentRuntimeProvider } from
'@novu/application-generic') and update any stubbing/spy usage that references
the old module identity (e.g., the stub around getAgentRuntimeProvider at the
test stub location) so it targets the exported function from the public module
instead of the deep build path.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts`:
- Line 87: The current guard if (isAdoptMode && provisionResult.adoptedName)
lets empty/falsy adoptedName skip renaming and leaves "__adopt_pending__" in
Mongo; update the logic in create-agent.usecase.ts around
isAdoptMode/provisionResult.adoptedName to either fail loudly or derive a
deterministic fallback name from externalAgentId: explicitly check for a
non-empty string (e.g., typeof provisionResult.adoptedName === 'string' &&
provisionResult.adoptedName.trim().length > 0), and if empty/undefined in adopt
mode either throw a specific error (so the request fails) or set adoptedName =
`adopted-${externalAgentId.slice(0,8)}` (or similar deterministic scheme) and
use that variable for the document rename/update instead of relying on the
original truthy guard.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`:
- Around line 49-51: The current throw in provision-managed-agent.usecase.ts
uses NotFoundException when decryptCredentials(...) returns no apiKey, which is
misleading; replace the NotFoundException with an UnprocessableEntityException
(or throw a dedicated AGENT_RUNTIME_* error your dashboard understands) in the
branch that checks for apiKey so callers can distinguish "integration exists but
not configured" from a true 404; update any tests or error mappings that expect
NotFoundException from the decryptCredentials -> apiKey check to expect the new
exception.
- Around line 73-77: The mapping for resolvedMcpServers silently substitutes an
empty url when a serverId is not found in CLAUDE_MCP_SERVERS; instead validate
and reject unknown IDs: in the provision-managed-agent use case (where
resolvedMcpServers is computed and later passed to runtimeProvider.createAgent)
detect any serverId with no matching CLAUDE_MCP_SERVERS entry and throw a
BadRequestException (imported from `@nestjs/common`) with a clear message listing
the invalid IDs, or alternatively filter them out and log a warning before
proceeding—do not pass {name: id, url: ''} to runtimeProvider.createAgent.

In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx`:
- Around line 211-213: handleTemplateRotate is a no-op when
AGENT_TEMPLATES.length === 4 because it advances offset by 4; change the
rotation to advance by 1 (i.e., setTemplateOffset(prev => (prev + 1) %
AGENT_TEMPLATES.length)) or alternatively disable/hide the refresh control when
AGENT_TEMPLATES.length <= visibleCount; update the logic around
handleTemplateRotate, setTemplateOffset, and the visibleTemplates slice to use
the chosen approach so clicking the refresh actually rotates the shown
templates.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 193-202: The PATCH handler currently rebuilds the entire
agent_toolset_20260401 when either patch.tools or patch.mcpServers is present,
and buildToolsPayload(undefined, ...) produces an empty enabledSet which
disables all existing tools; change the logic in
anthropic-agent-runtime.provider so it does not overwrite the side the caller
didn't touch by either (A) fetching current config via getConfig and merging
existing tool entries with the new tool list before calling
buildToolsPayload/updatePayload.tools, or (B) splitting the update into two
independent payload pieces so patch.tools only updates tool-enabled flags and
patch.mcpServers only updates MCP server entries (ensure buildToolsPayload is
only called with explicitly-supplied toolTypes or merged toolTypes, and do not
clear MCP servers when patch.mcpServers is undefined). Ensure references:
patch.tools, patch.mcpServers, buildToolsPayload, enabledSet,
updatePayload.tools, and getConfig are used to locate and implement the
merge/split.

---

Duplicate comments:
In `@apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts`:
- Line 15: The nested DTO validation fails because ManagedRuntimeDto is imported
as a type-only symbol and the property decorator uses `@Type`(() => Object), so
class-transformer never instantiates ManagedRuntimeDto and `@ValidateNested`()
walks a plain object; fix by importing ManagedRuntimeDto as a runtime value
(remove the type-only import) and change the property decorator for
managedRuntime to `@Type`(() => ManagedRuntimeDto) alongside `@ValidateNested`(), so
class-transformer will create a ManagedRuntimeDto instance and inner decorators
(e.g., `@IsEnum/`@IsString) will run; apply the same change to the other
managedRuntime occurrences around the existing decorators.

In `@apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts`:
- Around line 43-44: The current isManaged calculation can silently downgrade
runtime:'managed' when managedRuntime is missing; update the logic in
create-agent.usecase.ts to explicitly validate this: if command.runtime ===
'managed' and !command.managedRuntime, throw an UnprocessableEntityException
with a clear message instead of falling through to agentRepository.create; keep
the existing isManaged flag (e.g., const isManaged = command.runtime ===
'managed' && !!command.managedRuntime) but add the explicit guard before any
repository create calls to prevent persisting a mis-labeled agent.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts`:
- Line 1: The providerId field lacks enum validation; change the type-only
import of AgentRuntimeProviderIdEnum to a runtime import (remove the "type"
modifier) so the enum is available at runtime, and add the class-validator
decorator `@IsEnum`(AgentRuntimeProviderIdEnum) to the providerId property (in the
DTO used by provision-managed-agent.command and the related DTO on lines 22-23)
instead of only `@IsNotEmpty`(), so invalid strings are rejected before calling
getAgentRuntimeProvider.

---

Nitpick comments:
In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx`:
- Around line 254-263: The dialog currently always sets
managedRuntime.integrationId to an empty string in both submission branches (see
the body object built when isExistingMode and the other branch around lines
273-287), which forces callers to overwrite it; instead remove integrationId
from the dialog payload when it's empty and only include
managedRuntime.integrationId when a non-empty value is provided (e.g., check
externalIntegrationId or a local integrationId field and conditionally add that
property), so callers must explicitly supply an integrationId and the type
system will catch missing values.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 224-230: The parseRetryAfter function currently treats the header
only as delta-seconds; update parseRetryAfter(header: string | undefined) to
also handle the HTTP-date form: if parseFloat(header) yields NaN, attempt a date
parse (const ms = Date.parse(header) - Date.now()), ensure the result is a
non-negative integer (Math.max(0, Math.round(ms))), and return that value in
milliseconds; if the date parse is invalid fall back to the existing 60_000 ms
default. Ensure you reference parseRetryAfter in
anthropic-agent-runtime.provider.ts and preserve the original return type and
fallback semantics.
- Around line 306-327: buildToolsPayload currently always pushes the
agent_toolset_20260401 entry so callers' guards (toolsPayload.length > 0) in
createAgent/updateConfig are dead; fix by short‑circuiting buildToolsPayload to
return [] when both toolTypes is null/empty and mcpServers is null/empty (i.e.
check if (!toolTypes || toolTypes.length===0) && (!mcpServers ||
mcpServers.length===0) then return []), otherwise build the payload as before
(refer to buildToolsPayload, CLAUDE_BUILTIN_TOOLS, and the callers
createAgent/updateConfig).
🪄 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: 5f639558-7ec5-4548-86da-4f0d2c305cfd

📥 Commits

Reviewing files that changed from the base of the PR and between 5b7daee and 1f9fa26.

📒 Files selected for processing (12)
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/dtos/create-agent-request.dto.ts
  • apps/api/src/app/agents/e2e/managed-agent.e2e.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/provision-managed-agent/provision-managed-agent.command.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/components/agents/agents-list.tsx
  • apps/dashboard/src/components/agents/create-agent-dialog.tsx
  • libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts
  • libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/components/agents/agents-list.tsx
  • libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts

Comment thread apps/api/src/app/agents/e2e/managed-agent.e2e.ts
Comment on lines +211 to 213
const handleTemplateRotate = () => {
setTemplateOffset((prev) => (prev + 4) % AGENT_TEMPLATES.length);
};
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Template rotation is a no-op when there are exactly 4 templates.

AGENT_TEMPLATES currently has 4 entries and visibleTemplates already takes slice(templateOffset, templateOffset + 4). (prev + 4) % AGENT_TEMPLATES.length evaluates to (0 + 4) % 4 === 0, so clicking the refresh button never changes the visible set. Either reduce the step (e.g. rotate by 1) or guard the button when the list fits in one page.

♻️ Suggested fix
-  const handleTemplateRotate = () => {
-    setTemplateOffset((prev) => (prev + 4) % AGENT_TEMPLATES.length);
-  };
+  const handleTemplateRotate = () => {
+    if (AGENT_TEMPLATES.length <= 4) return;
+    setTemplateOffset((prev) => (prev + 1) % AGENT_TEMPLATES.length);
+  };
📝 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
const handleTemplateRotate = () => {
setTemplateOffset((prev) => (prev + 4) % AGENT_TEMPLATES.length);
};
const handleTemplateRotate = () => {
if (AGENT_TEMPLATES.length <= 4) return;
setTemplateOffset((prev) => (prev + 1) % AGENT_TEMPLATES.length);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/agents/create-agent-dialog.tsx` around lines
211 - 213, handleTemplateRotate is a no-op when AGENT_TEMPLATES.length === 4
because it advances offset by 4; change the rotation to advance by 1 (i.e.,
setTemplateOffset(prev => (prev + 1) % AGENT_TEMPLATES.length)) or alternatively
disable/hide the refresh control when AGENT_TEMPLATES.length <= visibleCount;
update the logic around handleTemplateRotate, setTemplateOffset, and the
visibleTemplates slice to use the chosen approach so clicking the refresh
actually rotates the shown templates.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

💬 Out of scope for this round. The current dialog has exactly 4 templates so the rotate button is functionally a no-op until we add more. Will either disable the button conditionally or change the step to +1 once we expand the catalog — tracking separately to keep this PR focused on the managed-runtime changes.

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.

@djabarovgeorge, understood — makes sense to keep the scope tight on the managed-runtime work. Would you like me to open a follow-up GitHub issue to track disabling/hiding the rotate button (or changing the step to +1) when the template catalog is expanded?


🧠 Learnings used
Learnt from: ChmaraX
Repo: novuhq/novu PR: 10922
File: apps/dashboard/src/components/welcome/inbox-connected-guide.tsx:131-135
Timestamp: 2026-04-29T14:47:11.569Z
Learning: In the apps/dashboard codebase, `useTelemetry` is intentionally implemented as a React Query `useMutation` wrapper whose callback uses fire-and-forget semantics (it returns the `mutate` function, effectively `void`). Therefore, `await telemetry(...)` calls are intentional and should NOT be flagged as incorrect/buggy or as needing `mutateAsync`. Do not suggest changing `useTelemetry` to use `mutateAsync`; treat this behavior as an established codebase-wide pattern.

Learnt from: ChmaraX
Repo: novuhq/novu PR: 10930
File: apps/dashboard/src/components/agents/agents-list.tsx:259-290
Timestamp: 2026-04-30T16:37:12.078Z
Learning: In apps/dashboard/src, follow the established tooltip pattern for disabled buttons: render the disabled <Button ... disabled> inside <TooltipTrigger className="cursor-not-allowed">...</TooltipTrigger> and do not require or flag the absence of the `asChild` prop on `TooltipTrigger` when it wraps a disabled `Button`. This convention should be treated as acceptable and not reported as an issue during code review.

Learnt from: scopsy
Repo: novuhq/novu PR: 11060
File: packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsx:55-56
Timestamp: 2026-05-11T05:04:35.896Z
Learning: In PR reviews for TypeScript/TSX code, do not suggest adding blank lines before `return` statements. Follow the repository’s existing formatting conventions and avoid raising review comments about this specific whitespace change (blank line insertion before `return`).

- Updated the agent provisioning process to support both `integrationId` and `apiKey`, allowing for auto-creation of integrations and environments when an API key is provided.
- Modified `CreateAgentRequestDto`, `ProvisionManagedAgentCommand`, and related use cases to handle the new `apiKey` field, ensuring mutual exclusivity with `integrationId`.
- Enhanced error handling to enforce the requirement that exactly one of `integrationId` or `apiKey` must be supplied.
- Updated dashboard components to reflect changes in the agent creation flow, simplifying the integration process for users.

These improvements streamline the agent management experience, enabling more flexible integration options with the Claude Platform.
- Updated the delete agent API to include an optional `deleteFromProvider` parameter, allowing users to archive agents on the provider side (e.g., Anthropic) when deleting managed-runtime agents.
- Modified the `DeleteAgentCommand` to handle the new parameter and updated the corresponding use case to implement the provider deletion logic.
- Enhanced the dashboard components to support the new deletion option, including a confirmation dialog that allows users to choose between deleting the agent from Novu only or from both Novu and the provider.

These changes improve the flexibility of agent management, enabling users to manage their agents more effectively across platforms.
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

♻️ Duplicate comments (8)
packages/shared/src/dto/agent/managed-runtime.dto.ts (1)

57-64: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't expose raw MCP auth tokens in a shared read DTO.

AgentRuntimeConfigDto is a cross-layer response shape, so carrying authToken here makes secret leakage into UI state and logging paths far too easy. Split this into a safe read DTO (hasAuthToken or masked metadata) and a separate write DTO for updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/dto/agent/managed-runtime.dto.ts` around lines 57 - 64,
AgentMcpServerDto currently includes a raw authToken which can leak secrets via
cross-layer responses; change the public/shared response shape by removing
authToken from AgentMcpServerDto and replace it with a safe indicator (e.g.,
hasAuthToken: boolean or masked metadata) and create a separate write/update DTO
(e.g., AgentMcpServerUpdateDto or AgentMcpServerWriteDto) that contains the
authToken for create/update flows; update all references/uses of
AgentMcpServerDto and any consumers (including where AgentRuntimeConfigDto
embeds MCP server info) to use the new safe read DTO for responses and the write
DTO for input paths so the secret is never emitted in read payloads or logs.
apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts (2)

185-202: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check the agent update result before treating provisioning as persisted.

agentRepository.update() can return { matched: 0 } without throwing. In that case the upstream agent is already created/adopted, but no local document stores externalAgentId, so the provider resource is orphaned.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`
around lines 185 - 202, The agent update call in
provision-managed-agent.usecase.ts uses this.agentRepository.update(...) but
doesn't inspect the returned result; because update() can return { matched: 0 }
without throwing, ensure you capture the update result, check matched/modified
(e.g., const result = await this.agentRepository.update(...)), and if matched
=== 0 (or modified === 0) treat provisioning as not persisted: either throw an
error to abort the flow or perform cleanup by deleting the created provider
resource using externalAgentId via the provider client (and/or rollback any
created integration), ensuring you pass the same session/options when performing
cleanup or throwing so you don't leave an orphaned externalAgentId.

155-159: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unknown MCP server IDs instead of sending empty URLs upstream.

Falling back to { name: serverId, url: '' } turns invalid input into a provider-side failure after provisioning has already started. Validate against CLAUDE_MCP_SERVERS and fail fast with a clear 400.

As per coding guidelines: apps/api/**: Check for proper error handling and input validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`
around lines 155 - 159, The current mapping of command.mcpServers creates
entries with empty URLs for unknown IDs; instead validate against
CLAUDE_MCP_SERVERS and fail fast: in the provision flow where resolvedMcpServers
is built (the mapping over command.mcpServers in
provision-managed-agent.usecase.ts), for each serverId look up
CLAUDE_MCP_SERVERS.find(s => s.id === serverId) and if not found throw a 400 Bad
Request (e.g., throw new BadRequestException or HttpException) with a clear
message listing the invalid serverId(s); only return the mapped array when all
IDs are valid and handle the case where command.mcpServers is undefined/null by
leaving resolvedMcpServers undefined or empty as intended.
libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts (3)

59-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use Headers.get() when normalizing Anthropic errors.

APIError.headers is a Headers object, so bracket access here leaves requestId and retry-after undefined. That breaks request correlation and drops the 429 backoff hint.

In `@anthropic-ai/sdk` v0.95.1, is APIError.headers a Headers object, and should request-id / retry-after be read with headers.get(...) instead of bracket notation?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 59 - 74, The code assumes APIError.headers is a plain object and
uses bracket access for request-id and retry-after, but APIError.headers is a
Headers instance; replace bracket access with Headers.get(...) to read values
(e.g., use err.headers?.get('request-id') for requestId and
err.headers?.get('retry-after') for parseRetryAfter), then pass those retrieved
strings into AgentRuntimeUnauthorizedError / AgentRuntimeForbiddenError /
AgentRuntimeNotFoundError / AgentRuntimeRateLimitedError (and into
parseRetryAfter) so request correlation and 429 backoff hints work correctly in
anthropic-agent-runtime.provider.ts where APIError, parseRetryAfter, and the
AgentRuntime* error constructors are used.

114-135: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't retry provider-side create calls without idempotency.

withRetry wraps both beta.agents.create and beta.environments.create. If the first write succeeds but the response is lost, the retry can provision a second upstream resource and Novu will persist only the retry result.

In `@anthropic-ai/sdk` v0.95.1, do beta.agents.create and beta.environments.create support request-level idempotencyKey, and are retries without it safe for resource creation?

Also applies to: 216-233

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 114 - 135, The createAgent method is wrapping the non-idempotent
upstream call (client.beta.agents.create) inside withRetry which can duplicate
resources if the first request succeeded but the response was lost; update
createAgent (and the similar beta.environments.create call) to either stop using
withRetry for the provider-side create path or, if the Anthropic SDK supports a
request-level idempotency key for beta.agents.create / beta.environments.create,
pass a stable idempotencyKey (derived from input or a persisted mapping) to the
SDK call so retries are safe—locate the calls in createAgent and the environment
creation block, remove the retry wrapper or add idempotencyKey handling and
ensure normaliseError still runs on unretriable failures.

193-202: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

A one-sided PATCH currently clears the untouched tool configuration.

When patch.mcpServers is set without patch.tools, buildToolsPayload(undefined, ...) disables every builtin tool. The inverse case rebuilds updatePayload.tools without existing MCP toolsets, so they get removed even though the caller didn't patch them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`
around lines 193 - 202, The PATCH logic currently calls buildToolsPayload with
undefined when only one side (patch.tools or patch.mcpServers) is provided,
which removes untouched tool configuration; to fix, when computing toolTypes and
mcpServers for buildToolsPayload in anthropic-agent-runtime.provider.ts, merge
the incoming patch values with the existing runtime's current values instead of
passing undefined: derive currentToolTypes = patch.tools ?
patch.tools.map(t=>t.name) : existingRuntime.tools?.map(t=>t.name) and
currentMcpServers = patch.mcpServers ?
patch.mcpServers.map(s=>({name:s.name,url:s.url})) :
existingRuntime.mcp_servers?.map(s=>({name:s.name,url:s.url})), then call
buildToolsPayload(currentToolTypes, currentMcpServers) and only assign
updatePayload.tools if (patch.tools !== undefined || patch.mcpServers !==
undefined) && toolsPayload.length > 0 so untouched toolsets are preserved when
the caller did not patch them.
apps/api/src/app/agents/agents.controller.ts (1)

123-134: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate /agents/runtime-providers behind the managed-runtime flag.

This route still returns the provider catalog for any authenticated tenant with AGENT_READ, so disabled tenants can discover managed-runtime metadata directly via the API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/agents.controller.ts` around lines 123 - 134, The
listAgentRuntimeProviders route currently returns AGENT_RUNTIME_PROVIDERS for
any user with AGENT_READ; update listAgentRuntimeProviders to first check the
tenant's managed-runtime feature flag (via the request/tenant context or the
FeatureFlagService used elsewhere) and if the managed-runtime flag is disabled
for that tenant return a ForbiddenException or NotFoundException (matching
existing pattern for hiding disabled features) instead of the provider list;
keep the RequirePermissions(PermissionsEnum.AGENT_READ) decorator but gate the
actual response using the managed-runtime check before returning
AGENT_RUNTIME_PROVIDERS.
apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts (1)

22-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate providerId as an enum, not just a non-empty value.

@IsNotEmpty() still accepts arbitrary strings here, so unsupported provider IDs can get through command validation and fail later in the provisioning flow. Add @IsEnum(AgentRuntimeProviderIdEnum).

In class-validator 0.14.1, does `@IsNotEmpty`() validate enum membership, or is `@IsEnum`(...) required to reject invalid enum values?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts`
around lines 22 - 23, The providerId property uses only `@IsNotEmpty`() so invalid
strings can pass validation; update the ProvisionManagedAgentCommand (property
providerId) to also use `@IsEnum`(AgentRuntimeProviderIdEnum) so only valid enum
members are accepted, and add the corresponding import for IsEnum from
class-validator; keep `@IsNotEmpty`() if you want both non-empty and enum checks
but ensure IsEnum(AgentRuntimeProviderIdEnum) is present to enforce membership.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app/agents/e2e/managed-agent.e2e.ts`:
- Around line 32-37: The fixture creates an Anthropic integration with the wrong
channel — change the channel passed to integrationRepository.create from
ChannelTypeEnum.IN_APP to ChannelTypeEnum.AGENT_RUNTIME so the test seeds a
runtime integration that matches managed runtimes; update the call that
constructs the integration (the object passed into integrationRepository.create)
to use ChannelTypeEnum.AGENT_RUNTIME where ChannelTypeEnum.IN_APP is currently
used.
- Around line 86-88: The test suite currently sets
process.env.IS_CONVERSATIONAL_AGENTS_ENABLED but also needs to explicitly enable
the managed runtime flag for the endpoints it exercises; in the before() hook
(the same setup block that sets IS_CONVERSATIONAL_AGENTS_ENABLED) add an
assignment to enable the managed-runtime feature (e.g. set
process.env.IS_MANAGED_RUNTIME_ENABLED = 'true' or whatever the repo's managed
runtime flag is named) so the suite no longer depends on ambient process state
when calling runtime endpoints like /v1/agents/runtime-providers.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`:
- Around line 82-107: The catch block after
runtimeProviderForEnv.createEnvironment() currently just logs and rethrows,
leaving the auto-created environment/integration orphaned; update that catch to
perform rollback: if createdExternalEnvironmentId is set call
runtimeProviderForEnv.deleteEnvironment({ externalEnvironmentId:
createdExternalEnvironmentId }) (using the same runtimeProviderForEnv instance),
and if resolvedIntegrationId is set remove the created integration via
this.integrationRepository.delete (or deleteOne) with the same filter used for
update ({ _id: resolvedIntegrationId, _environmentId: command.environmentId,
_organizationId: command.organizationId }) and pass session if present; wrap
those cleanup calls in their own try/catch to log cleanup errors (use
this.logger.error) but always rethrow the original envError after attempting
rollback.
- Around line 41-43: The current guard in provision-managed-agent.usecase.ts
only rejects when neither command.integrationId nor command.apiKey is provided;
update the validation in the ProvisionManagedAgent use case to enforce XOR by
also rejecting the case when both are present: check if command.integrationId &&
command.apiKey and throw a BadRequestException (e.g., "Provide either
integrationId or apiKey, not both") so the code does not proceed to the apiKey
branch when an integrationId was also supplied.

In `@apps/dashboard/src/components/agents/delete-agent-dialog.tsx`:
- Around line 39-45: The delete scope state (scope) can linger when the parent
closes the dialog by toggling the open prop; add a useEffect that watches the
dialog's open prop and calls setScope('novu-only') whenever open becomes false
so the scope is reset on prop-driven closes (in addition to existing resets in
handleOpenChange), ensuring handleConfirm() won't send stale
deleteFromProvider:true for non-managed agents; reference the scope state,
setScope, handleConfirm, and handleOpenChange in your change.

---

Duplicate comments:
In `@apps/api/src/app/agents/agents.controller.ts`:
- Around line 123-134: The listAgentRuntimeProviders route currently returns
AGENT_RUNTIME_PROVIDERS for any user with AGENT_READ; update
listAgentRuntimeProviders to first check the tenant's managed-runtime feature
flag (via the request/tenant context or the FeatureFlagService used elsewhere)
and if the managed-runtime flag is disabled for that tenant return a
ForbiddenException or NotFoundException (matching existing pattern for hiding
disabled features) instead of the provider list; keep the
RequirePermissions(PermissionsEnum.AGENT_READ) decorator but gate the actual
response using the managed-runtime check before returning
AGENT_RUNTIME_PROVIDERS.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts`:
- Around line 22-23: The providerId property uses only `@IsNotEmpty`() so invalid
strings can pass validation; update the ProvisionManagedAgentCommand (property
providerId) to also use `@IsEnum`(AgentRuntimeProviderIdEnum) so only valid enum
members are accepted, and add the corresponding import for IsEnum from
class-validator; keep `@IsNotEmpty`() if you want both non-empty and enum checks
but ensure IsEnum(AgentRuntimeProviderIdEnum) is present to enforce membership.

In
`@apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts`:
- Around line 185-202: The agent update call in
provision-managed-agent.usecase.ts uses this.agentRepository.update(...) but
doesn't inspect the returned result; because update() can return { matched: 0 }
without throwing, ensure you capture the update result, check matched/modified
(e.g., const result = await this.agentRepository.update(...)), and if matched
=== 0 (or modified === 0) treat provisioning as not persisted: either throw an
error to abort the flow or perform cleanup by deleting the created provider
resource using externalAgentId via the provider client (and/or rollback any
created integration), ensuring you pass the same session/options when performing
cleanup or throwing so you don't leave an orphaned externalAgentId.
- Around line 155-159: The current mapping of command.mcpServers creates entries
with empty URLs for unknown IDs; instead validate against CLAUDE_MCP_SERVERS and
fail fast: in the provision flow where resolvedMcpServers is built (the mapping
over command.mcpServers in provision-managed-agent.usecase.ts), for each
serverId look up CLAUDE_MCP_SERVERS.find(s => s.id === serverId) and if not
found throw a 400 Bad Request (e.g., throw new BadRequestException or
HttpException) with a clear message listing the invalid serverId(s); only return
the mapped array when all IDs are valid and handle the case where
command.mcpServers is undefined/null by leaving resolvedMcpServers undefined or
empty as intended.

In
`@libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts`:
- Around line 59-74: The code assumes APIError.headers is a plain object and
uses bracket access for request-id and retry-after, but APIError.headers is a
Headers instance; replace bracket access with Headers.get(...) to read values
(e.g., use err.headers?.get('request-id') for requestId and
err.headers?.get('retry-after') for parseRetryAfter), then pass those retrieved
strings into AgentRuntimeUnauthorizedError / AgentRuntimeForbiddenError /
AgentRuntimeNotFoundError / AgentRuntimeRateLimitedError (and into
parseRetryAfter) so request correlation and 429 backoff hints work correctly in
anthropic-agent-runtime.provider.ts where APIError, parseRetryAfter, and the
AgentRuntime* error constructors are used.
- Around line 114-135: The createAgent method is wrapping the non-idempotent
upstream call (client.beta.agents.create) inside withRetry which can duplicate
resources if the first request succeeded but the response was lost; update
createAgent (and the similar beta.environments.create call) to either stop using
withRetry for the provider-side create path or, if the Anthropic SDK supports a
request-level idempotency key for beta.agents.create / beta.environments.create,
pass a stable idempotencyKey (derived from input or a persisted mapping) to the
SDK call so retries are safe—locate the calls in createAgent and the environment
creation block, remove the retry wrapper or add idempotencyKey handling and
ensure normaliseError still runs on unretriable failures.
- Around line 193-202: The PATCH logic currently calls buildToolsPayload with
undefined when only one side (patch.tools or patch.mcpServers) is provided,
which removes untouched tool configuration; to fix, when computing toolTypes and
mcpServers for buildToolsPayload in anthropic-agent-runtime.provider.ts, merge
the incoming patch values with the existing runtime's current values instead of
passing undefined: derive currentToolTypes = patch.tools ?
patch.tools.map(t=>t.name) : existingRuntime.tools?.map(t=>t.name) and
currentMcpServers = patch.mcpServers ?
patch.mcpServers.map(s=>({name:s.name,url:s.url})) :
existingRuntime.mcp_servers?.map(s=>({name:s.name,url:s.url})), then call
buildToolsPayload(currentToolTypes, currentMcpServers) and only assign
updatePayload.tools if (patch.tools !== undefined || patch.mcpServers !==
undefined) && toolsPayload.length > 0 so untouched toolsets are preserved when
the caller did not patch them.

In `@packages/shared/src/dto/agent/managed-runtime.dto.ts`:
- Around line 57-64: AgentMcpServerDto currently includes a raw authToken which
can leak secrets via cross-layer responses; change the public/shared response
shape by removing authToken from AgentMcpServerDto and replace it with a safe
indicator (e.g., hasAuthToken: boolean or masked metadata) and create a separate
write/update DTO (e.g., AgentMcpServerUpdateDto or AgentMcpServerWriteDto) that
contains the authToken for create/update flows; update all references/uses of
AgentMcpServerDto and any consumers (including where AgentRuntimeConfigDto
embeds MCP server info) to use the new safe read DTO for responses and the write
DTO for input paths so the secret is never emitted in read payloads or logs.
🪄 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: eac1a4ce-0e3a-464f-a80e-0c5d52543027

📥 Commits

Reviewing files that changed from the base of the PR and between 1f9fa26 and 7c38fe8.

📒 Files selected for processing (17)
  • apps/api/src/app/agents/agents.controller.ts
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/e2e/managed-agent.e2e.ts
  • apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts
  • apps/api/src/app/agents/usecases/delete-agent/delete-agent.command.ts
  • apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.command.ts
  • apps/api/src/app/agents/usecases/provision-managed-agent/provision-managed-agent.usecase.ts
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/components/agents/agents-list.tsx
  • apps/dashboard/src/components/agents/create-agent-dialog.tsx
  • apps/dashboard/src/components/agents/delete-agent-dialog.tsx
  • apps/dashboard/src/pages/agent-details.tsx
  • libs/application-generic/src/agent-runtimes/anthropic/anthropic-agent-runtime.provider.ts
  • libs/application-generic/src/agent-runtimes/i-agent-runtime-provider.ts
  • packages/shared/src/dto/agent/managed-runtime.dto.ts
  • packages/shared/src/entities/integration/credential.interface.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/shared/src/entities/integration/credential.interface.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts
  • apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts
  • apps/dashboard/src/components/agents/create-agent-dialog.tsx

Comment thread apps/api/src/app/agents/e2e/managed-agent.e2e.ts Outdated
Comment thread apps/api/src/app/agents/e2e/managed-agent.e2e.ts
Comment thread apps/dashboard/src/components/agents/delete-agent-dialog.tsx
- Enhanced the agent runtime configuration by adding a `skills` field to the relevant DTOs and commands, allowing for better integration of skills during agent updates and creation.
- Updated the `UpdateAgentRuntimeConfigCommand` and `PatchAgentRuntimeConfigRequestDto` to include the new `skills` property, ensuring it is validated and processed correctly.
- Modified the `AgentsController` to handle the `skills` input in the agent creation process, improving the overall functionality and flexibility of agent management.

These changes facilitate a more comprehensive agent configuration, enabling users to specify skills alongside other runtime parameters.
- Introduced a new `creationSource` field across various agent-related DTOs and commands to track the origin of agent creation within the Novu Dashboard.
- Updated `CreateAgentRequestDto`, `AgentResponseDto`, and `CreateAgentCommand` to include the `creationSource` property, ensuring it is optional and validated.
- Enhanced the `AgentsController` and related mappers to handle the new field, improving the overall agent management experience.

These changes provide better insights into how agents are created, facilitating improved tracking and management.
Comment thread apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts Fixed
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 (3)
libs/dal/src/repositories/agent/agent.schema.ts (2)

31-34: ⚡ Quick win

Consider adding a default value for runtime.

The runtime field has no default, which means existing agents will have undefined runtime. Per the entity documentation, absence is treated as 'self-hosted' for backward compatibility, but this implicit behavior creates ambiguity in queries and application logic.

Adding default: 'self-hosted' would make the data model explicit and simplify query predicates (e.g., finding all self-hosted agents wouldn't need { $or: [{ runtime: 'self-hosted' }, { runtime: { $exists: false } }] }).

📝 Proposed fix to add explicit default
 runtime: {
   type: Schema.Types.String,
   enum: ['self-hosted', 'managed'],
+  default: 'self-hosted',
 },

Based on past review comments: scopsy previously asked "Wonder if we should default here?" on this field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/dal/src/repositories/agent/agent.schema.ts` around lines 31 - 34, The
runtime field in the Agent schema currently has type: Schema.Types.String and
enum ['self-hosted','managed'] but no default, leaving old documents undefined;
update the runtime schema definition (the runtime property in agent.schema.ts)
to include default: 'self-hosted' so new records are explicit and queries no
longer need to special-case missing runtime values.

60-61: ⚡ Quick win

Add indexes for runtime-based queries.

The schema currently indexes _environmentId and identifier+_environmentId, but there are no indexes on the new runtime or managedRuntime.providerId fields. If the application queries agents by runtime type (e.g., "find all managed agents") or by provider (e.g., "find all Anthropic agents"), these queries will require collection scans.

🔍 Proposed indexes for common runtime queries
 agentSchema.index({ _environmentId: 1 });
 agentSchema.index({ identifier: 1, _environmentId: 1 }, { unique: true });
+agentSchema.index({ runtime: 1, _environmentId: 1 });
+agentSchema.index({ 'managedRuntime.providerId': 1, _environmentId: 1 }, { sparse: true });

Note: The managedRuntime.providerId index should be sparse since only managed-runtime agents will have this field.

As per coding guidelines: "Review with focus on data integrity and query performance. Check for proper MongoDB indexes on new queries."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/dal/src/repositories/agent/agent.schema.ts` around lines 60 - 61, The
schema is missing indexes for runtime-based queries; add indexes via
agentSchema.index to cover queries on the runtime field and on
managedRuntime.providerId (use a sparse index for managedRuntime.providerId
since only managed agents have it) so lookups like "find by runtime" and "find
by managedRuntime.providerId" avoid collection scans; update the file to add
agentSchema.index({ runtime: 1 }) and agentSchema.index({
"managedRuntime.providerId": 1 }, { sparse: true }) alongside the existing
indexes.
apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts (1)

70-96: ⚖️ Poor tradeoff

Transaction ordering exposes a small inconsistency window.

The provider delete (line 95) executes before the local transaction begins (line 39). If the provider delete succeeds but the local transaction fails (e.g., database unavailable), the external agent is deleted but the local record remains.

While Mongoose transactions can be retried and will eventually succeed, this creates a brief inconsistency where the provider agent is gone but Novu still shows it.

Consider moving the provider delete inside the transaction scope if the runtime provider SDK supports idempotent deletes. This ensures atomicity: both succeed or both fail together. However, this increases transaction duration and may not be worth the complexity if provider SDKs don't handle idempotency well.

await this.agentRepository.withTransaction(async (session) => {
  // Delete from provider first (may throw and abort transaction)
  if (agent.runtime === 'managed' && agent.managedRuntime && shouldDeleteFromProvider) {
    await this.deleteFromProvider(agent.managedRuntime, command);
  }
  
  // Then clean up locally
  await this.cleanupNovuEmail.cleanupForAgent(agent._id, command.environmentId, command.organizationId, session);
  // ... rest of transaction
});

As per coding guidelines: "Check for proper error handling" and "Review with focus on data integrity" (libs/dal patterns).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts` around
lines 70 - 96, The provider delete currently runs outside the DB transaction and
can leave the external agent removed while the local delete fails; move the call
to deleteFromProvider(...) into the agentRepository.withTransaction(...)
callback before the local cleanup so both operations occur inside the same
transaction scope (use the same session passed to
cleanupNovuEmail.cleanupForAgent and other repository calls); ensure runtime
provider deletes are idempotent or handle delete errors to abort the transaction
(references: deleteFromProvider, agentRepository.withTransaction,
cleanupNovuEmail.cleanupForAgent, DeleteAgentCommand, managedRuntime).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts`:
- Around line 83-91: The current early returns after checking integration and
decryptedCredentials.apiKey in delete-agent.usecase.ts silently skip provider
cleanup and can orphan external agents; update the logic around
decryptCredentials(...) and the call to deleteFromProvider(...) so that either
(Option 1) you throw/return a propagated error to abort the local deletion when
integration or apiKey is missing (fail the whole delete flow), or (Option 2) you
convert the early returns into robust logged warnings that include the agent id
and externalAgentId and ensure deleteFromProvider is attempted only when valid
credentials exist (fail-open); pick one strategy, implement it in the
deleteFromProvider call path and surrounding checks (refer to
decryptCredentials, integration, and deleteFromProvider symbols) and ensure the
function returns/throws or logs appropriately so external cleanup is not
silently skipped.

---

Nitpick comments:
In `@apps/api/src/app/agents/usecases/delete-agent/delete-agent.usecase.ts`:
- Around line 70-96: The provider delete currently runs outside the DB
transaction and can leave the external agent removed while the local delete
fails; move the call to deleteFromProvider(...) into the
agentRepository.withTransaction(...) callback before the local cleanup so both
operations occur inside the same transaction scope (use the same session passed
to cleanupNovuEmail.cleanupForAgent and other repository calls); ensure runtime
provider deletes are idempotent or handle delete errors to abort the transaction
(references: deleteFromProvider, agentRepository.withTransaction,
cleanupNovuEmail.cleanupForAgent, DeleteAgentCommand, managedRuntime).

In `@libs/dal/src/repositories/agent/agent.schema.ts`:
- Around line 31-34: The runtime field in the Agent schema currently has type:
Schema.Types.String and enum ['self-hosted','managed'] but no default, leaving
old documents undefined; update the runtime schema definition (the runtime
property in agent.schema.ts) to include default: 'self-hosted' so new records
are explicit and queries no longer need to special-case missing runtime values.
- Around line 60-61: The schema is missing indexes for runtime-based queries;
add indexes via agentSchema.index to cover queries on the runtime field and on
managedRuntime.providerId (use a sparse index for managedRuntime.providerId
since only managed agents have it) so lookups like "find by runtime" and "find
by managedRuntime.providerId" avoid collection scans; update the file to add
agentSchema.index({ runtime: 1 }) and agentSchema.index({
"managedRuntime.providerId": 1 }, { sparse: true }) alongside the existing
indexes.
🪄 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: 7c43953b-ab53-4ab7-b8a4-9e536472a52f

📥 Commits

Reviewing files that changed from the base of the PR and between 7c38fe8 and 1dfea32.

📒 Files selected for processing (13)
  • apps/api/src/app/agents/agents.controller.ts
  • apps/api/src/app/agents/dtos/agent-response.dto.ts
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/dtos/create-agent-request.dto.ts
  • apps/api/src/app/agents/mappers/agent-response.mapper.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/delete-agent/delete-agent.usecase.ts
  • apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.command.ts
  • apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts
  • libs/dal/src/repositories/agent/agent.entity.ts
  • libs/dal/src/repositories/agent/agent.schema.ts
  • packages/shared/src/dto/agent/managed-runtime.dto.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/api/src/app/agents/mappers/agent-response.mapper.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.command.ts
  • apps/api/src/app/agents/dtos/agent-response.dto.ts
  • packages/shared/src/dto/agent/managed-runtime.dto.ts
  • apps/api/src/app/agents/usecases/update-agent-runtime-config/update-agent-runtime-config.usecase.ts
  • apps/api/src/app/agents/dtos/create-agent-request.dto.ts
  • apps/api/src/app/agents/dtos/agent-runtime-config.dto.ts
  • apps/api/src/app/agents/usecases/create-agent/create-agent.usecase.ts
  • apps/api/src/app/agents/usecases/create-agent/create-agent.command.ts
  • apps/api/src/app/agents/agents.controller.ts

- Deleted the `GET /v1/agents/runtime-providers` endpoint from the `AgentsController`, streamlining the API by removing unused functionality.
- Removed `AgentRuntimeProviderResponseDto` and associated capabilities DTOs from the codebase, as they are no longer needed.
- Introduced `AgentSkillInputDto` in the `agent-runtime-config.dto.ts` file to enhance agent skill management, ensuring better integration for future features.

These changes simplify the agent management API and prepare the codebase for upcoming enhancements related to agent skills.
…ation

- Updated E2E tests to manage environment variables for conversational agents and managed runtime flags, ensuring proper setup and teardown.
- Refactored `ProvisionManagedAgentCommand` to enforce the `providerId` as an enum, improving validation.
- Enhanced `ProvisionManagedAgent` use case to handle agent provisioning more robustly, including error handling for non-existent agents.
- Modified API types for patching agent configurations to allow optional `externalId` fields for MCP servers and tools.
- Improved error handling in the `AnthropicAgentRuntimeProvider` for better clarity and response management.

These changes improve the reliability and clarity of managed agent operations, enhancing the overall user experience.
- Enhanced `CreateAgentRequestDto` to require `name` and `identifier` fields, improving clarity in agent creation requirements.
- Updated validation logic in `CreateAgent` use case to throw `BadRequestException` for missing `name` and `identifier` in non-adopt mode.
- Modified API documentation to reflect changes in required fields for agent creation.

These updates ensure better validation and error handling during agent provisioning, enhancing the overall user experience.
…ypeEnum in workflow preferences and related use cases

- Updated WorkflowPreferencesDto, PreviewUsecase, and various command files to use ChannelTypeEnum instead of NotificationChannelTypeEnum for channel preferences.
- Adjusted related types and interfaces to ensure consistency across the application.
- These changes enhance type safety and align the codebase with the updated channel type definitions.
…actoryModule directly

- Replaced the import of AgentRuntimeFactoryModule from the package barrel with a direct import from the source module to enable proper stubbing in tests.
- This change addresses issues with TypeScript's `__exportStar` helper, ensuring that stubbing works as intended across related use cases.

These updates enhance the reliability of E2E tests for managed agents by improving the stubbing mechanism.
@djabarovgeorge djabarovgeorge changed the title feat(agents): managed-runtime mode with Claude Platform integration fixes NV-7618 feat(api-service): managed-runtime mode with Claude Platform integration fixes NV-7618 May 12, 2026
- Modified the expected URL for the Slack MCP server in the managed agent E2E tests to reflect the correct endpoint.
- This change ensures that the tests accurately validate the agent creation process with the updated MCP server configuration.
@djabarovgeorge djabarovgeorge marked this pull request as ready for review May 12, 2026 19:50
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Performed agentic security review on current PR head. Reporting 1 net-new high-severity finding after module triage and deduplication.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

…NV-7618)

PATCH /agents/:id/runtime/config previously forwarded caller-supplied
{name, url} MCP entries straight to the provider, while provisioning
already restricted IDs to the trusted CLAUDE_MCP_SERVERS catalog. A
caller with AGENT_WRITE could swap in attacker-controlled MCP endpoints
on a managed agent, enabling tool-chain hijack via provider-side calls.

Resolve every incoming MCP entry against the catalog on the update path,
discarding the client-supplied url and using the catalog value. Extract
the policy into a shared utils helper used by both provisioning and
update paths so the rule lives in one place.

Co-authored-by: Cursor <[email protected]>
@djabarovgeorge djabarovgeorge merged commit a9ee5c6 into next May 12, 2026
36 checks passed
@djabarovgeorge djabarovgeorge deleted the cursor/managed-agents-claude-platform branch May 12, 2026 21:30
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.

3 participants