| import type { DatabaseSync } from 'node:sqlite'; |
| |
| export const SQLITE_SESSION_METADATA_SCHEMA_VERSION = 22; |
| |
| export const SQLITE_AGENT_GRAPH_CONTROL_TABLES = [ |
| 'agent_graph_intent_claims', |
| 'agent_graph_schedule_updates', |
| 'agent_graph_operator_provisions', |
| 'agent_graph_client_projections', |
| 'agent_graph_client_operator_projections', |
| 'agent_graph_client_terminal_activity', |
| 'agent_graph_client_applied_records', |
| 'agent_graph_supervisor_wakes', |
| 'agent_graph_supervisor_wake_attempts', |
| ] as const; |
| |
| const MIGRATIONS: ReadonlyMap<number, string> = new Map([ |
| [ |
| 1, |
| ` |
| CREATE TABLE session_metadata ( |
| session_id TEXT PRIMARY KEY, |
| payload_json TEXT NOT NULL, |
| created_at INTEGER NOT NULL, |
| last_used_at INTEGER NOT NULL, |
| last_message_at INTEGER, |
| name TEXT NOT NULL, |
| is_flagged INTEGER NOT NULL CHECK (is_flagged IN (0, 1)), |
| is_archived INTEGER NOT NULL CHECK (is_archived IN (0, 1)), |
| status TEXT NOT NULL, |
| status_updated_at INTEGER, |
| parent_session_id TEXT, |
| revision_root_session_id TEXT, |
| revision_index INTEGER, |
| has_unread INTEGER NOT NULL CHECK (has_unread IN (0, 1)), |
| backend TEXT NOT NULL, |
| llm_connection_slug TEXT NOT NULL, |
| model TEXT NOT NULL, |
| metadata_version INTEGER NOT NULL CHECK (metadata_version > 0), |
| committed_at INTEGER NOT NULL |
| ); |
| |
| CREATE INDEX session_metadata_by_recency |
| ON session_metadata(is_archived, last_message_at DESC, last_used_at DESC, session_id); |
| |
| CREATE INDEX session_metadata_by_flag |
| ON session_metadata(is_flagged, is_archived, session_id); |
| |
| CREATE INDEX session_metadata_by_status |
| ON session_metadata(status, status_updated_at DESC, session_id); |
| |
| CREATE INDEX session_metadata_by_parent |
| ON session_metadata(parent_session_id, session_id); |
| |
| CREATE INDEX session_metadata_by_revision |
| ON session_metadata(revision_root_session_id, revision_index, session_id); |
| |
| CREATE TABLE session_metadata_labels ( |
| session_id TEXT NOT NULL, |
| label_index INTEGER NOT NULL CHECK (label_index >= 0), |
| label TEXT NOT NULL, |
| PRIMARY KEY(session_id, label_index), |
| FOREIGN KEY(session_id) REFERENCES session_metadata(session_id) ON DELETE CASCADE |
| ); |
| |
| CREATE INDEX session_metadata_labels_by_label |
| ON session_metadata_labels(label, session_id); |
| |
| `, |
| ], |
| [ |
| 2, |
| ` |
| CREATE TABLE session_metadata_tombstones ( |
| session_id TEXT PRIMARY KEY, |
| deleted_at INTEGER NOT NULL |
| ); |
| `, |
| ], |
| [ |
| 3, |
| ` |
| ALTER TABLE session_metadata ADD COLUMN subagent_parent_session_id TEXT; |
| |
| UPDATE session_metadata |
| SET subagent_parent_session_id = |
| json_extract(payload_json, '$.subagentParent.parentSessionId') |
| WHERE json_type(payload_json, '$.subagentParent.parentSessionId') = 'text'; |
| |
| CREATE INDEX session_metadata_by_subagent_parent |
| ON session_metadata(subagent_parent_session_id, session_id); |
| `, |
| ], |
| [ |
| 4, |
| ` |
| ALTER TABLE session_metadata ADD COLUMN subagent_parent_run_id TEXT; |
| ALTER TABLE session_metadata ADD COLUMN subagent_tool_call_id TEXT; |
| ALTER TABLE session_metadata ADD COLUMN subagent_swarm_id TEXT; |
| ALTER TABLE session_metadata ADD COLUMN subagent_item_id TEXT; |
| ALTER TABLE session_metadata ADD COLUMN subagent_request_fingerprint TEXT; |
| ALTER TABLE session_metadata ADD COLUMN subagent_initial_turn_id TEXT; |
| ALTER TABLE session_metadata ADD COLUMN subagent_initial_run_id TEXT; |
| |
| UPDATE session_metadata |
| SET |
| subagent_parent_run_id = |
| json_extract(payload_json, '$.subagentParent.spawnedBy.parentRunId'), |
| subagent_tool_call_id = |
| json_extract(payload_json, '$.subagentParent.spawnedBy.toolCallId'), |
| subagent_swarm_id = |
| json_extract(payload_json, '$.subagentParent.swarm.swarmId'), |
| subagent_item_id = |
| json_extract(payload_json, '$.subagentParent.swarm.itemId'), |
| subagent_request_fingerprint = |
| json_extract(payload_json, '$.subagentSpawn.requestFingerprint'), |
| subagent_initial_turn_id = |
| json_extract(payload_json, '$.subagentSpawn.initialTurnId'), |
| subagent_initial_run_id = |
| json_extract(payload_json, '$.subagentSpawn.initialRunId') |
| WHERE subagent_parent_session_id IS NOT NULL; |
| |
| CREATE UNIQUE INDEX session_metadata_by_subagent_spawn |
| ON session_metadata( |
| subagent_parent_session_id, |
| subagent_parent_run_id, |
| subagent_tool_call_id, |
| COALESCE(subagent_swarm_id, ''), |
| COALESCE(subagent_item_id, '') |
| ) |
| WHERE |
| subagent_parent_session_id IS NOT NULL |
| AND subagent_parent_run_id IS NOT NULL |
| AND subagent_tool_call_id IS NOT NULL |
| AND subagent_request_fingerprint IS NOT NULL; |
| `, |
| ], |
| [ |
| 5, |
| ` |
| CREATE TABLE subagent_spawns ( |
| parent_session_id TEXT NOT NULL, |
| parent_run_id TEXT NOT NULL, |
| tool_call_id TEXT NOT NULL, |
| swarm_id TEXT NOT NULL, |
| item_id TEXT NOT NULL, |
| request_fingerprint TEXT NOT NULL, |
| child_session_id TEXT NOT NULL UNIQUE, |
| initial_turn_id TEXT NOT NULL, |
| initial_run_id TEXT NOT NULL, |
| claimed_at INTEGER NOT NULL, |
| PRIMARY KEY(parent_session_id, parent_run_id, tool_call_id, swarm_id, item_id) |
| ); |
| |
| INSERT INTO subagent_spawns( |
| parent_session_id, |
| parent_run_id, |
| tool_call_id, |
| swarm_id, |
| item_id, |
| request_fingerprint, |
| child_session_id, |
| initial_turn_id, |
| initial_run_id, |
| claimed_at |
| ) |
| SELECT |
| subagent_parent_session_id, |
| subagent_parent_run_id, |
| subagent_tool_call_id, |
| COALESCE(subagent_swarm_id, ''), |
| COALESCE(subagent_item_id, ''), |
| subagent_request_fingerprint, |
| session_id, |
| subagent_initial_turn_id, |
| subagent_initial_run_id, |
| committed_at |
| FROM session_metadata |
| WHERE |
| subagent_parent_session_id IS NOT NULL |
| AND subagent_parent_run_id IS NOT NULL |
| AND subagent_tool_call_id IS NOT NULL |
| AND subagent_request_fingerprint IS NOT NULL |
| AND subagent_initial_turn_id IS NOT NULL |
| AND subagent_initial_run_id IS NOT NULL; |
| |
| DROP INDEX session_metadata_by_subagent_spawn; |
| `, |
| ], |
| [ |
| 6, |
| ` |
| CREATE TABLE agent_graph_intent_claims ( |
| claim_id TEXT PRIMARY KEY, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| graph_id TEXT NOT NULL, |
| intent_id TEXT NOT NULL, |
| intent_fingerprint TEXT NOT NULL, |
| readiness_context_fingerprint TEXT NOT NULL, |
| target_operator_id TEXT NOT NULL, |
| target_session_id TEXT NOT NULL, |
| target_turn_id TEXT NOT NULL, |
| target_run_id TEXT NOT NULL, |
| claimed_at INTEGER NOT NULL, |
| UNIQUE(graph_id, intent_id), |
| UNIQUE(target_session_id, target_turn_id), |
| UNIQUE(target_session_id, target_run_id) |
| ); |
| |
| CREATE INDEX agent_graph_intent_claims_by_graph |
| ON agent_graph_intent_claims(graph_id, claimed_at, intent_id); |
| `, |
| ], |
| [ |
| 7, |
| ` |
| CREATE TABLE agent_graph_schedule_updates ( |
| graph_id TEXT NOT NULL, |
| revision INTEGER NOT NULL CHECK (revision > 0), |
| update_id TEXT NOT NULL UNIQUE, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| update_fingerprint TEXT NOT NULL, |
| source_session_id TEXT NOT NULL, |
| source_run_id TEXT NOT NULL, |
| source_turn_id TEXT NOT NULL, |
| source_tool_call_id TEXT NOT NULL, |
| closes_graph INTEGER NOT NULL CHECK (closes_graph IN (0, 1)), |
| payload_json TEXT NOT NULL, |
| committed_at INTEGER NOT NULL CHECK (committed_at >= 0), |
| PRIMARY KEY(graph_id, revision), |
| UNIQUE(source_session_id, source_run_id, source_tool_call_id) |
| ); |
| |
| CREATE INDEX agent_graph_schedule_updates_by_graph |
| ON agent_graph_schedule_updates(graph_id, committed_at, update_id); |
| `, |
| ], |
| [ |
| 8, |
| ` |
| ALTER TABLE agent_graph_intent_claims |
| ADD COLUMN admission_status TEXT NOT NULL DEFAULT 'executing' |
| CHECK (admission_status IN ('claimed', 'executing', 'cancelled')); |
| ALTER TABLE agent_graph_intent_claims |
| ADD COLUMN admission_updated_at INTEGER NOT NULL DEFAULT 0 |
| CHECK (admission_updated_at >= 0); |
| ALTER TABLE agent_graph_intent_claims |
| ADD COLUMN cancellation_reason TEXT; |
| |
| UPDATE agent_graph_intent_claims |
| SET admission_updated_at = claimed_at; |
| `, |
| ], |
| [ |
| 9, |
| ` |
| CREATE TABLE agent_graph_operator_provisions ( |
| graph_id TEXT NOT NULL, |
| work_id TEXT NOT NULL, |
| provision_id TEXT NOT NULL UNIQUE, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| provision_fingerprint TEXT NOT NULL, |
| agent_id TEXT NOT NULL, |
| operator_id TEXT NOT NULL, |
| target_session_id TEXT NOT NULL UNIQUE, |
| payload_json TEXT NOT NULL, |
| provisioned_at INTEGER NOT NULL CHECK (provisioned_at >= 0), |
| PRIMARY KEY(graph_id, work_id), |
| UNIQUE(graph_id, operator_id) |
| ); |
| |
| CREATE INDEX agent_graph_operator_provisions_by_graph |
| ON agent_graph_operator_provisions(graph_id, provisioned_at, operator_id); |
| `, |
| ], |
| [ |
| 10, |
| ` |
| CREATE TABLE agent_graph_client_projections ( |
| graph_id TEXT PRIMARY KEY, |
| root_session_id TEXT NOT NULL, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| snapshot_version TEXT NOT NULL, |
| payload_json TEXT NOT NULL, |
| materialized_at INTEGER NOT NULL CHECK (materialized_at >= 0) |
| ); |
| |
| CREATE TABLE agent_graph_client_operator_projections ( |
| graph_id TEXT NOT NULL, |
| operator_id TEXT NOT NULL, |
| snapshot_version TEXT NOT NULL, |
| payload_json TEXT NOT NULL, |
| materialized_at INTEGER NOT NULL CHECK (materialized_at >= 0), |
| PRIMARY KEY(graph_id, operator_id) |
| ); |
| |
| CREATE TABLE agent_graph_client_terminal_activity ( |
| graph_id TEXT NOT NULL, |
| record_id TEXT NOT NULL, |
| event_time INTEGER NOT NULL CHECK (event_time >= 0), |
| payload_json TEXT NOT NULL, |
| PRIMARY KEY(graph_id, record_id) |
| ); |
| |
| CREATE TABLE agent_graph_client_applied_records ( |
| graph_id TEXT NOT NULL, |
| record_id TEXT NOT NULL, |
| event_time INTEGER NOT NULL CHECK (event_time >= 0), |
| PRIMARY KEY(graph_id, record_id) |
| ); |
| |
| CREATE INDEX agent_graph_client_terminal_activity_page |
| ON agent_graph_client_terminal_activity( |
| graph_id, |
| event_time DESC, |
| record_id DESC |
| ); |
| `, |
| ], |
| [ |
| 11, |
| ` |
| CREATE TABLE agent_graph_supervisor_wakes ( |
| graph_id TEXT NOT NULL, |
| wake_id TEXT NOT NULL, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| snapshot_version TEXT NOT NULL, |
| root_session_id TEXT NOT NULL, |
| status TEXT NOT NULL |
| CHECK (status IN ('pending', 'running', 'delivered', 'retryable_failed')), |
| attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), |
| current_attempt_id TEXT, |
| current_turn_id TEXT, |
| failure_reason TEXT, |
| created_at INTEGER NOT NULL CHECK (created_at >= 0), |
| updated_at INTEGER NOT NULL CHECK (updated_at >= 0), |
| PRIMARY KEY(graph_id, wake_id) |
| ); |
| |
| CREATE TABLE agent_graph_supervisor_wake_attempts ( |
| graph_id TEXT NOT NULL, |
| wake_id TEXT NOT NULL, |
| attempt_id TEXT NOT NULL UNIQUE, |
| turn_id TEXT NOT NULL, |
| status TEXT NOT NULL |
| CHECK (status IN ('running', 'delivered', 'retryable_failed')), |
| failure_reason TEXT, |
| started_at INTEGER NOT NULL CHECK (started_at >= 0), |
| completed_at INTEGER, |
| PRIMARY KEY(graph_id, wake_id, attempt_id), |
| FOREIGN KEY(graph_id, wake_id) |
| REFERENCES agent_graph_supervisor_wakes(graph_id, wake_id) |
| ON DELETE CASCADE |
| ); |
| |
| CREATE INDEX agent_graph_supervisor_wakes_by_status |
| ON agent_graph_supervisor_wakes(status, updated_at, graph_id, wake_id); |
| `, |
| ], |
| [ |
| 12, |
| ` |
| DROP INDEX agent_graph_supervisor_wakes_by_status; |
| |
| CREATE TABLE agent_graph_supervisor_wakes_v12 ( |
| graph_id TEXT NOT NULL, |
| wake_id TEXT NOT NULL, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| snapshot_version TEXT NOT NULL, |
| root_session_id TEXT NOT NULL, |
| status TEXT NOT NULL |
| CHECK ( |
| status IN ( |
| 'pending', |
| 'running', |
| 'waiting_permission', |
| 'delivered', |
| 'retryable_failed' |
| ) |
| ), |
| attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), |
| current_attempt_id TEXT, |
| current_turn_id TEXT, |
| failure_reason TEXT, |
| created_at INTEGER NOT NULL CHECK (created_at >= 0), |
| updated_at INTEGER NOT NULL CHECK (updated_at >= 0), |
| PRIMARY KEY(graph_id, wake_id) |
| ); |
| |
| CREATE TABLE agent_graph_supervisor_wake_attempts_v12 ( |
| graph_id TEXT NOT NULL, |
| wake_id TEXT NOT NULL, |
| attempt_id TEXT NOT NULL UNIQUE, |
| turn_id TEXT NOT NULL, |
| status TEXT NOT NULL |
| CHECK ( |
| status IN ( |
| 'running', |
| 'waiting_permission', |
| 'delivered', |
| 'retryable_failed' |
| ) |
| ), |
| failure_reason TEXT, |
| started_at INTEGER NOT NULL CHECK (started_at >= 0), |
| completed_at INTEGER, |
| PRIMARY KEY(graph_id, wake_id, attempt_id), |
| FOREIGN KEY(graph_id, wake_id) |
| REFERENCES agent_graph_supervisor_wakes_v12(graph_id, wake_id) |
| ON DELETE CASCADE |
| ); |
| |
| INSERT INTO agent_graph_supervisor_wakes_v12 |
| SELECT * FROM agent_graph_supervisor_wakes; |
| |
| INSERT INTO agent_graph_supervisor_wake_attempts_v12 |
| SELECT * FROM agent_graph_supervisor_wake_attempts; |
| |
| DROP TABLE agent_graph_supervisor_wake_attempts; |
| DROP TABLE agent_graph_supervisor_wakes; |
| |
| ALTER TABLE agent_graph_supervisor_wakes_v12 |
| RENAME TO agent_graph_supervisor_wakes; |
| ALTER TABLE agent_graph_supervisor_wake_attempts_v12 |
| RENAME TO agent_graph_supervisor_wake_attempts; |
| |
| CREATE INDEX agent_graph_supervisor_wakes_by_status |
| ON agent_graph_supervisor_wakes(status, updated_at, graph_id, wake_id); |
| `, |
| ], |
| [ |
| 13, |
| ` |
| CREATE TABLE sandbox_boundary_log ( |
| session_id TEXT NOT NULL, |
| entry_id TEXT NOT NULL, |
| entry_kind TEXT NOT NULL |
| CHECK (entry_kind IN ('genesis', 'expansion_request', 'user_change')), |
| request_id TEXT, |
| status TEXT NOT NULL |
| CHECK (status IN ('applied', 'pending', 'approved', 'denied', 'conflict')), |
| base_revision INTEGER CHECK (base_revision >= 0), |
| applied_revision INTEGER CHECK (applied_revision >= 0), |
| boundary_json TEXT, |
| expansion_json TEXT, |
| justification TEXT, |
| outcome_reason TEXT, |
| created_at INTEGER NOT NULL CHECK (created_at >= 0), |
| settled_at INTEGER CHECK (settled_at >= 0), |
| PRIMARY KEY(session_id, entry_id), |
| UNIQUE(session_id, request_id), |
| FOREIGN KEY(session_id) REFERENCES session_metadata(session_id) ON DELETE CASCADE |
| ); |
| |
| CREATE UNIQUE INDEX sandbox_boundary_log_applied_revision |
| ON sandbox_boundary_log(session_id, applied_revision) |
| WHERE applied_revision IS NOT NULL; |
| |
| CREATE INDEX sandbox_boundary_log_pending_requests |
| ON sandbox_boundary_log(session_id, status, created_at, entry_id); |
| `, |
| ], |
| [ |
| 14, |
| ` |
| ALTER TABLE sandbox_boundary_log ADD COLUMN turn_id TEXT; |
| ALTER TABLE sandbox_boundary_log ADD COLUMN run_id TEXT; |
| |
| CREATE INDEX sandbox_boundary_log_settled_closures |
| ON sandbox_boundary_log(session_id, outcome_reason, created_at, entry_id) |
| WHERE outcome_reason IS NOT NULL; |
| `, |
| ], |
| [ |
| 15, |
| ` |
| CREATE TABLE session_create_claims ( |
| session_id TEXT PRIMARY KEY, |
| request_fingerprint TEXT NOT NULL, |
| claimed_at INTEGER NOT NULL CHECK (claimed_at >= 0) |
| ); |
| `, |
| ], |
| [ |
| 16, |
| ` |
| CREATE TABLE IF NOT EXISTS session_catalog_state ( |
| scope TEXT PRIMARY KEY CHECK (scope = 'catalog'), |
| epoch TEXT NOT NULL CHECK (length(epoch) = 32), |
| generation INTEGER NOT NULL CHECK (generation >= 0), |
| pending_writes INTEGER NOT NULL CHECK (pending_writes >= 0) |
| ); |
| |
| INSERT OR IGNORE INTO session_catalog_state(scope, epoch, generation, pending_writes) |
| SELECT |
| 'catalog', |
| lower(hex(randomblob(16))), |
| 0, |
| CASE WHEN EXISTS (SELECT 1 FROM session_metadata) THEN 1 ELSE 0 END; |
| |
| CREATE TABLE IF NOT EXISTS session_catalog_projection ( |
| session_id TEXT PRIMARY KEY, |
| activity_at INTEGER NOT NULL CHECK (activity_at >= 0), |
| last_message_at INTEGER, |
| last_message_preview TEXT |
| CHECK (last_message_preview IS NULL OR length(last_message_preview) <= 96), |
| is_archived INTEGER NOT NULL CHECK (is_archived IN (0, 1)), |
| is_flagged INTEGER NOT NULL CHECK (is_flagged IN (0, 1)), |
| subagent_parent_session_id TEXT, |
| FOREIGN KEY(session_id) REFERENCES session_metadata(session_id) ON DELETE CASCADE |
| ); |
| |
| INSERT OR IGNORE INTO session_catalog_projection( |
| session_id, |
| activity_at, |
| last_message_at, |
| last_message_preview, |
| is_archived, |
| is_flagged, |
| subagent_parent_session_id |
| ) |
| SELECT |
| session_id, |
| COALESCE(last_message_at, last_used_at, created_at), |
| last_message_at, |
| NULL, |
| is_archived, |
| is_flagged, |
| subagent_parent_session_id |
| FROM session_metadata; |
| |
| CREATE INDEX IF NOT EXISTS session_catalog_by_activity |
| ON session_catalog_projection(activity_at DESC, session_id ASC); |
| |
| CREATE INDEX IF NOT EXISTS session_catalog_by_archived_activity |
| ON session_catalog_projection(is_archived, activity_at DESC, session_id ASC); |
| |
| CREATE INDEX IF NOT EXISTS session_catalog_by_flagged_activity |
| ON session_catalog_projection(is_flagged, activity_at DESC, session_id ASC); |
| |
| CREATE INDEX IF NOT EXISTS session_catalog_by_archived_flagged_activity |
| ON session_catalog_projection( |
| is_archived, |
| is_flagged, |
| activity_at DESC, |
| session_id ASC |
| ); |
| |
| CREATE INDEX IF NOT EXISTS session_catalog_by_subagent_activity |
| ON session_catalog_projection( |
| subagent_parent_session_id, |
| activity_at DESC, |
| session_id ASC |
| ); |
| |
| CREATE TABLE IF NOT EXISTS session_catalog_label_projection ( |
| session_id TEXT NOT NULL, |
| label TEXT NOT NULL, |
| activity_at INTEGER NOT NULL CHECK (activity_at >= 0), |
| PRIMARY KEY(session_id, label), |
| FOREIGN KEY(session_id) REFERENCES session_metadata(session_id) ON DELETE CASCADE |
| ); |
| |
| INSERT OR IGNORE INTO session_catalog_label_projection(session_id, label, activity_at) |
| SELECT labels.session_id, labels.label, projection.activity_at |
| FROM session_metadata_labels labels |
| JOIN session_catalog_projection projection |
| ON projection.session_id = labels.session_id; |
| |
| CREATE INDEX IF NOT EXISTS session_catalog_labels_by_label_activity |
| ON session_catalog_label_projection(label, activity_at DESC, session_id ASC); |
| |
| CREATE TRIGGER IF NOT EXISTS session_catalog_after_insert |
| AFTER INSERT ON session_metadata |
| BEGIN |
| INSERT INTO session_catalog_projection( |
| session_id, |
| activity_at, |
| last_message_at, |
| last_message_preview, |
| is_archived, |
| is_flagged, |
| subagent_parent_session_id |
| ) VALUES ( |
| NEW.session_id, |
| COALESCE(NEW.last_message_at, NEW.last_used_at, NEW.created_at), |
| NEW.last_message_at, |
| NULL, |
| NEW.is_archived, |
| NEW.is_flagged, |
| NEW.subagent_parent_session_id |
| ); |
| |
| UPDATE session_catalog_state |
| SET generation = generation + 1 |
| WHERE scope = 'catalog'; |
| END; |
| |
| CREATE TRIGGER IF NOT EXISTS session_catalog_after_update |
| AFTER UPDATE ON session_metadata |
| BEGIN |
| UPDATE session_catalog_projection |
| SET |
| activity_at = COALESCE(NEW.last_message_at, NEW.last_used_at, NEW.created_at), |
| last_message_at = NEW.last_message_at, |
| is_archived = NEW.is_archived, |
| is_flagged = NEW.is_flagged, |
| subagent_parent_session_id = NEW.subagent_parent_session_id |
| WHERE session_id = NEW.session_id; |
| |
| UPDATE session_catalog_label_projection |
| SET activity_at = COALESCE(NEW.last_message_at, NEW.last_used_at, NEW.created_at) |
| WHERE session_id = NEW.session_id; |
| |
| UPDATE session_catalog_state |
| SET generation = generation + 1 |
| WHERE scope = 'catalog'; |
| END; |
| |
| CREATE TRIGGER IF NOT EXISTS session_catalog_after_delete |
| AFTER DELETE ON session_metadata |
| BEGIN |
| UPDATE session_catalog_state |
| SET generation = generation + 1 |
| WHERE scope = 'catalog'; |
| END; |
| |
| CREATE TRIGGER IF NOT EXISTS session_catalog_label_after_insert |
| AFTER INSERT ON session_metadata_labels |
| BEGIN |
| INSERT OR IGNORE INTO session_catalog_label_projection(session_id, label, activity_at) |
| SELECT NEW.session_id, NEW.label, projection.activity_at |
| FROM session_catalog_projection projection |
| WHERE projection.session_id = NEW.session_id; |
| |
| UPDATE session_catalog_state |
| SET generation = generation + 1 |
| WHERE scope = 'catalog'; |
| END; |
| |
| CREATE TRIGGER IF NOT EXISTS session_catalog_label_after_delete |
| AFTER DELETE ON session_metadata_labels |
| BEGIN |
| DELETE FROM session_catalog_label_projection |
| WHERE |
| session_id = OLD.session_id |
| AND label = OLD.label |
| AND NOT EXISTS ( |
| SELECT 1 |
| FROM session_metadata_labels labels |
| WHERE labels.session_id = OLD.session_id |
| AND labels.label = OLD.label |
| ); |
| |
| UPDATE session_catalog_state |
| SET generation = generation + 1 |
| WHERE scope = 'catalog'; |
| END; |
| `, |
| ], |
| [ |
| 17, |
| ` |
| DROP TRIGGER IF EXISTS session_catalog_label_after_insert; |
| DROP TRIGGER IF EXISTS session_catalog_label_after_delete; |
| |
| CREATE TRIGGER session_catalog_label_after_insert |
| AFTER INSERT ON session_metadata_labels |
| BEGIN |
| INSERT OR IGNORE INTO session_catalog_label_projection(session_id, label, activity_at) |
| SELECT NEW.session_id, NEW.label, projection.activity_at |
| FROM session_catalog_projection projection |
| WHERE projection.session_id = NEW.session_id; |
| END; |
| |
| CREATE TRIGGER session_catalog_label_after_delete |
| AFTER DELETE ON session_metadata_labels |
| BEGIN |
| DELETE FROM session_catalog_label_projection |
| WHERE |
| session_id = OLD.session_id |
| AND label = OLD.label |
| AND NOT EXISTS ( |
| SELECT 1 |
| FROM session_metadata_labels labels |
| WHERE labels.session_id = OLD.session_id |
| AND labels.label = OLD.label |
| ); |
| END; |
| `, |
| ], |
| [ |
| 18, |
| ` |
| ALTER TABLE session_metadata_tombstones ADD COLUMN retirement_unit_id TEXT; |
| ALTER TABLE session_metadata_tombstones |
| ADD COLUMN cleanup_pending INTEGER NOT NULL DEFAULT 0 |
| CHECK (cleanup_pending IN (0, 1)); |
| |
| UPDATE session_metadata_tombstones |
| SET retirement_unit_id = session_id, cleanup_pending = 1; |
| |
| CREATE INDEX session_metadata_tombstones_by_retirement_unit |
| ON session_metadata_tombstones(retirement_unit_id, cleanup_pending, session_id); |
| `, |
| ], |
| [ |
| 19, |
| ` |
| DROP INDEX agent_graph_supervisor_wakes_by_status; |
| |
| CREATE TABLE agent_graph_supervisor_wakes_v19 ( |
| graph_id TEXT NOT NULL, |
| wake_id TEXT NOT NULL, |
| schema_version INTEGER NOT NULL CHECK (schema_version = 1), |
| snapshot_version TEXT NOT NULL, |
| root_session_id TEXT NOT NULL, |
| status TEXT NOT NULL |
| CHECK ( |
| status IN ( |
| 'pending', |
| 'running', |
| 'waiting_permission', |
| 'delivered', |
| 'superseded', |
| 'retryable_failed' |
| ) |
| ), |
| attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), |
| current_attempt_id TEXT, |
| current_turn_id TEXT, |
| failure_reason TEXT, |
| created_at INTEGER NOT NULL CHECK (created_at >= 0), |
| updated_at INTEGER NOT NULL CHECK (updated_at >= 0), |
| PRIMARY KEY(graph_id, wake_id) |
| ); |
| |
| CREATE TABLE agent_graph_supervisor_wake_attempts_v19 ( |
| graph_id TEXT NOT NULL, |
| wake_id TEXT NOT NULL, |
| attempt_id TEXT NOT NULL UNIQUE, |
| turn_id TEXT NOT NULL, |
| status TEXT NOT NULL |
| CHECK ( |
| status IN ( |
| 'running', |
| 'waiting_permission', |
| 'delivered', |
| 'superseded', |
| 'retryable_failed' |
| ) |
| ), |
| failure_reason TEXT, |
| started_at INTEGER NOT NULL CHECK (started_at >= 0), |
| completed_at INTEGER, |
| PRIMARY KEY(graph_id, wake_id, attempt_id), |
| FOREIGN KEY(graph_id, wake_id) |
| REFERENCES agent_graph_supervisor_wakes_v19(graph_id, wake_id) |
| ON DELETE CASCADE |
| ); |
| |
| INSERT INTO agent_graph_supervisor_wakes_v19 |
| SELECT * FROM agent_graph_supervisor_wakes; |
| |
| INSERT INTO agent_graph_supervisor_wake_attempts_v19 |
| SELECT * FROM agent_graph_supervisor_wake_attempts; |
| |
| DROP TABLE agent_graph_supervisor_wake_attempts; |
| DROP TABLE agent_graph_supervisor_wakes; |
| |
| ALTER TABLE agent_graph_supervisor_wakes_v19 |
| RENAME TO agent_graph_supervisor_wakes; |
| ALTER TABLE agent_graph_supervisor_wake_attempts_v19 |
| RENAME TO agent_graph_supervisor_wake_attempts; |
| |
| CREATE INDEX agent_graph_supervisor_wakes_by_status |
| ON agent_graph_supervisor_wakes(status, updated_at, graph_id, wake_id); |
| `, |
| ], |
| [ |
| 20, |
| ` |
| CREATE TABLE session_messages ( |
| session_id TEXT NOT NULL, |
| sequence INTEGER NOT NULL CHECK (sequence >= 0), |
| message_id TEXT NOT NULL, |
| message_type TEXT NOT NULL, |
| message_ts INTEGER NOT NULL CHECK (message_ts >= 0), |
| record_json TEXT NOT NULL, |
| PRIMARY KEY(session_id, sequence), |
| FOREIGN KEY(session_id) REFERENCES session_metadata(session_id) ON DELETE CASCADE |
| ); |
| |
| CREATE INDEX session_messages_by_identity |
| ON session_messages(session_id, message_id); |
| |
| CREATE INDEX session_messages_by_time |
| ON session_messages(session_id, message_ts, sequence); |
| `, |
| ], |
| [ |
| 21, |
| ` |
| CREATE TABLE projects ( |
| project_id TEXT PRIMARY KEY, |
| identity TEXT NOT NULL UNIQUE, |
| name TEXT NOT NULL, |
| last_used_at INTEGER NOT NULL, |
| archived_at INTEGER |
| ); |
| |
| CREATE TABLE project_locations ( |
| project_id TEXT NOT NULL, |
| path TEXT NOT NULL, |
| is_worktree INTEGER NOT NULL CHECK (is_worktree IN (0, 1)), |
| last_used_at INTEGER NOT NULL, |
| PRIMARY KEY(project_id, path), |
| FOREIGN KEY(project_id) REFERENCES projects(project_id) ON DELETE CASCADE |
| ); |
| |
| CREATE TABLE project_aliases ( |
| alias TEXT PRIMARY KEY, |
| project_id TEXT NOT NULL, |
| FOREIGN KEY(project_id) REFERENCES projects(project_id) ON DELETE CASCADE |
| ); |
| |
| CREATE INDEX project_aliases_by_project |
| ON project_aliases(project_id, alias); |
| `, |
| ], |
| [ |
| 22, |
| ` |
| UPDATE session_metadata |
| SET |
| payload_json = json_set(payload_json, '$.connectionLocked', json('true')), |
| metadata_version = metadata_version + 1, |
| committed_at = MAX( |
| committed_at, |
| CAST(strftime('%s', 'now') AS INTEGER) * 1000 |
| ) |
| WHERE |
| json_extract(payload_json, '$.connectionLocked') = 0 |
| AND EXISTS ( |
| SELECT 1 |
| FROM session_messages messages |
| WHERE |
| messages.session_id = session_metadata.session_id |
| AND messages.message_type = 'user' |
| ); |
| `, |
| ], |
| ]); |
| |
| export function configureSqliteSessionMetadataDatabase(db: DatabaseSync): void { |
| db.exec('PRAGMA busy_timeout = 5000'); |
| db.exec('PRAGMA journal_mode = WAL'); |
| db.exec('PRAGMA synchronous = FULL'); |
| db.exec('PRAGMA foreign_keys = ON'); |
| } |
| |
| export function migrateSqliteSessionMetadataDatabase(db: DatabaseSync): void { |
| db.exec(` |
| CREATE TABLE IF NOT EXISTS session_metadata_schema ( |
| scope TEXT PRIMARY KEY, |
| version INTEGER NOT NULL CHECK (version >= 0) |
| ) |
| `); |
| db.exec('BEGIN IMMEDIATE'); |
| try { |
| const current = readSqliteSessionMetadataSchemaVersion(db); |
| if (current > SQLITE_SESSION_METADATA_SCHEMA_VERSION) { |
| throw new Error( |
| `SQLite session metadata schema ${current} is newer than supported version ${SQLITE_SESSION_METADATA_SCHEMA_VERSION}`, |
| ); |
| } |
| for ( |
| let version = current + 1; |
| version <= SQLITE_SESSION_METADATA_SCHEMA_VERSION; |
| version += 1 |
| ) { |
| const sql = MIGRATIONS.get(version); |
| if (!sql) throw new Error(`Missing SQLite session metadata migration ${version}`); |
| db.exec(sql); |
| db.prepare(` |
| INSERT INTO session_metadata_schema(scope, version) |
| VALUES ('session_metadata', ?) |
| ON CONFLICT(scope) DO UPDATE SET version = excluded.version |
| `).run(version); |
| } |
| db.exec('COMMIT'); |
| } catch (error) { |
| rollback(db); |
| throw error; |
| } |
| } |
| |
| export function readSqliteSessionMetadataSchemaVersion(db: DatabaseSync): number { |
| const row = db |
| .prepare(` |
| SELECT version |
| FROM session_metadata_schema |
| WHERE scope = 'session_metadata' |
| `) |
| .get() as { version?: unknown } | undefined; |
| if (!row) return 0; |
| const value = row.version; |
| if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) { |
| throw new Error('Invalid SQLite session metadata schema version'); |
| } |
| return value; |
| } |
| |
| function rollback(db: DatabaseSync): void { |
| try { |
| db.exec('ROLLBACK'); |
| } catch { |
| // Preserve the migration failure that triggered rollback. |
| } |
| } |