Matthew Meszaros
|
5178acd874
|
feat: in-composer AI writing experience — select text in the unibox reply textarea or the campaign TipTap editor and a floating Edit-with-AI pill opens quick actions (improve, shorten, expand, fix grammar, friendlier, more formal) plus free instructions backed by a new fenced /generation/edit endpoint (1 credit, idempotent, refund on failure, prompt-injection fencing around the passage), rewrites type themselves in with undo/again/done review, and Draft reply now runs through an inline draft bar with staged shimmer status and Keep/Adjust/Retry/Discard instead of a toast
|
2026-07-18 11:55:37 +02:00 |
|
Matthew Meszaros
|
4f9e6e6282
|
feat: full attribution on every AI credit charge — new actor_user_id + context jsonb columns on the transaction log (migration 000070), a typed models.CreditContext carried via request context (models.WithCreditMeta) so base charges, usage settles, web-search fees, and refunds all inherit it without signature churn, wired at every spend site (campaign switches record campaign/step/contact, automation nodes and Ask AI record automation/node/run and the question asked, reply drafts and inbox agent record the thread, the dashboard agent records the session, research records contact/run, and manual features record the triggering user), rendered as a detail line with token counts in the billing transaction log with corrected reason labels, and documented in the AI credits guide
|
2026-07-18 08:52:16 +02:00 |
|
Matthew Meszaros
|
053ca6a71d
|
feat: usage-based AI credit system — every AI call reserves its flat minimum then settles the real token cost per model (light 1500/standard 400 tokens per credit, drain-to-zero overage settle wired into writing assistant, reply drafts, dashboard agent, inbox agent, research, automation nodes, and campaign switches), org spend controls in a new org_ai_settings table (day/week/month hard limits enforced in Consume, low-balance alert threshold, auto top-up config), a credit-watch monitor hook that fires BILLING_CREDITS_LOW realtime alerts once per day and buys the configured pack off-session via a new Stripe AutoTopUpCredits (idempotent on the PaymentIntent, bounded per month, Redis-locked), GET /subscription/credits/usage + GET/PATCH settings endpoints, and an AI usage & spend controls billing card (spend vs limits, 30-day chart, per-feature and per-model breakdowns, limits/reminder/auto-top-up form) with a realtime low-credit toast
|
2026-07-18 08:39:41 +02:00 |
|
Matthew Meszaros
|
d0e8bc1c97
|
feat: add one-command MCP OAuth connect on api.warmbly.com/v1/mcp — RFC 7591 dynamic client registration for public PKCE clients (dcr.go), RFC 9728 protected-resource metadata + WWW-Authenticate challenge via MCPAuthMiddleware, /v1/mcp now accepts an API key or OAuth token, public-client auth with no secret and mandatory PKCE reusing the existing OAuth 2.1 server (nullable-org clients, migration 000066), executable-redirect-scheme hardening on the open register endpoint, plus mcp/oauth/authentication/endpoints docs
|
2026-07-16 08:57:19 +02:00 |
|
Matthew Meszaros
|
e64b0b161d
|
feat: sweep stale OPENAI_API_KEY/AI_LOCAL_MODEL mentions out of comments, the provider-not-configured and warmup-admin error strings, env.example, and the deployment guide now that AI_* is the only config surface
|
2026-07-16 06:39:07 +02:00 |
|
Matthew Meszaros
|
44797191b8
|
feat: skip credits for reply drafts when running on a free/local model
|
2026-07-15 18:52:07 +02:00 |
|
Matthew Meszaros
|
18a98944ae
|
feat: skip credits for the writing assistant when running on a free/local model, returning the unchanged balance
|
2026-07-15 18:52:07 +02:00 |
|
Matthew Meszaros
|
59044aedf2
|
feat: the dashboard agent runs un-metered on a free/local model, streams a free_model signal, and injects the org voice profile into its system prompt so its writing sounds human
|
2026-07-15 18:51:51 +02:00 |
|
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
|
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
|
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
|
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
|
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 |
|
Matthew Meszaros
|
167cc370b3
|
feat: re-key dedicated_worker_assignments to organizations (migration 000055) - the assignment service has always keyed by org id but the column carried a users FK, so every runtime bind failed with an FK violation and dedicated-plan orgs could never get a worker; renames the column, remaps seed rows, and updates the repository, admin filter, and convert-to-dedicated endpoint to org semantics
|
2026-07-11 17:31:23 +02:00 |
|
Matthew Meszaros
|
07a6777536
|
feat: email OAuth callback page gains a native-app fallback - when there is no web opener to postMessage (an ASWebAuthenticationSession has no popup parent) it redirects to warmbly://email-oauth with provider/code/state/error so the iOS app can finish the connect; web popup behavior is unchanged and the docs describe both delivery paths
|
2026-07-11 10:13:10 +02:00 |
|
Matthew Meszaros
|
cc0871d1b8
|
feat: contacts search gains an additive lead_status filter plus per-status lead_counts on the first page when exactly one campaign_id is set - the SQL predicates reproduce the read-time status derivation exactly (unsubscribed > bounced > replied > active > pending via LATERAL bool_or rollups), invalid lead_status or multi-campaign combinations return 400, and the web types/table pick up the new lead_counts shape
|
2026-07-11 10:11:36 +02:00 |
|
Matthew Meszaros
|
3acb51df6c
|
feat: add server-side status-bucket filtering to GET /campaigns (draft/active/paused/completed, paused matches every paused_* variant, invalid values 400) and a new GET /campaigns-overview endpoint returning status and per-folder counts, with the campaigns API reference and endpoint scope map updated
|
2026-07-07 09:40:27 +02:00 |
|
Matthew Meszaros
|
13f193d91e
|
feat: unibox list filters - from now matches sender addresses by case-insensitive substring as documented, and a new uncategorized=true param returns only threads with no labels, documented in the unibox API reference
|
2026-07-07 05:56:47 +02:00 |
|
Matthew Meszaros
|
a20ba926cb
|
feat: add native Apple/Google sign-in to the backend - POST /auth/apple and /auth/google exchange provider-signed ID tokens for sessions, GET /auth/providers exposes configured providers for app discovery, with a JWKS-verifying idtoken package, auth config, stable error codes, and API/security docs
|
2026-07-07 05:56:35 +02:00 |
|
Matthew Meszaros
|
d4ead05456
|
feat: persist campaign sequence canvas coordinates with x/y columns (migration 000053) plus a bulk sequence-layout service/repo path that scopes by campaign owner and never bumps updated_at
|
2026-06-30 10:06:20 +02:00 |
|
Matthew Meszaros
|
73838cecf5
|
feat: add an unaudited automation node-layout endpoint handler/service/repo backed by an atomic jsonb x/y merge that leaves edges, config, and updated_at untouched
|
2026-06-30 10:06:19 +02:00 |
|
Matthew Meszaros
|
5e94b945ff
|
feat: add GetIdentity handler returning user, org, auth type, and granted scopes for GET /v1/me
|
2026-06-28 19:09:37 +02:00 |
|
Matthew Meszaros
|
9dd37b0d62
|
Merge pull request #52 from warmbly/referral-credits
feat: implement referral program with credit-based rewards
|
2026-06-28 09:12:57 +02:00 |
|
Matthew Meszaros
|
7e75394ab8
|
feat: decode the admin discount list and redemption cursors as opaque offset tokens, returning 400 on a malformed cursor instead of silently resetting to the first page
|
2026-06-28 05:58:25 +00:00 |
|
Matthew Meszaros
|
789da671d6
|
feat: send organization member invitations through GenerateInvitationHTML so they match the rest of the transactional mail
|
2026-06-28 05:27:18 +00:00 |
|
Matthew Meszaros
|
8bd3a1137e
|
feat: expose the JWT referral and applied-discounts endpoints with handlers and manage_billing-gated routes
|
2026-06-28 05:09:50 +00:00 |
|
Matthew Meszaros
|
03acdb87e3
|
fix: thread organization_id through contact, CRM, and unibox reads and writes so non-owner org members see and edit their workspace data, and backfill contacts.organization_id for single-membership users (migration 000049)
|
2026-06-15 08:11:53 +02:00 |
|
Matthew Meszaros
|
9d0c432be7
|
feat: let OAuth apps subscribe to webhooks by declaring a url/events/secret and materializing per-org endpoints gated by each grant's scopes, with secret rotation and delivery observability (migration 000050)
|
2026-06-15 08:11:20 +02:00 |
|
Matthew Meszaros
|
173736a004
|
feat: add a Stripe-grade webhook platform with a typed event catalog, HMAC-signed delivery, retries with backoff, endpoint ownership verification, per-endpoint throttling, an audit-spine event bridge, and firehose emit sites (migration 000048)
|
2026-06-15 08:11:20 +02:00 |
|
Matthew Meszaros
|
fcdb31cda7
|
feat: OAuth apps always issue a client secret (drop the public/PKCE-only client type, secret required for the token exchange), add an app-logo upload endpoint, and align the docs to OAuth2 with optional PKCE
|
2026-06-14 09:52:49 +02:00 |
|
Matthew Meszaros
|
8c175d4221
|
feat: add an OAuth 2.1 authorization server (migration 000047 apps/codes/grants, app registration CRUD, authorization-code-with-PKCE authorize+token+refresh-rotation+revoke endpoints, RFC 8414 discovery, bearer-token validation wired into the auth middleware reusing the API-permission gates, scopes mapped to API permission bits)
|
2026-06-13 13:56:39 +02:00 |
|
Matthew Meszaros
|
331745ecc5
|
feat: add a generic per-automation inbound webhook trigger (inbound.webhook event, token-gated POST /api/v1/integrations/inbound/automation/:token, migration 000046 inbound_token, background graph run with the JSON body as event payload)
|
2026-06-13 13:04:10 +02:00 |
|
Matthew Meszaros
|
aed4a06190
|
feat: unify pagination by making the offset-based CRM deals/tasks search and meetings endpoints expose the same opaque next_cursor and {total, next_cursor, has_more} envelope as every keyset list (offset hidden inside the token), updating web clients, the OpenAPI spec, and the reference docs
|
2026-06-13 10:47:17 +02:00 |
|
Matthew Meszaros
|
57e8705a82
|
feat: make public API list cursors opaque base64 tokens (internal/utils/paging) instead of raw record UUIDs, decoding on input with a 400 on a bad cursor; admin endpoints keep transparent UUID cursors
|
2026-06-13 07:18:23 +02:00 |
|
Matthew Meszaros
|
a7a43e0c4c
|
feat: rename the campaign sequences resource to steps across the API and URL (/campaigns/:id/steps), the JSON fields (target_step_id, step_id/step_name/step_index, analytics steps[], create body steps), the web client/models/route segment/labels, the audit step entity type, and the wire-contract docs; internal Go type names and the Kafka avro schema stay
|
2026-06-12 09:38:11 +02:00 |
|
Matthew Meszaros
|
0fd3d03f55
|
feat: add org-wide team presence privacy columns (presence_show_online, presence_show_activity) with update plumbing and a PRESENCE_POLICY_UPDATED realtime event so a settings change re-gates connected sockets live
|
2026-06-12 05:59:01 +02:00 |
|
Matthew Meszaros
|
8547fe97eb
|
feat: scope unibox MarkSeenBulk by organization so a non-owner member opening a thread clears the shared org-wide unread state instead of updating zero rows
|
2026-06-12 05:14:50 +02:00 |
|
Matthew Meszaros
|
4db027a342
|
feat: scope unibox GetByThread by organization instead of user_id so non-owner members see the full conversation they already see in the org-scoped inbox list, not an empty thread
|
2026-06-12 05:10:00 +02:00 |
|
Matthew Meszaros
|
6a3b8ac0eb
|
fix: scope the unibox unread-count endpoint by organization_id so the sidebar Inbox badge counts the workspace's unread mail instead of always showing 0 for non-owner members
|
2026-06-11 18:05:18 +02:00 |
|