872 Commits

Author SHA1 Message Date
Abhinav Raut a3af592b6b add authenticator app two-factor login for agents 2026-09-07 14:46:15 +05:30
Abhinav Raut 85a1b477f6 Merge pull request #475 from fredrikblau/feat/private-note-ticket-links
Add conversation reference links to private notes
2026-09-06 01:30:42 +05:30
Abhinav Raut c1b999b014 fix stale searches and mention email links 2026-09-06 01:29:26 +05:30
Abhinav Raut 8109d1b1d6 filter conversation and message search by read permissions in sql 2026-09-03 00:51:13 +05:30
Abhinav Raut afeb120a5f apply the search limit after the read-permission filter 2026-09-03 00:10:44 +05:30
Abhinav Raut c47e8e0c5b let search callers pass a result limit and show the palette spinner while typing 2026-09-02 23:48:44 +05:30
Abhinav Raut 9c0d242549 rebuild the command palette and add keyboard shortcuts for conversation actions 2026-09-02 15:44:27 +05:30
Abhinav Raut b136346b95 fetch only the edited view's count after saving a view 2026-09-02 01:19:50 +05:30
Abhinav Raut b001d7a713 use one deadline for the whole sidebar counts request 2026-09-02 00:48:02 +05:30
Abhinav Raut 2ba716cb58 Merge remote-tracking branch 'origin/main' into pr/sidebar-counts
# Conflicts:
#	frontend/apps/main/src/stores/conversation.js
#	internal/conversation/conversation.go
2026-09-02 00:45:50 +05:30
Abhinav Raut 455f745e08 apply code review fixes to sidebar counts
Refresh badges on new notifications, bulk actions, snooze and unassign. Drop the second count request that fired on every local status change, look up the timezone once per counts request, and move the count base query into queries.sql.
2026-09-02 00:44:42 +05:30
Abhinav Raut d22de18112 count all sidebar views in batches instead of dropping past 50 2026-09-02 00:26:55 +05:30
Abhinav Raut 12bb634b0f Merge remote-tracking branch 'origin/main' into shrink-macro-payloads
# Conflicts:
#	frontend/apps/main/src/features/conversation/ReplyBox.vue
#	frontend/apps/main/src/stores/macro.js
#	frontend/apps/main/src/stores/macro.test.js
#	internal/image/image.go
2026-09-02 00:01:42 +05:30
Abhinav Raut 51db29ab9d move agent lookup into its own sql queries 2026-09-01 22:45:07 +05:30
Abhinav Raut c934758ca8 cap sidebar view counts and move the count badge into a component 2026-09-01 21:18:48 +05:30
Abhinav Raut 939ca59598 Merge pull request #540 from eozsahin1993/feat/read-receipt-indicator
Feat: read receipt indicator
2026-08-31 02:15:28 +05:30
Abhinav Raut 01ec83c7ce fix same-second read receipt and add sent/read tooltips 2026-08-31 01:56:19 +05:30
Abhinav Raut c1d613c942 Merge pull request #548 from malpou/fix/oidc-redirect-live-lookup
oidc: resolve redirect URL live so Root URL changes without a restart (preferred option)
2026-08-30 23:48:47 +05:30
Abhinav Raut 60cb85494c Merge pull request #544 from malpou/api/source-id
api: accept source_id on conversation and message endpoints
2026-08-30 23:47:26 +05:30
Abhinav Raut 4402cee153 auth: move resolveRedirectURL below the public methods 2026-08-30 23:45:22 +05:30
Abhinav Raut baeade99a6 oidc: always read the redirect URL from settings, drop the session copy 2026-08-30 23:26:34 +05:30
Abhinav Raut c0471ae230 gate message delete on messages:write_private & drop doc str 2026-08-30 22:50:45 +05:30
Marc Vila 08a12801d9 feat: add separate permission for private conversation notes 2026-08-30 10:22:09 +02:00
malpou 8eeea388d6 oidc: resolve redirect URI once per login and propagate errors
Address CodeRabbit review on the live-lookup change:

- Resolve the redirect URI live in LoginURL and return it so the caller
  persists it in the session; ExchangeOIDCToken reuses that stored value
  instead of resolving again. The token exchange redirect_uri must match
  the one in the auth request (RFC 6749 4.1.3); resolving twice would let
  a Root URL change between login and callback produce a mismatch the IdP
  rejects.
- Propagate the redirect URL resolution error out of LoginURL rather
  than discarding it, so a setting read failure fails the login loudly
  instead of sending an empty redirect_uri.
- Pass rootURL as a formatting argument in oidc.RedirectURL instead of
  concatenating it into the format string, so a root URL containing a
  %-sequence (e.g. %2F) is not interpreted as a verb.
2026-08-29 11:51:43 +02:00
malpou 02e9d1d3bf oidc: resolve redirect URL live so Root URL changes without a restart
Auth providers captured the redirect URL as a string at build time, so
changing app.root_url in General settings left OIDC providers sending
the old redirect_uri until the process restarted. The identity provider
then rejected login with "redirect_uri is missing in the client
configuration" even though the database and the IdP client both held the
right value.

Make auth.Provider.RedirectURL a closure and resolve it in LoginURL and
ExchangeOIDCToken from the current root URL, matching the media store's
existing rootURL closure. The redirect URL is computed by a new
oidc.Manager.RedirectURL(id) helper so the path format stays in one
place. Other provider fields (client ID, provider URL) remain snapshotted
and are refreshed by reloadAuth on OIDC changes, as before.

This removes the reload requirement for Root URL entirely; the settings
handler no longer needs to know that auth depends on it.
2026-08-29 11:32:28 +02:00
malpou 5d84d5af5c Reject line breaks in source_id
NormalizeMessageID now returns empty for a value containing CR or LF.
A source_id with an embedded line break would render as a malformed
In-Reply-To/References header on outbound replies. The IMAP path never
sees such values; the HTTP API can, so the helper rejects them. An empty
result leaves the column NULL, so a bad value degrades threading rather
than producing invalid mail headers.
2026-08-29 11:08:19 +02:00
malpou 3b0d3d4415 api: accept source_id on conversation and message endpoints
Add an optional source_id to createConversationRequest and messageReq and
store it on the created contact message, mirroring the IMAP ingestion path
which is the only place source_id is otherwise populated.

BuildEmailThreadingHeaders builds every outgoing reply's References and
In-Reply-To from prior messages' source_id. A desk fed over the API, as
ours is, therefore references only its own previous sends, so the customer's
mail client has nothing of theirs to thread on and every reply arrives
looking standalone. Letting the API set source_id fixes this for any API-fed
inbox without a direct database write.

The value is normalized (whitespace and angle brackets stripped) since
source_id is stored unbracketed and the brackets are re-added when composing
the threading headers. An empty value leaves the column NULL as before, so
existing callers are unaffected.
2026-08-29 10:59:03 +02:00
Emre 409c5bab91 feat(read-indicator): show read indicator for chat messages
This implementation uses already tracked contact_last_seen conversation field and compares the message createdAt value.
It's also confirmed that this field is only updated for live chat conversations so email conversations falls back to the single check mark.

Testing done on the embedded widget and working as expected.

Fixes #434
2026-08-28 13:19:10 -04:00
Emre 70c4ea0d54 fix(thumbnail): fixes thumbnail rotation issues
Adding `imaging.AutoOrientation(true)` ensures that the image is decoded in the correct orientation based on the EXIF data without making assumptions.

Manually verified the fix after the change and confirmed that it's working as expected

Fixes #538
2026-08-28 12:16:52 -04:00
Abhinav Raut 7ae63c16dc log each time trigger batch fetch 2026-08-28 17:26:00 +05:30
Abhinav Raut 387071117d cut memory spikes from uploads, i18n and the hourly time trigger
Heap profiles on a large install showed three hot spots: image uploads
decoded the full bitmap twice, every i18n request rebuilt the language
pack, and the hourly time trigger loaded all conversations from the
last 30 days into one slice.
2026-08-28 17:06:08 +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
Amberger Moritz 1d88ef13b1 fix: address CodeRabbit review on sidebar counts PR
Restore upstream conversation create/transcript and mobile sidebar behavior that was accidentally overwritten during cherry-pick. Import onMounted in Sidebar.vue and bound view-count queries with a 10s timeout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 15:57:55 +02:00
Amberger Moritz d12c4446a6 feat: show open conversation counts in the inbox sidebar
Adds badges with the number of open conversations next to My inbox,
Mentions, Unassigned, All and each personal or shared view. "Open" means
a conversation whose status category is open, so snoozed and resolved
conversations are not counted.

Backend:
- GET /api/v1/conversations/sidebar-counts returns the counts, gated by
  the conversations:read permission. Each individual count is additionally
  gated server-side by the permission for that list, so an agent never
  receives a number for a list they cannot open.
- View counts are unioned into a single statement, so the number of views
  does not drive the number of queries.
- The list-type conditions shared by the conversation list and count
  queries are extracted into appendListTypeConditions, so both stay in
  sync on what each list type means.
- An empty team list now renders as IN (NULL) instead of the invalid
  IN (), which an agent holding a team permission without a team hit.
- The feature can be turned off via app.sidebar_counts_enabled, which
  defaults to enabled when the setting is missing.

Frontend:
- Counts refresh when switching inboxes, after the agent's own status or
  assignee changes, and from new_conversation and status-carrying
  conversation_update websocket events.
- Refreshes are throttled and share a short TTL, so bursts of incoming
  conversations collapse into a single request. Re-enabling the feature
  bypasses the TTL, because being disabled leaves a zeroed result cached.

Refs #466

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 16:58:30 +02:00
Abhinav Raut b8ddd7e3ab resize avatars once on the server at 512px 2026-08-22 03:14:27 +05:30
Abhinav Raut fe243aa9ab downscale avatars to 256px on upload 2026-08-22 03:08:29 +05:30
Abhinav Raut 85a404c8a0 count only sent outgoing messages in the overview volume 2026-08-22 01:31:57 +05:30
Abhinav Raut cccd2dec25 exclude private notes from the overview message volume counts 2026-08-22 01:23:57 +05:30
Abhinav Raut beb8aeafff update agents offline count to include 'away' status 2026-08-21 23:15:05 +05:30
Abhinav Raut 5813ac3e73 remove comment 2026-08-21 21:59:40 +05:30
Abhinav Raut 8a881909b8 actually close agent websockets on kick and shutdown
KickUser and CloseAll only wrote a close frame and called Conn.Close,
which fasthttp turns into a no-op on a hijacked conn, so a client that
ignores the frame stayed connected and stayed in the hub. Expire the read
deadline and tear the client down so Serve returns.

The agent app had the same replaced-socket problem as the widget, where a
stale socket's close event cleared the live socket's ping timer and the
server dropped it 60s later. Also check the widget client's closed flag
before refreshing the read deadline, and reset inbox and user id on
re-join.
2026-08-21 21:00:39 +05:30
Abhinav Raut e324e136f5 drop widget sockets when a livechat inbox reloads
Saving, enabling or disabling a livechat inbox rebuilds the channel,
which calls LiveChat.Close. That only closed each client's outbound
channel. The socket stayed open, so the widget kept exchanging
ping/pong and looked connected while every later agent reply went
nowhere. Only a page reload fixed it.

Calling conn.Close was not enough either. fasthttp makes Close a no-op
on a hijacked conn unless KeepHijackedConns is set, so it returned nil
and the read loop stayed blocked. Client now holds a disconnect func
that expires the read deadline, and the read loop also breaks if its
client was closed.

On the widget side, connect() replaced this.socket without closing the
old one, so a reconnect could leave two live sockets. It now closes the
previous socket and ignores events from it.

Also return input errors from the pre-chat form validation. A blank
required field used to come back as a 500 with no hint about which
field was wrong.

Adds a livechat e2e harness in cypress/support/livechat.js and four
specs: the inbox reload regression, messaging, session and auth, and
settings. 26 tests, and the reload one fails against a binary without
this fix.
2026-08-21 20:11:24 +05:30
Abhinav Raut 3393b6e1cb close websockets and bound the http server shutdown 2026-08-21 18:11:44 +05:30
Abhinav Raut 364d11aa7f stop duplicate key errors when adding conversation participants 2026-08-21 12:13:17 +05:30
Abhinav Raut 9475e6ddd3 Merge pull request #516 from abhinavxd/geist-and-hc-page-cache
switch to Geist and cache help center pages
2026-08-21 05:16:41 +05:30
Abhinav Raut 9626d512d8 count only away_manual agents as away 2026-08-21 05:10:34 +05:30
Abhinav Raut f4cff729a1 keep days 0 as today in report clamp 2026-08-21 05:00:27 +05:30
Abhinav Raut 3218f79169 fix away agent count, index resolved_at and clamp report days 2026-08-21 04:54:49 +05:30
Abhinav Raut 51f7bace9e fix and speed up the report overview queries
The resolved conversations chart filtered on created_at but grouped by resolved_at, so anything created before the window never showed up, and the tag list dropped tags whose conversations were all older than the window. Dropped the message volume COUNT(DISTINCT) and three of the four users table scans, and added the created_at indexes these queries need. The Pending tile is now labelled Awaiting First Reply since it counts conversations with no agent reply yet.
2026-08-21 04:19:03 +05:30