`main` moved next 16.2.6 → 16.2.12 (#441) and applied transitive audit
fixes (#442) since this branch was cut, so both manifests conflicted.
Resolved by taking main's manifests and re-applying the group's bumps on
top: prettier ^3.9.6 and prettier-plugin-tailwindcss ^0.8.1 in
package.json, plus tailwindcss and @tailwindcss/postcss 4.3.2 → 4.3.3,
which are lockfile-only here because both are specced as `^4`. Lockfile
regenerated with npm 10 — the version CI's Node 20 ships, so the nested
optional-peer @swc/helpers entry `npm ci` needs is preserved.
Verified next 16.2.12 and #442's transitive versions all hold, and
`npm ci` succeeds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`main` moved next 16.2.6 → 16.2.12 (#441) and applied transitive audit
fixes (#442) since this branch was cut, so both manifests conflicted.
Resolved by taking main's manifests and re-applying only this branch's
`lucide-react` bump, then regenerating package-lock.json with npm 10 —
the version CI's Node 20 ships, so the nested optional-peer @swc/helpers
entry `npm ci` needs is preserved. Verified next 16.2.12, lucide-react
1.27.0, and #442's transitive versions all hold, and `npm ci` succeeds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`main` moved next 16.2.6 → 16.2.12 (#441) and applied transitive audit
fixes (#442) since this branch was cut, so both manifests conflicted.
Resolved by keeping main's `next` and this branch's `next-intl`, then
regenerating package-lock.json from the merged package.json with npm 10 —
the version CI's Node 20 ships, so the nested optional-peer @swc/helpers
entry `npm ci` needs is preserved. Verified next 16.2.12, next-intl
4.13.4, and every audit-fixed transitive version from #442 survive, and
`npm ci` succeeds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16.2.6 is in the vulnerable range for eight advisories, all patched in
16.2.11: SSRF in rewrites via an attacker-controlled destination
hostname, SSRF in Server Actions on custom servers, middleware/proxy
bypass in App Router, DoS in App Router Server Actions, DoS in the image
optimizer via SVG, unauthenticated disclosure of internal Server
Function endpoints, and two response-body cache-confusion issues.
`eslint-config-next` moves with it to keep the pair on one version.
Lockfile regenerated with npm 10 to match CI's Node 20 — the default
npm 11 drops next-intl's nested optional-peer `@swc/helpers` entry and
breaks `npm ci`.
Verified: lint (39 warnings, unchanged from main), typecheck, 645 tests,
and `next build` all pass on 16.2.12.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Makes the AI auto-reply bot visible and controllable from the inbox,
completes the handoff, and records token spend. Builds on the existing
lib/ai engine rather than replacing it.
Track A — user-facing
- messages.ai_generated flag → small "AI" badge on bot-generated bubbles
- "Take over" / "Resume AI" banner per conversation
(POST /api/ai/autoreply/[id]) — take-over pauses + assigns to the agent
- handoff now assigns to a configurable target (agent or unassigned
queue) and writes an internal note summarizing the exchange; the
existing on_conversation_assigned trigger notifies the assignee
- Settings → AI: "Hand off to" target picker
Track B — observability + safety
- providers return normalized token usage; logged per run to ai_usage_log
(admin-readable) from both auto-reply and draft
- account-wide auto-reply rate limit (30/min) on top of the
per-conversation cap
Migration: 033_ai_reply_polish.sql (messages.ai_generated,
ai_configs.handoff_agent_id, conversations.ai_handoff_summary,
ai_usage_log). Tests + typecheck + lint pass (607 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promotes the AI assistant out of Settings into a first-class "AI Agents"
sidebar area.
- Sidebar: new "AI Agents" item → /agents.
- /agents page with two tabs:
- Playground — a test chat (POST /api/ai/playground) that runs the
real auto-reply path (KB retrieval + provider, auto_reply prompt,
multi-turn, shows handoff). loadAiConfig gains requireActive so you
can test before enabling.
- Setup — the existing provider/key + knowledge-base config, relocated.
- Removed the AI section from Settings (settings-sections + page); same
account config, new home. No migration.
Tests +6 (query, loadAiConfig requireActive). 593 pass; typecheck, lint,
build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grounds the AI assistant (0.5.0) in the account's own content so it can
answer instead of handing off.
- Hybrid retrieval: Postgres full-text search for every account; optional
semantic search (pgvector + OpenAI text-embedding-3-small) when an
embeddings key is set. Semantic-primary, topped up with FTS.
- src/lib/ai: chunk, embeddings, knowledge (ingest + retrieve), query;
buildSystemPrompt gains a knowledge block; config carries an
(optional) embeddings key.
- Wired into /api/ai/draft and the auto-reply bot (best-effort — never
blocks a reply).
- KB API (/api/ai/knowledge CRUD + /reindex) and a Settings → AI
Assistant knowledge-base manager + embeddings-key field.
- Migration 030: pgvector, ai_knowledge_documents, ai_knowledge_chunks
(fts + embedding), match RPCs, ai_configs.embeddings_api_key.
Migration required: supabase/migrations/030_ai_knowledge.sql.
Stacked on feat/ai-reply-assistant (#329).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an AI reply assistant powered by the account's own OpenAI or
Anthropic key (stored AES-256-GCM-encrypted; no per-seat AI fee).
- AI-drafted replies in the inbox composer (✨ button, agent+), via
read-only POST /api/ai/draft.
- Optional auto-reply bot on inbound messages that no Flow consumed and
no agent owns, with a per-conversation cap and clean [[HANDOFF]] to a
human. Stands down when a message-level automation would also reply.
- Settings → AI Assistant (admin+): provider/model, key, business
context, toggles, and a Test-key button that verifies before saving.
- Provider layer (src/lib/ai) with OpenAI + Anthropic adapters behind
one interface; migration 029 (ai_configs + per-conversation columns +
atomic claim_ai_reply_slot RPC).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This introduces the base i18n setup using next-intl without routing. It adds the default messages/en.json and sets up the app layout and next.config.ts to consume the locale from the environment. The login page has been migrated as a first example.
Completes the public API roadmap (#245): register HTTPS endpoints to be
POSTed to when things happen in an account, so automations can react to
inbound activity instead of polling.
- Migration 028: account-scoped webhook_endpoints (secret stored
AES-GCM-encrypted) + an atomic record_webhook_failure() function.
- Scope webhooks:manage; management API GET/POST /api/v1/webhooks and
GET/PATCH/DELETE /api/v1/webhooks/{id} (secret returned once).
- Events message.received / conversation.created / message.status_updated
dispatched from the inbound webhook handler's after() block.
- Signed delivery (X-Wacrm-Signature = HMAC-SHA256 over t.body), single
best-effort attempt, auto-disable after consecutive failures.
- Versioned 0.4.0; docs/public-api.md Webhooks section; CHANGELOG.
Fixes from the adversarial code review of this diff:
- SSRF: block private/loopback/link-local (incl. cloud metadata) targets
at delivery time + redirect:'manual' so a public URL can't bounce
internal (src/lib/webhooks/ssrf.ts).
- Status mirror UPDATE no longer uses .select().maybeSingle() (message_id
is non-unique); account resolved via a bounded limit(1) read.
- Atomic failure counter via SQL RPC (was a racy read-modify-write).
- conversation.created emitted before the reaction short-circuit.
- Per-delivery id added for receiver dedupe; docs state at-least-once /
unordered delivery.
- Signature verify tolerates hex case / header whitespace.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the public API roadmap on top of the existing key-auth layer
(#290). Re-lands POST /api/v1/messages (lost when the stacked #291 never
reached main) by extracting the dashboard send route's core into
`sendMessageToConversation`, then adds:
- Contacts: GET/POST /api/v1/contacts, GET/PATCH /api/v1/contacts/{id}
- Conversations: GET list + /{id} + /{id}/messages
- Broadcasts: POST /api/v1/broadcasts + GET /{id} (persist rows, fan out
in after(), poll for status)
All list endpoints share one keyset-pagination contract
({ data, meta: { next_cursor } }). No migration required — scopes and
tables already exist. Versioned as 0.3.0 (folded into [Unreleased]);
docs/public-api.md updated; outbound webhooks left as the sole roadmap
item. API stays v1 (additive).
Includes fixes from an adversarial code review: cursor validation
against filter injection, broadcast counts deferred to the DB aggregate
trigger, terminal broadcast status reflects failures, duplicate-phone
de-dup, up-front send-param validation (no orphan conversations),
PATCH null-clear semantics, error-checked tag sync, bounded tag filter,
unified audit-user attribution, and a maxDuration on the broadcast
fan-out.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to #259. Voice notes are encoded to Ogg/Opus in the browser via
opus-recorder (small WASM encoder, lazy-loaded on record), so there's no
server-side transcode and nothing to provision on the host. The server
stores the .ogg like any other attachment; WhatsApp renders it as a voice
note.
- Composer: MediaRecorder + /api/whatsapp/transcode-audio replaced with
opus-recorder. Encoder worker vendored at public/opus/encoderWorker.min.js
(no install-time download to fail), ignored by eslint + prettier.
- Removed the transcode route, the audio-transcode lib + tests, the
ffmpeg-static dep + ambient types, the FFMPEG_PATH env doc, and the
unused transcodeAudio rate limit.
- Added opus-recorder ambient types.
Still behind the 'chat_media' beta flag with per-kind size caps.
typecheck/lint/build green; 414 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Agents can now attach a photo/video/document or record a voice note in
the inbox composer and send it live in a 1:1 thread — previously media
could only be received from customers or sent via the Flows builder.
- 023_chat_media.sql: new account-scoped `chat-media` Storage bucket
(mirrors flow-media RLS from 020) plus Meta-accepted audio MIME types.
- meta-api: sendMediaMessage gains an `audio` kind (link-only — Meta
rejects caption/filename on audio; WhatsApp renders OGG/Opus as a
voice note).
- /api/whatsapp/send: media branch reusing the phone-variant retry loop.
- /api/whatsapp/transcode-audio: Node-runtime route remuxing the
WebM/Opus that Chromium's MediaRecorder produces to OGG/Opus via
ffmpeg-static (Firefox/Safari record accepted formats and skip it).
- composer: attach menu, file pickers, voice recorder + auto-transcode,
preview with caption; thread: optimistic handleSendMedia.
- shared uploadAccountMedia helper (flows form refactored onto it).
- next.config: allow microphone=(self) (was fully blocked) + CSP
media-src; document optional FFMPEG_PATH override.
Tests: meta-api audio payload shape, buildMediaPath, audio-transcode
helpers (14 new). typecheck/lint/build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>