Profiled the prod alloc with pprof. Three things stood out.
SLA evaluation was refetching about 234k pending rows every 10 minutes and the
count only ever grew. A policy can leave the resolution time blank, which
stores a NULL resolution deadline. The evaluator needs a deadline to compare
against, so it could never set met_at or breached_at, and the row stayed
pending forever. The pending query now skips a metric with no deadline, and a
new set based query closes any pending row whose configured metrics have all
settled. It replaces the old per row status update, so there is one status
writer and the backlog drains itself on the first tick. No migration is needed
because nothing outside the sla package reads that status.
Static assets were copied into fasthttp's pooled per connection response
buffer on every request. That buffer keeps its grown size, so serving the
frontend bundle held about 165MB. SetBodyRaw points at the bytes instead of
copying them.
API key auth ran a full bcrypt compare on every request, which was 62 percent
of cpu samples. API secrets are 64 char random tokens, so bcrypt's work factor
buys nothing there. They are now hashed with sha256 and compared in constant
time. Old bcrypt hashes still verify and get upgraded in place on first use,
so no key needs reissuing. Password login still uses bcrypt.
This also fixes an unrelated bug the work turned up. apply-sla deleted pending
rows by status alone, so re-applying an SLA threw away a first response breach
that was already recorded. It now deletes only rows with nothing recorded and
closes the rest to their terminal status first.
Address CodeRabbit review on PR #426.
Log the error when deleting a contact's avatar file fails so a
leftover avatar (personal data) does not go unnoticed.
Add availability_status, last_active_at, last_login_at, and enabled
to the contact data export since these are personal data under GDPR
right to access.
First part of GDPR support (#244): the right to erasure and the right
to access.
- DELETE /api/v1/contacts/{id} permanently deletes a contact or
visitor. Conversations, messages, notes, and participants go with it
via DB cascades. The avatar file is removed too.
- GET /api/v1/contacts/{id}/export downloads a JSON file with
everything stored about the contact: profile, custom attributes, and
all conversations with their messages. Private notes stay internal.
- Both actions are gated by new permissions (contacts:delete,
contacts:export), granted to Admin in migration v2.6.0, and recorded
in the activity log with actor and IP.
- Contact page gets Export data and Delete contact buttons, with a
confirm dialog for delete.
- The unlinked media cleaner now also removes attachment files whose
message no longer exists. Before this, deleting a conversation left
its attachments on disk forever.
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
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.
Re-derived for the v2.x contact model. insert-contact-with-external-id
already updates names on conflict; this adds the same to
insert-contact-without-external-id, preserving existing non-empty names
via COALESCE(NULLIF(EXCLUDED.first_name, ''), users.first_name).
Without this, names from the email worker and Gravity Forms never update
existing contacts.
[Used Claude Code 🤖]
Reject disabled agents in FilterAuthorizedListUUIDs, kick role members on
permission removal or role delete, and re-sub the list and open conv on
ws reconnect using local state. Also plug the app logger into the ws
package so kicks no longer log when no connections exist.
Convs and messages now push the full list-row payload to perm-authorized agents on create/assign/message, so livechat returning customers bubble up instantly without waiting for the timer refresh. Background list refresh bumped 30s -> 60s as a drift safety net.
Targets broadcasts via per-tab list and open subs (validated at subscribe time)
instead of broadcasting message uuid to all agents. Adds frontend list typing, throttled sound,
and many perf improvements to reduce cpu memory & database queries.
Other fixes: Enforce message-to-conversation binding in handleGetMessage
- 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
Show a warning in sidebar when a conversation with a visitor is opened.
Move string.js utils to shared-ui utils
Modify user fetch queries to allow optional filter by multiple user types not just one.
Fix capitalizaiton for live chat en translations
For example, when a user selects the USA, the form also shows Canada, as both share the +1 calling code.
Rename column from `phone_number_calling_code` to `phone_number_country_code`.
Feat: Show the calling code alongside the country flag in the contact form for the selected country. Previously, only the flag was displayed.
- Update all SQL queries to add missing columns
- Update the create conversation API to allow setting the initiator of a conversation. For example, we might want to use this API to create a conversation on behalf of a customer, with the first message coming from the customer instead of the agent. This param allows this.
- Minor refactors and clean up
- Tidy go.mod
- Rename structs to reflect purpose
- Create focus structs for scanning JSON payloads for clarity.
- Deleted the VisitorInfoForm.vue component and its associated schema.
- Introduced a new preChatFormSchema.js to handle dynamic form validation.
- Updated ChatView.vue to conditionally display the PreChatForm based on user session and conversation state.
- Enhanced chat store to manage current conversation updates.
- Implemented WebSocket event handling for conversation updates.
- Updated localization files to include new terms related to the pre-chat form.
- Modified conversation management logic to support broadcasting updates to widget clients.
- Updated SQL queries to accommodate custom attributes for visitors.
fix: Chat initialization failing due to the JWT authenticated user doesn't exist in the DB yet.
fix: Always upsert custom attribues instead of replacing.
feat: Add HTTP utility functions for trusted origin checks
feat: Implement typing status broadcasting for live chat clients and agents.
feat: Add support for signed URLs in media manager
fix: Update database migration to handle duplicate visitors with same email address.
feat: Add conversation subscription and typing message models for WebSocket communication
feat: Implement conversation subscription management in WebSocket hub this is used for broadcasting typing indicator.
feat: Revamp widget JavaScript to improve mobile responsiveness and show unread messages if any.
- new vue app for serving live chat widget, created subdirectories inside frontend dir `main` and `widget`
- vite changes for both main app and widget app.
- new backend live chat channel
- apis for live chat widget
- Added endpoints to generate and revoke API keys for agents.
- Updated user model to include API key fields.
- Update authentication middleware to support API key validation.
- Modified database schema to accommodate API key fields.
- Updated frontend to manage API keys, including generation and revocation.
- Added localization strings for API key related messages.
- use cached agent in all middlewares
- fix: race in casbin that gave permission denied error.
- stop loading permissions into casbin on every `Enforce` function call instead cache user permissions in authz package and when permissions change only the load permission as policies atomically.
- sort permissions in get-agents to make the permissions slice comparsion using slices.Equal work
fix: filter out unknown permissions from role form while submitting
fix: correct strong password validation logic on reset
refactor: remove contact note update handler as it adds more complexity agents can simply delete note.
feat: new block contact api which required contacts:block perm