Matthew Meszaros
|
81bfb41d4e
|
feat: add GET /ai/sessions/:id/messages returning a session transcript hydrated into the client turn and block shape plus any pending approval, so a reopened assistant conversation rehydrates
|
2026-07-15 17:54:16 +02:00 |
|
Matthew Meszaros
|
22edb6f445
|
feat: enrich the Sunrise sandbox with realistic history - a send funnel with done, processing, queued, replied and bounced leads, a live unified inbox, a CRM pipeline with deals, tasks and notes, reply templates, notifications, and daily campaign, warmup and email-count rollups
|
2026-07-15 17:54:16 +02:00 |
|
Matthew Meszaros
|
40740e17a3
|
feat: document the campaign lead statuses (queued, processing, done, replied, bounced, unsubscribed) in the campaigns guide
|
2026-07-15 17:54:03 +02:00 |
|
Matthew Meszaros
|
fd36061901
|
feat: make the campaign live-activity empty state status-aware so a finished campaign reads as all caught up instead of waiting for the next send
|
2026-07-15 17:54:03 +02:00 |
|
Matthew Meszaros
|
e7414f9f3b
|
feat: surface the Done lead status in the campaign Leads table pill and the progress strip as a distinct indigo state
|
2026-07-15 17:54:03 +02:00 |
|
Matthew Meszaros
|
dd7ae4d56c
|
feat: derive a completed (Done) campaign lead status when every email step has been sent with no reply or bounce, across the per-lead search, the lead-status filter, and the campaign lead counts
|
2026-07-15 17:54:03 +02:00 |
|
Matthew Meszaros
|
a0f92ab284
|
feat: sidebar live panel shows a connected-but-quiet workspace as green READY with a solid halo and an active one as pulsing LIVE, leaving gray only for a real disconnect so IDLE no longer looks offline
|
2026-07-15 17:54:03 +02:00 |
|
Matthew Meszaros
|
a6ccd90090
|
feat: lock plan-gated sidebar tabs and pop an upgrade dialog on click - a feature the org's plan doesn't include now renders a locked, dimmed sidebar row (lock icon + plan badge) that opens an access dialog on click instead of routing to a teasing empty upsell page, mirroring how permission-gated tabs already lock and pop AccessLockedDialog; the dialog gains a plan variant (sparkle, 'X is on the Starter plan', Upgrade CTA to billing for owners / 'ask the owner' for everyone else) so every no-access sidebar tab - by role permission or by plan - consistently locks and explains itself
|
2026-07-15 07:04:00 +02:00 |
|
Matthew Meszaros
|
98353d9ac7
|
fix: make-sandbox org owner gets full dashboard access - the sandbox seed inserted the owner organization_members row without a permissions mask so it defaulted to 0 and every org-scoped route 403'd with 'you don't have access to this feature' across the whole dashboard; seed the owner with RolePermissions[RoleOwner] (0xFFFF) exactly like a real signup and repair an existing broken row on re-seed via ON CONFLICT DO UPDATE, plus grant OrganizationMember.HasPermission an owner bypass so an owner is never locked out of their own org regardless of the stored mask (matching the web client which already short-circuits owners)
|
2026-07-15 06:59:35 +02:00 |
|
Matthew Meszaros
|
4962765a79
|
fix: format the org_channel AI_DRAFT access_unibox clause to satisfy mix format --check-formatted (Elixir CI)
|
2026-07-15 06:47:07 +02:00 |
|
Matthew Meszaros
|
7f895f0f42
|
docs: document the inbox agent grounds drafts in the contact's CRM record and skips trivial one-line replies so users understand when a draft appears and what it is based on
|
2026-07-14 07:33:47 +02:00 |
|
Matthew Meszaros
|
aa6a6a8525
|
feat: show the recipient address on the inbox-agent draft card so a reviewer sees who the suggested reply will go to before approving and sending
|
2026-07-14 07:33:09 +02:00 |
|
Matthew Meszaros
|
1dd1bf7098
|
docs: cross-link the inbox agent from the unibox reply-drafts guide so users discover the automatic-drafting option from the manual Draft reply flow, noting it still never sends on its own
|
2026-07-14 07:32:46 +02:00 |
|
Matthew Meszaros
|
d038635425
|
feat: inbox agent skips drafting trivial acknowledgement replies - reuse replyclassify.WorthModeling on the inbound reply snippet so a one-line 'thanks'/'ok, got it' never triggers a paid 5-credit reply draft, threading the reply snippet through the InboxAgentReply hook payload; the org is only charged when there is enough human text to warrant a real reply
|
2026-07-14 07:32:16 +02:00 |
|
Matthew Meszaros
|
78ff73e45c
|
feat: ground inbox-agent reply drafts in the counterpart contact's CRM record - the inbox agent now folds the replying contact's name, company, and known custom fields into the draft prompt (mirroring the manual unibox reply draft) via an optional ContactReader wired from repository.ContactRepository in both the backend and consumer, best-effort so a lookup miss just drops the grounding, so suggested replies reference what Warmbly already knows about the person
|
2026-07-14 07:29:56 +02:00 |
|
Matthew Meszaros
|
56de65380b
|
feat: inbox agent that drafts a suggested unibox reply on inbound human replies for human approve/edit/discard - paid + per-org opt-in (organizations.inbox_agent_enabled) feature where the consumer's reply hook, on a non-automated reply, detaches a goroutine (panic-contained, never blocks ingest) that checks entitlement, dedupes via ai_thread_drafts partial unique indexes (one pending per thread, unique source_message_id), pre-checks balance, grounds a reply in the thread history + org voice + skills via generation.Provider.Complete, reserves a draft row then charges 5 credits (idempotency inbox_agent:<draft.ID>, row unwound on a fresh context if the charge fails so no unpaid draft lingers), and emits an org-scoped AI_DRAFT_READY event gated on access_unibox; the agent never sends - only a human POST to /unibox/agent-drafts/:id/approve sends through the normal reply path, claiming pending->approved before send with an approved->pending revert on send failure, alongside list + discard endpoints; plus the unibox awaiting_agent_draft badge + agent_drafts search scope, an AgentDraftCard in the thread view with inline-editable approve-and-send/discard, the workspace settings opt-in toggle, CanUseInboxAgent paid gate, migration 000065, and docs (inbox-agent guide, endpoints, realtime)
|
2026-07-14 06:13:27 +02:00 |
|
Matthew Meszaros
|
a067bf5922
|
feat: AI action nodes in automations - ai_classify/ai_extract/ai_generate run one generation.Provider completion per node over the event data and merge the result back as a variable (ai_class, output_keys[], ai_text) so downstream conditions branch on it, charging 1 credit per node with a run_id+node_id idempotency key, refund on provider failure, out-of-credits fails only that node while the run continues, and a per-automation ai_credit_failures counter (migration 000064) auto-pauses a flow after 20 consecutive credit misses; classify/extract run deterministic (temperature 0), dry-run executes AI nodes for real and is charged but never touches the auto-pause lifecycle, provider + credit ledger wired into both backend and consumer integration services, plus the reply-classifier Layer 3 ported off its bespoke OpenAI client onto the platform provider via an injected ModelClassifyFunc (platform-paid, deterministic), a CompletionRequest.Temperature primitive threaded through the OpenAI and Anthropic providers, the flow builder palette/config panels/validation for the three AI steps, and a new AI-steps-in-automations guide
|
2026-07-14 05:30:55 +02:00 |
|
Matthew Meszaros
|
24a932c439
|
feat: Warmbly MCP server exposing the tool registry to any MCP client at /api/v1/mcp - a streamable-HTTP JSON-RPC endpoint (initialize/tools/list/tools/call/ping) authenticated by API key, where tools/list reflects only the static tools the key's permission mask allows and tools/call runs them gated by each tool's RequiredAPIPerm, send-class tools are never exposed or callable, per-key rate limits and usage logging apply, and the org's own connected MCP tools are not re-exposed; plus api/mcp.mdx documenting the connection url, bearer auth, the tool catalog, and Claude Code/Desktop and Cursor client configs, linked from endpoints and authentication
|
2026-07-13 20:11:21 +02:00 |
|
Matthew Meszaros
|
2aec6745fa
|
feat: connect external MCP servers whose tools the AI assistant can use - ai_mcp_servers table with bearer tokens sealed by the org DEK cipher (never returned) and SSRF-validated https urls, a dependency-free streamable-HTTP JSON-RPC client (initialize/tools/list/tools/call over safehttp dial-time IP blocking with SSE+JSON handling and body caps), an mcp service that discovers tools on connect and contributes only enabled servers' tools to the dashboard agent as namespaced mcp_<server>_<tool> defs that are always write-class and never auto-allowed, a registry DynamicToolSource hook so per-org tools join the agent's tool set and resume executes them through ToolDefs, /ai/connections CRUD gated on manage_settings with an mcp_server audit entity and spine, a Connections settings page to add servers and review/enable discovered tools, and docs
|
2026-07-13 20:05:24 +02:00 |
|
Matthew Meszaros
|
a2f50b6572
|
feat: org AI skills (playbooks) that every AI feature loads and follows - ai_skills table (org-scoped, unique name per org, 32KB content cap) with a skills service exposing CRUD plus an enabled-skills preamble injected into the dashboard agent, contact research, and reply-draft prompts and a load_skill read-tool that returns a playbook's full content by name, /ai/skills CRUD gated on manage_settings (JWT) or the AI_AGENT scope (API) with an ai_skill audit entity and spine entry, an AI skills settings page (list rows opening a right-side drawer with name, one-line description, enable toggle, and a markdown body), and docs with two example playbooks
|
2026-07-13 19:46:25 +02:00 |
|
Matthew Meszaros
|
f48d51bf16
|
feat: AI contact research agent that gathers cited web findings per contact with sync and background-batch runs - contact_research_runs table plus a strict save_research schema (every signal and public_artifact must carry a url, confidence high/medium/low, signals<=5 hooks<=3, reject-and-reprompt once then fail), a text/template runtime prompt injecting org voice, contact record, objective, and 5-search/6-fetch budgets, a research service that drives search_web/fetch_url through the provider loop and charges 2 credits on save (billable even for nothing_found, refund-free since charged only on save, balance and abuse-cap pre-checked so a capped org never does free work), POST/GET /contacts/:id/research plus a 500-cap /contacts/research/batch draining through a bounded pool of 4 workers over FOR-UPDATE-SKIP-LOCKED claims (no new Kafka), an org-scoped AI_RESEARCH_PROGRESS realtime event gated to view_contacts, APIPermAIResearch bit 23, a ContactEdit Research tab and a ContactsTable bulk Research action, and docs; also org-scopes every credit and research idempotency key so a client-supplied Idempotency-Key can never replay across tenants
|
2026-07-13 19:34:11 +02:00 |
|
Matthew Meszaros
|
8d208d8c41
|
feat: org voice profile grounding every AI writing surface plus context-grounded unibox reply drafts - organizations gains product_description/icp_notes/voice_profile (000059) with a manage_settings workspace settings section, generation.BuildVoiceRules and BuildReplyRules fold the humanizer rules and org grounding into every prompt, WritingGenerator.GenerateWriting now takes a VoiceContext and a provider.Complete primitive powers single completions, POST /unibox/reply/draft assembles thread history plus the counterpart contact (custom fields and campaigns) plus the voice profile into a reply-tuned prompt that charges 2 credits with refund-on-failure and a client idempotency key and never sends, a Draft reply button fills the composer, and docs cover the voice profile and AI reply drafts
|
2026-07-13 19:04:57 +02:00 |
|
Matthew Meszaros
|
7f425c1624
|
feat: dashboard-wide AI assistant with streamed tool-use runs, per-action approvals, and per-iteration credits - agent_sessions/agent_messages/ai_tool_policies migration, aiagent service runs the M2 registry through the provider loop over a resumable jsonb transcript, streams text/tool-step/approval/done events over SSE, gates write tools behind approve/deny/always-allow (send always per-action) and charges 1 credit per iteration (budget 20, refund on provider failure, out-of-credits 402 insufficient_credits, cap 429 usage_cap_exceeded) with a resume-safe idempotency namespace, provider PreIteration budget hook, /ai/sessions endpoints (cursor list + two SSE runs) gated by membership with tools bound to the member's org-permission bits, APIPermAIAgent bit 22, ai_session audit entity + spine, and a right-side resizable panel (Cmd+I / sparkle button) with streamed text, collapsible tool steps, inline approval and draft-artifact deep-link cards, stop, new chat, and a credits/budget meter; tenancy enforced at the SQL layer and docs for the assistant, endpoints, permissions, and error codes
|
2026-07-13 18:46:43 +02:00 |
|
Matthew Meszaros
|
6ad2f13a39
|
feat: shared AI tool registry (internal/app/aitools) that runs 16 permission-gated tools as the invoking user over existing service-layer calls - Registry+Invocation enforce org-permission bits (JWT) or API-key bits per tool, ToolDefs binds handlers into the provider agent loop and Call powers direct MCP invocation; tools cover contacts (search/get/update/add_tag/remove_tag), CRM (create_task/create_deal), campaigns (list/stats/create_campaign_draft), unibox (list_threads/get_thread/draft_reply, gated on CanUseUnibox, draft never sends), create_automation_draft (disabled), and web search_web + fetch_url (webhook SSRF validator + safehttp dial-block + 2MB cap + html-to-text + 15min Redis cache + rune-safe truncation); every write audits via AuditService.LogAction so the spine refreshes teammates, registry built once in main and exposed on the handler for M3/M8
|
2026-07-13 18:15:58 +02:00 |
|
Matthew Meszaros
|
e07c33d3cd
|
feat: provider-agnostic LLM agent-loop layer (OpenAI-first, Anthropic self-host connector) with RunAgent tool-use loop, approval/resume contract, pluggable web search, and extracted voice-rules builder - generation.Provider.RunAgent executes Go tool handlers over a resumable transcript with a read-auto/write-approve gate, openAIProvider drives it via a lean HTTP client retargetable with OPENAI_BASE_URL for any OpenAI-compatible endpoint and also serves the writing assistant, anthropicProvider mirrors it with tool_use/tool_result multi-turn plus hosted web_search, NewProvider prefers OPENAI_API_KEY, SearchClient supports Serper/SearXNG with a clean not-configured error, and BuildVoiceRules extracts the humanizer prompt for reuse; refreshed stale model ids to sonnet-4-6 and haiku-4-5
|
2026-07-13 17:52:09 +02:00 |
|
Matthew Meszaros
|
5d6ed2a068
|
feat: AI credit economy with monthly plan grants, non-expiring purchased top-ups, and Stripe billing - two-pool credit_ledger (monthly allowance reset each cycle on invoice.paid, purchased pool via checkout mode=payment fulfilled webhook-only), consume drains monthly-then-purchased, 3 fixed top-up packs, /subscription/credits balance+checkout+paginated transactions gated on manage_billing, one-time trial grant, billing credits card with balance ring and pack buttons, credit_purchase/credit_grant audit entities and spine, wired plan.monthly_credits through pg_plan reads/writes, retry-safe webhook idempotency, and an AI credits guide
|
2026-07-13 17:40:20 +02:00 |
|
Matthew Meszaros
|
b9a99f91a9
|
Merge pull request #67 from warmbly/feature/ios-realtime-steps-preview
iOS realtime parity, richer sequence preview, and mobile push notifications
|
2026-07-13 16:15:18 +02:00 |
|
Matthew Meszaros
|
d6b7885e8b
|
feat: mobile push notifications end to end - APNs provider-token client, device_tokens table with session-scoped register/delete endpoints, a push channel in notification preferences (web + iOS toggles), and Redis-backed immediate-then-digest batching (first event pushes now, bursts summarize when the 5h window closes) wired in backend and consumer, with iOS registration/badge sync and docs for the channel, endpoints, and APNS_* deploy env
|
2026-07-13 16:11:15 +02:00 |
|
Matthew Meszaros
|
927da9805d
|
feat: kind-aware campaign sequence preview in the iOS app - decode the conditions/action jsonb the steps endpoint already returns and render wait/action nodes with plain-language summaries plus per-step branch routes (if opened within 3 days, goes to step 4), keeping the read-only edit-on-web handoff
|
2026-07-13 16:11:00 +02:00 |
|
Matthew Meszaros
|
38f32abb91
|
feat: close the iOS realtime gaps against the web spine - route EMAIL_STATUS/EMAIL_ERROR to mailboxes+analytics, SUBSCRIPTION/PLAN/BILLING/LIMIT to billing, DEAL and BOOKING to CRM, refresh analytics on inbox arrivals, and add a foreground socket nudge so a suspension-killed connection recovers instantly instead of ~30s late
|
2026-07-13 16:10:47 +02:00 |
|
Matthew Meszaros
|
7dc67c0c92
|
Merge pull request #66 from warmbly/admin-warmup-automation
Admin dashboard: realtime events, system diagnosability, full control surface, continuous warmup-content generation
|
2026-07-12 09:07:22 +02:00 |
|
Matthew Meszaros
|
9f1f511c8b
|
feat: replace the AWS credential dialog's example-key placeholders with descriptive text - the AKIA-prefixed sample matched Trivy's aws-access-key-id secret rule and failed the security scan
|
2026-07-12 09:04:02 +02:00 |
|
Matthew Meszaros
|
262d25779c
|
feat: realtime admin dashboard + diagnosability - new Elixir admin:platform channel (JWT-only, gated on users.admin_permissions) mirrors every platform event, the admin app connects through a /getaway-tokenized Phoenix socket client with a throttled react-query invalidation spine, a Live Events firehose page streams the platform with filters/pause/JSON expand, a System Status page runs parallel infra probes (postgres/redis/kafka/schema-registry/realtime/tracking via /admin/system/status), and the worker logs viewer gains follow mode, line-count selection, and copy
|
2026-07-12 09:00:10 +02:00 |
|
Matthew Meszaros
|
a26d144b49
|
feat: continuous warmup-content refresh + full admin control surface - the scheduler now recycles the most-used AI threads each run (refresh_enabled/refresh_per_run) so fresh warmup messages keep generating after the target is reached, the warmup-content overview exposes the whole automation pipeline (AI configured/enabled/scheduled/stocked, per-segment stock vs target, daily budget), state legends explain risk-pool/health/job-status enums, and new settings pages cover worker profiles, releases, provisioning policy, and AWS credentials
|
2026-07-12 08:59:59 +02:00 |
|
Matthew Meszaros
|
940397d72c
|
Merge pull request #65 from warmbly/feature/demo-sandbox
Sandbox: fully working local demo environment (live sends, replies, opens, clicks, warmup)
|
2026-07-12 05:24:13 +02:00 |
|
Matthew Meszaros
|
15127d1426
|
feat: rename the assignment test stub's GetDedicatedWorkerByUserID to GetDedicatedWorkerByOrgID to match the org-keyed WorkerRepository interface, so the call no longer falls through to the nil embedded interface and panics
|
2026-07-11 20:01:14 +02:00 |
|
Matthew Meszaros
|
7cfd9b26e9
|
feat: register the tracking-events Avro schema via a one-shot schema-registry-init infra service - the Rust tracking service encodes fetch-only against Schema Registry, so with no registered schema every open/click event failed serialization and was silently dropped in dev
|
2026-07-11 19:48:05 +02:00 |
|
Matthew Meszaros
|
bb646709fa
|
feat: return the message count from the sync SELECT and skip FETCH on empty mailboxes - a 1:* sequence set against zero messages is a server error, so every fresh mailbox failed its first sync pass before any mail arrived
|
2026-07-11 19:47:14 +02:00 |
|
Matthew Meszaros
|
1403ed4e80
|
feat: render-ready sandbox sequence templates - use the Go template dot-syntax the renderer actually supports ({{.FirstName}}/{{.Company}} instead of the snake_case placeholders that shipped literally), and linkify bare URLs into anchors during the plain-to-HTML conversion so click tracking has hrefs to wrap into tickets
|
2026-07-11 19:40:38 +02:00 |
|
Matthew Meszaros
|
cd00d06e8b
|
feat: select the mailbox (read-only, CONDSTORE) before each sync FETCH - the IMAP sync loop issued FETCH against a session with no selected mailbox, so every generic-IMAP sync pass failed and inbound mail never reached the consumer
|
2026-07-11 19:20:29 +02:00 |
|
Matthew Meszaros
|
2306167a5d
|
feat: type the send/warmup-action/email-validation worker handlers so the dispatcher round-trip decodes their payloads - they took any and did their own value-type assertions, which can never match the JSON codec map bodies, so SEND_EMAIL events were dropped with "invalid body type" and nothing ever reached SMTP
|
2026-07-11 19:18:09 +02:00 |
|
Matthew Meszaros
|
c896c0c919
|
feat: use path-style S3 addressing when an AWS endpoint override is set - virtual-hosted bucket.host requests do not resolve as buckets on LocalStack/MinIO, so every email body upload failed with NoSuchBucket in dev; real AWS (no override) keeps the default addressing
|
2026-07-11 18:37:10 +02:00 |
|
Matthew Meszaros
|
b784f76e24
|
feat: select worker_id in the email repository GetByID - the column was missing from the query, so every caller saw a nil WorkerID: the email sender could never publish a send (every campaign task dead-lettered with "no worker assigned") and the worker reconciler re-ran assignment on every pass, endlessly reshuffling mailboxes between workers
|
2026-07-11 18:03:20 +02:00 |
|
Matthew Meszaros
|
41a95bbe0e
|
feat: stabilize the sandbox against two live-run failure modes - seed and repair fixture contacts as verified (the live verifier finds no MX for .test domains and the pre-send gate then skips every campaign send) and deactivate the seeded workers the native stack never runs, whose active-but-heartbeatless rows kept winning placement and ping-ponging mailbox assignments with the dead-worker sweep
|
2026-07-11 17:57:21 +02:00 |
|
Matthew Meszaros
|
e2ccd0d75a
|
feat: record the new operational contracts in the agent notes and env example - CODEC_PROVIDER=json wherever workers run, CREDENTIALS_ENCRYPTION_KEY for credential sealing, the two native workers, and the make sandbox entry point
|
2026-07-11 17:31:58 +02:00 |
|
Matthew Meszaros
|
9a3551c5ea
|
feat: document the sandbox at /development/sandbox/ (quick start, seeded data, architecture of the simulation, env vars, demo script, troubleshooting) and refresh /development/local-development/ with dovecot, the sandbox target, and the emulator host.docker.internal callback note
|
2026-07-11 17:31:58 +02:00 |
|
Matthew Meszaros
|
48ec53d781
|
feat: add the sandbox seeder and simulator (cmd/sandbox, internal/sandbox) - seeds the Sunrise Labs showcase org (paid plan, six live mailboxes with sealed mailpit/dovecot credentials, active tracked campaigns, pre-verified contacts, warmup pool membership) and repairs every fixture smtp_imap account and .test contact; the simulator plays the internet by polling mailpit, appending mail into dovecot inboxes for the real IMAP sync to pick up, hitting tracking pixels and click tickets, and replying as hash-stable contact personas with correct In-Reply-To threading
|
2026-07-11 17:31:42 +02:00 |
|
Matthew Meszaros
|
f8cf7e930e
|
feat: native dev targets for the sandbox - make sandbox / sandbox-seed, a second native worker (worker-premium, required because paid orgs place strictly onto premium-tier workers) joined into make run, CODEC_PROVIDER=json + CREDENTIALS_ENCRYPTION_KEY + MAIL_TLS_INSECURE in the dev env, dovecot in INFRA_SVCS, and SELF_HOST defaulting to host.docker.internal so the dockerized cloud-tasks emulator can reach the host-run backend
|
2026-07-11 17:31:42 +02:00 |
|
Matthew Meszaros
|
1634d195d3
|
feat: fix the DEK cache path in the cipher service - a redis cache miss (every first use of an org key) returned redis.Nil as a fatal error before the KMS fallback could run, and a cache hit was fetched then discarded; now a hit returns immediately and any cache miss or error falls through to KMS
|
2026-07-11 17:31:23 +02:00 |
|
Matthew Meszaros
|
9d7e3aa61f
|
feat: make scheduled sending self-healing - dispatch Cloud Tasks callbacks by the task row type (all enqueues share one webhook URL, so campaign callbacks were run through the warmup handler), cancel pending tasks stranded past their slot so the reconcilers re-seed chains after a lost callback or emulator restart, and cast the status enum in the two UPDATE ... CASE queries that failed parameter type deduction on every callback
|
2026-07-11 17:31:23 +02:00 |
|