Conversation
✅ Deploy Preview for dashboard-v2-novu-staging canceled.
|
📝 WalkthroughWalkthroughAdds a nested Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant InboundHandler as AgentInboundHandler
participant CredentialSvc as AgentCredentialService
participant Thread
participant DB
participant Bridge
Client->>InboundHandler: Send inbound message (agentId, message)
InboundHandler->>CredentialSvc: resolve(agentId, integrationId)
CredentialSvc-->>InboundHandler: ResolvedPlatformConfig (thinkingIndicatorEnabled)
alt thinkingIndicatorEnabled == true
InboundHandler->>Thread: startTyping('Thinking...')
Thread-->>InboundHandler: started
end
InboundHandler->>DB: persist inbound message / thread state
DB-->>InboundHandler: persisted
InboundHandler->>Bridge: execute bridge with message/thread
Bridge-->>InboundHandler: bridge result
InboundHandler-->>Client: response / ack
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 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 |
f5c8485 to
4b5056b
Compare
…ttings fixes NV-7366 Introduce an `AgentBehavior` subdocument on the Agent entity to hold per-agent runtime behavior settings (starting with `thinkingIndicatorEnabled`). This architecture supports future settings (reactions, interrupt mode, etc.) without adding flat fields to the entity. Defaults to enabled — only an explicit `false` suppresses the indicator. The status text is "Thinking..." (not the generic "typing" label). Made-with: Cursor
4b5056b to
9c46139
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/api/src/app/agents/e2e/agents.e2e.ts (1)
68-99: Prefertry/finallycleanup to avoid leaking test data on assertion failures.If any assertion throws before Line 98, the created agent is never deleted.
♻️ Suggested refactor
it('should update and return agent behavior settings', async () => { const identifier = `e2e-behavior-${Date.now()}`; - - const createRes = await session.testAgent.post('/v1/agents').send({ - name: 'Behavior Agent', - identifier, - }); - - expect(createRes.status).to.equal(201); - expect(createRes.body.data.behavior).to.equal(undefined); - - const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ - behavior: { typingIndicatorEnabled: false }, - }); - - expect(patchRes.status).to.equal(200); - expect(patchRes.body.data.behavior).to.deep.equal({ typingIndicatorEnabled: false }); - - const getRes = await session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`); - - expect(getRes.status).to.equal(200); - expect(getRes.body.data.behavior.typingIndicatorEnabled).to.equal(false); - - const reEnableRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ - behavior: { typingIndicatorEnabled: true }, - }); - - expect(reEnableRes.status).to.equal(200); - expect(reEnableRes.body.data.behavior.typingIndicatorEnabled).to.equal(true); - - await session.testAgent.delete(`/v1/agents/${encodeURIComponent(identifier)}`); + try { + const createRes = await session.testAgent.post('/v1/agents').send({ + name: 'Behavior Agent', + identifier, + }); + + expect(createRes.status).to.equal(201); + expect(createRes.body.data.behavior).to.equal(undefined); + + const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ + behavior: { typingIndicatorEnabled: false }, + }); + + expect(patchRes.status).to.equal(200); + expect(patchRes.body.data.behavior).to.deep.equal({ typingIndicatorEnabled: false }); + + const getRes = await session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`); + + expect(getRes.status).to.equal(200); + expect(getRes.body.data.behavior.typingIndicatorEnabled).to.equal(false); + + const reEnableRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ + behavior: { typingIndicatorEnabled: true }, + }); + + expect(reEnableRes.status).to.equal(200); + expect(reEnableRes.body.data.behavior.typingIndicatorEnabled).to.equal(true); + } finally { + await session.testAgent.delete(`/v1/agents/${encodeURIComponent(identifier)}`); + } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/src/app/agents/e2e/agents.e2e.ts` around lines 68 - 99, The test "should update and return agent behavior settings" can leak the created agent if an assertion fails; wrap the agent lifecycle in a try/finally: create the agent with session.testAgent.post(...) as before, then put all subsequent assertions and patch/get calls inside a try block, and perform the cleanup in a finally block that calls await session.testAgent.delete(`/v1/agents/${encodeURIComponent(identifier)}`) (guarding for a successful create or non-empty identifier and optionally catching/ignoring delete errors so cleanup doesn't fail the test).
🤖 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/e2e/agents.e2e.ts`:
- Around line 79-96: The E2E test uses the wrong field name: replace all
occurrences of thinkingIndicatorEnabled with typingIndicatorEnabled in the
requests and assertions for the agent update flow (update calls made via
session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`), the
subsequent GET via
session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`), and the
response checks on patchRes.body.data.behavior, getRes.body.data.behavior, and
reEnableRes.body.data.behavior) so the PATCH payloads and expect(...) checks
validate typingIndicatorEnabled instead of thinkingIndicatorEnabled.
---
Nitpick comments:
In `@apps/api/src/app/agents/e2e/agents.e2e.ts`:
- Around line 68-99: The test "should update and return agent behavior settings"
can leak the created agent if an assertion fails; wrap the agent lifecycle in a
try/finally: create the agent with session.testAgent.post(...) as before, then
put all subsequent assertions and patch/get calls inside a try block, and
perform the cleanup in a finally block that calls await
session.testAgent.delete(`/v1/agents/${encodeURIComponent(identifier)}`)
(guarding for a successful create or non-empty identifier and optionally
catching/ignoring delete errors so cleanup doesn't fail the test).
🪄 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: 41ac2c61-3121-4786-9085-8e6505b769cf
📒 Files selected for processing (13)
apps/api/src/app/agents/agents.controller.tsapps/api/src/app/agents/dtos/agent-behavior.dto.tsapps/api/src/app/agents/dtos/agent-response.dto.tsapps/api/src/app/agents/dtos/index.tsapps/api/src/app/agents/dtos/update-agent-request.dto.tsapps/api/src/app/agents/e2e/agents.e2e.tsapps/api/src/app/agents/mappers/agent-response.mapper.tsapps/api/src/app/agents/services/agent-credential.service.tsapps/api/src/app/agents/services/agent-inbound-handler.service.tsapps/api/src/app/agents/usecases/update-agent/update-agent.command.tsapps/api/src/app/agents/usecases/update-agent/update-agent.usecase.tslibs/dal/src/repositories/agent/agent.entity.tslibs/dal/src/repositories/agent/agent.schema.ts
✅ Files skipped from review due to trivial changes (4)
- apps/api/src/app/agents/dtos/index.ts
- apps/api/src/app/agents/services/agent-inbound-handler.service.ts
- libs/dal/src/repositories/agent/agent.schema.ts
- apps/api/src/app/agents/dtos/agent-behavior.dto.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- apps/api/src/app/agents/mappers/agent-response.mapper.ts
- apps/api/src/app/agents/dtos/update-agent-request.dto.ts
- apps/api/src/app/agents/dtos/agent-response.dto.ts
- libs/dal/src/repositories/agent/agent.entity.ts
- apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
- apps/api/src/app/agents/usecases/update-agent/update-agent.command.ts
- apps/api/src/app/agents/services/agent-credential.service.ts
| const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ | ||
| behavior: { thinkingIndicatorEnabled: false }, | ||
| }); | ||
|
|
||
| expect(patchRes.status).to.equal(200); | ||
| expect(patchRes.body.data.behavior).to.deep.equal({ thinkingIndicatorEnabled: false }); | ||
|
|
||
| const getRes = await session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`); | ||
|
|
||
| expect(getRes.status).to.equal(200); | ||
| expect(getRes.body.data.behavior.thinkingIndicatorEnabled).to.equal(false); | ||
|
|
||
| const reEnableRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ | ||
| behavior: { thinkingIndicatorEnabled: true }, | ||
| }); | ||
|
|
||
| expect(reEnableRes.status).to.equal(200); | ||
| expect(reEnableRes.body.data.behavior.thinkingIndicatorEnabled).to.equal(true); |
There was a problem hiding this comment.
Use typingIndicatorEnabled instead of thinkingIndicatorEnabled in this E2E flow.
The test currently validates a different field name than the feature contract in the PR objective, which can mask a real API mismatch.
✅ Suggested fix
- const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({
- behavior: { thinkingIndicatorEnabled: false },
- });
+ const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({
+ behavior: { typingIndicatorEnabled: false },
+ });
expect(patchRes.status).to.equal(200);
- expect(patchRes.body.data.behavior).to.deep.equal({ thinkingIndicatorEnabled: false });
+ expect(patchRes.body.data.behavior).to.deep.equal({ typingIndicatorEnabled: false });
const getRes = await session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`);
expect(getRes.status).to.equal(200);
- expect(getRes.body.data.behavior.thinkingIndicatorEnabled).to.equal(false);
+ expect(getRes.body.data.behavior.typingIndicatorEnabled).to.equal(false);
const reEnableRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({
- behavior: { thinkingIndicatorEnabled: true },
+ behavior: { typingIndicatorEnabled: true },
});
expect(reEnableRes.status).to.equal(200);
- expect(reEnableRes.body.data.behavior.thinkingIndicatorEnabled).to.equal(true);
+ expect(reEnableRes.body.data.behavior.typingIndicatorEnabled).to.equal(true);📝 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.
| const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ | |
| behavior: { thinkingIndicatorEnabled: false }, | |
| }); | |
| expect(patchRes.status).to.equal(200); | |
| expect(patchRes.body.data.behavior).to.deep.equal({ thinkingIndicatorEnabled: false }); | |
| const getRes = await session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`); | |
| expect(getRes.status).to.equal(200); | |
| expect(getRes.body.data.behavior.thinkingIndicatorEnabled).to.equal(false); | |
| const reEnableRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ | |
| behavior: { thinkingIndicatorEnabled: true }, | |
| }); | |
| expect(reEnableRes.status).to.equal(200); | |
| expect(reEnableRes.body.data.behavior.thinkingIndicatorEnabled).to.equal(true); | |
| const patchRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ | |
| behavior: { typingIndicatorEnabled: false }, | |
| }); | |
| expect(patchRes.status).to.equal(200); | |
| expect(patchRes.body.data.behavior).to.deep.equal({ typingIndicatorEnabled: false }); | |
| const getRes = await session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`); | |
| expect(getRes.status).to.equal(200); | |
| expect(getRes.body.data.behavior.typingIndicatorEnabled).to.equal(false); | |
| const reEnableRes = await session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`).send({ | |
| behavior: { typingIndicatorEnabled: true }, | |
| }); | |
| expect(reEnableRes.status).to.equal(200); | |
| expect(reEnableRes.body.data.behavior.typingIndicatorEnabled).to.equal(true); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/app/agents/e2e/agents.e2e.ts` around lines 79 - 96, The E2E test
uses the wrong field name: replace all occurrences of thinkingIndicatorEnabled
with typingIndicatorEnabled in the requests and assertions for the agent update
flow (update calls made via
session.testAgent.patch(`/v1/agents/${encodeURIComponent(identifier)}`), the
subsequent GET via
session.testAgent.get(`/v1/agents/${encodeURIComponent(identifier)}`), and the
response checks on patchRes.body.data.behavior, getRes.body.data.behavior, and
reEnableRes.body.data.behavior) so the PATCH payloads and expect(...) checks
validate typingIndicatorEnabled instead of thinkingIndicatorEnabled.
Summary
AgentBehaviorsubdocument on the Agent entity to hold per-agent runtime behavior settings, starting withthinkingIndicatorEnabledbehaviorwithout adding flat fields to the entityfalsesuppresses the indicator. Status text is "Thinking..." (not the generic platform "typing" label)How it works
Data model —
AgentEntitygets an optionalbehavior?: AgentBehaviorsubdocument (interface + inline Mongoose nested paths, following the same pattern asOrganization.branding,Environment.widget,Integration.configurations):API —
PATCH /agents/:identifieraccepts a nestedbehaviorobject. Uses dot-notation$setso individual fields can be updated without overwriting the whole subdocument:{ "behavior": { "thinkingIndicatorEnabled": false } }Runtime —
AgentCredentialService.resolve()readsagent.behavior?.thinkingIndicatorEnabledand resolves it to a boolean (defaulting totrue).AgentInboundHandlergatesthread.startTyping('Thinking...')behind this resolved value.Test plan
PATCHwith behavior persists and round-trips correctlythinkingIndicatorEnabled: falsedoes not trigger indicator on inbound messagesbehaviorset shows "Thinking..." indicator as beforeLinear ticket
https://linear.app/novu/issue/NV-7366/add-typing-indicator-toggle-to-agent-behavior-settings
What changed
Added an optional behavior.typingIndicatorEnabled boolean to per-agent settings and persisted it in the database so agents can opt out of showing typing indicators. The API accepts and returns the nested behavior object on PATCH/GET /agents/:identifier and updates use dot-notation so individual behavior fields can be patched without overwriting the subdocument. At runtime AgentCredentialService resolves the flag (defaulting to enabled) and AgentInboundHandler only calls thread.startTyping() when the setting is not explicitly false.
Affected areas
Key technical decisions
Testing
Added an E2E test covering PATCH/GET persistence and behavior toggling; runtime behavior verified by tests that inbound messages respect typingIndicatorEnabled=false and remain unchanged when true/unset.