Migration Required

Supabase Migration v2

Add 3 new tables to your Supabase project: platform_settings, api_collections, and api_requests.

🚨 Workforce Dashboard Not Showing Status? — Fix It Here
If Workforce Dashboard shows all employees as "No Status", pick the option that matches your situation:
Option A — Tables don't exist yet
Error message: relation "staff_status" does not exist
→ Run the SQL in the green block below (creates tables + RLS in one shot)
Option B — Tables exist, status still 0
Tables visible in Supabase Table Editor, but workforce shows 0 rows / "No Status"
→ Run the SQL in the orange block below (re-applies RLS policy only)
A
Create Tables + Enable RLS (run this if you got the "relation does not exist" error)
SQL — Migration v7: Create staff_status + status_logs
-- ============================================================
--  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;
Expected result: "Success. No rows returned" + the final SELECT shows 2 rows (one per table, both with policy anon_all). Then open staff-status.html → set a status → reload workforce.html — it should appear.
✅ Migration Complete! "Success. No rows returned" is correct.
The tables and RLS policies were created successfully. The table is empty because nobody has logged in yet — that's expected. Now follow these steps:
1
Each agent logs in at login.html
→ Login automatically sets status to "Available" and starts the 30s heartbeat
2
Open workforce.html — wait 15 seconds for auto-refresh
→ Logged-in agents now show as 🟢 Online with their status
If an agent still shows offline after login:
→ Open status-debug.html while logged in as that agent → click "🟢 Force Set ONLINE Now"
B
Re-apply RLS Only (run this if tables already exist but status shows 0)
SQL — RLS Fix only (tables must already exist)
-- ============================================================
--  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;
1
Go to supabase.com/dashboard → your project → SQL Editor
2
Click + New Query → paste the SQL from Option A (or B) above → click Run
3
See "Success. No rows returned" → open staff-status.html → set any status → reload workforce.html
Post-fix verification
Migration: Add "bot" role to sb_users constraint
Required to create ChatBot Agent users. Your Supabase 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';
1
Go to supabase.com/dashboard → your project → SQL Editor → New Query
2
Click Copy SQL above → paste → click Run → see "Success. No rows returned"
3
Go to admin.html → Add Team Member → Role: 🤖 ChatBot Agent → Save — it will work now
⚡ Step Path / Variables Empty? — Run This One SQL to Fix Everything
If Conversation Details → Step Path tab shows "No step data recorded" and Variables tab is empty, one or more conversations table columns are missing from your Supabase schema.

This single SQL block adds ALL optional columns that may be missing (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;
1
Go to supabase.com/dashboard → your project → SQL Editor → New Query
2
Click Copy SQL above → paste into the editor → click Run
3
You should see: "Success. No rows returned" AND the verification SELECT at the bottom shows all 9 column names
4
Run a new chatbot conversation → open Conversations → click the row → Step Path and Variables tabs should now show data
⚠️ Note: Existing conversations saved before this migration was run will still show empty Step Path/Variables — only new conversations will capture this data. This is expected.
Migration: Ensure path & variables columns exist on conversations
Step Path tab and Variables tab show empty in Conversation Details? This migration ensures the 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.

If you ran the original 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;
1
Go to supabase.com/dashboard → your project → SQL Editor → New Query
2
Click Copy SQL above → paste → click Run → verify it shows 3 rows (path, variables, messages_log)
3
Run any flow in chat.html → open Conversations → click any row → Step Path and Variables tabs now show data
Note: This migration only adds the columns — existing conversations will still show empty step data because they were saved before the columns existed. Only new conversations run after this migration will have step path + variables populated.
Why you're seeing 404 errors
The new API Collection and Platform Settings features require 3 tables that don't exist yet in your Supabase project. Run the SQL below in your Supabase SQL Editor to fix this — it takes about 30 seconds.
How to run this migration
Follow these 4 steps. The SQL is safe to run multiple times — uses IF NOT EXISTS throughout.
1
Open Supabase SQL Editor
Go to supabase.com/dashboard → your project → SQL Editor in the left sidebar
2
Create a new query
Click New query (top left of SQL Editor)
3
Copy & paste the SQL below
Click the Copy SQL button → paste into the query editor
4
Click Run
Hit Run (or Ctrl+Enter) — you should see "Success. No rows returned"
SQL — 3 new tables
-- ============================================================
--  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.
v9
Migration v9 — Team Chat Messages
Required for team-chat.html — WhatsApp-style internal messaging between staff members
SQL — Migration v9: team_messages table
-- ============================================================
--  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';
After running: Open team-chat.html — messages will now persist in Supabase. Before running this, messages still work via browser localStorage (no persistence across devices).
Bucket already created via Dashboard? — You only need to run Steps 2–5 (the RLS policies).
The bucket insert uses ON CONFLICT DO UPDATE so it's safe to run the full block too.

Still getting 403 on upload? — Scroll up in the SQL block to the RLS FIX section and run just that part.
v10
Migration v10 — Skill Matrix
Required for skill-matrix.html — Admin rates each team member's expertise per flow category
Run this if skill-matrix.html shows 404 errors — the skill_matrix table must be created in Supabase before ratings can be saved.
SQL — Migration v10: skill_matrix table
-- ============================================================
--  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';
After running: Open admin.html → click ⭐ on any team member → rate their skills → click Save. The ratings will also appear read-only in each user's profile panel inside team-chat.html.
v11
Migration v11 — Team Calendar
Required for team-calendar.html — Shift templates, user assignments & calendar events
Run this to enable the Team Calendar — creates 3 tables: shift_templates, user_shifts, and calendar_events.
SQL — Migration v11: Team Calendar tables
-- ============================================================
--  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;
After running: Open team-calendar.html → click Shifts to create your first shift template → click + New Event to add calendar entries. All data saves to Supabase immediately.
Migration v11 Checklist
v12
Migration v12 — Extended User Profile
Adds 7 profile columns to sb_users: about, phone, extension, mobile_cc, mobile_num, personal_email, timezone
Run this if Admin → Edit Member shows error: "Could not find the 'about' column of 'sb_users' in the schema cache"
SQL — Migration v12: extend sb_users profile columns
-- ============================================================
--  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;
After running: Go to Admin → Team Management → click ✏️ on any member → fill in About, Work Phone, Mobile, Timezone → click Save Member — should save without any error. The profile details also appear in each user's Team Chat profile panel.
Migration v12 Checklist
v13
Migration v13 — Timezone-Aware Calendar
Required for per-employee timezone rendering in team-calendar.html — adds timezone to calendar_events & shift_templates, plus a lookup index on sb_users.timezone
Run this after Migration v11 + v12. Enables the timeline to correctly position Vipul (EDT 9–5), Kaushik (IST 9–5), and Meet (PDT 9–5) side-by-side in any viewer timezone.
What this migration adds
calendar_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)
Index on sb_users(timezone) — fast lookup when building the employee list
Index on calendar_events(user_id, start_ts) — faster per-user date-range queries
notify pgrst, 'reload schema' — forces PostgREST to see the new columns immediately
SQL — Migration v13: timezone columns & indexes
-- ============================================================
--  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;
Note: The calendar page already reads timezone from 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.
Optional — backfill existing events with the timezone of their assigned user:
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;
Migration v13 Checklist
v14
Migration v14 — Conversations: path, variables & messages_log JSONB columns
Fixes Step Path tab and Variables tab showing empty in Conversation Details
Run this if Conversation Details → Step Path tab shows "No step data recorded" and Variables tab is empty. The 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.
SQL — Migration v14: conversations path + variables + messages_log columns
-- ============================================================
--  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;
After running: Open chat.html → run any flow to completion → go to Conversations → click the new conversation row → Step Path shows the node audit trail and Variables shows all captured variable values.
Important: Only new conversations (run after this migration) will have step data. Existing conversations were saved before the columns existed — they will still show "No step data recorded".
Migration v14 Checklist
Tables Created / Modified by All Migrations
platform_settings
api_collections
api_requests
staff_status
status_logs
skill_matrix
shift_templates
user_shifts
calendar_events
sb_users (+7 profile cols)
conversations (+path, variables, messages_log)
Verification Checklist
Check each item after running the SQL to confirm everything is working.
v15
Migration v15 — Add google_places_api_key to platform_settings
Required for the Place Search flow node — lets users search & select a real address using Google Places Autocomplete. Run this once in Supabase SQL Editor.
Run this if: Superadmin → Platform Settings → Save Google Places Key shows a Save Error — the google_places_api_key column doesn't exist in your Supabase platform_settings table yet.
SQL — Migration v15: Add google_places_api_key + ai_function_url columns
-- ============================================================
--  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;
1
Go to supabase.com/dashboard → your project → SQL Editor → New Query
2
Click Copy above → paste the SQL → click Run → see "Success. No rows returned"
3
The verify SELECT at the bottom should return 2 rows: ai_function_url and google_places_api_key
4
Go to Superadmin → Platform Settings → paste your Google Places API key → click Save Google Places Key → should show ✓ Saved
Migration v15 Checklist
After migration: what's now available
Superadmin → Platform Settings — enter your AI Model API key + model
Knowledge Base → API Integrations — full Postman-like API Collection UI
AI Auto-describe — AI model generates API purpose descriptions from real responses
MCP Tool Definitions — auto-generated OpenAI function-calling JSON for each request
Outcome Mapping — visual rule builder maps API responses → flow node outcomes
Photo / Card node image upload — drag & drop or click to upload images to Supabase Storage; images display inline in chat
Contact Reference Fields — CustomerName, ContactPerson, CallbackNumber auto-captured from flow variables and displayed in Conversations list
Employee Live Status (v7) — 8 live status types (Available, Busy, In Meeting, Driving, On-site, Break, Off Duty, Emergency); self-service My Status page; manager Workforce dashboard with time analytics, location grouping, and per-employee detail panel; live status dots on Team Management cards
Skill Matrix (v10) — Admin sets 1–5 star ratings per team member per flow category; displayed read-only in user's profile panel in team-chat.html
Back to App Superadmin Settings Full Setup Guide