Feat/ai agent - #421
Conversation
Adds two agent-facing AI features: a copilot chat panel in the conversation sidebar and a generate-reply button in the reply box. Both run an agentic tool-calling loop whose first tool searches the knowledge base. Snippets are chunked and embedded on save, then searched in memory with brute-force cosine similarity (no pgvector). Providers are split into completion and embedding types. Both are OpenAI-compatible and the API key is encrypted at rest. Admins can also register custom HTTP tools the model can call. A new admin AI settings page covers provider config, snippets, and tools. The v2.6.0 migration and schema add the ai_knowledge_base, embeddings, and ai_tools tables plus the ai_providers type column.
Medium (title + short body): add WIP autonomous AI agent New internal/aiagent package runs AI assistants that reply to customers on conversations assigned to them, grounded on a knowledge base. Also mines resolved conversations for FAQ suggestions. Adds admin UI and the v2.7.0 schema. Still work in progress.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds provider-scoped AI configuration, embeddings, knowledge-base search, custom tools, autonomous assistants, FAQ mining, Copilot chat, and administration interfaces. It also adds migrations, conversation integration, SSRF-controlled outbound clients, avatar cropping, reporting refresh changes, and related UI updates. ChangesAI platform and assistant operations
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
The agent now only replies to a turn the primary contact authored, so a CC'd or plus-address participant hands off to a human instead of driving tool calls under the contact's identity. It also asks the customer to confirm before resolving, and can read the contact's other recent conversations for context.
CSAT was only sent when a human resolved from the UI. AI and automation resolves go straight through UpdateConversationStatus and skipped it. Moved the send there so all paths trigger it. It is idempotent and no-ops when the contact has no email.
Limit custom tools to GET and POST and validate the method on save. GET args now go on the query string and POST args in the body. Also stop sending the raw stored secret when its decryption fails, which would leak ciphertext and fail auth confusingly.
Add a reasoning effort field to the completion provider config, sent as-is to the model. Reasoning models such as GPT-5.x need it set to "none" to use tools. Also switch to max_completion_tokens.
Reply drafts are now written in first person as the agent and never offer to escalate, since a human is already handling the conversation. Add quick preset buttons to the copilot panel and reuse a shared transcript helper.
Show an animated border on the reply box while a reply is generating, and hide generate-reply for private notes. Link an AI assistant message author to the assistants settings page for admins who can manage AI.
Fetch agents through the API in AgentList instead of the shared users store, so the list does not depend on store state populated elsewhere.
A single-file page to test the livechat widget's JWT contact auth locally. Signs an HS256 token in the browser from an editable payload and loads the widget with it.
Stop dropping customer follow-ups sent mid-response, reset the turn cap only on reassignment, send max_tokens for non-reasoning models, and make FAQ approval atomic. Also exclude CSAT surveys from assistant stats and remove unused scoped-search code.
Pass the conversation subject to the AI agent, show the AI's own expectation in the chat widget while it handles a chat, and label AI messages in continuity emails.
…ff, and stats bugs Replies from the AI agent are now converted from markdown to HTML with goldmark before queueing, so bold, links, and lists render properly in the widget, agent app, and email. The prompt now allows simple markdown. Raw HTML in model output is escaped by goldmark, and both frontends sanitize on render anyway. Other fixes bundled in: - validate avatar type and size before creating or updating an assistant, and roll back the assistant if the avatar upload fails after create - return 404 from agent update and API key endpoints for AI assistant identity users, and hide assistants from mention and SLA user pickers - reserve the autonomous assistant's built-in tool names so custom tools cannot shadow them - apply resolve after the reply is posted so the CSAT survey follows the answer instead of preceding it - unassign the assistant on handoff even when the fallback team is the same team - count reopens by status category instead of status name, and exclude CSAT messages from the turn cap - split oversized wrapper divs into child blocks when chunking KB HTML instead of truncating them - return 404 when soft-deleting an already-deleted agent, and keep AI assistants (which have no email) visible in the compact users list
Custom tools are HTTP calls with no read-only guarantee, so a mutating tool could fire from copilot chat or while drafting a reply. Copilot and generate-reply now get only the built-in knowledge search, and custom tools stay exclusive to assistants where admins pick them explicitly. This matches Intercom, Chatwoot, and Freshdesk. The contact identity headers now flow only on the assistant path. Also: - new "Offer handoff to a human" switch on assistants (default on). When off, the hand_off_to_human tool is not registered and the prompt tells the assistant to say it cannot help instead of offering a human. Safety exits (error, max turns, other participant) still unassign as before. - workspace admin instructions from the AI config no longer leak into the customer-facing assistant prompt. - copilot and reply-draft prompts now treat conversation text and tool outputs as untrusted data.
The model marks its trailing confirmation question with a [[confirm]] line. We split that off and send it as its own chat bubble so the widget reads like a real conversation. The customer never sees the marker. Email is left as one combined message since separate bubbles only suit the widget. Confirmation messages are tagged with is_confirmation in meta so they no longer inflate the reply count in assistant stats. Keep the widget typing indicator alive during long agent runs by re-broadcasting every 3s, under the widget's 5s typing expiry. Fix the AI assistant and tool edit forms to stay on the page after save. They now only go back to the list when creating, matching the rest of the admin forms.
…rrors Reasoning models like gpt-5 reject max_tokens and non-default temperature. The client now reads the structured 400, renames max_tokens to max_completion_tokens or drops the bad tuning param, and retries. This removes the old rule that only sent max_completion_tokens when reasoning effort was set. Provider form gets a "Test connection" button that makes one live call with the form values and shows the provider's real error. Embedding test also checks the returned vector length against the Dimensions field. Add an Anthropic preset (their OpenAI-compatible endpoint) and stop pre-filling temperature since blank is safe on every model. Fix the shared Button loader so the spinner is visible on non-filled variants, and polish the copilot panel: bot avatars on messages, dot loader while thinking, cleaner input box, and tab slide-in animation.
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (4)
internal/ai/queries.sql (1)
43-44: 🚀 Performance & Scalability | 🔵 Trivial
get-all-embeddingsloads the entire embeddings table on every search.With no vector index (embeddings stored as
BYTEA), fetching all rows for in-memory similarity works at small scale but degrades linearly as the knowledge base grows (memory, deserialization, and IO per query). If snippet volume is expected to grow, consider pgvector with an ANN index (ivfflat/hnsw) or at least filtering bysource_typeand paginating.🤖 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 `@internal/ai/queries.sql` around lines 43 - 44, Update the get-all-embeddings query and its callers to avoid loading the entire embeddings table for every search: prefer pgvector similarity search with an appropriate ANN index, or minimally add source_type filtering and pagination while preserving search results. Ensure the related embedding retrieval method passes the filter and page parameters instead of unconditionally fetching all rows.internal/conversation/message.go (1)
704-716: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider logging the
json.Marshalerror for diagnostics.
meta, _ := json.Marshal(...)suppresses the error at line 705. Whilejson.Marshalonmap[string]stringcannot fail in practice (Go strings are never nil), logging the error would aid debugging if the input ever changes to a richer type in the future.♻️ Optional: log the marshal error
- meta, _ := json.Marshal(map[string]string{"activity_type": activityType}) + meta, err := json.Marshal(map[string]string{"activity_type": activityType}) + if err != nil { + m.lo.Error("error marshaling activity meta", "activity_type", activityType, "error", 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 `@internal/conversation/message.go` around lines 704 - 716, Handle the error returned by json.Marshal in the activity message construction before assigning Meta, and log it through the existing conversation/message logging mechanism while preserving the current metadata behavior. Update the code around the activity message creation rather than changing the Message fields or activity_type payload.internal/stringutil/htmlchunker.go (1)
273-287: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueOptional: replace rune-by-rune truncation with binary search.
The loop strips one rune at a time and re-tokenizes on each step, giving O(n²) behavior for oversized blocks. Since
TokenizerFuncis monotonic in length, a binary search over the rune length reaches the fit point in O(log n) tokenizer calls.🤖 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 `@internal/stringutil/htmlchunker.go` around lines 273 - 287, Optimize the truncation loop in the boundary-processing logic by replacing rune-by-rune removal with a binary search over the rune slice length. Use the monotonic TokenizerFunc result to find the longest prefix whose token count is at most cfg.MaxTokens, then preserve the existing warning, boundary.Content, boundary.Tokens, and return behavior.internal/ai/ai.go (1)
240-240: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer
ExecContextoverExecfor the prepared statements. golangci-lint (noctx) flags Lines 240 and 298. Threading acontext.ContextthroughUpdateProviderConfig/UpdateProviderand usingExecContextenables cancellation and honors request deadlines on these writes.🤖 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 `@internal/ai/ai.go` at line 240, Update the call sites in the relevant AI manager methods around UpdateProviderConfig and UpdateProvider to accept and propagate a context.Context, then replace the prepared-statement Exec calls with ExecContext using that context. Preserve the existing write behavior while ensuring request cancellation and deadlines reach both updates.Source: Linters/SAST tools
🤖 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 `@cmd/aiagent.go`:
- Around line 48-56: Make the assistant/avatar mutation flows failure-safe in
cmd/aiagent.go: in the CreateAssistant flow around lines 48-56, replace sole
best-effort deletion rollback with coordinated cleanup that prevents partial
state; in the avatar-application flow around lines 78-83, restore the
assistant’s prior fields when applyAssistantAvatar fails; and around lines
171-179, update deletion ordering so media is not removed while database records
still reference it. Use the existing assistant, avatar, and media mutation
symbols and preserve error responses.
In `@frontend/apps/main/src/features/conversation/ReplyBox.vue`:
- Around line 306-314: Update handleGenerateReply to capture the current
conversation UUID before awaiting api.aiGenerateReply, then compare it with
currentConversationUUID.value before assigning htmlContent.value. Only apply the
generated response when the UUID still matches; preserve the existing request
and generation-state behavior.
In `@frontend/apps/main/src/features/conversation/sidebar/CopilotPanel.vue`:
- Line 4: Update the CopilotPanel.vue conversation operations around hydrate(),
clearChat, and aiCopilot() to maintain a per-UUID operation revision. Capture
the current revision before each asynchronous request, increment it when
clearing or otherwise starting a newer operation, and ignore any response whose
captured revision no longer matches before updating the store, preventing stale
hydration or assistant messages from restoring cleared history.
In `@internal/ai/agent.go`:
- Around line 58-98: Thread the caller context through the agent’s
chat-completion path: update chatCompletion and its underlying
SendChatCompletion/doRequest calls to accept and propagate ctx, including retry
or backoff waits. Update both completion invocations in the agent loop so
cancellation and deadlines stop in-flight requests promptly.
In `@internal/ai/embedding.go`:
- Around line 107-114: Update the debug logging around the RAG search loop in
the embedding method to remove raw query text and chunk previews. Retain safe
metadata such as query length, result count, ranks, scores, source types, and
source IDs, ensuring no customer or knowledge-base content is logged.
- Around line 146-151: Update the deferred rollback in the reindex transaction
flow around BeginTxx to handle or explicitly discard the Rollback result so it
satisfies errcheck, while preserving the existing transaction error propagation
and cleanup behavior.
In `@internal/ai/knowledgebase.go`:
- Around line 61-69: Update DeleteKnowledgeBaseItem to execute both the snippet
deletion and embedding cleanup within one database transaction, rolling back and
returning an error if either operation fails. Only invoke the in-memory index
removal after the transaction commits successfully, rather than calling
RemoveEmbeddings independently and ignoring its failure.
- Around line 72-100: Update the reindexing flow around reindexSnippet and
reindexSnippetWith so concurrent updates for the same snippet cannot commit
out-of-date vectors or fingerprints. Replace the independent per-update
goroutine behavior with synchronous indexing or a serialized/versioned queue
keyed by snippet ID, ensuring an older request cannot overwrite a newer edit
while preserving the existing enabled/disabled handling.
- Around line 116-119: Handle the error returned by fmt.Fprintf in
snippetFingerprint instead of ignoring it, while preserving the existing hash
input and fingerprint output behavior.
In `@internal/ai/openai.go`:
- Line 264: Update the response-body read in the OpenAI request flow to use the
bounded-reader pattern from internal/ai/tools.go, enforcing the provider
response size limit. Stop ignoring the read error: propagate failures from
reading resp.Body, including oversized or truncated responses, through the
existing error-handling path.
- Around line 175-186: Update the embedding loop in the parsed.Data handling to
track indices already seen and reject any duplicate before assigning to
out[d.Index]. Preserve the existing range validation and return an error
identifying the repeated index, ensuring every output slot is populated
uniquely.
In `@internal/ai/provider.go`:
- Around line 6-10: Update internal/ai/provider.go:6-10 so every ProviderClient
method accepts a context.Context, then update internal/ai/openai.go:210-250 and
the corresponding OpenAIClient implementations to propagate that context into
HTTP requests and make retry backoff cancellation-aware, returning promptly when
the context is cancelled; update all affected callers to pass their context
through.
In `@internal/aiagent/faq.go`:
- Around line 83-96: Make FAQ approval atomic and recoverable in the approval
flow around ApproveFAQSuggestionIfPending and CreateKnowledgeBaseItem: ensure
snippet creation and the pending-to-approved transition cannot leave
inconsistent state, using a transaction or an explicitly recoverable
intermediate state. In internal/aiagent/faq.go lines 100-106, update rejection
to conditionally transition only pending suggestions to rejected and return the
existing conflict error when no rows are affected.
In `@internal/aiagent/tools.go`:
- Line 82: Update the debug log in the knowledge-search flow to remove the raw
in.Query field, while preserving non-content metadata such as hit count, top
score, and minimum confidence.
In `@internal/stringutil/htmlchunker.go`:
- Around line 130-136: Explicitly discard the html.Render error at both affected
call sites: internal/stringutil/htmlchunker.go lines 130-136 in the extract
closure, and lines 204-206 where rendering uses buf and c. Update each call to
assign the returned error to the blank identifier without changing surrounding
behavior.
In `@scripts/widget-jwt-test/index.html`:
- Around line 92-100: Update loadWidgetScript and the surrounding widget test
flow so the signing secret is never exposed to user-controlled widget.js or
persisted in localStorage: keep signing in the parent context, then load the
widget inside an isolated sandboxed iframe and pass only the resulting JWT to
it. Remove secret persistence and direct same-document script injection, and
preserve the existing widget initialization behavior through the JWT-only frame
communication.
In `@scripts/widget-jwt-test/README.md`:
- Around line 11-13: Update the Markdown command fence in the widget JWT test
README to include the shell language identifier, changing the untyped fence
around the http.server command to a shell-labeled fence so it satisfies
markdownlint MD040.
---
Nitpick comments:
In `@internal/ai/ai.go`:
- Line 240: Update the call sites in the relevant AI manager methods around
UpdateProviderConfig and UpdateProvider to accept and propagate a
context.Context, then replace the prepared-statement Exec calls with ExecContext
using that context. Preserve the existing write behavior while ensuring request
cancellation and deadlines reach both updates.
In `@internal/ai/queries.sql`:
- Around line 43-44: Update the get-all-embeddings query and its callers to
avoid loading the entire embeddings table for every search: prefer pgvector
similarity search with an appropriate ANN index, or minimally add source_type
filtering and pagination while preserving search results. Ensure the related
embedding retrieval method passes the filter and page parameters instead of
unconditionally fetching all rows.
In `@internal/conversation/message.go`:
- Around line 704-716: Handle the error returned by json.Marshal in the activity
message construction before assigning Meta, and log it through the existing
conversation/message logging mechanism while preserving the current metadata
behavior. Update the code around the activity message creation rather than
changing the Message fields or activity_type payload.
In `@internal/stringutil/htmlchunker.go`:
- Around line 273-287: Optimize the truncation loop in the boundary-processing
logic by replacing rune-by-rune removal with a binary search over the rune slice
length. Use the monotonic TokenizerFunc result to find the longest prefix whose
token count is at most cfg.MaxTokens, then preserve the existing warning,
boundary.Content, boundary.Tokens, and return behavior.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0f619979-265e-466b-8365-11fd95839bbc
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (86)
cmd/ai.gocmd/aiagent.gocmd/conversation.gocmd/handlers.gocmd/init.gocmd/main.gocmd/upgrade.gocmd/users.goconfig.sample.tomlfrontend/apps/main/src/api/index.jsfrontend/apps/main/src/components/sidebar/Sidebar.vuefrontend/apps/main/src/constants/navigation.jsfrontend/apps/main/src/features/admin/ai/AssistantForm.vuefrontend/apps/main/src/features/admin/ai/ProviderForm.vuefrontend/apps/main/src/features/admin/ai/SnippetForm.vuefrontend/apps/main/src/features/admin/ai/ToolForm.vuefrontend/apps/main/src/features/admin/ai/assistantColumns.jsfrontend/apps/main/src/features/admin/ai/assistantDropdown.vuefrontend/apps/main/src/features/admin/ai/snippetColumns.jsfrontend/apps/main/src/features/admin/ai/snippetDropdown.vuefrontend/apps/main/src/features/admin/ai/suggestionColumns.jsfrontend/apps/main/src/features/admin/ai/toolColumns.jsfrontend/apps/main/src/features/admin/ai/toolDropdown.vuefrontend/apps/main/src/features/admin/general/GeneralSettingForm.vuefrontend/apps/main/src/features/admin/general/formSchema.jsfrontend/apps/main/src/features/admin/sla/SLAForm.vuefrontend/apps/main/src/features/conversation/ReplyBox.vuefrontend/apps/main/src/features/conversation/ReplyBoxContent.vuefrontend/apps/main/src/features/conversation/ReplyBoxMenuBar.vuefrontend/apps/main/src/features/conversation/message/MessageBubble.vuefrontend/apps/main/src/features/conversation/sidebar/ConversationSideBar.vuefrontend/apps/main/src/features/conversation/sidebar/CopilotPanel.vuefrontend/apps/main/src/layouts/admin/AdminLayout.vuefrontend/apps/main/src/router/index.jsfrontend/apps/main/src/stores/appSettings.jsfrontend/apps/main/src/stores/conversation.jsfrontend/apps/main/src/stores/users.jsfrontend/apps/main/src/views/admin/agents/AgentList.vuefrontend/apps/main/src/views/admin/ai/AIAssistants.vuefrontend/apps/main/src/views/admin/ai/AIProviders.vuefrontend/apps/main/src/views/admin/ai/AISnippets.vuefrontend/apps/main/src/views/admin/ai/AISuggestions.vuefrontend/apps/main/src/views/admin/ai/AITools.vuefrontend/apps/main/src/views/admin/ai/CreateOrEditAssistant.vuefrontend/apps/main/src/views/admin/ai/CreateOrEditTool.vuefrontend/apps/widget/src/components/ChatTitle.vuefrontend/shared-ui/components/ui/button/Button.vuego.modi18n/en-US.jsoninternal/ai/agent.gointernal/ai/ai.gointernal/ai/copilot.gointernal/ai/embedding.gointernal/ai/knowledgebase.gointernal/ai/models/models.gointernal/ai/openai.gointernal/ai/provider.gointernal/ai/queries.sqlinternal/ai/tools.gointernal/ai/toolstore.gointernal/aiagent/aiagent.gointernal/aiagent/faq.gointernal/aiagent/models/models.gointernal/aiagent/prompt.gointernal/aiagent/queries.sqlinternal/aiagent/tools.gointernal/aiagent/worker.gointernal/conversation/continuity.gointernal/conversation/conversation.gointernal/conversation/message.gointernal/conversation/models/models.gointernal/conversation/queries.sqlinternal/image/image.gointernal/migrations/v2.7.0.gointernal/setting/models/models.gointernal/stringutil/htmlchunker.gointernal/stringutil/htmlchunker_test.gointernal/stringutil/stringutil.gointernal/user/agent.gointernal/user/models/models.gointernal/user/queries.sqlschema.sqlscripts/tools-server/README.mdscripts/tools-server/main.goscripts/widget-jwt-test/README.mdscripts/widget-jwt-test/index.html
💤 Files with no reviewable changes (1)
- cmd/conversation.go
Thread context through provider calls so cancelled requests stop retrying, make snippet delete and FAQ review transitions atomic, cap provider response reads, guard stale AI replies and copilot responses from overwriting newer conversation state, and stop logging raw search queries and chunk content.
…y languages Snippet import: new "Import from URL" flow fetches a page and stores its readable content as a snippet. Extraction uses the mackee/go-readability library (Mozilla Readability port) and outputs Markdown, so nav/footer boilerplate is dropped. The snippet list shows the source, and the edit dialog is now wider with a taller content box. Summarize: new "Summarize with AI" action on a conversation calls the AI and adds the result as a private note. It shows an info toast right away so the user knows it started, since the call can take a few seconds. This adds an "info" toast variant that any feature can use. Assistant languages: assistants can be given a list of allowed reply languages. The assistant replies in the customer's language when it is one of them, otherwise it falls back to the first. The preview also lists the knowledge sources it used. Cleanup: replace hardcoded gray/zinc/white colors with theme tokens (text-muted-foreground, text-foreground, bg-accent) across several components.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@internal/ai/urlimport.go`:
- Around line 27-32: Update the URL-fetch flow around url.Parse and m.fetchURL
to use a dedicated HTTP client with a transport that resolves and rejects
loopback, link-local, RFC1918, and other private or reserved destination IPs at
connection time, disables untrusted proxy use, and applies the validation to
every redirect. Preserve the existing HTTP/HTTPS and host checks while ensuring
fetchURL uses this client for the complete request chain.
- Around line 32-35: Sanitize the URL logged in the fetchURL error path before
passing it to m.lo.Error. Update the error context in the URL import flow to
include only the host and path, or an equivalent representation with query
parameters and credentials removed, while preserving the existing fetch failure
handling.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9712b5dd-fda7-40e6-95c0-73a82c1d8e69
📒 Files selected for processing (35)
cmd/ai.gocmd/aiagent.gocmd/handlers.gofrontend/apps/main/src/App.vuefrontend/apps/main/src/api/index.jsfrontend/apps/main/src/components/button/CopyButton.vuefrontend/apps/main/src/components/importer/Importer.vuefrontend/apps/main/src/components/layout/MenuCard.vuefrontend/apps/main/src/components/sidebar/Sidebar.vuefrontend/apps/main/src/features/admin/agents/AgentForm.vuefrontend/apps/main/src/features/admin/ai/AssistantForm.vuefrontend/apps/main/src/features/admin/ai/ProviderForm.vuefrontend/apps/main/src/features/admin/ai/snippetColumns.jsfrontend/apps/main/src/features/admin/business-hours/BusinessHoursForm.vuefrontend/apps/main/src/features/conversation/Conversation.vuefrontend/apps/main/src/features/conversation/list/ConversationEmptyList.vuefrontend/apps/main/src/views/admin/ai/AISnippets.vuefrontend/apps/main/src/views/admin/ai/CreateOrEditAssistant.vuefrontend/apps/main/src/views/reports/OverviewView.vuei18n/en-US.jsoninternal/ai/copilot.gointernal/ai/knowledgebase.gointernal/ai/models/models.gointernal/ai/queries.sqlinternal/ai/urlimport.gointernal/aiagent/aiagent.gointernal/aiagent/faq.gointernal/aiagent/models/models.gointernal/aiagent/prompt.gointernal/aiagent/queries.sqlinternal/aiagent/tools.gointernal/aiagent/worker.gointernal/migrations/v2.7.0.gointernal/stringutil/htmlextract.goschema.sql
🚧 Files skipped from review as they are similar to previous changes (18)
- frontend/apps/main/src/features/admin/ai/snippetColumns.js
- internal/aiagent/prompt.go
- cmd/handlers.go
- internal/ai/copilot.go
- frontend/apps/main/src/features/admin/ai/ProviderForm.vue
- frontend/apps/main/src/components/sidebar/Sidebar.vue
- internal/ai/queries.sql
- internal/aiagent/tools.go
- frontend/apps/main/src/features/admin/ai/AssistantForm.vue
- internal/ai/knowledgebase.go
- cmd/aiagent.go
- internal/aiagent/faq.go
- internal/aiagent/queries.sql
- frontend/apps/main/src/api/index.js
- internal/aiagent/aiagent.go
- internal/ai/models/models.go
- schema.sql
- internal/migrations/v2.7.0.go
…tch to a green theme Embedding: - Persist embedding_max_tokens. It was dropped when saving the provider config and always reset to the 8192 default, so self-hosted models with a smaller limit kept getting rejected. - Split embedding requests to stay under the provider's item and token caps. A large snippet or URL import used to go in one request, hit the cap, fail, and get retried every minute forever. - Skip blank chunks. An empty input string makes the API reject the whole batch. - Add the tiktoken tokenizer (cl100k_base) with tests for token counting. Agent prompt: - Move contact fields, the conversation subject, and custom attributes out of the system prompt and into a delimited user-role block, so a crafted name or subject can't act as an instruction. Frontend: - Switch the primary and sidebar colors to a green theme. - Show a "summarizing" info toast while the AI summary runs, and add an info toast variant. - Make the snippet content box taller and the snippet dialog wider. - Drop a hardcoded dark hover color on the scroll-to-bottom button.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@internal/ai/tokenizer.go`:
- Around line 53-65: Update capToTokens to return an empty string immediately
when maxTokens is zero or negative, before either the encoder or fallback branch
performs length checks or slicing; preserve the existing behavior for positive
limits.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fbf01fea-f583-4849-9fc7-f712427bd4c1
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (17)
frontend/apps/main/src/App.vuefrontend/apps/main/src/features/admin/ai/SnippetForm.vuefrontend/apps/main/src/features/conversation/Conversation.vuefrontend/apps/main/src/views/admin/ai/AISnippets.vuefrontend/shared-ui/assets/styles/main.scssfrontend/shared-ui/components/ScrollToBottomButton/ScrollToBottomButton.vuego.modi18n/en-US.jsoninternal/ai/ai.gointernal/ai/embedding.gointernal/ai/models/models.gointernal/ai/openai.gointernal/ai/openai_test.gointernal/ai/tokenizer.gointernal/ai/tokenizer_test.gointernal/aiagent/prompt.gointernal/aiagent/worker.go
🚧 Files skipped from review as they are similar to previous changes (9)
- frontend/apps/main/src/features/conversation/Conversation.vue
- frontend/apps/main/src/features/admin/ai/SnippetForm.vue
- frontend/apps/main/src/views/admin/ai/AISnippets.vue
- i18n/en-US.json
- internal/ai/embedding.go
- internal/ai/models/models.go
- internal/aiagent/worker.go
- internal/ai/ai.go
- internal/ai/openai.go
Fix a reindex race: snippet embedding runs outside the lock, so a slower job from an older edit could commit stale vectors after a newer edit. Split Reindex into embed (lock-free) and commit (locked), and gate the commit with a per-snippet generation counter so only the latest edit wins. Delete drops the counter so an in-flight job can't re-insert vectors for a deleted snippet. Clear the DB avatar reference before deleting the avatar media file. The old order deleted the file first, so a failed DB update left the DB pointing at a missing file and a broken image. Also handle unchecked errors flagged by errcheck (tx.Rollback, html.Render, fmt.Fprintf), guard capToTokens against a negative limit that would panic, and stop logging the full import URL since it can carry credentials.
Move the webhook SSRF guard into a shared internal/ssrf package and wire it into every place the server fetches an admin-set URL: webhooks, OIDC discovery, the AI provider base URL, and custom AI tool calls. Add a global [ssrf] config block, off by default with an allowed_cidrs bypass, so single-tenant self-hosters keep reaching internal hosts while multi-tenant or hosted deploys can turn it on. The old [webhook] allowed_hosts key is still read for backward compat and folds into the guard.
An invalid custom tool name (bad characters or over 64 chars) used to skip validation, hit the database check constraint, and return a 500. The name format and length are now checked up front and return a 400 with the name hint, matching how the reserved-name check already works. Bad tool URLs and parameter JSON now return their own specific message instead of a generic "Something went wrong". Snippet create and update now reject an empty title, like they already do for empty content. Adds a unit test covering the tool validation cases.
Colors: the brand color moves from indigo to green in both themes, and the sidebar, tooltip, card and link styles follow it. Three new tokens replace hardcoded values: foreground-lighter for idle sidebar items, warning-600 for warning text that needs 4.5:1 contrast on light backgrounds, and link for anchors inside rendered email content. DESIGN_SYSTEM.md now lists the real values from main.scss instead of the old indigo ones. Forms: every button inside a form that is not the submit button now has an explicit type. Without it the browser treats it as a submit button, so clicking Cancel on a contact note posted an empty note, "New holiday" saved the whole business-hours form, and pressing Enter in an SLA field deleted the first alert row. The login page also highlights an empty password field on a failed submit, which a broken condition prevented before, and the two password fields on the set-password page get their own show and hide toggles. Search: conversation and contact search now drop responses from an older query, so clearing the box no longer repopulates the list with stale results. Permissions: /api/v1/ai/summarize now needs messages:write, since it writes a private note, and the menu item is hidden for agents without it.
The surfaces section still described the old gray sidebar and the pre-swap canvas values. Sidebar background now matches the app background in both themes, so the tier diagram was wrong. Also documents what the code already does: the .box, .sidebar-section-label and .link-style utilities, the badge success variant, the variant prop on AlertDialogAction, the hover-reveal pattern for table row actions, and the rule that every non-submit button in a form needs type="button". Trimmed the prose throughout.
Final review pass before taking the AI agent branch live. Knowledge base: - Text not wrapped in a block tag was never collected, so prose around a table or list never reached the index. The assistant answered "no relevant information" for questions the snippet covered. - Blocks over the token limit were truncated and the remainder dropped. They are split into several chunks now. - Trimming an oversized block ran one rune at a time and re-tokenized the whole string each step. A large table took minutes. It uses a binary search now. - Overlap text was not escaped, so a sentence containing markup swallowed the rest of the chunk. - SVG and template text no longer reaches the index. AI agent: - Verification codes are capped per address and per conversation. The cap was per conversation only, so a customer correcting a mistyped email was told to check an inbox that never got a code. - Livechat verification sends synchronously. A queued send returned nil even when SMTP failed, so a failure counted as a sent code. - Queued jobs drain on shutdown and hand off to a human instead of being dropped with no reply. - Deleting an assistant no longer moves resolved and closed conversations into the fallback team. - Image decode is capped at 25 MP. The old bound allowed a 400 MB decode per attachment. Auth and admin: - A blank OIDC client secret no longer overwrites the stored one. Blank id or secret is rejected instead. - OIDC token exchange uses the SSRF guarded client with a timeout. - Renaming a tool auth header no longer attaches the secret of whichever row now sits at that position. - Clearing embedding dimensions no longer refills 1536 on the next load, which pushed a wrong value to the provider on the next save. - Copilot conversation lookups filter by access before capping at 10.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/ai/embedding.go (1)
279-297: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winReconcile swallows provider/config errors silently.
getRawProviderConfigandGetKnowledgeBaseItemsfailures both justreturnwith no logging. Since this runs on every reconcile tick, a DB blip, decryption failure, or missing provider row will silently stop the entire embedding reconciliation loop indefinitely with zero operator signal — the knowledge base can go stale for RAG search without any trace in the logs.🛠️ Proposed fix
cfg, err := m.getRawProviderConfig(models.ProviderTypeEmbedding) if err != nil { + m.lo.Error("error fetching embedding provider config for reconcile", "error", err) return } // The API key is stored encrypted, so a non-empty value is enough to know a provider is configured. if cfg.APIKey == "" { return } items, err := m.GetKnowledgeBaseItems() if err != nil { + m.lo.Error("error fetching knowledge base items for reconcile", "error", err) return }🤖 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 `@internal/ai/embedding.go` around lines 279 - 297, Update reconcile to log the errors returned by getRawProviderConfig and GetKnowledgeBaseItems before returning, including enough context to identify whether provider configuration loading or knowledge-base item retrieval failed. Preserve the existing early-return behavior after logging.internal/aiagent/worker.go (1)
340-345: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFail closed when the freshness check errors.
A failed
GetConversationfalls through to reply and resolve using stale assignment/status data, so the agent can talk over a human takeover during a database failure. Log and return (or retry) before mutating the conversation.Proposed fix
- if fresh, ferr := m.convo.GetConversation(convID, "", ""); ferr == nil { - if !fresh.AssignedUserID.Valid || int(fresh.AssignedUserID.Int) != assistant.UserID || nonActionableCategories[fresh.StatusCategory.String] { - m.lo.Debug("ai agent conversation changed mid-run, dropping reply", "conversation_uuid", conv.UUID) - return - } + fresh, ferr := m.convo.GetConversation(convID, "", "") + if ferr != nil { + m.lo.Error("error rechecking conversation before ai reply", "conversation_uuid", conv.UUID, "error", ferr) + return + } + if !fresh.AssignedUserID.Valid || int(fresh.AssignedUserID.Int) != assistant.UserID || nonActionableCategories[fresh.StatusCategory.String] { + m.lo.Debug("ai agent conversation changed mid-run, dropping reply", "conversation_uuid", conv.UUID) + return }🤖 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 `@internal/aiagent/worker.go` around lines 340 - 345, Update the freshness check around m.convo.GetConversation in the worker flow to fail closed when ferr is non-nil: log the lookup error and return before sending a reply or resolving the conversation. Preserve the existing mismatch checks for successful lookups, including the assigned-user and non-actionable-status validation.
🤖 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 `@frontend/apps/main/src/features/admin/inbox/EmailInboxForm.vue`:
- Line 1043: Replace the direct setValues calls in the form watchers with the
supported hydration API that updates initial values without validation and
resets dirty/touched state. Apply this change at
frontend/apps/main/src/features/admin/inbox/EmailInboxForm.vue:1043,
frontend/apps/main/src/features/admin/inbox/LivechatInboxForm.vue:1431, and
frontend/apps/main/src/features/admin/macros/MacroForm.vue:228, preserving each
watcher’s existing rewritten values.
In `@frontend/apps/main/src/features/view/ViewForm.vue`:
- Line 198: Update the view-loading logic around form.setValues to reset the
form state when replacing the current record. Use form.resetForm with
processedVal as the new values, or explicitly clear validation errors and
touched state before loading, while preserving the processed values.
In `@frontend/apps/main/src/views/auth/SetPasswordView.vue`:
- Around line 172-181: Align validateForm(), passwordHasError, and
confirmPasswordHasError in SetPasswordView so they use identical password
requirements: reject empty or fewer-than-8-character passwords and reject
mismatched confirmation values. Ensure the computed error states also mark empty
invalid fields, including the confirmation field, so destructive styling matches
validation and prevents api.setPassword for any invalid password.
In `@frontend/DESIGN.md`:
- Around line 82-86: Add the text language identifier to the opening fenced code
block in DESIGN.md, preserving the existing diagram content and closing fence.
In `@internal/aiagent/worker.go`:
- Around line 677-683: Update the debug logging in the trimmed-message handling
block to stop emitting the removed customer content. Keep the existing removal
calculation if needed, but replace the “removed” log value with its length,
using the surrounding `trimmed`, `full`, and `m.lo.Debug` flow.
In `@internal/stringutil/emailquote.go`:
- Around line 80-100: Update the class checks in isQuoteContainerNode and
isQuoteMarkerNode to split the class attribute into whitespace-delimited tokens
and compare each token exactly against quoteContainerClasses or
quoteMarkerClasses. Remove the substring-based strings.Contains matching while
preserving the existing ID check and boolean behavior.
---
Outside diff comments:
In `@internal/ai/embedding.go`:
- Around line 279-297: Update reconcile to log the errors returned by
getRawProviderConfig and GetKnowledgeBaseItems before returning, including
enough context to identify whether provider configuration loading or
knowledge-base item retrieval failed. Preserve the existing early-return
behavior after logging.
In `@internal/aiagent/worker.go`:
- Around line 340-345: Update the freshness check around m.convo.GetConversation
in the worker flow to fail closed when ferr is non-nil: log the lookup error and
return before sending a reply or resolving the conversation. Preserve the
existing mismatch checks for successful lookups, including the assigned-user and
non-actionable-status validation.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 485076b5-e502-44ae-a354-ba7c1ed37be1
📒 Files selected for processing (168)
cmd/aitools.gocmd/conversation.gocmd/handlers.gocmd/init.gocmd/main.gofrontend/DESIGN.mdfrontend/apps/main/src/components/DownloadLink.vuefrontend/apps/main/src/components/KeyboardShortcutsDialog.vuefrontend/apps/main/src/components/banner/AdminBanner.vuefrontend/apps/main/src/components/button/CloseButton.vuefrontend/apps/main/src/components/button/CopyButton.vuefrontend/apps/main/src/components/datatable/DataTable.vuefrontend/apps/main/src/components/editor/TextEditor.vuefrontend/apps/main/src/components/sidebar/NotificationBell.vuefrontend/apps/main/src/components/sidebar/Sidebar.vuefrontend/apps/main/src/components/sidebar/SidebarNavUser.vuefrontend/apps/main/src/components/table/SimpleTable.vuefrontend/apps/main/src/constants/navigation.jsfrontend/apps/main/src/features/admin/agents/AgentForm.vuefrontend/apps/main/src/features/admin/agents/dataTableColumns.jsfrontend/apps/main/src/features/admin/agents/dataTableDropdown.vuefrontend/apps/main/src/features/admin/ai/AssistantForm.vuefrontend/apps/main/src/features/admin/ai/ProviderForm.vuefrontend/apps/main/src/features/admin/ai/SnippetForm.vuefrontend/apps/main/src/features/admin/ai/ToolForm.vuefrontend/apps/main/src/features/admin/ai/assistantColumns.jsfrontend/apps/main/src/features/admin/ai/assistantDropdown.vuefrontend/apps/main/src/features/admin/ai/snippetColumns.jsfrontend/apps/main/src/features/admin/ai/snippetDropdown.vuefrontend/apps/main/src/features/admin/ai/suggestionColumns.jsfrontend/apps/main/src/features/admin/ai/toolColumns.jsfrontend/apps/main/src/features/admin/ai/toolDropdown.vuefrontend/apps/main/src/features/admin/automation/ActionBox.vuefrontend/apps/main/src/features/admin/automation/RuleBox.vuefrontend/apps/main/src/features/admin/automation/RuleList.vuefrontend/apps/main/src/features/admin/automation/RuleTab.vuefrontend/apps/main/src/features/admin/business-hours/BusinessHoursForm.vuefrontend/apps/main/src/features/admin/business-hours/dataTableColumns.jsfrontend/apps/main/src/features/admin/business-hours/dataTableDropdown.vuefrontend/apps/main/src/features/admin/context-links/dataTableColumns.jsfrontend/apps/main/src/features/admin/context-links/dataTableDropdown.vuefrontend/apps/main/src/features/admin/custom-attributes/dataTableColumns.jsfrontend/apps/main/src/features/admin/custom-attributes/dataTableDropdown.vuefrontend/apps/main/src/features/admin/general/GeneralSettingForm.vuefrontend/apps/main/src/features/admin/inbox/EmailInboxForm.vuefrontend/apps/main/src/features/admin/inbox/InboxDataTableDropDown.vuefrontend/apps/main/src/features/admin/inbox/LivechatInboxForm.vuefrontend/apps/main/src/features/admin/inbox/LivechatWidgetPreview.vuefrontend/apps/main/src/features/admin/inbox/PreChatFormConfig.vuefrontend/apps/main/src/features/admin/macros/ActionBuilder.vuefrontend/apps/main/src/features/admin/macros/MacroForm.vuefrontend/apps/main/src/features/admin/macros/dataTableColumns.jsfrontend/apps/main/src/features/admin/macros/dataTableDropdown.vuefrontend/apps/main/src/features/admin/notification/NotificationSettingForm.vuefrontend/apps/main/src/features/admin/oidc/OIDCForm.vuefrontend/apps/main/src/features/admin/oidc/dataTableColumns.jsfrontend/apps/main/src/features/admin/oidc/dataTableDropdown.vuefrontend/apps/main/src/features/admin/oidc/formSchema.jsfrontend/apps/main/src/features/admin/roles/RoleForm.vuefrontend/apps/main/src/features/admin/roles/dataTableColumns.jsfrontend/apps/main/src/features/admin/roles/dataTableDropdown.vuefrontend/apps/main/src/features/admin/shared-views/SharedViewForm.vuefrontend/apps/main/src/features/admin/shared-views/dataTableColumns.jsfrontend/apps/main/src/features/admin/shared-views/dataTableDropdown.vuefrontend/apps/main/src/features/admin/sla/SLAForm.vuefrontend/apps/main/src/features/admin/sla/dataTableColumns.jsfrontend/apps/main/src/features/admin/sla/dataTableDropdown.vuefrontend/apps/main/src/features/admin/status/dataTableColumns.jsfrontend/apps/main/src/features/admin/status/dataTableDropdown.vuefrontend/apps/main/src/features/admin/tags/dataTableColumns.jsfrontend/apps/main/src/features/admin/tags/dataTableDropdown.vuefrontend/apps/main/src/features/admin/teams/TeamDataTableDropdown.vuefrontend/apps/main/src/features/admin/teams/TeamForm.vuefrontend/apps/main/src/features/admin/teams/TeamsDataTableColumns.jsfrontend/apps/main/src/features/admin/templates/TemplateForm.vuefrontend/apps/main/src/features/admin/templates/dataTableColumns.jsfrontend/apps/main/src/features/admin/templates/dataTableDropdown.vuefrontend/apps/main/src/features/admin/webhooks/WebhookForm.vuefrontend/apps/main/src/features/admin/webhooks/dataTableColumns.jsfrontend/apps/main/src/features/admin/webhooks/dataTableDropdown.vuefrontend/apps/main/src/features/command/CommandBox.vuefrontend/apps/main/src/features/contact/ContactNotes.vuefrontend/apps/main/src/features/contact/ContactsList.vuefrontend/apps/main/src/features/conversation/Conversation.vuefrontend/apps/main/src/features/conversation/MacroActionsPreview.vuefrontend/apps/main/src/features/conversation/ReplyBox.vuefrontend/apps/main/src/features/conversation/ReplyBoxContent.vuefrontend/apps/main/src/features/conversation/list/ConversationListItem.vuefrontend/apps/main/src/features/conversation/list/ConversationListItemSkeleton.vuefrontend/apps/main/src/features/conversation/message/MessageBubble.vuefrontend/apps/main/src/features/conversation/message/MessageList.vuefrontend/apps/main/src/features/conversation/message/MessagesSkeleton.vuefrontend/apps/main/src/features/conversation/message/attachment/BubbleAttachmentItem.vuefrontend/apps/main/src/features/conversation/message/attachment/ReplyBoxAttachmentPreview.vuefrontend/apps/main/src/features/conversation/sidebar/ConversationSideBar.vuefrontend/apps/main/src/features/conversation/sidebar/ConversationSideBarContact.vuefrontend/apps/main/src/features/conversation/sidebar/ConversationSideBarPageVisits.vuefrontend/apps/main/src/features/conversation/sidebar/CopilotPanel.vuefrontend/apps/main/src/features/conversation/sidebar/CustomAttributes.vuefrontend/apps/main/src/features/conversation/sidebar/PreviousConversations.vuefrontend/apps/main/src/features/search/SearchResults.vuefrontend/apps/main/src/features/sla/SlaBadge.vuefrontend/apps/main/src/features/view/ViewForm.vuefrontend/apps/main/src/layouts/admin/AdminSplitLayout.vuefrontend/apps/main/src/views/admin/agents/AgentList.vuefrontend/apps/main/src/views/admin/ai/AISuggestions.vuefrontend/apps/main/src/views/admin/ai/CreateOrEditAssistant.vuefrontend/apps/main/src/views/admin/automations/CreateOrEditRule.vuefrontend/apps/main/src/views/admin/context-links/CreateEditContextLink.vuefrontend/apps/main/src/views/admin/custom-attributes/CustomAttributes.vuefrontend/apps/main/src/views/admin/inbox/InboxList.vuefrontend/apps/main/src/views/admin/status/StatusView.vuefrontend/apps/main/src/views/admin/tags/TagsView.vuefrontend/apps/main/src/views/admin/webhooks/CreateEditWebhook.vuefrontend/apps/main/src/views/auth/ResetPasswordView.vuefrontend/apps/main/src/views/auth/SetPasswordView.vuefrontend/apps/main/src/views/auth/UserLoginView.vuefrontend/apps/main/src/views/contact/ContactDetailView.vuefrontend/apps/main/src/views/conversation/ConversationDetailView.vuefrontend/apps/main/src/views/reports/OverviewView.vuefrontend/apps/main/src/views/search/SearchView.vuefrontend/apps/widget/src/components/ChatMessages.vuefrontend/apps/widget/src/components/ChatTitle.vuefrontend/apps/widget/src/components/ConnectionBanner.vuefrontend/apps/widget/src/components/HomeExternalLink.vuefrontend/apps/widget/src/components/PreChatForm.vuefrontend/shared-ui/assets/styles/main.scssfrontend/shared-ui/components/ScrollToBottomButton/ScrollToBottomButton.vuefrontend/shared-ui/components/StatusDot.vuefrontend/shared-ui/components/ui/alert-dialog/AlertDialogAction.vuefrontend/shared-ui/components/ui/badge/index.jsfrontend/shared-ui/components/ui/button/index.jsfrontend/shared-ui/components/ui/card/Card.vuefrontend/shared-ui/components/ui/sidebar/index.jsfrontend/shared-ui/components/ui/tooltip/TooltipContent.vuefrontend/tailwind.config.cjsi18n/en-US.jsoninternal/ai/agent.gointernal/ai/ai.gointernal/ai/embedding.gointernal/ai/knowledgebase.gointernal/ai/knowledgebase_test.gointernal/ai/openai.gointernal/ai/queries.sqlinternal/ai/tools.gointernal/ai/toolstore.gointernal/ai/toolstore_test.gointernal/aiagent/aiagent.gointernal/aiagent/faq.gointernal/aiagent/otp.gointernal/aiagent/prompt.gointernal/aiagent/prompt_test.gointernal/aiagent/queries.sqlinternal/aiagent/tools.gointernal/aiagent/worker.gointernal/auth/auth.gointernal/conversation/conversation.gointernal/conversation/message.gointernal/conversation/queries.sqlinternal/image/image.gointernal/migrations/v2.6.0.gointernal/notification/notification.gointernal/oidc/oidc.gointernal/stringutil/emailquote.gointernal/stringutil/emailquote_test.gointernal/stringutil/htmlchunker.gointernal/stringutil/htmlchunker_test.goschema.sql
🚧 Files skipped from review as they are similar to previous changes (49)
- frontend/apps/main/src/components/button/CopyButton.vue
- frontend/apps/main/src/features/admin/general/GeneralSettingForm.vue
- frontend/shared-ui/components/ScrollToBottomButton/ScrollToBottomButton.vue
- frontend/apps/main/src/constants/navigation.js
- frontend/apps/main/src/features/conversation/list/ConversationListItemSkeleton.vue
- frontend/apps/widget/src/components/ChatTitle.vue
- frontend/apps/main/src/views/admin/agents/AgentList.vue
- frontend/apps/main/src/features/admin/ai/suggestionColumns.js
- frontend/apps/widget/src/components/ChatMessages.vue
- frontend/apps/main/src/features/admin/ai/assistantDropdown.vue
- frontend/apps/main/src/views/admin/ai/AISuggestions.vue
- internal/ai/queries.sql
- frontend/apps/main/src/features/admin/sla/SLAForm.vue
- internal/conversation/queries.sql
- frontend/apps/widget/src/components/PreChatForm.vue
- frontend/apps/main/src/features/admin/ai/snippetColumns.js
- frontend/apps/main/src/features/admin/ai/assistantColumns.js
- frontend/apps/main/src/features/admin/ai/snippetDropdown.vue
- frontend/apps/main/src/features/conversation/Conversation.vue
- frontend/apps/main/src/features/admin/ai/ToolForm.vue
- frontend/apps/main/src/features/conversation/ReplyBoxContent.vue
- internal/auth/auth.go
- frontend/apps/main/src/features/conversation/sidebar/ConversationSideBar.vue
- internal/aiagent/prompt.go
- frontend/apps/main/src/features/admin/ai/AssistantForm.vue
- cmd/handlers.go
- frontend/apps/main/src/features/conversation/message/MessageBubble.vue
- cmd/main.go
- cmd/aitools.go
- cmd/init.go
- internal/ai/knowledgebase.go
- schema.sql
- internal/ai/agent.go
- frontend/apps/main/src/features/conversation/ReplyBox.vue
- frontend/apps/main/src/features/admin/ai/ProviderForm.vue
- frontend/apps/main/src/views/reports/OverviewView.vue
- internal/migrations/v2.6.0.go
- frontend/apps/main/src/views/admin/ai/CreateOrEditAssistant.vue
- internal/image/image.go
- internal/aiagent/faq.go
- i18n/en-US.json
- internal/aiagent/aiagent.go
- internal/ai/tools.go
- frontend/apps/main/src/features/conversation/sidebar/CopilotPanel.vue
- internal/ai/openai.go
- internal/aiagent/tools.go
- internal/ai/ai.go
- internal/aiagent/queries.sql
- internal/conversation/conversation.go
Two AI agent limits were hardcoded in Go: the tool-calling budget per reply (6) and the number of conversation messages sent to the model as history (30). Large installs need to tune these, so both are now read from config.toml as ai_agent.max_steps and ai_agent.max_history_messages. Defaults are unchanged, so an install that does not set the keys behaves exactly as before. Values are clamped at the config read layer, 1-20 for max_steps and 5-100 for history, so a typo cannot burn tokens on a runaway loop or drop the model's context to nothing. Also drop the dead re-slice in buildHistory. The message fetch already limits to max_history_messages and the filter above it only removes messages, so the second clamp could never fire.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
config.sample.toml (1)
141-147: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winFail closed on SSRF protection for hosted deployments.
ssrf.enableddefaults tofalse, so the shared outbound dial guard returnsniland AI providers, tools, webhooks, and OIDC clients do not block private, loopback, link-local, or metadata IPs by default. Set it totrueby default, or make the single-tenant opt-out explicit and enforceable in the runtime config instead of relying on README-level guidance.🤖 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 `@config.sample.toml` around lines 141 - 147, Change the [ssrf] configuration default so ssrf.enabled is true, ensuring outbound requests fail closed against private, loopback, link-local, and metadata IPs by default. If single-tenant deployments must opt out, make that opt-out explicit and enforce it through runtime configuration rather than comments alone.
🤖 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.
Outside diff comments:
In `@config.sample.toml`:
- Around line 141-147: Change the [ssrf] configuration default so ssrf.enabled
is true, ensuring outbound requests fail closed against private, loopback,
link-local, and metadata IPs by default. If single-tenant deployments must opt
out, make that opt-out explicit and enforce it through runtime configuration
rather than comments alone.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4bc3de65-968a-47df-baf2-bb9dbfbbcd6c
📒 Files selected for processing (4)
cmd/init.goconfig.sample.tomlinternal/aiagent/aiagent.gointernal/aiagent/worker.go
🚧 Files skipped from review as they are similar to previous changes (3)
- cmd/init.go
- internal/aiagent/aiagent.go
- internal/aiagent/worker.go
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@frontend/apps/main/src/stores/aiAssistant.js`:
- Around line 9-14: Update loadAssistants and invalidate in the assistant store
to track a request generation, incrementing it on invalidation and capturing the
current generation when a request starts. Only update assistants.value and
loaded.value when the response belongs to the current generation, so stale
in-flight responses cannot re-enable the cache or overwrite newer data.
- Around line 15-17: Update the assistant-loading flow surrounding the catch
block to deduplicate concurrent requests and prevent automatic retries after a
failed load. Track the in-flight promise and ensure all consumers share it, then
either implement explicit retry/reset behavior or mark the failed attempt as
cached by updating the relevant loaded/error state; preserve assistants.value as
an empty list on failure.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d0f4c653-b12e-4b79-98c7-55c6fa6b57b3
📒 Files selected for processing (4)
frontend/apps/main/src/features/admin/ai/assistantDropdown.vuefrontend/apps/main/src/features/conversation/sidebar/CopilotPanel.vuefrontend/apps/main/src/stores/aiAssistant.jsfrontend/apps/main/src/views/admin/ai/CreateOrEditAssistant.vue
🚧 Files skipped from review as they are similar to previous changes (3)
- frontend/apps/main/src/features/admin/ai/assistantDropdown.vue
- frontend/apps/main/src/views/admin/ai/CreateOrEditAssistant.vue
- frontend/apps/main/src/features/conversation/sidebar/CopilotPanel.vue
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@internal/ai/ai.go`:
- Around line 37-40: Remove the duplicate ErrInvalidAPIKey, ErrApiKeyNotSet,
ErrRateLimited, and ErrProviderUnavailable declarations from ai.go, retaining
the canonical package-level definitions already present in openai.go. Ensure all
existing references continue using those shared sentinels.
- Around line 396-403: Update the timeout and provider-unavailable branches in
the surrounding AI provider error handling to return envelope.NetworkError
instead of envelope.GeneralError, preserving their existing localized messages
and nil payloads so upstream failures map to HTTP 504.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ad7cafe3-c0cf-425f-bc7b-d14ef751904b
📒 Files selected for processing (4)
cmd/ai.goi18n/en-US.jsoninternal/ai/ai.gointernal/ai/openai.go
🚧 Files skipped from review as they are similar to previous changes (2)
- cmd/ai.go
- i18n/en-US.json
Summary by CodeRabbit