diff --git a/packages/opencode/migration/20260511173437_session-metadata/migration.sql b/packages/opencode/migration/20260511173437_session-metadata/migration.sql new file mode 100644 index 000000000000..0ce73631f0d7 --- /dev/null +++ b/packages/opencode/migration/20260511173437_session-metadata/migration.sql @@ -0,0 +1 @@ +ALTER TABLE `session` ADD `metadata` text; \ No newline at end of file diff --git a/packages/opencode/migration/20260511173437_session-metadata/snapshot.json b/packages/opencode/migration/20260511173437_session-metadata/snapshot.json new file mode 100644 index 000000000000..3678ed0ac05d --- /dev/null +++ b/packages/opencode/migration/20260511173437_session-metadata/snapshot.json @@ -0,0 +1,1574 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "85d2086c-3c95-4706-90b0-f7480b73db5c", + "prevIds": [ + "fdfcccee-fb3a-481f-b801-b9835fa30d5d" + ], + "ddl": [ + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "account", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "data_migration", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session_message", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "name": "event_sequence", + "entityType": "tables" + }, + { + "name": "event", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_org_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "''", + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_used", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "data_migration" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_completed", + "entityType": "columns", + "table": "data_migration" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url_override", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "session_message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "path", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "metadata", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "agent", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "model", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "owner_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "event" + }, + { + "columns": [ + "active_account_id" + ], + "tableTo": "account", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": [ + "message_id" + ], + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_message_session_id_session_id_fk", + "entityType": "fks", + "table": "session_message" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": [ + "aggregate_id" + ], + "tableTo": "event_sequence", + "columnsTo": [ + "aggregate_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_event_aggregate_id_event_sequence_aggregate_id_fk", + "entityType": "fks", + "table": "event" + }, + { + "columns": [ + "email", + "url" + ], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": [ + "session_id", + "position" + ], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + "name" + ], + "nameExplicit": false, + "name": "data_migration_pk", + "table": "data_migration", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": [ + "project_id" + ], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_message_pk", + "table": "session_message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "session_id" + ], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + "aggregate_id" + ], + "nameExplicit": false, + "name": "event_sequence_pk", + "table": "event_sequence", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "event_pk", + "table": "event", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_time_created_id_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_id_id_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_message_session_idx", + "entityType": "indexes", + "table": "session_message" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "type", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_message_session_type_idx", + "entityType": "indexes", + "table": "session_message" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_message_time_created_idx", + "entityType": "indexes", + "table": "session_message" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts index 2053aba3b4bd..46385802547f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/session.ts @@ -45,6 +45,7 @@ export const MessagesQuery = Schema.Struct({ export const StatusMap = Schema.Record(Schema.String, SessionStatus.Info) export const UpdatePayload = Schema.Struct({ title: Schema.optional(Schema.String), + metadata: Schema.optional(Schema.NullOr(Session.Info.fields.metadata)), permission: Schema.optional(Permission.Ruleset), time: Schema.optional( Schema.Struct({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 99645f3da3c3..35330c409cc7 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -173,6 +173,9 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", if (ctx.payload.title !== undefined) { yield* session.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title }) } + if ("metadata" in ctx.payload) { + yield* session.setMetadata({ sessionID: ctx.params.sessionID, metadata: ctx.payload.metadata ?? {} }) + } if (ctx.payload.permission !== undefined) { yield* session.setPermission({ sessionID: ctx.params.sessionID, @@ -190,7 +193,11 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", payload?: typeof ForkPayload.Type }) { return yield* SessionError.mapStorageNotFound( - session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload?.messageID }), + session.fork({ + sessionID: ctx.params.sessionID, + messageID: ctx.payload?.messageID, + copyMetadata: ctx.payload?.copyMetadata, + }), ) }) diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 93acd4546db2..12ed04f76e26 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -54,6 +54,7 @@ export function toPartialRow(info: DeepPartial) { summary_deletions: grab(info, "summary", (v) => grab(v, "deletions")), summary_files: grab(info, "summary", (v) => grab(v, "files")), summary_diffs: grab(info, "summary", (v) => grab(v, "diffs")), + metadata: grab(info, "metadata"), revert: grab(info, "revert"), permission: grab(info, "permission"), time_created: grab(info, "time", (v) => grab(v, "created")), diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 421fa68694d2..490604bc1ab1 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -33,6 +33,7 @@ export const SessionTable = sqliteTable( summary_deletions: integer(), summary_files: integer(), summary_diffs: text({ mode: "json" }).$type(), + metadata: text({ mode: "json" }).$type>(), revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string }>(), permission: text({ mode: "json" }).$type(), agent: text(), diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 92b4329e6f1d..969b791dfd6b 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -88,6 +88,7 @@ export function fromRow(row: SessionRow): Info { version: row.version, summary, share, + metadata: row.metadata ?? {}, revert, permission: row.permission ?? undefined, time: { @@ -117,6 +118,7 @@ export function toRow(info: Info) { summary_deletions: info.summary?.deletions, summary_files: info.summary?.files, summary_diffs: info.summary?.diffs, + metadata: info.metadata, revert: info.revert ?? null, permission: info.permission, time_created: info.time.created, @@ -175,6 +177,8 @@ const Model = Schema.Struct({ variant: optionalOmitUndefined(Schema.String), }) +const Metadata = Schema.Record(Schema.String, Schema.Any) + export const Info = Schema.Struct({ id: SessionID, slug: Schema.String, @@ -189,6 +193,7 @@ export const Info = Schema.Struct({ agent: optionalOmitUndefined(Schema.String), model: optionalOmitUndefined(Model), version: Schema.String, + metadata: Metadata, time: Time, permission: optionalOmitUndefined(Permission.Ruleset), revert: optionalOmitUndefined(Revert), @@ -214,6 +219,7 @@ export const CreateInput = Schema.optional( title: Schema.optional(Schema.String), agent: Schema.optional(Schema.String), model: Schema.optional(Model), + metadata: Schema.optional(Metadata), permission: Schema.optional(Permission.Ruleset), workspaceID: Schema.optional(WorkspaceID), }), @@ -223,6 +229,7 @@ export type CreateInput = Types.DeepMutable + metadata?: Record permission?: Permission.Ruleset workspaceID?: WorkspaceID }) => Effect.Effect - readonly fork: (input: { sessionID: SessionID; messageID?: MessageID }) => Effect.Effect + readonly fork: (input: { sessionID: SessionID; messageID?: MessageID; copyMetadata?: boolean }) => Effect.Effect readonly touch: (sessionID: SessionID) => Effect.Effect readonly get: (id: SessionID) => Effect.Effect readonly setTitle: (input: { sessionID: SessionID; title: string }) => Effect.Effect readonly setArchived: (input: { sessionID: SessionID; time?: number }) => Effect.Effect + readonly setMetadata: (input: typeof SetMetadataInput.Type) => Effect.Effect readonly setPermission: (input: { sessionID: SessionID; permission: Permission.Ruleset }) => Effect.Effect readonly setRevert: (input: { sessionID: SessionID @@ -487,6 +501,7 @@ export const layer: Layer.Layer permission?: Permission.Ruleset }) { const ctx = yield* InstanceState.context @@ -502,6 +517,7 @@ export const layer: Layer.Layer + metadata?: Record permission?: Permission.Ruleset workspaceID?: WorkspaceID }) { @@ -626,12 +643,17 @@ export const layer: Layer.Layer() @@ -687,6 +710,10 @@ export const layer: Layer.Layer { }) describe("session action routes", () => { + test("session routes expose metadata on create, update, get, and fork", async () => { + await using tmp = await tmpdir({ git: true }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const app = Server.Default().app + + const created = await app.request("/session", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title: "meta-session", + metadata: { source: "sdk", trace: { id: "abc" } }, + }), + }) + expect(created.status).toBe(200) + + const session = (await created.json()) as SessionNs.Info + expect(session.metadata).toEqual({ source: "sdk", trace: { id: "abc" } }) + + const updated = await app.request(`/session/${session.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ metadata: { source: "sdk", trace: { id: "def" }, tags: ["one"] } }), + }) + expect(updated.status).toBe(200) + + const next = (await updated.json()) as SessionNs.Info + expect(next.metadata).toEqual({ source: "sdk", trace: { id: "def" }, tags: ["one"] }) + + const fetched = await app.request(`/session/${session.id}`) + expect(fetched.status).toBe(200) + expect(((await fetched.json()) as SessionNs.Info).metadata).toEqual(next.metadata) + + const forked = await app.request(`/session/${session.id}/fork`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({}), + }) + expect(forked.status).toBe(200) + + const fork = (await forked.json()) as SessionNs.Info + expect(fork.metadata).toEqual(next.metadata) + + const blanked = await app.request(`/session/${session.id}/fork`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ copyMetadata: false }), + }) + expect(blanked.status).toBe(200) + const empty = (await blanked.json()) as SessionNs.Info + expect(empty.metadata).toEqual({}) + + const cleared = await app.request(`/session/${session.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ metadata: null }), + }) + expect(cleared.status).toBe(200) + expect(((await cleared.json()) as SessionNs.Info).metadata).toEqual({}) + + await svc.remove(fork.id) + await svc.remove(empty.id) + await svc.remove(session.id) + }, + }) + }) + test("abort route returns success", async () => { await using tmp = await tmpdir({ git: true }) await WithInstance.provide({ diff --git a/packages/opencode/test/server/session-list.test.ts b/packages/opencode/test/server/session-list.test.ts index 20478dde844e..2138e270aeae 100644 --- a/packages/opencode/test/server/session-list.test.ts +++ b/packages/opencode/test/server/session-list.test.ts @@ -1,6 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test" import { Effect } from "effect" -import { Instance } from "../../src/project/instance" import { WithInstance } from "../../src/project/with-instance" import { Session as SessionNs } from "@/session/session" import * as Log from "@opencode-ai/core/util/log" @@ -236,4 +235,19 @@ describe("session.list", () => { }, }) }) + + test("includes metadata in listed sessions", async () => { + await using tmp = await tmpdir({ git: true }) + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const meta = { source: "sdk", trace: { id: "abc" } } + const session = await svc.create({ title: "meta-session", metadata: meta }) + + const listed = (await svc.list({ search: "meta-session" })).find((item) => item.id === session.id) + + expect(listed?.metadata).toEqual(meta) + }, + }) + }) }) diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts index 3a367fa6c687..468667246a9b 100644 --- a/packages/opencode/test/session/schema-decoding.test.ts +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -41,6 +41,7 @@ describe("Session.Info", () => { directory: "/tmp/proj", title: "First session", version: "0.1.0", + metadata: {}, time: { created: 1, updated: 2 }, } expect(decode(input)).toEqual(input) @@ -64,6 +65,7 @@ describe("Session.Info", () => { share: { url: "https://share.example.com/s/1" }, title: "Full session", version: "1.0.0", + metadata: { source: "test" }, time: { created: 100, updated: 200, compacting: 150, archived: 300 }, permission: [{ action: "allow" as const, pattern: "*", permission: "read" }], revert: { @@ -84,6 +86,7 @@ describe("Session.Info", () => { directory: "/tmp/proj", title: "Legacy diff", version: "0.1.0", + metadata: {}, summary: { additions: 1, deletions: 0, @@ -128,6 +131,7 @@ describe("Session.GlobalInfo", () => { directory: "/tmp/proj", title: "global", version: "0", + metadata: {}, time: { created: 0, updated: 0 }, project: null, } @@ -142,6 +146,7 @@ describe("Session.GlobalInfo", () => { directory: "/tmp/proj", title: "global", version: "0", + metadata: {}, time: { created: 0, updated: 0 }, project: { id: projectID, worktree: "/tmp/wt", name: "alpha" }, } @@ -157,6 +162,7 @@ describe("Session input schemas", () => { const populated = { parentID: sessionID, title: "child", + metadata: { source: "test" }, permission: [{ action: "ask" as const, pattern: "*", permission: "bash" }], workspaceID, } @@ -165,7 +171,7 @@ describe("Session input schemas", () => { test("ForkInput round-trips", () => { const decode = decodeUnknown(Session.ForkInput) - const input = { sessionID, messageID } + const input = { sessionID, messageID, copyMetadata: false } expect(decode(input)).toEqual(input) // messageID is optional const bare = { sessionID } diff --git a/packages/opencode/test/session/session-schema.test.ts b/packages/opencode/test/session/session-schema.test.ts index 38531d15b49a..5178bfbcaaf5 100644 --- a/packages/opencode/test/session/session-schema.test.ts +++ b/packages/opencode/test/session/session-schema.test.ts @@ -15,6 +15,7 @@ const info = { share: undefined, title: "Test session", version: "1.0.0", + metadata: {}, time: { created: 1, updated: 2, diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index bb69e459bc05..391e12c8340f 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -183,4 +183,63 @@ describe("Session", () => { expect(missing).toBe(true) }) + + test("persists metadata and copies it on fork by default", async () => { + await using tmp = await tmpdir({ git: true }) + + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const meta = { source: "sdk", trace: { id: "abc" } } + const session = await create({ title: "with-meta", metadata: meta }) + const saved = await get(session.id) + const fork = await AppRuntime.runPromise( + SessionNs.Service.use((svc) => svc.fork({ sessionID: session.id, copyMetadata: true })), + ) + + expect(saved.metadata).toEqual(meta) + expect(fork.metadata).toEqual(meta) + expect(fork.metadata).not.toBe(meta) + + await remove(fork.id) + await remove(session.id) + }, + }) + }) + + test("can fork without copying metadata", async () => { + await using tmp = await tmpdir({ git: true }) + + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const session = await create({ metadata: { source: "sdk" } }) + const fork = await AppRuntime.runPromise( + SessionNs.Service.use((svc) => svc.fork({ sessionID: session.id, copyMetadata: false })), + ) + + expect(fork.metadata).toEqual({}) + + await remove(fork.id) + await remove(session.id) + }, + }) + }) + + test("defaults metadata to an empty object", async () => { + await using tmp = await tmpdir({ git: true }) + + await WithInstance.provide({ + directory: tmp.path, + fn: async () => { + const session = await create({ title: "empty-meta" }) + const saved = await get(session.id) + + expect(session.metadata).toEqual({}) + expect(saved.metadata).toEqual({}) + + await remove(session.id) + }, + }) + }) }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index bf3201a5c081..7820cea3a673 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -3006,6 +3006,9 @@ export class Session2 extends HeyApiClient { providerID: string variant?: string } + metadata?: { + [key: string]: unknown + } permission?: PermissionRuleset workspaceID?: string }, @@ -3022,6 +3025,7 @@ export class Session2 extends HeyApiClient { { in: "body", key: "title" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, + { in: "body", key: "metadata" }, { in: "body", key: "permission" }, { in: "body", key: "workspaceID" }, ], @@ -3145,6 +3149,9 @@ export class Session2 extends HeyApiClient { directory?: string workspace?: string title?: string + metadata?: { + [key: string]: unknown + } permission?: PermissionRuleset time?: { archived?: number @@ -3161,6 +3168,7 @@ export class Session2 extends HeyApiClient { { in: "query", key: "directory" }, { in: "query", key: "workspace" }, { in: "body", key: "title" }, + { in: "body", key: "metadata" }, { in: "body", key: "permission" }, { in: "body", key: "time" }, ], @@ -3456,6 +3464,7 @@ export class Session2 extends HeyApiClient { directory?: string workspace?: string messageID?: string + copyMetadata?: boolean }, options?: Options, ) { @@ -3468,6 +3477,7 @@ export class Session2 extends HeyApiClient { { in: "query", key: "directory" }, { in: "query", key: "workspace" }, { in: "body", key: "messageID" }, + { in: "body", key: "copyMetadata" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index c7a479f5ac90..126469400469 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -752,6 +752,9 @@ export type Session = { variant?: string } version: string + metadata: { + [key: string]: unknown + } time: { created: number updated: number @@ -1441,6 +1444,9 @@ export type GlobalSession = { variant?: string } version: string + metadata: { + [key: string]: unknown + } time: { created: number updated: number @@ -1904,6 +1910,9 @@ export type SyncEventSessionUpdated = { variant?: string } | null version?: string | null + metadata?: { + [key: string]: unknown + } | null time?: { created?: number | null updated?: number | null @@ -5168,6 +5177,9 @@ export type SessionCreateData = { providerID: string variant?: string } + metadata?: { + [key: string]: unknown + } permission?: PermissionRuleset workspaceID?: string } @@ -5298,6 +5310,9 @@ export type SessionGetResponse = SessionGetResponses[keyof SessionGetResponses] export type SessionUpdateData = { body?: { title?: string + metadata?: { + [key: string]: unknown + } permission?: PermissionRuleset time?: { archived?: number @@ -5592,6 +5607,7 @@ export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessag export type SessionForkData = { body?: { messageID?: string + copyMetadata?: boolean } path: { sessionID: string