@fayz-ai/plugin-conversations 0.9.4 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/context.d.ts +4 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/data/channels.d.ts +70 -0
- package/dist/data/channels.d.ts.map +1 -0
- package/dist/data/payment-preflight.d.ts +42 -0
- package/dist/data/payment-preflight.d.ts.map +1 -0
- package/dist/data/tables.d.ts +1 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1357 -228
- package/dist/index.js.map +1 -1
- package/dist/integrations/tyxter/connectorDef.d.ts +14 -0
- package/dist/integrations/tyxter/connectorDef.d.ts.map +1 -0
- package/dist/integrations/tyxter/data.d.ts +70 -0
- package/dist/integrations/tyxter/data.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +7 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/ConversationList.d.ts.map +1 -1
- package/dist/views/InboxView.d.ts.map +1 -1
- package/dist/views/NewConversationPanel.d.ts +13 -0
- package/dist/views/NewConversationPanel.d.ts.map +1 -0
- package/package.json +9 -7
- package/dist/views/NewConversationModal.d.ts +0 -6
- package/dist/views/NewConversationModal.d.ts.map +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ConnectorDefinition } from '@fayz-ai/core';
|
|
2
|
+
export declare const TYXTER_CONNECTOR_ID = "tyxter";
|
|
3
|
+
/**
|
|
4
|
+
* Four seconds.
|
|
5
|
+
*
|
|
6
|
+
* Nobody is staring at a spinner here — the caller is an automation reacting to
|
|
7
|
+
* a booking — but the budget is what stops a slow provider from holding the
|
|
8
|
+
* automation executor's worker while the next twenty events queue behind it.
|
|
9
|
+
* Tyxter answers `POST /v1/messages` with a 202 and does the delivery
|
|
10
|
+
* asynchronously, so the call being timed is an accept, not a send.
|
|
11
|
+
*/
|
|
12
|
+
export declare const TYXTER_LATENCY_BUDGET_MS = 4000;
|
|
13
|
+
export declare const tyxterConnectorDef: ConnectorDefinition;
|
|
14
|
+
//# sourceMappingURL=connectorDef.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectorDef.d.ts","sourceRoot":"","sources":["../../../src/integrations/tyxter/connectorDef.tsx"],"names":[],"mappings":"AAsBA,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAA;AAwBtB,eAAO,MAAM,mBAAmB,WAAW,CAAA;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAA;AA6X5C,eAAO,MAAM,kBAAkB,EAAE,mBAuFhC,CAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** The edge function that holds every phone-number call this connector makes. */
|
|
2
|
+
export declare const TYXTER_NUMBER_CLAIM_FUNCTION = "tyxter-number-claim";
|
|
3
|
+
/** The channel row, as the function hands it back. A subset of the table on purpose. */
|
|
4
|
+
export interface ClaimedChannel {
|
|
5
|
+
id: string;
|
|
6
|
+
channel: string;
|
|
7
|
+
provider: string;
|
|
8
|
+
/** Null while the row is still a reservation with no number behind it. */
|
|
9
|
+
providerNumberId: string | null;
|
|
10
|
+
phoneE164: string | null;
|
|
11
|
+
kind: string;
|
|
12
|
+
status: string;
|
|
13
|
+
displayName: string | null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Where to send the human to finish the Meta registration, and how good the
|
|
17
|
+
* link is.
|
|
18
|
+
*
|
|
19
|
+
* `source` is surfaced rather than hidden because the three are not equivalent:
|
|
20
|
+
* 'session' returns the person to this very panel and names the tenant to Meta;
|
|
21
|
+
* 'error_link' is the provider's own pre-targeted page; 'default' is the generic
|
|
22
|
+
* dashboard, which works but assumes the browser is signed into the right
|
|
23
|
+
* organization. The panel says less when the source is worse.
|
|
24
|
+
*/
|
|
25
|
+
export interface MetaRegistrationHandoff {
|
|
26
|
+
source: 'session' | 'error_link' | 'default';
|
|
27
|
+
url: string;
|
|
28
|
+
note: string | null;
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
expiresAt?: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface ClaimResult {
|
|
33
|
+
channel: ClaimedChannel;
|
|
34
|
+
metaRegistration: MetaRegistrationHandoff | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A page address, cleaned up for `return_url`.
|
|
38
|
+
*
|
|
39
|
+
* The provider's allowlist refuses a URL carrying a fragment, credentials or a
|
|
40
|
+
* non-https scheme, so the fragment goes — and this app is a hash router, which
|
|
41
|
+
* means the fragment is the WHOLE route. Losing it returns the human to the
|
|
42
|
+
* app's front door rather than to this panel, which is a smaller failure than a
|
|
43
|
+
* session the provider refuses to create at all.
|
|
44
|
+
*/
|
|
45
|
+
export declare function returnUrlFrom(href: string | null | undefined): string | null;
|
|
46
|
+
/** The page the human is standing on, as a `return_url` the provider will accept. */
|
|
47
|
+
export declare function currentReturnUrl(): string | null;
|
|
48
|
+
/** Rent a dedicated number for this tenant, in the given area code. */
|
|
49
|
+
export declare function claimDedicatedNumber(input: {
|
|
50
|
+
tenantId: string;
|
|
51
|
+
ddd: string;
|
|
52
|
+
}): Promise<ClaimResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Re-read the tenant's numbers at the provider and sync the rows.
|
|
55
|
+
*
|
|
56
|
+
* The poll the panel runs while a claim is pending. It is not redundant with the
|
|
57
|
+
* webhook: four of the five lifecycle steps emit no event at all (verified
|
|
58
|
+
* live — a full provision to `active` produced zero `phone_number.*` events), so
|
|
59
|
+
* without this the row would sit on "Solicitado" until the day the number is
|
|
60
|
+
* disconnected.
|
|
61
|
+
*/
|
|
62
|
+
export declare function refreshDedicatedNumbers(tenantId: string): Promise<{
|
|
63
|
+
channels: ClaimedChannel[];
|
|
64
|
+
}>;
|
|
65
|
+
/** The link that hands the human to Meta's Embedded Signup, which no key can do headlessly. */
|
|
66
|
+
export declare function metaRegistrationHandoff(tenantId: string): Promise<{
|
|
67
|
+
metaRegistration: MetaRegistrationHandoff | null;
|
|
68
|
+
channel: ClaimedChannel;
|
|
69
|
+
}>;
|
|
70
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/integrations/tyxter/data.ts"],"names":[],"mappings":"AAWA,iFAAiF;AACjF,eAAO,MAAM,4BAA4B,wBAAwB,CAAA;AAEjE,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAA;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,gBAAgB,EAAE,uBAAuB,GAAG,IAAI,CAAA;CACjD;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAW5E;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD;AAED,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAOnG;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,CAAC,CAEjG;AAED,+FAA+F;AAC/F,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACjE,gBAAgB,EAAE,uBAAuB,GAAG,IAAI,CAAA;IAChD,OAAO,EAAE,cAAc,CAAA;CACxB,CAAC,CAED"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export declare const MIGRATION_001_CONVERSATIONS = "-- ============================================================================\n-- plugin-conversations 001: omni-channel inbox model (SMS / WhatsApp /\n-- Instagram / Email / Web chat). Prefix: plg_conversations / plg_conversation_messages.\n-- \u00A71 plg_conversations \u2014 one thread per contact+channel\n-- \u00A72 plg_conversation_messages \u2014 inbound/outbound messages within a thread\n-- \u00A73 RLS: authenticated tenant-scoped CRUD on both tables + GRANTs\n--\n-- Column names mirror exactly what supabase.ts's mapConversation / mapMessage\n-- read. Real channel connectors (Twilio, WhatsApp Cloud, Meta, IMAP) deliver\n-- inbound rows here out-of-band; the provider is the read/compose surface.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\n-- \u00A71 \u2014 conversations (threads)\nCREATE TABLE IF NOT EXISTS public.plg_conversations (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n contact_name text NOT NULL,\n contact_handle text,\n channel text NOT NULL\n CHECK (channel IN ('sms', 'whatsapp', 'instagram', 'email', 'webchat')),\n last_message_preview text,\n last_message_at timestamptz DEFAULT now(),\n unread_count int DEFAULT 0,\n status text DEFAULT 'open'\n CHECK (status IN ('open', 'snoozed', 'closed')),\n assigned_to text,\n accent text,\n tags text[],\n location text,\n note text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_conversations ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_tenant ON public.plg_conversations(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_tenant_recent ON public.plg_conversations(tenant_id, last_message_at DESC);\n\n-- \u00A72 \u2014 messages\nCREATE TABLE IF NOT EXISTS public.plg_conversation_messages (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n conversation_id uuid NOT NULL REFERENCES public.plg_conversations(id) ON DELETE CASCADE,\n channel text\n CHECK (channel IS NULL OR channel IN ('sms', 'whatsapp', 'instagram', 'email', 'webchat')),\n direction text\n CHECK (direction IN ('inbound', 'outbound')),\n body text NOT NULL,\n author text,\n at timestamptz DEFAULT now()\n);\nALTER TABLE public.plg_conversation_messages ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_conversation_messages_thread ON public.plg_conversation_messages(conversation_id, at);\n\n-- \u00A73 \u2014 RLS: authenticated tenant CRUD (the inbox reads/writes here)\nDROP POLICY IF EXISTS plg_conversations_select ON public.plg_conversations;\nDROP POLICY IF EXISTS plg_conversations_insert ON public.plg_conversations;\nDROP POLICY IF EXISTS plg_conversations_update ON public.plg_conversations;\nDROP POLICY IF EXISTS plg_conversations_delete ON public.plg_conversations;\nCREATE POLICY plg_conversations_select ON public.plg_conversations FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_conversations_insert ON public.plg_conversations FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_conversations_update ON public.plg_conversations FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_conversations_delete ON public.plg_conversations FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations TO authenticated;\n\nDROP POLICY IF EXISTS plg_conversation_messages_select ON public.plg_conversation_messages;\nDROP POLICY IF EXISTS plg_conversation_messages_insert ON public.plg_conversation_messages;\nDROP POLICY IF EXISTS plg_conversation_messages_update ON public.plg_conversation_messages;\nDROP POLICY IF EXISTS plg_conversation_messages_delete ON public.plg_conversation_messages;\nCREATE POLICY plg_conversation_messages_select ON public.plg_conversation_messages FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_conversation_messages_insert ON public.plg_conversation_messages FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_conversation_messages_update ON public.plg_conversation_messages FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nCREATE POLICY plg_conversation_messages_delete ON public.plg_conversation_messages FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversation_messages TO authenticated;\n";
|
|
2
2
|
export declare const MIGRATION_002_CONTACT_PERSON = "-- ============================================================================\n-- plugin-conversations 002: link a thread to a REAL person record.\n--\n-- The compose modal used to take a free-text name + handle, so a conversation\n-- with \"Maria\" had nothing to do with the Maria in the agenda, the CRM or the\n-- financial module. The shared ContactPicker (find-or-create over\n-- public.people) now resolves a person, and this column stores that link.\n--\n-- Nullable on purpose, in both directions of time:\n-- \u2022 rows created before this migration keep working (name/handle only);\n-- \u2022 an inbound message from an unknown number still opens a thread with no\n-- person attached \u2014 the contact panel can offer \"create contact\" later.\n-- ON DELETE SET NULL: deleting a person must never take their history with it.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nALTER TABLE public.plg_conversations\n ADD COLUMN IF NOT EXISTS contact_person_id uuid REFERENCES public.people(id) ON DELETE SET NULL;\n\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_person\n ON public.plg_conversations(tenant_id, contact_person_id)\n WHERE contact_person_id IS NOT NULL;\n";
|
|
3
|
+
export declare const MIGRATION_003_CHANNELS = "-- ============================================================================\n-- plugin-conversations 003: channel accounts \u2014 WHICH number a message leaves by.\n--\n-- The inbox has always been the read/compose surface for rows a connector\n-- delivered out-of-band. This is the first table that says where those rows\n-- come FROM, and it exists because WhatsApp has two number models at once:\n--\n-- \u2022 kind 'fallback' \u2014 the product's own number, shared by every tenant.\n-- tenant_id IS NULL: it belongs to the software, not to a salon, and a\n-- tenant column with a made-up value there would make every read lie.\n-- \u2022 kind 'dedicated' \u2014 a number rented for one tenant. Once it is `active`\n-- it wins over the fallback; until then the fallback keeps carrying.\n--\n-- Provider-neutral on purpose (`provider`, `provider_number_id`): the sender\n-- model is a WhatsApp fact, not a Tyxter fact, and the second messaging\n-- provider must not need a second table.\n--\n-- Writes are SERVICE-ROLE ONLY. Every row is created by a webhook or by an\n-- edge function acting on the provider's answer \u2014 a member who could edit\n-- `provider_number_id` could point another tenant's replies at their own inbox.\n-- Members read, and the fallback row is readable by all of them because it is\n-- the number their own messages go out on.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_conversations_channels (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n -- NULL = the product fallback row. Not a missing value: an owner.\n tenant_id uuid REFERENCES public.tenants(id) ON DELETE CASCADE,\n channel text NOT NULL DEFAULT 'whatsapp'\n CHECK (channel IN ('sms', 'whatsapp', 'instagram', 'email', 'webchat')),\n provider text NOT NULL,\n -- The provider's handle for the number ('pn_\u2026'); what a webhook routes on.\n provider_number_id text NOT NULL,\n phone_e164 text,\n kind text NOT NULL DEFAULT 'dedicated'\n CHECK (kind IN ('fallback', 'dedicated')),\n -- The provider's own lifecycle, kept verbatim so a status webhook never has\n -- to be translated into a vocabulary of ours that means slightly less.\n status text NOT NULL DEFAULT 'requested'\n CHECK (status IN ('requested', 'provisioning', 'provisioned', 'verifying',\n 'active', 'failed', 'released', 'disconnected')),\n display_name text,\n metadata jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_conversations_channels ENABLE ROW LEVEL SECURITY;\n\n-- One row per number at a provider: the webhook resolves the tenant by this\n-- pair, and a duplicate would make that resolution a coin toss.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversations_channels_number\n ON public.plg_conversations_channels(provider, provider_number_id);\n\n-- At most one fallback per (channel, provider). Sender resolution falls back to\n-- \"the product's number\", singular \u2014 two of them is an ambiguity no caller can\n-- resolve, and it would surface as messages leaving by the wrong one.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversations_channels_fallback\n ON public.plg_conversations_channels(channel, provider)\n WHERE tenant_id IS NULL AND kind = 'fallback';\n\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_channels_tenant\n ON public.plg_conversations_channels(tenant_id, channel, status)\n WHERE tenant_id IS NOT NULL;\n\n-- RLS: members read their own rows AND the product fallback (it is the number\n-- they send from, so hiding it would leave the panel with nothing to say).\n-- No INSERT/UPDATE/DELETE policy and no write GRANT: service role only.\nDROP POLICY IF EXISTS plg_conversations_channels_select ON public.plg_conversations_channels;\nCREATE POLICY plg_conversations_channels_select ON public.plg_conversations_channels\n FOR SELECT TO authenticated\n USING (tenant_id IS NULL OR tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT ON public.plg_conversations_channels TO authenticated;\n";
|
|
4
|
+
export declare const MIGRATION_004_MESSAGE_DELIVERY = "-- ============================================================================\n-- plugin-conversations 004: what the provider did with a message, and who wrote\n-- it. Four columns, ported from the legacy beautyplace inbox.\n--\n-- `direction` + `author` was enough while every row was typed by a person in\n-- this app. It stops being enough the moment a message is sent by a connector:\n--\n-- provider_message_id the provider's handle for the row. A delivery webhook\n-- names only this, so without it a status can never be applied to anything.\n-- UNIQUE (where present) so an at-least-once webhook that redelivers the\n-- same inbound message cannot open a second copy of it in the thread.\n-- delivery_status the last thing the provider said, applied MONOTONICALLY\n-- by the webhook handler \u2014 'read' must not be overwritten by a 'delivered'\n-- that arrived late (they are unordered).\n-- sender_kind person / app / assistant / automation. The backbone of\n-- a shared inbox: legacy proved that a human and an AI writing into the same\n-- thread with no attribution is a thread nobody trusts.\n-- sender_label who exactly, in words \u2014 \"Ana\", \"Lembrete de hor\u00E1rio\".\n--\n-- Nullable throughout: rows written before this migration are not wrong, they\n-- are simply from a time when only people wrote here.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nALTER TABLE public.plg_conversation_messages\n ADD COLUMN IF NOT EXISTS provider_message_id text,\n ADD COLUMN IF NOT EXISTS delivery_status text,\n ADD COLUMN IF NOT EXISTS sender_kind text,\n ADD COLUMN IF NOT EXISTS sender_label text;\n\n-- Added apart from the column so a re-run on a pool that already has the column\n-- still installs the constraint (ADD COLUMN IF NOT EXISTS skips its inline ones).\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint WHERE conname = 'plg_conversation_messages_delivery_status_check'\n ) THEN\n ALTER TABLE public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_delivery_status_check\n CHECK (delivery_status IS NULL OR delivery_status IN\n ('queued', 'sent', 'delivered', 'read', 'failed', 'expired'));\n END IF;\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint WHERE conname = 'plg_conversation_messages_sender_kind_check'\n ) THEN\n ALTER TABLE public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_sender_kind_check\n CHECK (sender_kind IS NULL OR sender_kind IN ('user', 'system', 'ai', 'automation'));\n END IF;\nEND\n$$;\n\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversation_messages_provider_message\n ON public.plg_conversation_messages(provider_message_id)\n WHERE provider_message_id IS NOT NULL;\n";
|
|
5
|
+
export declare const MIGRATION_005_WEBHOOK_EVENTS = "-- ============================================================================\n-- plugin-conversations 005: the dedupe ledger \u2014 one row per event the provider\n-- ever delivered, and the reason a retry is free.\n--\n-- Tyxter delivers AT LEAST ONCE: 8 attempts over ~32.7 hours, and an attempt\n-- counts as failed on anything that is not a 2xx inside 10 seconds. So the same\n-- `message.received` arrives again whenever our answer was slow, and without\n-- this table the second delivery opens a second message in the thread and\n-- re-applies the button the customer pressed once.\n--\n-- Deliberately NOT keyed on the message: the same message produces several\n-- events (sent, delivered, read) and the same event may name no message at all\n-- (a template approval). The identity that is unique per DELIVERED FACT is the\n-- provider's event id, so that is the key.\n--\n-- The insert is the lock. `ON CONFLICT DO NOTHING \u2026 RETURNING id` returns a row\n-- for the first delivery and nothing for every later one, which is a claim\n-- taken in a single statement \u2014 a SELECT-then-INSERT would let two concurrent\n-- retries both find nothing and both apply.\n--\n-- Service-role only, and not because the rows are secret: a member who could\n-- delete one could make a webhook re-apply, and one who could insert could make\n-- a real delivery be skipped as a duplicate.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_conversations_webhook_events (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n provider text NOT NULL,\n -- The provider's id for the fact ('evt_\u2026'), NOT for the message.\n event_id text NOT NULL,\n event_type text NOT NULL,\n received_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_conversations_webhook_events ENABLE ROW LEVEL SECURITY;\n\n-- No tenant column on purpose: at claim time the tenant is not known yet \u2014 the\n-- whole point of the row is to stop the SECOND delivery before any of the work\n-- that would resolve one. Scoped by provider so two providers cannot collide on\n-- an id shape neither of them controls.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversations_webhook_events_event\n ON public.plg_conversations_webhook_events(provider, event_id);\n\n-- For the sweep that keeps this table from growing forever. Nothing prunes it\n-- yet; retention past the provider's 32.7h retry horizon buys nothing, and an\n-- index the pruner will need is cheaper to add now than to add under load.\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_webhook_events_received\n ON public.plg_conversations_webhook_events(received_at);\n\n-- RLS on with NO policy and no GRANT: service role only, in both directions.\n";
|
|
6
|
+
export declare const MIGRATION_006_OPTOUTS = "-- ============================================================================\n-- plugin-conversations 006: who told us to stop.\n--\n-- Tyxter keeps its own consent ledger and refuses a send to an opted-out\n-- contact at its edge. This table is not a copy of that for redundancy's sake \u2014\n-- it exists so the REFUSAL IS OURS. A send that fails at the provider is a\n-- failed send: a queued row, a failed delivery status, a retry, and a line in\n-- an operator's screen saying WhatsApp is broken. A send that never leaves\n-- because this row exists is a suppression, which is a different fact and the\n-- only one a person can act on.\n--\n-- `tenant_id` is NULLABLE and the null carries meaning, the same way it does on\n-- plg_conversations_channels:\n-- \u2022 NULL \u2014 the customer opted out of the PRODUCT's shared fallback number.\n-- They cannot have consented to one salon and not another on a number all\n-- the salons share, so the suppression is product-wide.\n-- \u2022 set \u2014 the customer opted out of that tenant's dedicated number. Their\n-- other conversations are untouched.\n--\n-- Written by the webhook (contact.opted_out / contact.erased) and read by\n-- messaging-send before every send. Nothing else writes it: a member who could\n-- delete a row could resume messaging somebody who asked us to stop, which is\n-- the one mistake in this file with a legal name.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_conversations_optouts (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n channel text NOT NULL DEFAULT 'whatsapp'\n CHECK (channel IN ('sms', 'whatsapp', 'instagram', 'email', 'webchat')),\n phone_e164 text NOT NULL,\n -- NULL = the product's shared number. Not a missing value: a scope.\n tenant_id uuid REFERENCES public.tenants(id) ON DELETE CASCADE,\n -- No DEFAULT: the table is provider-neutral, and a column that quietly\n -- defaults to one provider is how the second messaging provider's rows end up\n -- filed under the first one's name. Every writer names it.\n provider text NOT NULL,\n reason text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_conversations_optouts ENABLE ROW LEVEL SECURITY;\n\n-- NULL-safe uniqueness. A plain UNIQUE(channel, phone_e164, tenant_id) does not\n-- constrain the product-wide rows at all \u2014 in SQL two NULLs are not equal, so\n-- every redelivered contact.opted_out would insert another row and the\n-- suppression read would have to be a DISTINCT. The sentinel uuid is never a\n-- real tenant id, so COALESCE gives the three columns one identity.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversations_optouts_contact\n ON public.plg_conversations_optouts(\n channel, phone_e164,\n COALESCE(tenant_id, '00000000-0000-0000-0000-000000000000'::uuid));\n\n-- The read messaging-send makes on every send: \"is this number suppressed for\n-- this tenant, or product-wide?\"\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_optouts_lookup\n ON public.plg_conversations_optouts(channel, phone_e164);\n\n-- RLS: members READ the suppressions that apply to them (their own tenant's and\n-- the product-wide ones), because \"why did this customer stop getting messages\"\n-- is a question the inbox has to be able to answer. Writes are service-role\n-- only \u2014 no INSERT/UPDATE/DELETE policy, no write GRANT.\nDROP POLICY IF EXISTS plg_conversations_optouts_select ON public.plg_conversations_optouts;\nCREATE POLICY plg_conversations_optouts_select ON public.plg_conversations_optouts\n FOR SELECT TO authenticated\n USING (tenant_id IS NULL OR tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT ON public.plg_conversations_optouts TO authenticated;\n";
|
|
7
|
+
export declare const MIGRATION_007_INBOUND_ROUTING = "-- ============================================================================\n-- plugin-conversations 007: the two things the webhook needs that 004 could not\n-- know it would.\n--\n-- \u00A71 delivery_status gains the statuses the provider actually emits.\n-- \u00A72 plg_conversations_match_by_phone \u2014 find the thread a WhatsApp number\n-- belongs to, when the number was typed by a person and delivered by Meta.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\n-- \u00A71 \u2014 the closed set was one delivery short --------------------------------\n--\n-- 004 wrote the vocabulary from the design. Registering a webhook endpoint at\n-- Tyxter answers with the ENUMERATED list of deliverable events, and it holds\n-- three the CHECK would have refused: `message.delivery_timeout` (the provider\n-- gave up before the recipient's device ever acknowledged), `message.cancelled`\n-- (an accepted message pulled back before it left), and `message.opted_out`\n-- (refused at the provider because the contact withdrew consent \u2014 see 006).\n--\n-- Refusing them is worse than it sounds: the handler's UPDATE fails, the\n-- webhook answers non-2xx, and Tyxter retries the same event eight times over a\n-- day and a half before giving up. The row keeps whatever it said before, which\n-- is 'sent'. An operator reads \"sent\" for a message that was never delivered.\nDO $$\nBEGIN\n IF EXISTS (\n SELECT 1 FROM pg_constraint WHERE conname = 'plg_conversation_messages_delivery_status_check'\n ) THEN\n ALTER TABLE public.plg_conversation_messages\n DROP CONSTRAINT plg_conversation_messages_delivery_status_check;\n END IF;\n ALTER TABLE public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_delivery_status_check\n CHECK (delivery_status IS NULL OR delivery_status IN\n ('queued', 'sent', 'delivered', 'read',\n 'failed', 'expired', 'delivery_timeout', 'cancelled', 'opted_out'));\nEND\n$$;\n\n-- \u00A72 \u2014 matching a phone number to a thread ----------------------------------\n--\n-- The problem, ported from beautyplace where it cost a duplicate thread per\n-- returning customer: the receptionist typed `(21) 99889-9889`, WhatsApp\n-- delivers `+5521998899889`, and the mobile ninth digit is optional on records\n-- older than 2016. Three spellings of one person, and `=` matches none of them.\n--\n-- The rule is digit-suffix, longest first:\n-- last 10 = DDD + the 8 significant digits. Specific: a collision needs two\n-- customers in the same area code sharing 8 digits.\n-- last 8 = the subscriber digits alone. The FALLBACK, tried only when the\n-- first finds nothing, because it crosses area codes.\n--\n-- SECURITY DEFINER and CROSS-TENANT by construction, which is the part to read\n-- carefully. The product's fallback number is shared by every tenant in the\n-- pool, so an inbound message on it arrives with no tenant attached and finding\n-- one means looking at all of them. That is exactly the read that must never be\n-- reachable by a member, so EXECUTE is granted to service_role ONLY and\n-- explicitly revoked from authenticated and anon. The webhook holds the\n-- service-role key; nothing in the browser can call this.\n-- `p_tenant_id` narrows the search to one tenant, which is what a DEDICATED\n-- number wants: the tenant is already known from the channel row, and the\n-- cross-tenant search could hand back another salon's thread for the same\n-- customer. NULL searches the whole pool, which is the fallback number's case\n-- and the only one that has no other answer.\nCREATE OR REPLACE FUNCTION public.plg_conversations_match_by_phone(\n p_channel text,\n p_last10 text,\n p_last8 text,\n p_tenant_id uuid DEFAULT NULL\n) RETURNS TABLE (\n id uuid,\n tenant_id uuid,\n contact_name text,\n contact_handle text,\n contact_person_id uuid\n)\nLANGUAGE sql\nSTABLE\nSECURITY DEFINER\nSET search_path = public\nAS $fn$\n WITH candidate AS (\n SELECT c.id, c.tenant_id, c.contact_name, c.contact_handle, c.contact_person_id,\n c.last_message_at,\n regexp_replace(COALESCE(c.contact_handle, ''), '[^0-9]', '', 'g') AS digits\n FROM public.plg_conversations c\n WHERE c.channel = p_channel\n AND (p_tenant_id IS NULL OR c.tenant_id = p_tenant_id)\n )\n SELECT id, tenant_id, contact_name, contact_handle, contact_person_id\n FROM candidate\n WHERE length(digits) >= 8\n AND ((p_last10 IS NOT NULL AND right(digits, 10) = p_last10)\n OR (p_last8 IS NOT NULL AND right(digits, 8) = p_last8))\n -- The 10-digit match wins over an 8-digit one even when the 8-digit thread\n -- is newer: a wrong area code is a different person, and a newer wrong\n -- answer is still wrong.\n ORDER BY (p_last10 IS NOT NULL AND right(digits, 10) = p_last10) DESC,\n last_message_at DESC NULLS LAST\n LIMIT 1;\n$fn$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_match_by_phone(text, text, text, uuid) FROM public;\nREVOKE ALL ON FUNCTION public.plg_conversations_match_by_phone(text, text, text, uuid) FROM anon;\nREVOKE ALL ON FUNCTION public.plg_conversations_match_by_phone(text, text, text, uuid) FROM authenticated;\nGRANT EXECUTE ON FUNCTION public.plg_conversations_match_by_phone(text, text, text, uuid) TO service_role;\n\n-- The suffix is computed per row, so without this the match is a full scan of\n-- every WhatsApp thread in the pool on every inbound message. All three\n-- functions are IMMUTABLE, which is what makes the expression indexable.\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_handle_suffix\n ON public.plg_conversations(\n channel,\n right(regexp_replace(COALESCE(contact_handle, ''), '[^0-9]', '', 'g'), 8),\n last_message_at DESC);\n";
|
|
8
|
+
export declare const MIGRATION_008_MESSAGE_SUBJECT = "-- ============================================================================\n-- plugin-conversations 008: what a message was ABOUT.\n--\n-- Two columns, and they exist because of one unproved assumption in the\n-- WhatsApp design. The template send attaches a per-button payload carrying the\n-- booking id (`act:confirm:booking:<uuid>`), and the whole confirm/cancel flow\n-- rests on Meta echoing that payload back when the customer taps. Tyxter's\n-- sandbox accepts the components with a 202 and stores them verbatim \u2014 but it\n-- validates that array not at all (an index of 7 on a two-button template was\n-- accepted the same way), so the 202 proves nothing about Meta. Only a live\n-- WABA can.\n--\n-- If Meta turns out to echo the button's static TEXT instead, the reply arrives\n-- saying \"Confirmar\" and naming nothing. These columns are what it is then\n-- correlated against: the last outbound message on that conversation, and the\n-- subject recorded on it. Which is exactly the seam legacy beautyplace had to\n-- build after the fact \u2014 their keyword matcher confirmed EVERY future\n-- appointment the customer had, and the 48-hour re-enrichment subsystem existed\n-- only to guess which one was meant. Recording the subject at send time costs\n-- two nullable columns and removes the guess.\n--\n-- messaging-send (WP3) already writes them, tolerating 42703 and retrying\n-- without \u2014 so a pool that has not run this migration keeps sending, and one\n-- that has gains the anchor. This is the migration that closes that gap.\n--\n-- Nullable, and deliberately not a foreign key: the subject is polymorphic\n-- ('booking', later 'order', 'invoice'), lives in tables this plugin does not\n-- own, and a message about a deleted booking is still a message somebody sent.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nALTER TABLE public.plg_conversation_messages\n ADD COLUMN IF NOT EXISTS subject_type text,\n ADD COLUMN IF NOT EXISTS subject_id text;\n\n-- The correlation read: the most recent outbound message on this thread that\n-- was about something. Partial, because the rows that carry a subject are the\n-- minority and an index over the rest would be paid for on every insert.\nCREATE INDEX IF NOT EXISTS idx_plg_conversation_messages_subject\n ON public.plg_conversation_messages(conversation_id, at DESC)\n WHERE subject_id IS NOT NULL;\n\n-- The other direction, for \"show me everything ever said about this booking\" \u2014\n-- the thread view a booking detail page wants, and the audit answer to \"was the\n-- customer actually told?\".\nCREATE INDEX IF NOT EXISTS idx_plg_conversation_messages_by_subject\n ON public.plg_conversation_messages(tenant_id, subject_type, subject_id)\n WHERE subject_id IS NOT NULL;\n";
|
|
9
|
+
export declare const MIGRATION_009_PAYMENT_REQUESTS = "-- ============================================================================\n-- plugin-conversations 009: the charge a customer was asked for inside a\n-- conversation, and the row that stops it being asked for twice.\n--\n-- The flow this table exists for: the customer taps \"Pagar\" on a WhatsApp\n-- confirmation, a Pix is opened at the provider, the copy-and-paste code is\n-- sent back into the same thread, and the money \u2014 or the expiry \u2014 comes back as\n-- a webhook. Four hops, at least one of them delivered more than once.\n--\n-- `id` IS the `external_reference` sent to the provider, and that is the whole\n-- reconciliation story: the provider echoes it on every payment event, so a\n-- delivery names the row without a lookup table, and a payment created by this\n-- pool can never be confused with one created by the merchant's own AbacatePay\n-- account (the redundancy the design accepted on purpose). It is also the\n-- Idempotency-Key of the create call, so a retried create replays the provider's\n-- own first answer instead of opening a second charge.\n--\n-- \u2500\u2500 The two columns that are not data, but claims \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- `link_delivered_at` and `settled_notified_at` are LOCKS, taken with a\n-- conditional UPDATE \u2026 WHERE \u2026 IS NULL RETURNING. They exist because of a fact\n-- verified against the sandbox on 2026-08-24: ONE Pix becoming available emits\n-- TWO webhook events with two different event ids \u2014\n--\n-- payment.link_generated evt_2448\u2026 occurred_at 16:52:49.640Z\n-- payment.approval_available evt_750b\u2026 occurred_at 16:52:49.639Z\n--\n-- \u2014 carrying identical `data`. The dedupe ledger (005) cannot help: they are\n-- genuinely two events. Without a claim on the ROW the customer receives the\n-- same Pix code twice, one second apart, which reads as a double charge.\n--\n-- \u2500\u2500 Money moves forward only \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- `status` is applied monotonically by the same rule the delivery statuses use,\n-- because webhooks are unordered: a `link_generated` arriving after a `paid`\n-- must not walk a settled charge back to \"waiting\". The CHECK carries the\n-- provider's own vocabulary verbatim, including `approval_requested` and\n-- `approved`, which the design's list did not have and the OpenAPI does.\n--\n-- Writes are SERVICE-ROLE ONLY. A member who could update `status` could mark a\n-- charge paid; one who could update `amount_cents` could change what a customer\n-- is asked for after the fact. Members read \u2014 \"was this booking paid, and how\n-- much for\" is a question the inbox and the booking page both have to answer.\n-- Idempotent + safe to re-run.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_conversations_payment_requests (\n -- Ours, and the provider's `external_reference`, and the create's\n -- Idempotency-Key. One identity, so there is nothing to keep in step.\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n conversation_id uuid REFERENCES public.plg_conversations(id) ON DELETE SET NULL,\n -- The outbound message carrying the Pix code, once it has been sent. NULL\n -- until then, and that is exactly how \"the code has not gone out yet\" is read.\n message_id uuid REFERENCES public.plg_conversation_messages(id) ON DELETE SET NULL,\n -- Polymorphic and deliberately not a foreign key, for the same reason 008's\n -- subject columns are not: 'booking' today, 'order' and 'invoice' later, in\n -- tables this plugin does not own.\n subject_type text,\n subject_id text,\n -- Integers, because the provider takes `amount_brl_centavos` and a float that\n -- rounds to 4499 is a customer charged one centavo less than the salon reads.\n amount_cents integer NOT NULL CHECK (amount_cents > 0),\n currency text NOT NULL DEFAULT 'BRL',\n description text,\n -- Provider-neutral, like every other column in this plugin: the second\n -- payment rail must not need a second table.\n provider text NOT NULL,\n status text NOT NULL DEFAULT 'created'\n CHECK (status IN ('created', 'link_generated', 'approval_requested', 'approved',\n 'paid', 'failed', 'expired', 'cancelled')),\n provider_payment_id text,\n payment_link_url text,\n -- Kept because the customer may lose the message and ask for it again, and\n -- re-reading a code we already have beats opening a second charge.\n pix_copy_paste text,\n -- The claims. See the header.\n link_delivered_at timestamptz,\n settled_notified_at timestamptz,\n settled_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_conversations_payment_requests ENABLE ROW LEVEL SECURITY;\n\n-- The webhook's lookup: an event names the payment, and this finds the row.\n-- UNIQUE where present \u2014 two rows claiming one provider payment would make the\n-- settlement a coin toss.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversations_payment_requests_provider\n ON public.plg_conversations_payment_requests(provider, provider_payment_id)\n WHERE provider_payment_id IS NOT NULL;\n\n-- At most ONE live charge per subject. A customer tapping \"Pagar\" twice sends\n-- two `message.received` events with two different event ids, so the dedupe\n-- ledger lets both through \u2014 this is what stops the second one opening a second\n-- Pix for the same appointment. Settled rows are excluded: a booking legitimately\n-- gets a new charge after one expired.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_conversations_payment_requests_open_subject\n ON public.plg_conversations_payment_requests(tenant_id, subject_type, subject_id)\n WHERE subject_id IS NOT NULL\n AND status IN ('created', 'link_generated', 'approval_requested', 'approved');\n\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_payment_requests_tenant\n ON public.plg_conversations_payment_requests(tenant_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_payment_requests_subject\n ON public.plg_conversations_payment_requests(tenant_id, subject_type, subject_id)\n WHERE subject_id IS NOT NULL;\n\n-- RLS: members read their own tenant's charges. No INSERT/UPDATE/DELETE policy\n-- and no write GRANT \u2014 every write here is made by the webhook with the\n-- service-role key, acting on a signed delivery.\nDROP POLICY IF EXISTS plg_conversations_payment_requests_select\n ON public.plg_conversations_payment_requests;\nCREATE POLICY plg_conversations_payment_requests_select\n ON public.plg_conversations_payment_requests\n FOR SELECT TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT ON public.plg_conversations_payment_requests TO authenticated;\n";
|
|
3
10
|
export declare const MIGRATIONS: Array<{
|
|
4
11
|
id: string;
|
|
5
12
|
sql: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,2BAA2B,gyJA0EvC,CAAA;AAED,eAAO,MAAM,4BAA4B,mvCAsBxC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,2BAA2B,gyJA0EvC,CAAA;AAED,eAAO,MAAM,4BAA4B,mvCAsBxC,CAAA;AAED,eAAO,MAAM,sBAAsB,knIAyElC,CAAA;AAED,eAAO,MAAM,8BAA8B,ozFAuD1C,CAAA;AAED,eAAO,MAAM,4BAA4B,8vFAkDxC,CAAA;AAED,eAAO,MAAM,qBAAqB,wwHAkEjC,CAAA;AAED,eAAO,MAAM,6BAA6B,wuLAmHzC,CAAA;AAED,eAAO,MAAM,6BAA6B,8xFAgDzC,CAAA;AAED,eAAO,MAAM,8BAA8B,soOAqH1C,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAUzD,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/views/ConversationList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/views/ConversationList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,qBA2GrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxView.d.ts","sourceRoot":"","sources":["../../src/views/InboxView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"InboxView.d.ts","sourceRoot":"","sources":["../../src/views/InboxView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAYzB,wBAAgB,SAAS,sBA2ExB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Starting a conversation, in the right column.
|
|
4
|
+
*
|
|
5
|
+
* It was a dialog over the inbox — over the very list you read to decide
|
|
6
|
+
* whether a thread already exists. Beside it, that list stays legible while the
|
|
7
|
+
* contact is chosen.
|
|
8
|
+
*/
|
|
9
|
+
export declare function NewConversationPanel({ open, onOpenChange, }: {
|
|
10
|
+
open: boolean;
|
|
11
|
+
onOpenChange: (open: boolean) => void;
|
|
12
|
+
}): React.JSX.Element;
|
|
13
|
+
//# sourceMappingURL=NewConversationPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NewConversationPanel.d.ts","sourceRoot":"","sources":["../../src/views/NewConversationPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAmCzB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,GACb,EAAE;IACD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACtC,qBAoKA"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fayz-ai/plugin-conversations",
|
|
3
3
|
"fayz": {
|
|
4
|
-
"status": "preview"
|
|
4
|
+
"status": "preview",
|
|
5
|
+
"dependencies": []
|
|
5
6
|
},
|
|
6
|
-
"version": "0.
|
|
7
|
+
"version": "0.11.0",
|
|
7
8
|
"description": "[experimental] Fayz SDK — unified conversations / omni-channel inbox plugin",
|
|
8
9
|
"type": "module",
|
|
9
10
|
"sideEffects": false,
|
|
@@ -26,9 +27,9 @@
|
|
|
26
27
|
"lucide-react": ">=0.400.0 <1.0.0"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@fayz-ai/core": "^0.
|
|
30
|
-
"@fayz-ai/ui": "^0.
|
|
31
|
-
"@fayz-ai/admin": "^0.
|
|
30
|
+
"@fayz-ai/core": "^0.16.0",
|
|
31
|
+
"@fayz-ai/ui": "^0.16.0",
|
|
32
|
+
"@fayz-ai/admin": "^0.16.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/react": "^18.3.0",
|
|
@@ -53,7 +54,8 @@
|
|
|
53
54
|
"build": "tsup && tsc -b --force",
|
|
54
55
|
"dev": "tsup --watch",
|
|
55
56
|
"typecheck": "tsc -b tsconfig.test.json",
|
|
56
|
-
"test": "vitest run",
|
|
57
|
-
"clean": "rm -rf dist .tsbuildinfo"
|
|
57
|
+
"test": "vitest run && pnpm run test:functions",
|
|
58
|
+
"clean": "rm -rf dist .tsbuildinfo",
|
|
59
|
+
"test:functions": "node --test functions/_shared/*.test.mjs functions/tyxter-webhook/*.test.mjs"
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NewConversationModal.d.ts","sourceRoot":"","sources":["../../src/views/NewConversationModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAgCzB,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,GACb,EAAE;IACD,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACtC,qBAqKA"}
|