- Fork: POST /api/chats/:id/fork creates a new chat in the same session, copies messages up to target (status=complete) with row-offset clock_timestamp() for stable ordering. Client emits open_chat_in_active_pane event; Workspace opens it in the active pane. No maybeAutoNameChat on forks. - Delete: DELETE /api/chats/:id/messages/:message_id with 409 if the chat is currently streaming. Cascading-forward delete (created_at >= target). MessageBubble Trash button + confirm Dialog. - Header: Projects -> Project -> Session breadcrumb, model badge pill, inline session rename, active file path via new useActivePane() hook. Server now publishes session_renamed on PATCH /api/sessions/:id; client-side dup emit removed from Session.tsx. - Housekeeping: NOW() -> clock_timestamp() in schema.sql defaults, dead PaneTab.tsx and panes/PaneShell.tsx removed, session_panes backfill INSERT removed (CREATE TABLE retained), Tailnet trust comment near app.listen(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
156 lines
6.4 KiB
SQL
156 lines
6.4 KiB
SQL
CREATE TABLE IF NOT EXISTS projects (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
path TEXT NOT NULL UNIQUE,
|
|
added_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
last_session_id UUID
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
model TEXT NOT NULL,
|
|
system_prompt TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id, updated_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL,
|
|
content TEXT NOT NULL DEFAULT '',
|
|
tool_calls JSONB,
|
|
tool_results JSONB,
|
|
status TEXT NOT NULL DEFAULT 'complete',
|
|
last_seq INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, created_at);
|
|
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS tokens_used INTEGER;
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS ctx_used INTEGER;
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS ctx_max INTEGER;
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ;
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS finished_at TIMESTAMPTZ;
|
|
|
|
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp();
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value JSONB NOT NULL
|
|
);
|
|
|
|
INSERT INTO settings (key, value) VALUES ('default_model', '"qwen3.6-35b-a3b-mxfp4"') ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- DEPRECATED: client-side pane state as of v1.2-batch4. Table retained per
|
|
-- additive schema rule; no writes. Drop in a future destructive migration.
|
|
CREATE TABLE IF NOT EXISTS session_panes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
position INTEGER NOT NULL,
|
|
kind TEXT NOT NULL CHECK (kind IN ('chat', 'file_browser')),
|
|
state JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
UNIQUE (session_id, position)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_session_panes_session ON session_panes (session_id);
|
|
|
|
-- v1.4: backfill removed. Pane layout is client-side (localStorage) since v1.2-batch4.
|
|
-- The CREATE TABLE above is retained for additive-schema discipline; drop is a
|
|
-- future destructive migration.
|
|
|
|
-- v1.2: sessions.status (open | archived)
|
|
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'open';
|
|
|
|
-- v1.2: chats table
|
|
CREATE TABLE IF NOT EXISTS chats (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
name TEXT,
|
|
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'archived')),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_chats_session_status ON chats (session_id, status, updated_at DESC);
|
|
|
|
-- v1.2: messages.chat_id + messages.kind
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS chat_id UUID REFERENCES chats(id) ON DELETE CASCADE;
|
|
ALTER TABLE messages ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'message';
|
|
CREATE INDEX IF NOT EXISTS idx_messages_chat ON messages (chat_id, created_at);
|
|
|
|
-- Backfill: one chat per existing session that has none yet
|
|
INSERT INTO chats (session_id, name, status, created_at, updated_at)
|
|
SELECT s.id, s.name, 'open', s.created_at, s.updated_at
|
|
FROM sessions s
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM chats c WHERE c.session_id = s.id
|
|
);
|
|
|
|
-- Backfill: link orphaned messages to their session's first chat
|
|
UPDATE messages SET chat_id = (
|
|
SELECT c.id FROM chats c WHERE c.session_id = messages.session_id ORDER BY c.created_at ASC LIMIT 1
|
|
)
|
|
WHERE chat_id IS NULL;
|
|
|
|
-- Enforce NOT NULL on chat_id once all rows are backfilled
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name = 'messages' AND column_name = 'chat_id' AND is_nullable = 'YES'
|
|
) AND NOT EXISTS (
|
|
SELECT 1 FROM messages WHERE chat_id IS NULL
|
|
) THEN
|
|
ALTER TABLE messages ALTER COLUMN chat_id SET NOT NULL;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- v1.2.1: CHECK constraints for sessions.status and messages (role, status)
|
|
-- KEEP IN SYNC: apps/server/src/types/api.ts (MESSAGE_ROLES, MESSAGE_STATUSES, SessionStatus)
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'sessions_status_chk') THEN
|
|
ALTER TABLE sessions ADD CONSTRAINT sessions_status_chk
|
|
CHECK (status IN ('open', 'archived'));
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'messages_role_chk') THEN
|
|
ALTER TABLE messages ADD CONSTRAINT messages_role_chk
|
|
CHECK (role IN ('user', 'assistant', 'system', 'tool'));
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'messages_status_chk') THEN
|
|
ALTER TABLE messages ADD CONSTRAINT messages_status_chk
|
|
CHECK (status IN ('streaming', 'complete', 'failed', 'cancelled'));
|
|
END IF;
|
|
END $$;
|
|
|
|
-- v1.2-project-ux: projects.status + projects.gitea_remote
|
|
-- KEEP IN SYNC: apps/server/src/types/api.ts PROJECT_STATUSES
|
|
ALTER TABLE projects ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'open';
|
|
ALTER TABLE projects ADD COLUMN IF NOT EXISTS gitea_remote TEXT;
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'projects_status_chk') THEN
|
|
ALTER TABLE projects ADD CONSTRAINT projects_status_chk
|
|
CHECK (status IN ('open', 'archived'));
|
|
END IF;
|
|
END $$;
|
|
|
|
-- v1.3-tab-close-chat-archive: align chats.status vocabulary with projects ('archived' not 'closed')
|
|
-- KEEP IN SYNC: apps/server/src/types/api.ts CHAT_STATUSES
|
|
-- Order matters: (1) drop the OLD inline CHECK that only allowed ('open','closed');
|
|
-- (2) migrate existing rows; (3) add new named CHECK allowing ('open','archived').
|
|
ALTER TABLE chats DROP CONSTRAINT IF EXISTS chats_status_check;
|
|
UPDATE chats SET status = 'archived' WHERE status = 'closed';
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'chats_status_chk') THEN
|
|
ALTER TABLE chats ADD CONSTRAINT chats_status_chk
|
|
CHECK (status IN ('open', 'archived'));
|
|
END IF;
|
|
END $$;
|