Conversation
…n NV-7373 Add bridgeUrl, devBridgeUrl, and devBridgeActive fields to AgentEntity and schema. Thread them through ResolvedAgentConfig and replace the environment-level bridge lookup in BridgeExecutorService with agent-level two-tier resolution (dev bridge -> production bridge -> null). Made-with: Cursor
…uard NV-7373 Extend agent CRUD DTOs, command, and usecase with bridgeUrl, devBridgeUrl, and devBridgeActive fields. Add dedicated PUT /v1/agents/:identifier/bridge endpoint (ExternalApiAccessible for CLI) that reuses the update usecase. The usecase refuses to activate dev bridges on production environments (403). Made-with: Cursor
Made-with: Cursor
…ev NV-7373 Made-with: Cursor
…V-7373 Made-with: Cursor
…NV-7373 Made-with: Cursor
…ork discover NV-7373 Made-with: Cursor
✅ Deploy preview added
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds per-agent bridge configuration (production and dev URLs + dev override), new PUT bridge endpoint, persistence and resolver changes, executor URL selection, dashboard UI/toggle, CLI dev/init/sync discovery & activation flows, framework JSX/runtime additions, and tests; blocks dev-override changes in production environments. Changes
Sequence Diagram(s)sequenceDiagram
participant DevCLI as Dev CLI
participant Tunnel as Local Tunnel
participant Bridge as Dev Bridge Endpoint
participant API as Novu API
participant DB as MongoDB
DevCLI->>Tunnel: start tunnel, compute bridgeUrl
DevCLI->>Bridge: GET {bridgeUrl}?action=discover
Bridge-->>DevCLI: { agents: [{agentId}] }
DevCLI->>API: PUT /v1/agents/{agentId}/bridge { devBridgeUrl, devBridgeActive: true } (Authorization: ApiKey)
API->>DB: Update agent.bridgeUrl / devBridgeUrl / devBridgeActive
DB-->>API: ack
API-->>DevCLI: 200 OK or 403 Forbidden
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
…o, and cleanup NV-7373 - Add --agent-identifier flag to `novu init` so the dashboard onboarding can pass the exact agent identifier into the scaffolded template - Add --template flag plumbing through init → createApp → installTemplate - Add APP_AGENT to TemplateTypeEnum - Fix JSX Card elements not being serialized by ctx.reply() — use chat's isJSX()/toCardElement() before the wire-format check - Add --no-studio flag to `novu dev` to skip starting Studio server - Remove DEV badge from agent list table (keep on detail page only) - Clean up mock-agent-handler e2e fixture Made-with: Cursor
…-7373 - Load .env.local before .env so NOVU_SECRET_KEY set by `novu init` is picked up automatically by `novu dev` - Fix Commander --no-studio flag: use `studio: boolean` (Commander's negation convention) instead of `noStudio` Made-with: Cursor
Reconcile templateChoice (from next) with agentIdentifier (from this branch). Remove redundant template: templateFlag param since templateChoice already carries the template selection. Made-with: Cursor
Verifies that ctx.reply() correctly serializes JSX-produced Card elements (via isJSX/toCardElement) — distinct from the existing function-call Card test. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
apps/dashboard/src/components/agents/agent-sidebar-widget.tsx (2)
70-102: Add blank line beforereturnstatement inBridgeUrlSection.As per coding guidelines, include a blank line before every
returnstatement.🔧 Proposed fix
function BridgeUrlSection({ agent, canWrite, isUpdatePending, onUpdate }: BridgeUrlSectionProps) { const activeBridgeUrl = agent.devBridgeActive && agent.devBridgeUrl ? agent.devBridgeUrl : agent.bridgeUrl; + return ( <>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/agents/agent-sidebar-widget.tsx` around lines 70 - 102, The function BridgeUrlSection currently has no blank line immediately before its return; update the BridgeUrlSection function so there is a single blank line separating the function-level declarations (e.g., the activeBridgeUrl const) from the return statement to comply with the coding guideline; modify the body of BridgeUrlSection (the area after const activeBridgeUrl = ...) to insert a blank line before the return (...) while leaving the return JSX and existing logic (Badges, TruncatedUrl, SidebarRow, Switch, onUpdate) unchanged.
48-61: Add blank line beforereturnstatement.As per coding guidelines, include a blank line before every
returnstatement.🔧 Proposed fix
function TruncatedUrl({ url }: { url: string }) { + return ( <Tooltip>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/agents/agent-sidebar-widget.tsx` around lines 48 - 61, Add a blank line before the return statement in the TruncatedUrl component to follow the coding guideline: locate the function TruncatedUrl and insert a single empty line immediately above the line starting with "return (" so the return is separated from the preceding function signature/body.packages/novu/src/commands/init/templates/index.ts (1)
74-80: Consider sanitizingagentIdentifierbefore string interpolation.If
agentIdentifiercontains characters like',\, or$, the generated code could have syntax errors or unexpected behavior. Consider validating the identifier matches expected slug format before interpolation.🛡️ Proposed validation
if (template === TemplateTypeEnum.APP_AGENT && agentIdentifier) { + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agentIdentifier)) { + throw new Error(`Invalid agent identifier: ${agentIdentifier}. Must be a valid slug.`); + } const agentFile = path.join(root, 'app', 'novu', 'agents', 'support-agent.tsx'); await fs.writeFile( agentFile, (await fs.readFile(agentFile, 'utf8')).replace("agent('support-agent',", `agent('${agentIdentifier}',`) ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/index.ts` around lines 74 - 80, The code interpolates agentIdentifier directly into the agent(...) call (see TemplateTypeEnum.APP_AGENT branch and variable agentFile pointing to support-agent.tsx), which can break generated code if agentIdentifier contains quotes or other illegal chars; validate and sanitize agentIdentifier first (e.g., enforce a slug/regex allowed chars like [a-z0-9-_] or escape problematic characters) and only write the file after the identifier passes validation or has been safely escaped to ensure the generated agent('...') string is syntactically valid.packages/novu/src/commands/dev/dev.ts (1)
186-205: Add blank line beforereturnstatements.As per coding guidelines, include a blank line before every
returnstatement.🔧 Proposed fix
async function discoverAgents(endpointUrl: string): Promise<string[]> { try { const res = await fetch(`${endpointUrl}?action=discover`, { method: 'GET', headers: { accept: 'application/json', 'Content-Type': 'application/json', 'User-Agent': `novu@${version}`, }, }); - if (!res.ok) return []; + if (!res.ok) { + return []; + } const data = (await res.json()) as DiscoverResponse; return (data.agents ?? []).map((a) => a.agentId); } catch { + return []; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/dev/dev.ts` around lines 186 - 205, In the discoverAgents function ensure there is a blank line immediately before each return statement; update the GET response error branch (before "return []"), the successful mapping return (before "return (data.agents ?? []).map(...)"), and the catch block return (before "return []") so each return is preceded by one empty line to satisfy the style guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts`:
- Around line 30-32: The current guard calls assertNotProductionEnvironment
whenever command.devBridgeUrl is present in the request, which blocks callers
from clearing a stale prod URL; change the condition in update-agent.usecase.ts
so you only assert for unsafe transitions: when command.devBridgeActive === true
OR when command.devBridgeUrl is being set to a non-null value (i.e. devBridgeUrl
!== undefined && devBridgeUrl !== null). Update the identical check around lines
113-122 as well (same use of command.devBridgeActive / command.devBridgeUrl and
the assertNotProductionEnvironment call) so clearing with null is allowed but
enabling or setting a non-null URL still triggers the production guard.
- Around line 16-24: The current hasGeneralFields treats any defined
command.behavior (even {}) as an update, but the updateOne payload only persists
specific nested flags so empty behavior objects pass validation but cause no
changes; create a helper (e.g., hasValidBehavior) and update the
hasGeneralFields check to use it: consider behavior as an update only if
command.behavior is a non-null object with at least one of the concrete nested
properties you later persist (e.g., reactions or other persisted keys) and for
nested objects ensure they contain non-empty own properties
(Object.keys(...).length > 0) so behavior: {} or behavior: { reactions: {} } is
rejected; also ensure the code building the update payload (the updateOne call)
ignores undefined/no-op behavior fields so you don't send empty updates.
In `@packages/novu/src/commands/dev/dev.ts`:
- Around line 207-230: The API path in activateAgentBridge currently
interpolates agentId directly into the URL which can break when agentId contains
characters like '/' or '%'; update the fetch call URL to use
encodeURIComponent(agentId) (e.g., replace
`${apiUrl}/v1/agents/${agentId}/bridge` with
`${apiUrl}/v1/agents/${encodeURIComponent(agentId)}/bridge`) so special
characters are safely encoded while keeping all other logic (headers/body/status
handling) unchanged.
In `@packages/novu/src/commands/sync.ts`:
- Around line 64-77: The API path concatenates agent.agentId directly into the
URL in the agents.map callback inside sync.ts, which can break if agent IDs
contain special characters; update the axios PUT call that builds
`${apiUrl}/v1/agents/${agent.agentId}/bridge` to URL-encode the agent identifier
(use encodeURIComponent or equivalent) when constructing the path so the request
URL is safe and robust (adjust any other occurrences in the same module if
present).
- Around line 52-56: The discovery URL in syncAgentBridgeUrls is built
incorrectly by appending "/novu" to bridgeUrl; change the URL construction to
use `${bridgeUrl}?action=discover` (i.e., remove the redundant "/novu") so that
when bridgeUrl already contains the /api/novu route the discovery call is
correct; update the discoverUrl variable and ensure axios.get still uses the
same timeout and typing (DiscoverResponse) and that subsequent use of
agents/discoverRes remains unchanged.
---
Nitpick comments:
In `@apps/dashboard/src/components/agents/agent-sidebar-widget.tsx`:
- Around line 70-102: The function BridgeUrlSection currently has no blank line
immediately before its return; update the BridgeUrlSection function so there is
a single blank line separating the function-level declarations (e.g., the
activeBridgeUrl const) from the return statement to comply with the coding
guideline; modify the body of BridgeUrlSection (the area after const
activeBridgeUrl = ...) to insert a blank line before the return (...) while
leaving the return JSX and existing logic (Badges, TruncatedUrl, SidebarRow,
Switch, onUpdate) unchanged.
- Around line 48-61: Add a blank line before the return statement in the
TruncatedUrl component to follow the coding guideline: locate the function
TruncatedUrl and insert a single empty line immediately above the line starting
with "return (" so the return is separated from the preceding function
signature/body.
In `@packages/novu/src/commands/dev/dev.ts`:
- Around line 186-205: In the discoverAgents function ensure there is a blank
line immediately before each return statement; update the GET response error
branch (before "return []"), the successful mapping return (before "return
(data.agents ?? []).map(...)"), and the catch block return (before "return []")
so each return is preceded by one empty line to satisfy the style guideline.
In `@packages/novu/src/commands/init/templates/index.ts`:
- Around line 74-80: The code interpolates agentIdentifier directly into the
agent(...) call (see TemplateTypeEnum.APP_AGENT branch and variable agentFile
pointing to support-agent.tsx), which can break generated code if
agentIdentifier contains quotes or other illegal chars; validate and sanitize
agentIdentifier first (e.g., enforce a slug/regex allowed chars like [a-z0-9-_]
or escape problematic characters) and only write the file after the identifier
passes validation or has been safely escaped to ensure the generated
agent('...') string is syntactically valid.
🪄 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: 923ca0c6-1d7b-40df-b252-e27defd00e5e
📒 Files selected for processing (35)
.sourceapps/api/src/app/agents/agents.controller.tsapps/api/src/app/agents/dtos/agent-response.dto.tsapps/api/src/app/agents/dtos/index.tsapps/api/src/app/agents/dtos/update-agent-bridge-request.dto.tsapps/api/src/app/agents/dtos/update-agent-request.dto.tsapps/api/src/app/agents/e2e/agents.e2e.tsapps/api/src/app/agents/e2e/mock-agent-handler.tsapps/api/src/app/agents/mappers/agent-response.mapper.tsapps/api/src/app/agents/services/agent-config-resolver.service.tsapps/api/src/app/agents/services/bridge-executor.service.tsapps/api/src/app/agents/usecases/update-agent/update-agent.command.tsapps/api/src/app/agents/usecases/update-agent/update-agent.usecase.tsapps/dashboard/src/api/agents.tsapps/dashboard/src/components/agents/agent-details-header.tsxapps/dashboard/src/components/agents/agent-sidebar-widget.tsxlibs/dal/src/repositories/agent/agent.entity.tslibs/dal/src/repositories/agent/agent.schema.tspackages/framework/jsx-dev-runtime/package.jsonpackages/framework/package.jsonpackages/framework/src/client.tspackages/framework/src/jsx-dev-runtime.tspackages/framework/src/resources/agent/agent.context.tspackages/framework/src/resources/agent/agent.test.tspackages/framework/src/types/discover.types.tspackages/framework/tsup.config.tspackages/novu/src/commands/dev/dev.tspackages/novu/src/commands/dev/types.tspackages/novu/src/commands/init/create-app.tspackages/novu/src/commands/init/index.tspackages/novu/src/commands/init/templates/index.tspackages/novu/src/commands/init/templates/types.tspackages/novu/src/commands/sync.tspackages/novu/src/constants/constants.tspackages/novu/src/index.ts
💤 Files with no reviewable changes (1)
- apps/api/src/app/agents/e2e/mock-agent-handler.ts
- Reject empty behavior payloads (behavior: {} no longer counts as update)
- Allow clearing devBridgeUrl with null on production environments
- URL-encode agentId in CLI bridge activation and sync paths
- Remove redundant /novu from sync discovery URL
- Validate agentIdentifier as slug before template interpolation
- Add blank lines before return statements per style convention
- Use NOVU_SECRET_KEY/NOVU_API_URL from env instead of CLI flags
Made-with: Cursor
Keep per-agent bridge URL resolution from config (HEAD) over the older environment-level lookup introduced on next. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/novu/src/commands/dev/dev.ts (1)
198-207: Add the required blank line before thesereturnstatements.The new helper branches here don't follow the repo's return-spacing rule.
As per coding guidelines "Include a blank line before every
returnstatement".Also applies to: 222-234, 241-241
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/dev/dev.ts` around lines 198 - 207, Add a blank line immediately before every return in the helper that handles the fetch response: insert an empty line before the `return [];` after the `if (!res.ok)` check, before the `return (data.agents ?? []).map((a) => a.agentId);` line, and before the `return [];` inside the `catch` block; apply the same blank-line-before-return rule to the other new helper branches that return early in this area so all `return` statements in this function are preceded by a blank line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/dashboard/src/components/agents/agent-sidebar-widget.tsx`:
- Around line 52-56: The URL tooltip trigger uses TooltipTrigger with a
non-focusable span, preventing keyboard users from revealing the tooltip; make
the trigger element focusable so TooltipTrigger can open on keyboard
focus/Enter/Space by replacing the plain <span> used around {url} with a
focusable element (e.g., a <button> or the same <span> with tabIndex={0} and
role="button"/aria-label) while preserving the existing className styling (the
text-text-sub font-code text-label-xs block max-w-[160px] truncate
tracking-tight) so visual layout doesn't change; ensure the element remains
focusable and accessible to screen readers so TooltipTrigger responds to
keyboard interactions.
- Around line 72-83: The DEV badge rendering in agent-sidebar-widget.tsx can be
misleading because it checks only agent.devBridgeActive while the actual
activeBridgeUrl is computed from agent.devBridgeActive && agent.devBridgeUrl;
update the badge condition to reflect the true active source by checking both
agent.devBridgeActive and agent.devBridgeUrl (or compare activeBridgeUrl ===
agent.devBridgeUrl) before rendering the Badge inside the SidebarRow so the DEV
badge only appears when the dev bridge URL is actually being used.
In `@packages/novu/src/commands/dev/dev.ts`:
- Around line 211-234: activateAgentBridge currently treats non-2xx HTTP
responses as best-effort but does not catch network/DNS/TLS exceptions from
fetch, allowing those errors to bubble up and abort the dev command; wrap the
fetch call (and subsequent response handling) in a try/catch inside
activateAgentBridge, catch any thrown errors from fetch, log a yellow warning
using chalk with the agentId and error message, and return false on error so
bridge activation failures remain best-effort and do not crash devCommand.
---
Nitpick comments:
In `@packages/novu/src/commands/dev/dev.ts`:
- Around line 198-207: Add a blank line immediately before every return in the
helper that handles the fetch response: insert an empty line before the `return
[];` after the `if (!res.ok)` check, before the `return (data.agents ??
[]).map((a) => a.agentId);` line, and before the `return [];` inside the `catch`
block; apply the same blank-line-before-return rule to the other new helper
branches that return early in this area so all `return` statements in this
function are preceded by a blank line.
🪄 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: 0f5aad58-f1f5-46c5-be5a-a1458f4f0d98
📒 Files selected for processing (10)
apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.tsapps/dashboard/src/components/agents/agent-sidebar-widget.tsxpackages/novu/src/commands/dev/dev.tspackages/novu/src/commands/dev/types.tspackages/novu/src/commands/init/create-app.tspackages/novu/src/commands/init/index.tspackages/novu/src/commands/init/templates/index.tspackages/novu/src/commands/init/templates/types.tspackages/novu/src/commands/sync.tspackages/novu/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/novu/src/commands/init/templates/types.ts
- packages/novu/src/commands/dev/types.ts
- packages/novu/src/commands/init/create-app.ts
- packages/novu/src/commands/init/index.ts
- packages/novu/src/index.ts
- packages/novu/src/commands/init/templates/index.ts
- apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
- packages/novu/src/commands/sync.ts
- Make URL tooltip keyboard-accessible (span → button) - Fix DEV badge showing when devBridgeUrl is missing - Wrap fetch in try/catch so network errors don't crash novu dev Made-with: Cursor
There was a problem hiding this comment.
🧹 Nitpick comments (2)
apps/api/src/app/agents/services/bridge-executor.service.ts (1)
215-240: Bridge URL resolution logic is correct.The priority chain (
devBridgeActive && devBridgeUrl→bridgeUrl→null) aligns with the PR's per-agent bridge specification. Thenew URL()constructor provides implicit validation by throwing on malformed URLs.Minor: per coding guidelines, add a blank line before
returnstatements.♻️ Suggested formatting fix
url.searchParams.set('agentId', agentIdentifier); url.searchParams.set('event', event); + return url.toString(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/src/app/agents/services/bridge-executor.service.ts` around lines 215 - 240, In resolveBridgeUrl, add a blank line before each return to match formatting guidelines: insert an empty line before the early "return null;" after this.logger.warn(...) and another blank line before the final "return url.toString();" to separate logic from return statements; references: resolveBridgeUrl, this.logger.warn, return null, return url.toString().packages/framework/src/resources/agent/agent.test.ts (1)
489-533: Consider asserting serialized JSX children to harden this test.Right now the test verifies card presence/type/title, but a regression in JSX child conversion could still pass. Adding child assertions would make this scenario more robust.
Suggested assertion extension
expect(replyBody.reply.card).toBeDefined(); expect(replyBody.reply.card.type).toBe('card'); expect(replyBody.reply.card.title).toBe('JSX Card'); + expect(replyBody.reply.card.children).toHaveLength(2); + expect(replyBody.reply.card.children[0]).toMatchObject({ type: 'cardText', content: 'Hello from JSX' }); + expect(replyBody.reply.card.children[1]).toMatchObject({ type: 'button', id: 'ok', label: 'OK' }); expect(replyBody.reply.text).toBeUndefined(); expect(replyBody.reply.markdown).toBeUndefined();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/framework/src/resources/agent/agent.test.ts` around lines 489 - 533, The test currently checks only card presence/type/title but should also assert the serialized JSX children to prevent regressions: after parsing replyBody in the 'should serialize JSX Card elements on reply' test, verify that replyBody.reply.card.children includes the CardText content ('Hello from JSX') and that the Button child serializes expected properties (id 'ok', label 'OK', style 'primary'); reference the jsxCard, CardText, Button, testBot and NovuRequestHandler symbols to locate the test and add assertions against replyBody.reply.card.children and the specific child objects for text/button fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/api/src/app/agents/services/bridge-executor.service.ts`:
- Around line 215-240: In resolveBridgeUrl, add a blank line before each return
to match formatting guidelines: insert an empty line before the early "return
null;" after this.logger.warn(...) and another blank line before the final
"return url.toString();" to separate logic from return statements; references:
resolveBridgeUrl, this.logger.warn, return null, return url.toString().
In `@packages/framework/src/resources/agent/agent.test.ts`:
- Around line 489-533: The test currently checks only card presence/type/title
but should also assert the serialized JSX children to prevent regressions: after
parsing replyBody in the 'should serialize JSX Card elements on reply' test,
verify that replyBody.reply.card.children includes the CardText content ('Hello
from JSX') and that the Button child serializes expected properties (id 'ok',
label 'OK', style 'primary'); reference the jsxCard, CardText, Button, testBot
and NovuRequestHandler symbols to locate the test and add assertions against
replyBody.reply.card.children and the specific child objects for text/button
fields.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 96f28d6e-adfa-4a06-9732-7e71ec3b7668
📒 Files selected for processing (3)
apps/api/src/app/agents/e2e/mock-agent-handler.tsapps/api/src/app/agents/services/bridge-executor.service.tspackages/framework/src/resources/agent/agent.test.ts
💤 Files with no reviewable changes (1)
- apps/api/src/app/agents/e2e/mock-agent-handler.ts
Summary
Adds per-agent bridge URLs with a dev/production toggle, replacing the single environment-level bridge URL. Each agent now stores its own
bridgeUrl(production),devBridgeUrl(dev tunnel), anddevBridgeActive(boolean toggle), enabling independent local development without affecting other agents or production traffic.Changes by layer
libs/dal): AddedbridgeUrl,devBridgeUrl,devBridgeActivefields toAgentEntityand schemaapps/api): Extended update DTO/command/usecase, addedPUT /v1/agents/:identifier/bridgeendpoint, production environment guard (403 ifdevBridgeActiveordevBridgeUrlon prod)packages/framework):Client.discover()now includes registered agents;ctx.reply()handles JSX Card elements viaisJSX()/toCardElement()conversionpackages/novu):npx novu devauto-discovers agents and activates dev bridges;--no-studioflag to skip Studio servernpx novu syncsets per-agent productionbridgeUrlnpx novu init --template agent --agent-identifier <id>injects the correct agent identifier into the scaffolded template.env.localis now loaded alongside.envsoNOVU_SECRET_KEYset bynovu initis picked up bynovu devapps/dashboard): Bridge URL config section in agent sidebar with dev override toggle, DEV badge on agent detail header (removed from agent list)Bridge resolution order
flowchart TD A[Inbound message] --> B{devBridgeActive && devBridgeUrl?} B -- Yes --> C[Use devBridgeUrl] B -- No --> D{bridgeUrl set?} D -- Yes --> E[Use bridgeUrl] D -- No --> F[No bridge — skip]Safety
devBridgeActive: trueordevBridgeUrlfrom being set on production environments (returns 403)Test plan
Client.discover()includes agents)npx novu devdiscovers agents and sets dev bridgenpx novu syncsets production bridge URL per agentnpx novu init --template agent --agent-identifier <id>scaffolds app with correct identifierctx.reply()Made with Cursor
What changed
Agents now support per-agent bridge configuration: each agent stores a production bridgeUrl, an optional devBridgeUrl, and a devBridgeActive flag. At runtime the system uses the devBridgeUrl when devBridgeActive is true, otherwise falls back to the agent’s production bridgeUrl; there is no environment-level fallback. The API prohibits enabling dev bridges or setting devBridgeUrl in production to protect live traffic. This enables independent local agent development without impacting other agents or production.
Affected areas
dal: Added bridgeUrl, devBridgeUrl, and devBridgeActive to AgentEntity and the Mongo schema.
api-service: DTOs, command, and usecase accept bridge fields; added PUT /v1/agents/:identifier/bridge; UpdateAgent enforces a PROD guard (returns 403) for dev overrides; BridgeExecutorService resolves bridge URL from per-agent config instead of environment lookup.
framework: Client.discover() now returns registered agents; added a jsx-dev-runtime entry; ctx.reply() converts JSX Card elements via isJSX()/toCardElement() so JSX replies serialize to Card payloads.
dashboard: Agent sidebar includes a Bridge URL section with truncated tooltip and a Dev override switch; agent detail header shows a DEV badge when devBridgeActive is true.
cli (novu): npx novu dev discovers agents and activates per-agent dev bridges (supports --no-studio); npx novu sync writes per-agent production bridgeUrl via the API; npx novu init accepts --agent-identifier and .env.local is loaded before .env so NOVU_API_URL/NOVU_SECRET_KEY from init are honored.
Key technical decisions
Testing
Added framework unit tests for discover() and JSX reply serialization; API E2E tests for bridge CRUD and the production guard are included (some checks pending); the PR documents manual verification steps.