-- v2.0.0: BooCoder schema — pending changes, tasks, agent registry. -- Applied on startup by apps/coder/src/db.ts:applySchema(). -- Lives in the same 'boochat' database as BooChat's tables. CREATE TABLE IF NOT EXISTS pending_changes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID NOT NULL, task_id UUID, file_path TEXT NOT NULL, operation TEXT NOT NULL, diff TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(), CONSTRAINT pending_changes_operation_chk CHECK (operation IN ('create', 'edit', 'delete')), CONSTRAINT pending_changes_status_chk CHECK (status IN ('pending', 'applied', 'rejected', 'reverted')) ); CREATE TABLE IF NOT EXISTS tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID NOT NULL, parent_task_id UUID REFERENCES tasks(id), state TEXT NOT NULL DEFAULT 'pending', input TEXT NOT NULL, output_summary TEXT, agent TEXT, model TEXT, execution_path TEXT, worktree_path TEXT, cost_tokens INTEGER, started_at TIMESTAMPTZ, ended_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(), CONSTRAINT tasks_state_chk CHECK (state IN ('pending', 'running', 'completed', 'failed', 'blocked', 'cancelled')), CONSTRAINT tasks_execution_path_chk CHECK (execution_path IS NULL OR execution_path IN ('native', 'acp', 'pty', 'qwen')) ); CREATE TABLE IF NOT EXISTS available_agents ( name TEXT PRIMARY KEY, install_path TEXT, version TEXT, supports_acp BOOLEAN NOT NULL DEFAULT false, supports_mcp_client BOOLEAN NOT NULL DEFAULT false, last_probed_at TIMESTAMPTZ ); -- v2.0.0 Phase 4: link tasks to their inference sessions. ALTER TABLE tasks ADD COLUMN IF NOT EXISTS session_id UUID REFERENCES sessions(id); -- v2.0.5: add 'qwen' to execution_path CHECK + arena_id column. ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_execution_path_chk; DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'tasks_execution_path_chk') THEN ALTER TABLE tasks ADD CONSTRAINT tasks_execution_path_chk CHECK (execution_path IS NULL OR execution_path IN ('native', 'acp', 'pty', 'qwen')); END IF; END $$; -- v2.0.5: arena support — group tasks into competitive arenas. ALTER TABLE tasks ADD COLUMN IF NOT EXISTS arena_id UUID; -- Human inbox: tasks needing attention CREATE OR REPLACE VIEW human_inbox AS SELECT * FROM tasks WHERE state IN ('blocked', 'failed'); -- v2.1.0: provider picker — extend available_agents with model discovery. ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS models JSONB DEFAULT '[]'::jsonb; ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS label TEXT; ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS transport TEXT DEFAULT 'pty'; -- v2.5.10: persisted ACP available_commands (captured during the cold probe), so -- an agent's live command set survives the tier-2 probe skip and shows without a -- dispatch. ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS commands JSONB DEFAULT '[]'::jsonb; -- v2.2.0: Paseo-style session config on tasks. ALTER TABLE tasks ADD COLUMN IF NOT EXISTS mode_id TEXT; ALTER TABLE tasks ADD COLUMN IF NOT EXISTS thinking_option_id TEXT; ALTER TABLE tasks ADD COLUMN IF NOT EXISTS feature_values JSONB; -- v2.6: one shared worktree per session (all agents/panes in the session operate in it). CREATE TABLE IF NOT EXISTS session_worktrees ( session_id UUID PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE, worktree_path TEXT NOT NULL, base_commit TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp() ); -- Migrate existing FK to CASCADE (idempotent: drops the old constraint if present). DO $$ BEGIN IF EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'session_worktrees_session_id_fkey' AND confdeltype <> 'c' ) THEN ALTER TABLE session_worktrees DROP CONSTRAINT session_worktrees_session_id_fkey; ALTER TABLE session_worktrees ADD CONSTRAINT session_worktrees_session_id_fkey FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE; END IF; END $$; -- v2.6: one backend session per (session, agent); resumed on switch-back. CREATE TABLE IF NOT EXISTS agent_sessions ( session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, agent TEXT NOT NULL, backend TEXT NOT NULL, agent_session_id TEXT, server_port INTEGER, status TEXT NOT NULL DEFAULT 'idle', last_active_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(), PRIMARY KEY (session_id, agent), CONSTRAINT agent_sessions_backend_chk CHECK (backend IN ('opencode_server', 'acp_warm')), CONSTRAINT agent_sessions_status_chk CHECK (status IN ('idle', 'active', 'crashed', 'closed')) ); -- Migrate existing agent_sessions FK to CASCADE. DO $$ BEGIN IF EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'agent_sessions_session_id_fkey' AND confdeltype <> 'c' ) THEN ALTER TABLE agent_sessions DROP CONSTRAINT agent_sessions_session_id_fkey; ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_session_id_fkey FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE; END IF; END $$; -- v2.6: config fingerprint for stale-session detection (auto-recover on model change). ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS config_hash TEXT; -- v2.6: attribution for DiffPanel badges (Phase 1 UX reads this). ALTER TABLE pending_changes ADD COLUMN IF NOT EXISTS agent TEXT; -- LISTEN/NOTIFY fast path: every tasks INSERT (from any call site — routes, -- new_task tool, arena, MCP server) fires pg_notify('tasks_new') in the same -- transaction, so the dispatcher reacts immediately instead of waiting for the -- fallback poll. Postgres holds the notification until COMMIT, so the listener -- always sees the committed row. A trigger covers all insert paths with no -- app-code drift. Idempotent: re-applied on every startup. CREATE OR REPLACE FUNCTION notify_tasks_new() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('tasks_new', ''); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS tasks_notify_new ON tasks; CREATE TRIGGER tasks_notify_new AFTER INSERT ON tasks FOR EACH ROW EXECUTE FUNCTION notify_tasks_new();