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.
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.
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.
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.
The surfaces section still described the old gray sidebar and the pre-swap
canvas values. Sidebar background now matches the app background in both
themes, so the tier diagram was wrong.
Also documents what the code already does: the .box, .sidebar-section-label
and .link-style utilities, the badge success variant, the variant prop on
AlertDialogAction, the hover-reveal pattern for table row actions, and the
rule that every non-submit button in a form needs type="button". Trimmed the
prose throughout.
Colors: the brand color moves from indigo to green in both themes, and the
sidebar, tooltip, card and link styles follow it. Three new tokens replace
hardcoded values: foreground-lighter for idle sidebar items, warning-600 for
warning text that needs 4.5:1 contrast on light backgrounds, and link for
anchors inside rendered email content. DESIGN_SYSTEM.md now lists the real
values from main.scss instead of the old indigo ones.
Forms: every button inside a form that is not the submit button now has an
explicit type. Without it the browser treats it as a submit button, so
clicking Cancel on a contact note posted an empty note, "New holiday" saved
the whole business-hours form, and pressing Enter in an SLA field deleted the
first alert row. The login page also highlights an empty password field on a
failed submit, which a broken condition prevented before, and the two
password fields on the set-password page get their own show and hide toggles.
Search: conversation and contact search now drop responses from an older
query, so clearing the box no longer repopulates the list with stale results.
Permissions: /api/v1/ai/summarize now needs messages:write, since it writes a
private note, and the menu item is hidden for agents without it.
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.
Design system:
- add semantic success and warning tokens (light + dark) and wire them into Tailwind
- move all hardcoded status colors (green/amber/red) onto tokens across main app and widget; keep file-type icons as identity colors
- give light mode real surface depth: gray chrome sidebars vs white content, deeper canvas gutter, crisper borders, wider gray spread so selected/hover states show
- unify radius (cards rounded-lg, controls rounded-md) and elevation (card shadow-sm, menu shadow-md)
- make reports overview colors uniform: neutral numbers, green met / red breached
- normalize the two page-title heading outliers to text-xl font-semibold
Form fixes:
- require content on AI snippets
- only include non-checkbox prechat fields when they have a value
AI context:
- skip continuity and CSAT messages in AI history, mining, and previous-conversation tools
When the agent built conversation history and mined FAQs, it used the
raw message text, which included the full quoted reply chain from every
email. That wasted tokens and confused the model with old back-and-forth.
Add emailquote.go to strip quoted blocks. HTML messages get their quote
containers pruned (gmail, yahoo, protonmail, outlook markers, and
blockquotes); plain text gets trailing ">" lines and "On ... wrote:" /
"Original Message" markers trimmed. If stripping leaves nothing (a
quote-only reply or forward), we fall back to the full text so the
message is not dropped. Add the matching protonmail_quote selector to
the frontend hide-quoted-text styles so the two stay in sync.
Also fix knowledge base chunking: plain text with no block structure was
kept as one chunk and could overflow the model limit. It now packs into
size-bound pieces on sentence boundaries, and oversized atomic blocks are
flushed and truncated on their own so they can never sneak through.