Commit Graph

714 Commits

Author SHA1 Message Date
Abhinav Raut b3c162381a fix AI agent OTP verification flow
The verification tools and OTP prompt block loaded for every unverified
customer, so an assistant with no account-acting tools still asked people
for an email code it had no use for. The worker now checks the assistant's
tools first and skips both unless one is flagged requires_verification. A
lookup error fails open and keeps the flow.

The send tool's result did not say which address it mailed, so the model
filled one in from chat history. After a sibling conversation rebound the
contact's email, it named the old address while the mail went to the new
one. The result now names the address and mentions the spam folder.

The prompt did not say to check every code the customer sends, so after one
rejection the model declared the next code wrong without calling the tool
and handed off to a human.
2026-08-08 12:40:04 +05:30
Abhinav Raut 4710177ca0 skip disabled agents and blank emails in notify action 2026-08-07 22:52:21 +05:30
Abhinav Raut cb96d9f087 pass all previous values when claiming an unassigned conversation 2026-08-07 22:41:19 +05:30
Abhinav Raut 057d798fd7 add missing evaluator operator and field tests 2026-08-07 22:06:23 +05:30
Abhinav Raut ca7aad6a0a snapshot previous values before reopening conversation on incoming message 2026-08-07 21:37:41 +05:30
Abhinav Raut 49f287f843 add tests for automation loop guards 2026-08-07 21:33:24 +05:30
Abhinav Raut e67aa28baf suppress only automation's own events, not concurrent agent updates
Automation update events now carry the actor. A suppressed conversation drops only system-user events (the engine's own in-flight actions), so an agent updating the conversation during that window is evaluated instead of silently dropped. Autoassigner events still trigger automations since they arrive outside the suppress window.
2026-08-07 20:42:06 +05:30
Abhinav Raut ae0b6ee9f2 rework notify action with subject, message and picked recipients
The notify action only created a fixed in-app notification and recipients
were typed as a raw team:<id> / user:<id> DSL where typos silently notified
nobody. Now:

- recipients are picked by name (assignee, assigned team, any team or agent)
- admin writes the subject and message; both show in the bell notification
  and go out as an email (message plus conversation link, wrapped in the
  default outgoing email template, so nothing is hardcoded in English)
- the action serializes as typed fields {subject, message, recipients}
  instead of a positional value array

Also: previous_* condition fields are hidden for time triggers and populated
on message events (previous = current) so those rules can actually match,
action row widths now follow the RuleBox pattern (fixed type select, value
widgets fill the row), and three new i18n keys merged into existing globals.
2026-08-06 02:38:38 +05:30
Abhinav Raut 89b40f9dac add compact webhooks endpoint and fix previous_* automation values 2026-08-06 00:17:13 +05:30
Abhinav Raut c4270f48d3 reject non-positive webhook IDs for targeted delivery
strconv.Atoi accepts "0" and negative values, and the delivery worker treats a
non-positive WebhookID as a fan out to every subscriber of the event. So a
malformed target value sent the conversation payload to unintended webhooks.
Reject it in the action and drop it in TriggerWebhook as well.
2026-08-05 22:55:09 +05:30
Abhinav Raut aa0deedd48 fix automation rule feedback loops and scope the starts with operator
An automation rule that listens on an event and then writes the same field
re-fires its own event, so the rule matches again and loops forever. Status
already returned early on a no-op write, but priority and user assignment did
not, so those two could spin a worker doing a DB write per lap. Add the same
unchanged-value guard to both.

Suppression while the engine applies actions was a plain flag in a sync.Map,
so with more than one worker on the same conversation the first one to finish
deleted the key while the other was still applying actions. Make it a refcount
so each worker releases only its own claim.

The starts with operator is only implemented by the automation evaluator, not
the SQL filter builder, so give automation text fields their own operator list
instead of adding it to the shared one.

Also switch the notify action to its own recipients field type, fix the snooze
duration hint since the backend only takes Go duration units, trim and
lowercase notify recipient entries, and treat a missing previous value as no
match rather than an empty string.
2026-08-05 18:01:20 +05:30
Abhinav Raut 2aa4a1a86e Merge branch 'main' into new-automations
# Conflicts:
#	i18n/da-DK.json
#	i18n/de-DE.json
#	i18n/es-ES.json
#	i18n/fa-IR.json
#	i18n/fr-FR.json
#	i18n/it-IT.json
#	i18n/ja-JP.json
#	i18n/mr-IN.json
#	i18n/pt-BR.json
#	internal/conversation/conversation.go
2026-08-05 16:14:06 +05:30
Abhinav Raut 178136cda5 add notify, snooze and trigger webhook automation actions 2026-08-05 16:12:55 +05:30
Abhinav Raut 67f885d21a Merge pull request #467 from abhinavxd/pr433-imap-junk-parts
PR-433
2026-08-05 12:25:07 +05:30
Abhinav Raut 086eb4e448 skip junk email parts and name nameless attachments on IMAP ingest
Disposition-less parts with no content-id and no filename (bounce reports, pgp signatures, calendar bodies) no longer become attachments. Parts without a filename now get one from their content type instead of being saved as ".".
2026-08-05 12:06:04 +05:30
Abhinav Raut 46c6f84cfc Merge remote-tracking branch 'origin/main' into fix/draft-inline-cid-scope
# Conflicts:
#	internal/media/media.go
#	internal/media/queries.sql
2026-08-02 10:27:25 +05:30
Abhinav Raut 2d8f9534cf resolve draft inline media scoped to the draft's own conversation so pasted same-conversation images keep working 2026-08-02 10:18:22 +05:30
Abhinav Raut 642b5a1942 add tests for the OTP verification email binding 2026-08-02 10:08:09 +05:30
Abhinav Raut 3f7c50c885 treat a pending OTP without an attested email as expired 2026-08-02 10:05:50 +05:30
Abhinav Raut d855a45a66 bind OTP verification to the attested email so a sibling conversation can't rebind the contact to an unverified address 2026-08-02 09:57:17 +05:30
Abhinav Raut 2e89586a11 send links to the LLM as markdown so drafts keep real hyperlinks
Transcripts flattened links to "text ( url )", so the model copied that shape and drafts came back with the URL in brackets instead of a link. HTML to text for the LLM now writes "[text](url)", which Markdown2HTML turns back into an anchor.
2026-07-31 18:02:22 +05:30
Abhinav Raut da378e7615 refine reply drafting instructions for clarity and precision 2026-07-31 17:33:00 +05:30
Abhinav Raut 0efd643146 move improve-draft plain-text fallback to the server 2026-07-31 17:20:05 +05:30
Abhinav Raut fa4305d880 address PR review: stricter HTML detection and fence label punctuation 2026-07-31 17:02:37 +05:30
Abhinav Raut a5a029d894 fix lossy text conversions in AI features 2026-07-31 16:36:04 +05:30
Abhinav Raut 72c3f29830 fix hyperlinks getting lost across AI features
AI grammar fix rewrote the draft as plain text, so links in the reply box were dropped. It now sends the editor HTML and asks the model to keep tags. Conversation transcripts, the AI agent history and copilot context also stripped link URLs when converting HTML to text, so the model never saw them. They now keep links as "text ( url )".
2026-07-31 16:08:19 +05:30
Abhinav Raut 189c0860e1 Merge pull request #455 from abhinavxd/chore/tidy-migration-comments
remove redundant comments in v2.6.0 migration
2026-07-31 10:20:57 +05:30
Abhinav Raut 232d445442 Merge pull request #454 from amaanJvd/fix/unsnoozer-dropped-error-log
fix(log): don't drop the error detail in unsnoozeAll log call
2026-07-31 10:20:28 +05:30
Abhinav Raut 081cc46aa2 remove redundant comments in v2.6.0 migration 2026-07-31 10:19:51 +05:30
amaanJvd 3d5d67b554 fix(log): don't drop the error in unsnoozeAll log call
The error was passed as a positional argument without a key. logf drops
the last field when the field count is odd (handleLog: "If there are odd
number of fields, ignore the last"), so the actual error was silently
discarded and the log line carried no diagnostic detail when the
unsnooze query failed. Pass it as an "error" key/value like everywhere
else in the codebase.
2026-07-30 18:23:26 -07:00
Abhinav Raut f2edc74e18 hardcode copilot name and tidy up AI admin copy 2026-07-31 03:50:39 +05:30
Abhinav Raut 859777dfb4 Merge pull request #447 from abhinavxd/feat/ai-tag-embeddings
shortlist tags with embeddings before asking the LLM to suggest them
2026-07-31 02:46:14 +05:30
Abhinav Raut 52a455f1e8 link message media in one query inside the insert transaction
Message attachments and inline images are now linked with a single UPDATE
that runs in the same transaction as the message insert, so a failure can no
longer leave media orphaned. The inline content_id is stamped by the query
itself, and inlineContentID lowercases the uuid to match it - an uppercase
hex uuid in an image URL used to save a cid the DB never matched, so the
image rendered broken. Drops the now unused Attach and SetContentID.
2026-07-31 02:20:01 +05:30
Abhinav Raut cda53690b0 use shared attachment URL signing in message handlers 2026-07-31 01:55:41 +05:30
Abhinav Raut 75a092514b Fix WS image URLs 2026-07-31 01:26:40 +05:30
Abhinav Raut 205ad8a29e Serve thumbnails inline 2026-07-31 01:20:37 +05:30
Abhinav Raut b553e9625c Keep widget images inline 2026-07-31 01:15:55 +05:30
Abhinav Raut 37224b1030 Clarify thumbnail store handling 2026-07-31 01:11:12 +05:30
Abhinav Raut 79da9e8988 Fix live chat image thumbnails 2026-07-31 01:05:32 +05:30
Abhinav Raut 11d0b523ff bound the tag query by tokens and always clear the tag index on purge 2026-07-30 23:21:07 +05:30
Abhinav Raut 000d789ed8 shortlist tags with embeddings before asking the LLM to suggest them
Tag suggestion used to send the first 300 tags to the model, so anything past
that was invisible. Tags are now embedded into the same index as the knowledge
base, and only the tags most similar to the conversation get sent. The reconcile
loop embeds new and renamed tags and drops vectors for deleted ones, and a
provider change purges them so they are rebuilt with the new model.
2026-07-30 23:09:23 +05:30
Abhinav Raut 16b99069b9 render the generated reply as html instead of showing raw markdown 2026-07-30 16:52:56 +05:30
Abhinav Raut f271462119 dedup mined FAQs against question and answer so near-duplicate suggestions get caught 2026-07-30 00:10:10 +05:30
Abhinav Raut 71fb197826 bump the default completion model off gpt-4o-mini 2026-07-27 02:39:57 +05:30
Abhinav Raut 8db2e34ab5 show specific AI provider errors instead of a generic message 2026-07-27 02:26:33 +05:30
Abhinav Raut e8e2419705 show a clear error when the AI agent runs out of steps and hoist openai response structs 2026-07-27 01:47:06 +05:30
Abhinav Raut b569777cf6 match email quote classes exactly and enforce password length on set password 2026-07-25 21:46:17 +05:30
Abhinav Raut 49c5cb3493 make AI agent max steps and history size configurable
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.
2026-07-25 17:16:59 +05:30
Abhinav Raut 6b8a0f9521 fix content loss in knowledge base chunking and harden AI agent limits
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.
2026-07-25 03:52:32 +05:30
Abhinav Raut 712fcbb642 return clear 400 errors for invalid AI tool and snippet input
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.
2026-07-23 10:00:19 +05:30