140 Commits

Author SHA1 Message Date
Abhinav Raut 51db29ab9d move agent lookup into its own sql queries 2026-09-01 22:45:07 +05:30
Abhinav Raut e2ca7c7e5f avoid stale lookup results 2026-08-28 02:19:57 +05:30
Abhinav Raut 18514cd021 search and paginate agents, teams, tags and macros instead of loading them all at boot 2026-08-28 01:43:59 +05:30
Abhinav Raut d0daeb8659 Merge remote-tracking branch 'origin/main' into reduce-memory-and-cpu-hotpaths 2026-08-19 21:54:16 +05:30
Abhinav Raut 4277ff501f fix sla apply atomicity and superseded sla cleanup, read static file bytes once 2026-08-19 14:03:29 +05:30
Abhinav Raut 71e46490a9 only upgrade the api secret hash that was just verified 2026-08-19 13:04:23 +05:30
Abhinav Raut 9708f78c79 reduce memory and cpu use in sla, static asset and api key paths
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.
2026-08-19 12:55:52 +05:30
Abhinav Raut 76d0ef4f40 cut restating comments from the test suite 2026-08-19 12:41:41 +05:30
Abhinav Raut fb4ad5f067 add cypress api and page sweep tests, fix agent and context link bugs they found 2026-08-19 03:18:39 +05:30
Abhinav Raut ee6bc6ec49 add csat responses to contact export and fix download revoke 2026-08-16 13:21:37 +05:30
Abhinav Raut 5735214d1a Merge remote-tracking branch 'origin/main' into worktree-gdpr
# Conflicts:
#	cmd/upgrade.go
#	frontend/apps/main/src/views/contact/ContactDetailView.vue
#	i18n/en-US.json
#	internal/media/queries.sql
#	internal/migrations/v2.6.0.go
#	internal/user/contact.go
#	schema.sql
2026-08-15 13:17:49 +05:30
Abhinav Raut fad797263f add edge case tests for contact resolution 2026-08-11 16:42:06 +05:30
Abhinav Raut adf6aa4270 fall back to email lookup on unknown external ID and let the form opt out of contact sync
Two contact resolution fixes in create conversation:

Under the reuse policy, an unknown external_user_id went straight to
insert. If the email belonged to an existing contact, that inserted a
duplicate contact sharing the email, since the unique email index only
covers rows without an ext_id. The ext_id lookup now falls back to the
email match before inserting. Regression test added.

The new conversation form sends name fields as a snapshot from contact
search, so a contacts:write agent submitting the form could overwrite a
contact that was renamed after the search. The form now sends
reuse_contact: true and the backend downgrades to the reuse policy when
it is set. API callers like an external CRM omit the flag and keep the
full sync/upsert behavior. The flag can only downgrade, never escalate.

Also from review: hoist the repeated password generation block into
newContactPassword, drop testutil.NewLogger and the duplicate
newTestI18n in transcript_test.go in favor of testutil.NewI18n, and
clean up stale comments.
2026-08-11 15:33:32 +05:30
Abhinav Raut b622fc3658 extract shared Postgres test harness into internal/testutil with a make test-db target 2026-08-11 14:40:48 +05:30
Abhinav Raut 1ffcee31b7 unify contact resolution into ResolveContact with an explicit policy and add integration tests 2026-08-11 13:34:44 +05:30
Abhinav Raut 246dd5ba00 scope external ID contact lookup to contacts only 2026-08-11 13:04:12 +05:30
Abhinav Raut f2b3bcd9b6 log enforce errors and skip password generation for existing contacts in create conversation 2026-08-11 13:00:05 +05:30
Abhinav Raut 00ec7ff934 send first message to the matched contact's stored email and pass external_user_id from the new conversation form 2026-08-11 12:21:08 +05:30
Abhinav Raut f986981b44 make no-permission contact resolution race-safe in create conversation API 2026-08-11 12:08:23 +05:30
Abhinav Raut ab2a1471e6 log avatar delete failures and export more contact fields
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.
2026-07-18 12:51:57 +05:30
Abhinav Raut bfbcd0a885 add contact deletion and data export for GDPR compliance
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.
2026-07-14 01:30:59 +05:30
Abhinav Raut dad99bf0ee render AI agent replies as markdown and fix assistant identity, handoff, 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
2026-07-12 23:12:44 +05:30
Abhinav Raut a7288e11b0 add AI assistant expectation message shown in the livechat widget 2026-07-12 13:29:59 +05:30
Abhinav Raut 3ba2b8281e Merge remote-tracking branch 'origin/main' into feat/ai-agent 2026-07-12 04:50:24 +05:30
Abhinav Raut 7186c95e40 add WIP autonomous AI agent that replies to assigned conversations
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.
2026-07-11 04:54:49 +05:30
Abhinav Raut 2e203607e5 fix duplicate contact creation on transient error during ext_id enrichment
CreateContact treated any SetExternalUserID failure as "ext_id taken" and
fell through to the upsert, so a transient DB error created a second
contact with the same email. Now only a unique violation or a contact
deleted mid-flight falls through, other errors are returned.
SetExternalUserID also reports whether a row was actually updated, and
dbutil error checks use errors.As so wrapped errors match. Widget JWTs
with no email now store NULL instead of an empty string.
2026-07-10 01:06:38 +05:30
Abhinav Raut fc71d30b8a accept phone country code in livechat JWT auth 2026-07-09 23:15:05 +05:30
Abhinav Raut 8a8d372a70 use one phone field in livechat JWT and fix phone validation 2026-07-09 02:27:08 +05:30
Abhinav Raut 8f658f274d add livechat pre-chat phone field and fix default launcher logo 2026-07-09 01:49:38 +05:30
Abhinav Raut dc882e033a Merge pull request #260 from josephsellers/contact-name-upsert
Fix: update contact names on upsert instead of discarding them
2026-07-07 23:34:29 +05:30
Abhinav Raut 76d00c899e make the contact name upsert actually run for existing contacts 2026-06-19 04:01:56 +05:30
josephsellers 000326bbde Fix: update contact names on upsert instead of discarding them
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 🤖]
2026-06-18 11:23:42 +01:00
Abhinav Raut a629beaf0b resolve sql builder date filters in the app timezone instead of UTC 2026-06-18 11:56:43 +05:30
Abhinav Raut 3247a3aa58 feat: support nested AND/OR filter groups and new fields in views 2026-06-16 16:14:12 +05:30
Abhinav Raut 29881f2f82 fix: close ws stale-perm gaps and resub on reconnect
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.
2026-05-28 20:07:19 +05:30
Abhinav Raut a502039e59 perf: WS push for conv list, store/cache cleanups, ad-hoc UI polish
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.
2026-05-22 20:47:36 +05:30
Abhinav Raut edc82e9e55 refactor: drop Casbin, centralize agent cache invalidation, dedupe pending SLAs 2026-05-21 21:24:42 +05:30
Abhinav Raut 33572efa04 fix: address WS PR review - stale cache retry, livechat buffer, last_active TOCTOU 2026-05-21 00:46:43 +05:30
Abhinav Raut 7a8efcde16 fix: widget ping interval, last_active flush, ws ping error, slim new_message payload 2026-05-21 00:13:33 +05:30
Abhinav Raut 43ebed1c3a WS rewrite: per-client subscriptions and perf improvements
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
2026-05-20 23:42:07 +05:30
Abhinav Raut 6ef17eeae6 Make widget /auth/exchange idempotent
Prevent tokens sent twice from widget.js, send only once.
Update doc strings & comments for clarity.
Fix error handling
2026-04-16 01:16:24 +05:30
Abhinav Raut 3fb75fbdde Make contact email matching deterministic and scope contact blocking to the email address 2026-04-15 12:50:55 +05:30
Abhinav Raut a644ab2e44 Scope offline status broadcasts by user type instead of broadcasting to all 2026-04-15 02:21:18 +05:30
Abhinav Raut d0a3527269 Sync contact name/email from JWT on every widget request 2026-04-01 17:15:22 +05:30
Abhinav Raut 5590ae6ae3 Create contact from JWT in widget middleware for visitor merge, fix message input focus and pending cleanup 2026-03-30 00:28:33 +05:30
Abhinav Raut eb94f2b06b Refactor widget WS: fix concurrent writes
Update merge visitor to contact query to include other tables
Update default for continuity email config
2026-03-28 21:19:11 +05:30
Abhinav Raut 42f5a7faf5 Show ext id in contact list 2026-03-28 19:59:13 +05:30
Abhinav Raut 2b645fa4e8 Security fixes: rate-limit widget endpoints, remove JWT from logs, harden middleware checks
Increase max page size to 500
Allow large out of bound page sizes by overriding to maximum set.
Reset websocket reconnect attempts when widget is back online
2026-03-28 19:27:41 +05:30
Abhinav Raut 23e6a70844 Allow contacts with different external_user_ids to share email, add enrichment and remove email check from conversation matching 2026-03-28 12:29:44 +05:30
Abhinav Raut a788553137 Use first unread message as subject for continuity emails, add ref number to continuity email subject.
- Continuity email layout fixes and improvments.
- Replace haikunator with email local part for visitor names, fallback to "Visitor"
- Add optional website_url config field for "go to chat" link in continuity email footer
- Remove unused / unncessary i18n keys.
2026-03-16 00:49:20 +05:30