Each help center gets an optional public_url. When set, canonical links, hreflang
alternates, og:url, the sitemap and robots.txt point at that host instead of the
app root URL. Stored media URLs are rewritten to root-relative paths so logos,
favicons, header images and article images resolve on whichever host serves the
page. The URL is validated on both the form and the backend, and trailing slashes
are trimmed.
Also fixes issues found in code review:
- the help center template used a locale link as the data root inside the
alternates loop, which broke every public page on a help center with two or
more locales
- creating an article posted the clicked collection id, so changing the
collection in the sheet filed the article in the wrong place
- the collection field's error message was hidden when the locale had no
collections, so Create did nothing with no error shown
- switching the header type or unticking popular articles dropped those theme
values on save, because vee-validate unsets fields that unmount
- closing a new collection sheet with Esc left the parent id set, so the next
collection you edited got re-parented
- a table row with th labels in the first column was treated as a header row
when preparing article content for embeddings
- the img tag regex stopped at a > inside an attribute value and injected
loading/decoding in the middle of an attribute
The customize page preview now has a page selector (landing or a sample
article) and a device selector (desktop, tablet, mobile) with the frame
rendered at real device width and scaled to fit, wrapped in a browser
chrome mockup showing the live title, favicon and URL.
Themes gain an announcement banner shown above the header, a popular
articles section toggle with a custom label, and an icon tile option for
collection cards. Theme normalization now starts from defaults instead
of zero values.
Articles get a selectable author (validated as an agent or AI assistant)
separate from the new created_by column that records who created it. A
soft-deleted author no longer blocks saving an existing article.
The assistant test tab looked up every search hit as a snippet, so a help
article showed an unrelated snippet's title or vanished from the list. Also
drops the help center view count, which nothing ever read.
Resolved the AI embedding conflicts by keeping this branch's generic embedSource path and layering main's tag indexing on top. Search now covers snippets and help articles, tags stay excluded. Dropped SetContentID since main's link-message-media query stamps content_id itself.
Public pages were missing most of what a search engine needs. They now serve
a sitemap index at /sitemap.xml plus a robots.txt, and every page carries a
canonical URL, hreflang alternates, an x-default, and JSON-LD. Article pages
also send published and modified times. Search pages and the markdown view
are marked noindex. Requests from bots no longer bump the view counters, so
the insights numbers reflect real readers. Offscreen images in articles are
lazy loaded.
Public page text is now translated per locale instead of always using the
desk language, and pages with more than one translation show a language
switcher.
In the admin, collections and articles can be dragged to reorder, and an
article can be dragged into a different collection. Both save through new
sort order endpoints. Collections can have a lucide icon, picked from a new
icon picker backed by a sprite sheet. The landing page has layout options
for card grid or one per row, cards per row, and which details to show.
The help center list is a data table now, so HelpCenterCard is gone. The
tree page moved into AdminSplitLayout, its actions sit on their own row
below the breadcrumb, and it gained visit site and expand/collapse all.
Agents can now publish a public knowledge base per help center.
Backend:
- new internal/helpcenter package for help centers, collections and articles
- admin JSON API behind a new help_center:manage permission
- public /hc/{slug} pages, JSON API, search and sitemap, all rate limited
- migration v2.7.0 for the new tables
AI:
- published articles with the AI flag on get embedded, so the agent answers
from them. Unpublishing or clearing the flag removes the embeddings.
Media:
- media rows now carry a private flag. Help article images are public so the
public pages can serve them without auth, and only agents with
help_center:manage can upload them.
Frontend:
- admin help center list, tree view and article editor
- the old TextEditor is split into ArticleEditor and ConversationEditor over a
shared useTextEditor composable, so article-only tools like callouts,
collapsibles and YouTube embeds stay out of the reply box
Knowledge base: fold the embedding provider base URL into the snippet
fingerprint so re-pointing the provider triggers a reindex even when the
model name is unchanged. Reject empty knowledge base content. Cap
concurrent background snippet embeds at 4 and tie embedding work to the
app lifecycle context. Reindex when the embedding base URL changes, not
just the model or dimensions.
Prompt injection: neutralize << >> block delimiters in snippets,
transcripts, subjects, and contact fields so untrusted content can't
forge a boundary the model relies on.
OTP: set the verified flag inside the Lua match script so verification
is atomic. Only count codes that were actually emailed toward the resend
cap, and check the cap before sending instead of incrementing up front.
Tools: fetch only the enabled tools among the allowed IDs, and route all
registrations through one helper so custom tools can't shadow built-ins.
Agent queue: hand a dropped response job off to a human instead of
leaving the conversation assigned to the assistant with no reply.
Also let GetAllConversationMessages return every message when limit is
non-positive, populate admin forms without triggering validation, and
default the copilot name to Juno.
Custom tools can now require a verified contact before they run. This gives tools a trustworthy signal about who the customer is without relying on DMARC or the JWT login.
ai_tools gets a requires_verification column, defaulting to true (fail-closed). A flagged tool is blocked in httpTool.Execute until the conversation is verified, and every tool call now carries an X-Libredesk-Contact-Verified header so tool authors can tell an OTP-verified contact from a self-claimed one.
The AI agent gets three native tools: send_email_verification emails a 6-digit code out of band, check_email_verification confirms it, and set_contact_email lets an anonymous visitor add an email to send the code to. Codes and the verified window live in Redis, scoped per conversation, with attempt and resend caps. JWT livechat contacts stay trusted without OTP; email contacts and visitors verify by code.
The tool admin form gets a "require verified contact" toggle (default on) with a confirm dialog when turning it off.
Copilot and Generate Reply get four read-only, access-filtered tools to look up
a customer's history: list the contact's other conversations, search
conversations by exact email, fetch one conversation by reference number, and
search contacts. Tool output is marked untrusted and capped so a big response
can't blow the context window.
Copilot panel changes:
- per-agent persona picker that borrows an enabled assistant's voice, language
and instructions without changing the tool set (stored in localStorage)
- answers render as HTML; copy, insert-into-reply, and add-as-private-note
actions per answer
- chat history moves to its own copilot store; server history is persisted only
after a successful reply and read back with a limit
Adds AI tag suggestions: a new endpoint suggests up to 3 existing tags for a
conversation, applied from the sidebar, never auto-applied.
Hardening and fixes:
- OIDC callback rejects non-agent users
- custom tool URLs and params schema validated on save; tool HTTP client no
longer follows redirects; query keys pinned in the tool URL win
- GetAllConversationMessages takes an explicit limit (cap 1000)
- FAQ mining skips a candidate already pending review
- ai agent handoff records its event only after the move actually lands
- share chat message role constants; rename v2.7.0 migration to v2.6.0 and add
the fix_grammar_spelling prompt
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.
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.
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.
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.
Livechat:
- Add a live widget preview that renders in the inbox settings help rail.
- Warn when the primary or header colors are too close to the background.
- Require gradient/image background values and notice banner text when enabled.
- Show a default launcher logo in the widget when none is set, matching the preview.
- Drop the Beta badge from the livechat channel.
General settings:
- Add "set agents away on login". Agents log in as away and must go online manually.
- Add "show subject in conversation list" toggle.
Other:
- Add a keyboard shortcuts dialog from the user menu.
- Show a "snoozed until" badge on snoozed conversations.
- Contact search now matches emails partially instead of exact.
- Give outgoing message bubbles a secondary background.
Adds a from_name_template field to email inboxes. When set and the sender is an agent, the template renders with agent and inbox variables and becomes the From display name; the inbox email address is unchanged. Any render failure, or a system-user sender, falls back to the plain inbox From so a misconfigured template never blocks delivery.
Variables: .Agent.FirstName, .Agent.LastName, .Agent.FullName, .Inbox.Name (the inbox name). Based on #249.
Co-authored-by: josephsellers <6892567+josephsellers@users.noreply.github.com>
- Introduced a new category field for conversation statuses to classify them as 'open', 'waiting', or 'resolved'. Which allows max assignment limits in team to work with custom statuses as well.
- New meta JSONB field in CSAT responses to capture random adhoc data as that might be needed for custom CSAT pages.
- Add support prefilled rating for csat pages picked from query param
- fix duplicate initialization guard
- notification bell for agents
- Refactor and clean up dead code
- Fixes to pre chat form
- Add new column country to users table
- Update ratelimit pkg to be generic
- fix: live chat email inbox not being initialized.
- make launcher logo take entire launcher space.
- New columns last_interaction_sender_id and last_message_sender_id to track who sent last message in conversation
feat: make data table item names clickable.
fix(conversation): focus reply box editor on conversation change
fix(conversation): focus editor on tab change in ReplyBoxContent
fix(conversation): update conversation list item to use router-link
fix(conversation): update message bubble to use router-link for author
fix(conversation): update last interaction fields on message insert
fix(conversation): add last interaction fields to conversation model
fix(conversation): update SQL queries to include last interaction fields
feat(migrations): add migration for last interaction fields in conversations
fix(schema): update schema to include last interaction fields in conversations