Add 3 new tables to your Supabase project: platform_settings, api_collections, and api_requests.
relation "staff_status" does not exist-- ============================================================
-- Agent Builder — Migration v7
-- Creates: staff_status + status_logs tables WITH RLS
-- Safe to re-run (uses IF NOT EXISTS throughout)
-- ============================================================
-- ── 1. STAFF STATUS (one row per user — current live status) ──
create table if not exists staff_status (
id text primary key,
user_id text not null,
account_id text,
user_name text,
status_key text not null default 'available',
location text,
note text,
since_at bigint,
updated_at bigint,
last_seen_at bigint default null
);
-- ── 2. STATUS LOGS (append-only history) ──────────────────────
create table if not exists status_logs (
id text primary key,
user_id text not null,
account_id text,
user_name text,
status_key text not null,
location text,
note text,
logged_at bigint not null
);
-- ── Indexes ───────────────────────────────────────────────────
create index if not exists idx_staff_status_account on staff_status(account_id);
create index if not exists idx_staff_status_user on staff_status(user_id);
create index if not exists idx_staff_status_last_seen on staff_status(last_seen_at);
create index if not exists idx_status_logs_account on status_logs(account_id);
create index if not exists idx_status_logs_user on status_logs(user_id);
create index if not exists idx_status_logs_logged_at on status_logs(logged_at);
-- ── Row Level Security ────────────────────────────────────────
alter table staff_status enable row level security;
alter table status_logs enable row level security;
drop policy if exists "anon_all" on staff_status;
create policy "anon_all" on staff_status
for all to anon using (true) with check (true);
drop policy if exists "anon_all" on status_logs;
create policy "anon_all" on status_logs
for all to anon using (true) with check (true);
-- ── Verify ────────────────────────────────────────────────────
select tablename, policyname, cmd
from pg_policies
where tablename in ('staff_status','status_logs')
order by tablename;
anon_all).
Then open staff-status.html → set a status → reload workforce.html — it should appear.
login.htmlstatus-debug.html while logged in as that agent → click "🟢 Force Set ONLINE Now"-- ============================================================
-- RLS Fix — run ONLY if tables already exist in Table Editor
-- but workforce.html still shows 0 rows / "No Status"
-- ============================================================
drop policy if exists "anon_all" on staff_status;
create policy "anon_all" on staff_status
for all to anon using (true) with check (true);
drop policy if exists "anon_all" on status_logs;
create policy "anon_all" on status_logs
for all to anon using (true) with check (true);
-- Verify:
select tablename, policyname, cmd
from pg_policies
where tablename in ('staff_status','status_logs')
order by tablename;
sb_users table has a
CHECK constraint that only allows owner, admin, agent — it
needs bot added. Run this once in Supabase → SQL Editor:
-- Drop the old role check constraint and recreate with 'bot' included
ALTER TABLE sb_users DROP CONSTRAINT IF EXISTS sb_users_role_check;
ALTER TABLE sb_users ADD CONSTRAINT sb_users_role_check
CHECK (role IN ('owner', 'admin', 'agent', 'bot'));
-- Verify (should show the new constraint):
SELECT conname, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'sb_users'::regclass
AND contype = 'c';
conversations table columns are missing from your Supabase schema.path, variables, messages_log,
conv_title, conv_notes, contact_ref, customer_name, contact_person, callback_number).
Safe to re-run — uses IF NOT EXISTS throughout.
-- ══ One-Shot: Add ALL optional conversations columns ══════════════════════
-- Safe to re-run: every statement uses IF NOT EXISTS
-- Run in Supabase → SQL Editor → New Query → Run
-- ── Migration v3: chat transcript log ──────────────────────────────────────
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS messages_log jsonb DEFAULT '[]'::jsonb;
-- ── Migration v6: contact reference + conversation info node ───────────────
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS customer_name text DEFAULT NULL,
ADD COLUMN IF NOT EXISTS contact_person text DEFAULT NULL,
ADD COLUMN IF NOT EXISTS callback_number text DEFAULT NULL,
ADD COLUMN IF NOT EXISTS contact_ref jsonb DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS conv_title text DEFAULT NULL,
ADD COLUMN IF NOT EXISTS conv_notes text DEFAULT NULL;
-- ── Migration v14: step path + flow variables (REQUIRED for Step Path tab) ─
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS path jsonb DEFAULT '[]'::jsonb;
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS variables jsonb DEFAULT '{}'::jsonb;
-- ── GIN indexes for fast JSONB search ──────────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_convs_path ON conversations USING gin(path);
CREATE INDEX IF NOT EXISTS idx_convs_variables ON conversations USING gin(variables);
CREATE INDEX IF NOT EXISTS idx_convs_messages_log ON conversations USING gin(messages_log);
CREATE INDEX IF NOT EXISTS idx_convs_customer ON conversations(customer_name) WHERE customer_name IS NOT NULL;
-- ── Reload Supabase schema cache ────────────────────────────────────────────
NOTIFY pgrst, 'reload schema';
-- ── Verify all columns now exist ────────────────────────────────────────────
SELECT column_name, data_type, column_default
FROM information_schema.columns
WHERE table_name = 'conversations'
AND column_name IN ('path','variables','messages_log','conv_title','conv_notes',
'contact_ref','customer_name','contact_person','callback_number')
ORDER BY column_name;
path (JSONB array) and variables (JSONB object) columns
exist on the conversations table so the chat runtime can store and replay step history and captured variables.
supabase-setup.html script these already exist — but if your table was created
manually or from an older version, they may be missing. Safe to run either way.
-- ── Migration: Conversations — path & variables JSONB columns ──────────────
-- Safe to re-run: uses IF NOT EXISTS
-- Step 1: Add path column (step audit trail — array of {nodeId, title, type, answer, ts})
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS path jsonb DEFAULT '[]'::jsonb;
-- Step 2: Add variables column (captured flow variables — key/value object)
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS variables jsonb DEFAULT '{}'::jsonb;
-- Step 3: Add messages_log column (full chat transcript — array of {role, text, ts})
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS messages_log jsonb DEFAULT '[]'::jsonb;
-- Step 4: Add GIN indexes for fast search inside JSONB
CREATE INDEX IF NOT EXISTS idx_convs_path
ON conversations USING gin(path);
CREATE INDEX IF NOT EXISTS idx_convs_variables
ON conversations USING gin(variables);
CREATE INDEX IF NOT EXISTS idx_conv_messages_log
ON conversations USING gin(messages_log);
-- Verify:
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'conversations'
AND column_name IN ('path', 'variables', 'messages_log')
ORDER BY column_name;
IF NOT EXISTS throughout.Ctrl+Enter) — you should see "Success. No rows returned"
-- ============================================================
-- Agent Builder — Supabase Migration v2
-- Adds: platform_settings, api_collections, api_requests
-- Safe to re-run: uses IF NOT EXISTS throughout
-- ============================================================
-- ── 1. PLATFORM SETTINGS (global AI config — one row) ──────
create table if not exists platform_settings (
id text primary key default 'global',
openrouter_api_key text default '',
openrouter_model text default 'openai/gpt-4o-mini',
updated_at bigint default extract(epoch from now())*1000
);
-- ── 2. API COLLECTIONS (MCP Collection manager) ─────────────
create table if not exists api_collections (
id text primary key,
account_id text references sb_accounts(id) on delete cascade,
name text not null,
description text default '',
base_url text default '',
auth_type text default 'none',
auth_key text default '',
auth_value text default '',
icon text default '🔌',
color text default '#6366f1',
sort_order integer default 0,
created_at bigint default extract(epoch from now())*1000
);
-- ── 3. API REQUESTS (child of api_collections) ──────────────
create table if not exists api_requests (
id text primary key,
account_id text references sb_accounts(id) on delete cascade,
collection_id text references api_collections(id) on delete cascade,
name text default 'New Request',
method text default 'GET',
path text default '/',
headers text default '[]',
params text default '[]',
body text default '',
auth_override text default null,
purpose text default '',
outcome_map text default '[]',
last_response text default '',
mcp_tool_def text default '',
sort_order integer default 0,
created_at bigint default extract(epoch from now())*1000,
updated_at bigint default extract(epoch from now())*1000
);
-- ── Indexes ──────────────────────────────────────────────────
create index if not exists idx_api_col_account on api_collections(account_id);
create index if not exists idx_api_req_col on api_requests(collection_id);
create index if not exists idx_api_req_account on api_requests(account_id);
-- ── Row Level Security ───────────────────────────────────────
alter table platform_settings enable row level security;
alter table api_collections enable row level security;
alter table api_requests enable row level security;
drop policy if exists "anon_all" on platform_settings;
create policy "anon_all" on platform_settings
for all to anon using (true) with check (true);
drop policy if exists "anon_all" on api_collections;
create policy "anon_all" on api_collections
for all to anon using (true) with check (true);
drop policy if exists "anon_all" on api_requests;
create policy "anon_all" on api_requests
for all to anon using (true) with check (true);
-- ── Done! ────────────────────────────────────────────────────
-- Tables created: platform_settings | api_collections | api_requests
-- Verify in: Supabase Dashboard → Table Editor
-- ============================================================
-- Agent Builder — Migration v3 (run AFTER v2)
-- Adds: messages_log column to conversations table
-- Stores full chat transcript (bot + user bubbles) per session
-- Safe to re-run: uses IF NOT EXISTS / column existence check
-- ============================================================
-- Add messages_log JSONB column (array of {role,text,ts,flowName})
alter table conversations
add column if not exists messages_log jsonb default '[]'::jsonb;
-- Index for future filtering/search on message content
create index if not exists idx_conv_messages_log
on conversations using gin(messages_log);
-- Done! Every new conversation now stores its full message history.
-- Existing conversations will have messages_log = [] (empty array).
-- ============================================================
-- Agent Builder — Migration v4 (run AFTER v3)
-- Adds: starting_flow_id column to flow_categories table
-- Allows each category to have a default flow that auto-launches
-- when the category tile is tapped in the chat page.
-- Safe to re-run: uses IF NOT EXISTS column check
-- ============================================================
alter table flow_categories
add column if not exists starting_flow_id text default null;
-- Done! Categories can now have a starting flow configured.
-- Set via: Builder → select a category → Edit Category → Starting Flow dropdown.
-- ============================================================
-- Agent Builder — Migration v5 (run AFTER v4)
-- Creates: Supabase Storage bucket "flow-media"
-- Used by: Photo/Card (media_card) node image upload
-- Public bucket: images readable by anyone (no auth required)
-- Safe to re-run: uses ON CONFLICT DO NOTHING
-- ============================================================
-- Step 1: Create the public storage bucket
-- (Skip if already created via Dashboard UI — ON CONFLICT handles it)
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values (
'flow-media',
'flow-media',
true,
5242880, -- 5 MB per file limit
array['image/jpeg','image/png','image/gif','image/webp','image/svg+xml']
)
on conflict (id) do update set public = true;
-- Step 2: RLS policy — anyone can read images (public CDN-style)
drop policy if exists "flow-media public read" on storage.objects;
create policy "flow-media public read"
on storage.objects for select
to anon, authenticated
using ( bucket_id = 'flow-media' );
-- Step 3: RLS policy — anon users can upload (builder uses anon key)
drop policy if exists "flow-media anon upload" on storage.objects;
create policy "flow-media anon upload"
on storage.objects for insert
to anon, authenticated
with check ( bucket_id = 'flow-media' );
-- Step 4: RLS policy — allow delete (for remove button in editor)
drop policy if exists "flow-media anon delete" on storage.objects;
create policy "flow-media anon delete"
on storage.objects for delete
to anon, authenticated
using ( bucket_id = 'flow-media' );
-- Step 5: RLS policy — allow update (needed for upsert)
drop policy if exists "flow-media anon update" on storage.objects;
create policy "flow-media anon update"
on storage.objects for update
to anon, authenticated
using ( bucket_id = 'flow-media' );
-- Done! Bucket "flow-media" is ready.
-- Images uploaded via Builder → Photo/Card node → upload zone
-- will be stored at: /storage/v1/object/public/flow-media/flow-images/<filename>
-- Public URL pattern: https://YOUR_PROJECT.supabase.co/storage/v1/object/public/flow-media/flow-images/<filename>
-- ============================================================
-- IMPORTANT: If you already ran v5 but upload still fails
-- with "new row violates row-level security policy" error,
-- run this standalone RLS FIX block in Supabase SQL Editor:
-- ============================================================
-- RLS FIX — run this if upload fails with 403 / RLS error
drop policy if exists "flow-media public read" on storage.objects;
drop policy if exists "flow-media anon upload" on storage.objects;
drop policy if exists "flow-media anon delete" on storage.objects;
drop policy if exists "flow-media anon update" on storage.objects;
create policy "flow-media public read" on storage.objects for select to anon, authenticated using (bucket_id = 'flow-media');
create policy "flow-media anon upload" on storage.objects for insert to anon, authenticated with check (bucket_id = 'flow-media');
create policy "flow-media anon delete" on storage.objects for delete to anon, authenticated using (bucket_id = 'flow-media');
create policy "flow-media anon update" on storage.objects for update to anon, authenticated using (bucket_id = 'flow-media');
-- Also ensure the bucket itself is marked public:
update storage.buckets set public = true where id = 'flow-media';
-- ============================================================
-- Agent Builder — Migration v6 (run AFTER v5)
-- Adds: Contact fields + Conversation Info node columns
-- to the conversations table.
-- Safe to re-run: uses IF NOT EXISTS
-- ============================================================
alter table conversations
add column if not exists customer_name text default null,
add column if not exists contact_person text default null,
add column if not exists callback_number text default null,
add column if not exists contact_ref jsonb default '{}'::jsonb,
add column if not exists conv_title text default null,
add column if not exists conv_notes text default null;
-- contact_ref stores the full snapshot of all pinned ref vars
-- customer_name = primary display name in conversations list (👤 purple chip)
-- contact_person = contact / representative name (🧑💼 blue chip)
-- callback_number = phone number for callback (📞 green chip)
-- conv_title = custom conversation title set by Conversation Info node
-- conv_notes = internal notes set by Conversation Info node
-- Index for fast search by customer name
create index if not exists idx_convs_customer_name
on conversations(customer_name)
where customer_name is not null;
-- Done! Contact fields + conv_info fields are ready.
-- Set via: Builder → drag "Conv. Info" node into any flow
-- or: Builder → Set Variable node → "Pin to conversation" dropdown
-- Displayed in: Dashboard → Conversations list
-- ============================================================
-- Agent Builder — Migration v7 (run AFTER v6)
-- Creates: staff_status + status_logs tables
-- Used by: Employee Live Status Tracking System
-- staff-status.html (self-service status update)
-- workforce.html (manager workforce dashboard)
-- Safe to re-run: uses IF NOT EXISTS throughout
-- ============================================================
-- ── 1. STAFF STATUS (one row per user — current live status) ──
create table if not exists staff_status (
id text primary key,
user_id text not null,
account_id text,
user_name text,
status_key text not null default 'available',
location text,
note text,
since_at bigint,
updated_at bigint,
last_seen_at bigint default null
);
-- ── 2. STATUS LOGS (append-only event log — full history) ─────
create table if not exists status_logs (
id text primary key,
user_id text not null,
account_id text,
user_name text,
status_key text not null,
location text,
note text,
logged_at bigint not null
);
-- ── Indexes ──────────────────────────────────────────────────
create index if not exists idx_staff_status_account on staff_status(account_id);
create index if not exists idx_staff_status_user on staff_status(user_id);
create index if not exists idx_status_logs_account on status_logs(account_id);
create index if not exists idx_status_logs_user on status_logs(user_id);
create index if not exists idx_status_logs_logged_at on status_logs(logged_at);
-- ── Row Level Security ───────────────────────────────────────
alter table staff_status enable row level security;
alter table status_logs enable row level security;
drop policy if exists "anon_all" on staff_status;
create policy "anon_all" on staff_status
for all to anon using (true) with check (true);
drop policy if exists "anon_all" on status_logs;
create policy "anon_all" on status_logs
for all to anon using (true) with check (true);
-- ── Done! ────────────────────────────────────────────────────
-- Tables created: staff_status | status_logs
-- status_key values: available | busy_customer | in_meeting |
-- driving | on_site | break | off_duty | emergency
-- Verify in: Supabase Dashboard → Table Editor
-- ============================================================
-- Agent Builder — Migration v8 (run AFTER v7)
-- Adds: last_seen_at column to staff_status
-- Used by: Heartbeat system — detects online/offline
-- automatically without relying on logout hooks
-- Safe to re-run: uses IF NOT EXISTS / IF EXISTS
-- ============================================================
-- Add last_seen_at column (bigint milliseconds timestamp)
alter table staff_status
add column if not exists last_seen_at bigint default null;
-- Index for fast "who is online?" queries
create index if not exists idx_staff_status_last_seen
on staff_status(last_seen_at);
-- Done! The app now pings last_seen_at every 30s.
-- Anyone with last_seen_at older than 90s = Offline.
-- ============================================================ -- Agent Builder — Migration v9 (run AFTER v7/v8) -- Creates: team_messages — internal staff chat messages -- Used by: team-chat.html — WhatsApp-style team messaging -- Safe to re-run: uses IF NOT EXISTS throughout -- ============================================================ create table if not exists team_messages ( id text primary key, conv_id text not null, -- sorted user_id pair: "uid1__uid2" sender_id text not null, -- sb_users.id of sender sender_name text, receiver_id text, -- null = group/broadcast body text not null, sent_at bigint not null, -- Date.now() milliseconds read boolean default false, account_id text -- for multi-tenant isolation ); -- Indexes for fast conversation queries create index if not exists idx_team_messages_conv on team_messages(conv_id); create index if not exists idx_team_messages_sent on team_messages(sent_at); create index if not exists idx_team_messages_sender on team_messages(sender_id); -- Row Level Security (allow anon key — same as other tables) alter table team_messages enable row level security; drop policy if exists "anon_all" on team_messages; create policy "anon_all" on team_messages for all to anon using (true) with check (true); -- Verify select tablename, policyname, cmd from pg_policies where tablename = 'team_messages';
ON CONFLICT DO UPDATE so it's safe to run the full block too.skill_matrix table must be created in Supabase before ratings can be saved.
-- ============================================================ -- Agent Builder — Migration v10 -- Creates: skill_matrix — admin-set skill ratings per user -- Used by: skill-matrix.html (admin) + team-chat.html profile panel (read-only) -- Safe to re-run: uses IF NOT EXISTS throughout -- ============================================================ create table if not exists skill_matrix ( id text primary key, account_id text not null, user_id text not null, category_id text not null, rating integer not null default 0 check (rating >= 0 and rating <= 5), note text default null, updated_at bigint default extract(epoch from now())*1000 ); -- Unique constraint: one rating per user per category per account -- This allows the UPSERT (merge-duplicates) to work correctly create unique index if not exists idx_skill_matrix_unique on skill_matrix(account_id, user_id, category_id); -- Additional indexes for fast lookups create index if not exists idx_skill_matrix_account on skill_matrix(account_id); create index if not exists idx_skill_matrix_user on skill_matrix(user_id); create index if not exists idx_skill_matrix_category on skill_matrix(category_id); -- Row Level Security (allow anon key — same as all other tables) alter table skill_matrix enable row level security; drop policy if exists "anon_all" on skill_matrix; create policy "anon_all" on skill_matrix for all to anon using (true) with check (true); -- Verify select tablename, policyname, cmd from pg_policies where tablename = 'skill_matrix';
shift_templates, user_shifts, and calendar_events.
-- ============================================================
-- Agent Builder — Migration v11
-- Creates: shift_templates, user_shifts, calendar_events
-- Used by: team-calendar.html
-- Safe to re-run: uses IF NOT EXISTS throughout
-- ============================================================
-- 1. Shift templates (recurring schedules)
create table if not exists shift_templates (
id text primary key,
account_id text not null,
name text not null,
start_time text not null default '09:00',
end_time text not null default '17:00',
days jsonb default '[]', -- array of day numbers [0-6]
color text default '#34d399',
updated_at bigint default extract(epoch from now())*1000
);
create index if not exists idx_shift_templates_account on shift_templates(account_id);
alter table shift_templates enable row level security;
drop policy if exists "anon_all" on shift_templates;
create policy "anon_all" on shift_templates
for all to anon using (true) with check (true);
-- 2. User–shift assignments
create table if not exists user_shifts (
id text primary key,
account_id text not null,
user_id text not null,
template_id text not null references shift_templates(id) on delete cascade,
updated_at bigint default extract(epoch from now())*1000
);
create unique index if not exists idx_user_shifts_unique
on user_shifts(account_id, user_id, template_id);
create index if not exists idx_user_shifts_account on user_shifts(account_id);
alter table user_shifts enable row level security;
drop policy if exists "anon_all" on user_shifts;
create policy "anon_all" on user_shifts
for all to anon using (true) with check (true);
-- 3. Calendar events
create table if not exists calendar_events (
id text primary key,
account_id text not null,
user_id text not null,
type text not null default 'other',
title text,
start_ts bigint not null, -- epoch ms
end_ts bigint not null,
start_time text, -- HH:MM string for easy filtering
end_time text,
note text,
recur text default 'none', -- none | daily | weekly | weekdays
visibility text default 'team', -- team | private
updated_at bigint default extract(epoch from now())*1000
);
create index if not exists idx_calendar_events_account on calendar_events(account_id);
create index if not exists idx_calendar_events_user on calendar_events(user_id);
create index if not exists idx_calendar_events_time on calendar_events(start_ts);
alter table calendar_events enable row level security;
drop policy if exists "anon_all" on calendar_events;
create policy "anon_all" on calendar_events
for all to anon using (true) with check (true);
-- Verify all 3 tables created
select tablename, rowsecurity
from pg_tables
where tablename in ('shift_templates','user_shifts','calendar_events')
order by tablename;
sb_users: about, phone, extension, mobile_cc, mobile_num, personal_email, timezone"Could not find the 'about' column of 'sb_users' in the schema cache"
-- ============================================================
-- Agent Builder — Migration v12
-- Extends: sb_users — adds 7 profile columns
-- Used by: admin.html (Edit Member), team-chat.html (profile panel),
-- staff-status.html (profile settings)
-- Safe to re-run: uses ADD COLUMN IF NOT EXISTS throughout
-- ============================================================
alter table sb_users
add column if not exists about text default null,
add column if not exists phone text default null,
add column if not exists extension text default null,
add column if not exists mobile_cc text default null,
add column if not exists mobile_num text default null,
add column if not exists personal_email text default null,
add column if not exists timezone text default null;
-- Force PostgREST to reload its schema cache immediately
-- (run this in the SQL editor after the ALTER TABLE above)
notify pgrst, 'reload schema';
-- Verify columns were added
select column_name, data_type, is_nullable
from information_schema.columns
where table_name = 'sb_users'
and column_name in ('about','phone','extension','mobile_cc','mobile_num','personal_email','timezone')
order by column_name;
timezone to calendar_events & shift_templates, plus a lookup index on sb_users.timezonecalendar_events.timezone — IANA tz of the person the event belongs to (e.g. America/New_York)shift_templates.timezone — override tz for a shift template (optional; user's own tz takes precedence in JS)sb_users(timezone) — fast lookup when building the employee listcalendar_events(user_id, start_ts) — faster per-user date-range queriesnotify pgrst, 'reload schema' — forces PostgREST to see the new columns immediately-- ============================================================
-- Agent Builder — Migration v13
-- Timezone-Aware Calendar
-- Extends: calendar_events, shift_templates, sb_users
-- Used by: team-calendar.html (per-employee TZ rendering)
-- Safe to re-run: uses IF NOT EXISTS / ADD COLUMN IF NOT EXISTS
-- Requires: Migration v11 (calendar tables) + v12 (sb_users tz col)
-- ============================================================
-- 1. Add timezone column to calendar_events
-- Stores the IANA timezone of the assigned user at the time
-- the event was created (e.g. 'America/New_York', 'Asia/Kolkata').
-- start_time / end_time are already stored in that local timezone.
alter table calendar_events
add column if not exists timezone text default null;
-- 2. Add optional timezone override to shift_templates
-- Normally the user's own sb_users.timezone is used; this column
-- lets you override per template (e.g. a night-shift template
-- explicitly in 'Asia/Kolkata' regardless of who is assigned).
alter table shift_templates
add column if not exists timezone text default null;
-- 3. Performance index — sb_users.timezone
-- Speeds up the employee list query that groups by timezone
-- when the calendar renders the viewer-TZ dropdown.
create index if not exists idx_sb_users_timezone
on sb_users(timezone)
where timezone is not null;
-- 4. Composite index — calendar_events per-user date-range scan
-- The calendar fetches events with:
-- WHERE account_id = ? AND user_id = ? AND start_ts BETWEEN ? AND ?
-- This index covers that pattern directly.
create index if not exists idx_calendar_events_user_ts
on calendar_events(account_id, user_id, start_ts);
-- 5. Composite index — shift_templates by account + name
-- Speeds up the shift manager list sort.
create index if not exists idx_shift_templates_account_name
on shift_templates(account_id, name);
-- 6. Reload PostgREST schema cache so new columns are visible immediately
notify pgrst, 'reload schema';
-- 7. Verify — should return 2 rows (calendar_events + shift_templates)
select table_name, column_name, data_type, is_nullable
from information_schema.columns
where table_name in ('calendar_events', 'shift_templates')
and column_name = 'timezone'
order by table_name;
sb_users.timezone (set via Admin → Edit Member or Team Chat profile). The new calendar_events.timezone column is a snapshot that future event saves will populate — existing events will have null and the calendar will fall back to the assigned user's current sb_users.timezone.
update calendar_events ce set timezone = u.timezone from sb_users u where ce.user_id = u.id and ce.timezone is null and u.timezone is not null;
path and variables JSONB columns may not exist in your Supabase conversations table if it was created from an older script. Safe to re-run.
-- ============================================================
-- Agent Builder — Migration v14
-- Adds: path, variables, messages_log JSONB columns
-- to the conversations table.
-- Required for: Step Path tab + Variables tab in Conversation Details
-- Safe to re-run: uses ADD COLUMN IF NOT EXISTS throughout
-- ============================================================
-- Step 1: Add path column (step audit trail — array of {nodeId,title,type,answer,ts})
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS path jsonb DEFAULT '[]'::jsonb;
-- Step 2: Add variables column (captured flow variables — {key: value} object)
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS variables jsonb DEFAULT '{}'::jsonb;
-- Step 3: Add messages_log column (full chat transcript)
ALTER TABLE conversations
ADD COLUMN IF NOT EXISTS messages_log jsonb DEFAULT '[]'::jsonb;
-- Step 4: GIN indexes for fast JSON search
CREATE INDEX IF NOT EXISTS idx_convs_path ON conversations USING gin(path);
CREATE INDEX IF NOT EXISTS idx_convs_variables ON conversations USING gin(variables);
CREATE INDEX IF NOT EXISTS idx_conv_messages_log ON conversations USING gin(messages_log);
-- Step 5: Force PostgREST schema cache reload (required after ALTER TABLE)
NOTIFY pgrst, 'reload schema';
-- Step 6: Verify — should return 3 rows
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'conversations'
AND column_name IN ('path', 'variables', 'messages_log')
ORDER BY column_name;
google_places_api_key to platform_settingsgoogle_places_api_key column doesn't exist in your Supabase platform_settings table yet.
-- ============================================================
-- Agent Builder — Migration v15
-- Adds: google_places_api_key + ai_function_url to platform_settings
-- Required for: Place Search flow node (Google Places Autocomplete)
-- Safe to re-run: uses IF NOT EXISTS / ADD COLUMN IF NOT EXISTS
-- ============================================================
-- Add google_places_api_key column (stores the Google Places API key)
ALTER TABLE platform_settings
ADD COLUMN IF NOT EXISTS google_places_api_key text DEFAULT '';
-- Add ai_function_url column (optional AI proxy Edge Function URL)
ALTER TABLE platform_settings
ADD COLUMN IF NOT EXISTS ai_function_url text DEFAULT '';
-- Ensure the global singleton row exists (safe if already present)
INSERT INTO platform_settings (id, google_places_api_key, ai_function_url)
VALUES ('global', '', '')
ON CONFLICT (id) DO NOTHING;
-- Reload PostgREST schema cache so the new column is immediately accessible
NOTIFY pgrst, 'reload schema';
-- Verify: should return 2 rows (one per new column)
SELECT column_name, data_type, column_default
FROM information_schema.columns
WHERE table_name = 'platform_settings'
AND column_name IN ('google_places_api_key', 'ai_function_url')
ORDER BY column_name;
ai_function_url and google_places_api_key