diff --git a/AGENTS.md b/AGENTS.md index 733d1c1ee6b..d624db7724a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,6 +75,10 @@ 分享管理页,或受邀者使用分享供应商的任何路径前,必须先读 `docs/product-rules/provider-sharing.md`;它为远程 Agent 通道新增跨账号授权,并允许 中国大陆版与 Global 互享,与共享任务是两套独立授权。 +- 新增或修改供应商组(把多台电脑上的同一个供应商合成一组)、组策略、组内电脑的添加与状态、 + 经供应商组的任务分配或转发,或「远程与分享」页的组与用量前,必须先读 + `docs/product-rules/provider-groups.md`:Agent 始终在被选中的组内电脑上运行,组所在电脑 + 只选电脑与转发,并负责按使用方隔离。 - 新增或修改任何界面、组件、布局、样式、动效或 UI 文案前,必须先读权威设计规范 `docs/design-rules/DESIGN.md`;设计文档索引见 `docs/design-rules/cindy-design-system.md`。 diff --git a/apps/desktop/drizzle/0124_funny_glorian.sql b/apps/desktop/drizzle/0124_funny_glorian.sql new file mode 100644 index 00000000000..e0ac49d1ecf --- /dev/null +++ b/apps/desktop/drizzle/0124_funny_glorian.sql @@ -0,0 +1 @@ +SELECT 1; diff --git a/apps/desktop/drizzle/0125_magical_night_thrasher.sql b/apps/desktop/drizzle/0125_magical_night_thrasher.sql new file mode 100644 index 00000000000..ff619bfcb66 --- /dev/null +++ b/apps/desktop/drizzle/0125_magical_night_thrasher.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS `orca_remote_opens` ( + `remote_session_id` text PRIMARY KEY NOT NULL, + `device_id` text NOT NULL, + `created_at` integer NOT NULL +); diff --git a/apps/desktop/drizzle/meta/0124_snapshot.json b/apps/desktop/drizzle/meta/0124_snapshot.json new file mode 100644 index 00000000000..828fbdaaa4d --- /dev/null +++ b/apps/desktop/drizzle/meta/0124_snapshot.json @@ -0,0 +1,6885 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "86f88929-384d-4831-bf8e-a29b3d83ec85", + "prevId": "d06b22f6-84f0-4f67-b5ab-0f13f2e21a2a", + "tables": { + "account_usage_snapshots": { + "name": "account_usage_snapshots", + "columns": { + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "snapshot": { + "name": "snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_input_queue_snapshots": { + "name": "agent_input_queue_snapshots", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_input_queue_snapshots_session_id_sessions_id_fk": { + "name": "agent_input_queue_snapshots_session_id_sessions_id_fk", + "tableFrom": "agent_input_queue_snapshots", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "auto_review_projections": { + "name": "auto_review_projections", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "lead_id": { + "name": "lead_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "projected_revision": { + "name": "projected_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": -1 + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "auto_review_projections_lead_idx": { + "name": "auto_review_projections_lead_idx", + "columns": [ + "lead_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "auto_review_projections_session_id_sessions_id_fk": { + "name": "auto_review_projections_session_id_sessions_id_fk", + "tableFrom": "auto_review_projections", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_review_projections_lead_id_sessions_id_fk": { + "name": "auto_review_projections_lead_id_sessions_id_fk", + "tableFrom": "auto_review_projections", + "tableTo": "sessions", + "columnsFrom": [ + "lead_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "auto_review_projections_session_id_lead_id_pk": { + "columns": [ + "session_id", + "lead_id" + ], + "name": "auto_review_projections_session_id_lead_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_delegations": { + "name": "bot_delegations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "requesting_bot_id": { + "name": "requesting_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_bot_id": { + "name": "target_bot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "child_session_id": { + "name": "child_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "objective": { + "name": "objective", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_refs_json": { + "name": "context_refs_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "artifact_refs_json": { + "name": "artifact_refs_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_snapshot_json": { + "name": "permission_snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "lineage_json": { + "name": "lineage_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "target_profile_version": { + "name": "target_profile_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "budget_tokens": { + "name": "budget_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tokens_used": { + "name": "tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'queued'" + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_artifacts_json": { + "name": "output_artifacts_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "pending_interaction_json": { + "name": "pending_interaction_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "run_sequence": { + "name": "run_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_delivered_at": { + "name": "completion_delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_delegations_requester_status": { + "name": "idx_bot_delegations_requester_status", + "columns": [ + "requesting_bot_id", + "status" + ], + "isUnique": false + }, + "idx_bot_delegations_target_status": { + "name": "idx_bot_delegations_target_status", + "columns": [ + "target_bot_id", + "status" + ], + "isUnique": false + }, + "idx_bot_delegations_parent_session": { + "name": "idx_bot_delegations_parent_session", + "columns": [ + "parent_session_id" + ], + "isUnique": false + }, + "uniq_bot_delegations_child_session": { + "name": "uniq_bot_delegations_child_session", + "columns": [ + "child_session_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "bot_delegations_requesting_bot_id_bot_profiles_id_fk": { + "name": "bot_delegations_requesting_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "bot_profiles", + "columnsFrom": [ + "requesting_bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_delegations_target_bot_id_bot_profiles_id_fk": { + "name": "bot_delegations_target_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "bot_profiles", + "columnsFrom": [ + "target_bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_delegations_parent_session_id_sessions_id_fk": { + "name": "bot_delegations_parent_session_id_sessions_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "bot_delegations_child_session_id_sessions_id_fk": { + "name": "bot_delegations_child_session_id_sessions_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "sessions", + "columnsFrom": [ + "child_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_direct_message_threads": { + "name": "bot_direct_message_threads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_a_id": { + "name": "bot_a_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_b_id": { + "name": "bot_b_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "close_reason": { + "name": "close_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "max_messages": { + "name": "max_messages", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "blocked_until": { + "name": "blocked_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "closed_at": { + "name": "closed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_dm_threads_active_pair": { + "name": "uniq_bot_dm_threads_active_pair", + "columns": [ + "bot_a_id", + "bot_b_id" + ], + "isUnique": true, + "where": "\"bot_direct_message_threads\".\"status\" = 'active'" + }, + "idx_bot_dm_threads_pair_updated": { + "name": "idx_bot_dm_threads_pair_updated", + "columns": [ + "bot_a_id", + "bot_b_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_direct_messages": { + "name": "bot_direct_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sender_bot_id": { + "name": "sender_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_bot_id": { + "name": "recipient_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sender_session_id": { + "name": "sender_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_session_id": { + "name": "recipient_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delivery_status": { + "name": "delivery_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_name": { + "name": "recipient_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bridge_session_id": { + "name": "bridge_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_direct_messages_thread_sequence": { + "name": "uniq_bot_direct_messages_thread_sequence", + "columns": [ + "thread_id", + "sequence" + ], + "isUnique": true + }, + "idx_bot_direct_messages_thread_created": { + "name": "idx_bot_direct_messages_thread_created", + "columns": [ + "thread_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_direct_messages_thread_id_bot_direct_message_threads_id_fk": { + "name": "bot_direct_messages_thread_id_bot_direct_message_threads_id_fk", + "tableFrom": "bot_direct_messages", + "tableTo": "bot_direct_message_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_direct_messages_sender_session_id_sessions_id_fk": { + "name": "bot_direct_messages_sender_session_id_sessions_id_fk", + "tableFrom": "bot_direct_messages", + "tableTo": "sessions", + "columnsFrom": [ + "sender_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "bot_direct_messages_recipient_session_id_sessions_id_fk": { + "name": "bot_direct_messages_recipient_session_id_sessions_id_fk", + "tableFrom": "bot_direct_messages", + "tableTo": "sessions", + "columnsFrom": [ + "recipient_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_members": { + "name": "bot_group_members", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen_sequence": { + "name": "last_seen_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_group_members_bot": { + "name": "idx_bot_group_members_bot", + "columns": [ + "bot_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_group_members_group_id_bot_groups_id_fk": { + "name": "bot_group_members_group_id_bot_groups_id_fk", + "tableFrom": "bot_group_members", + "tableTo": "bot_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_group_members_bot_id_bot_profiles_id_fk": { + "name": "bot_group_members_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_group_members", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bot_group_members_group_id_bot_id_pk": { + "columns": [ + "group_id", + "bot_id" + ], + "name": "bot_group_members_group_id_bot_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_messages": { + "name": "bot_group_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'message'" + }, + "author_kind": { + "name": "author_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_bot_id": { + "name": "author_bot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "mentions_json": { + "name": "mentions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"all\":false,\"botIds\":[]}'" + }, + "notice_code": { + "name": "notice_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "files_json": { + "name": "files_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "attachments_json": { + "name": "attachments_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_group_messages_group_sequence": { + "name": "uniq_bot_group_messages_group_sequence", + "columns": [ + "group_id", + "sequence" + ], + "isUnique": true + }, + "uniq_bot_group_messages_group_client": { + "name": "uniq_bot_group_messages_group_client", + "columns": [ + "group_id", + "client_id" + ], + "isUnique": true, + "where": "\"bot_group_messages\".\"client_id\" IS NOT NULL" + } + }, + "foreignKeys": { + "bot_group_messages_group_id_bot_groups_id_fk": { + "name": "bot_group_messages_group_id_bot_groups_id_fk", + "tableFrom": "bot_group_messages", + "tableTo": "bot_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_plan_steps": { + "name": "bot_group_plan_steps", + "columns": { + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_name": { + "name": "bot_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "result_message_id": { + "name": "result_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "bot_group_plan_steps_plan_id_bot_group_plans_id_fk": { + "name": "bot_group_plan_steps_plan_id_bot_group_plans_id_fk", + "tableFrom": "bot_group_plan_steps", + "tableTo": "bot_group_plans", + "columnsFrom": [ + "plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bot_group_plan_steps_plan_id_position_pk": { + "columns": [ + "plan_id", + "position" + ], + "name": "bot_group_plan_steps_plan_id_position_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_plans": { + "name": "bot_group_plans", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_text": { + "name": "request_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments_json": { + "name": "attachments_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "organizer_bot_id": { + "name": "organizer_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizer_name": { + "name": "organizer_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "current_step": { + "name": "current_step", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "work_dir": { + "name": "work_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_group_plans_group_created": { + "name": "idx_bot_group_plans_group_created", + "columns": [ + "group_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_group_plans_group_id_bot_groups_id_fk": { + "name": "bot_group_plans_group_id_bot_groups_id_fk", + "tableFrom": "bot_group_plans", + "tableTo": "bot_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_groups": { + "name": "bot_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reply_mode": { + "name": "reply_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "speaking_mode": { + "name": "speaking_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "organizer_bot_id": { + "name": "organizer_bot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_dir": { + "name": "project_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_groups_updated": { + "name": "idx_bot_groups_updated", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_lifecycle_events": { + "name": "bot_lifecycle_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_lifecycle_events_bot_created": { + "name": "idx_bot_lifecycle_events_bot_created", + "columns": [ + "bot_id", + "created_at" + ], + "isUnique": false + }, + "idx_bot_lifecycle_events_session_created": { + "name": "idx_bot_lifecycle_events_session_created", + "columns": [ + "session_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_lifecycle_events_bot_id_bot_profiles_id_fk": { + "name": "bot_lifecycle_events_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_lifecycle_events", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_lifecycle_events_session_id_sessions_id_fk": { + "name": "bot_lifecycle_events_session_id_sessions_id_fk", + "tableFrom": "bot_lifecycle_events", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_profile_versions": { + "name": "bot_profile_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "identity_source": { + "name": "identity_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "capabilities_json": { + "name": "capabilities_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_profile_versions_bot_version": { + "name": "uniq_bot_profile_versions_bot_version", + "columns": [ + "bot_id", + "version" + ], + "isUnique": true + }, + "idx_bot_profile_versions_bot_created": { + "name": "idx_bot_profile_versions_bot_created", + "columns": [ + "bot_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_profile_versions_bot_id_bot_profiles_id_fk": { + "name": "bot_profile_versions_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_profile_versions", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_profiles": { + "name": "bot_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'🤖'" + }, + "avatar_color": { + "name": "avatar_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'violet'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "hidden_at": { + "name": "hidden_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attention_reason": { + "name": "attention_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attention_at": { + "name": "attention_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "current_version": { + "name": "current_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "canonical_session_id": { + "name": "canonical_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_profiles_status_updated": { + "name": "idx_bot_profiles_status_updated", + "columns": [ + "status", + "updated_at" + ], + "isUnique": false + }, + "idx_bot_profiles_canonical_session": { + "name": "idx_bot_profiles_canonical_session", + "columns": [ + "canonical_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_profiles_canonical_session_id_sessions_id_fk": { + "name": "bot_profiles_canonical_session_id_sessions_id_fk", + "tableFrom": "bot_profiles", + "tableTo": "sessions", + "columnsFrom": [ + "canonical_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_runtime_snapshots": { + "name": "bot_runtime_snapshots", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_version": { + "name": "profile_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_scope_key": { + "name": "memory_scope_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "configured_json": { + "name": "configured_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "resolved_json": { + "name": "resolved_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_at": { + "name": "applied_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "failed_at": { + "name": "failed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "failure_json": { + "name": "failure_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_runtime_snapshots_bot_prepared": { + "name": "idx_bot_runtime_snapshots_bot_prepared", + "columns": [ + "bot_id", + "prepared_at" + ], + "isUnique": false + }, + "idx_bot_runtime_snapshots_session_prepared": { + "name": "idx_bot_runtime_snapshots_session_prepared", + "columns": [ + "session_id", + "prepared_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_runtime_snapshots_bot_id_bot_profiles_id_fk": { + "name": "bot_runtime_snapshots_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_runtime_snapshots", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_runtime_snapshots_session_id_sessions_id_fk": { + "name": "bot_runtime_snapshots_session_id_sessions_id_fk", + "tableFrom": "bot_runtime_snapshots", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_session_links": { + "name": "bot_session_links", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_version": { + "name": "profile_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_session_links_session": { + "name": "uniq_bot_session_links_session", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "uniq_bot_session_links_canonical_per_bot": { + "name": "uniq_bot_session_links_canonical_per_bot", + "columns": [ + "bot_id" + ], + "isUnique": true, + "where": "\"bot_session_links\".\"role\" = 'canonical'" + }, + "idx_bot_session_links_bot_role": { + "name": "idx_bot_session_links_bot_role", + "columns": [ + "bot_id", + "role" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_session_links_bot_id_bot_profiles_id_fk": { + "name": "bot_session_links_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_session_links", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_session_links_session_id_sessions_id_fk": { + "name": "bot_session_links_session_id_sessions_id_fk", + "tableFrom": "bot_session_links", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_mcp_servers": { + "name": "custom_mcp_servers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_custom_mcp_servers_sort_order": { + "name": "idx_custom_mcp_servers_sort_order", + "columns": [ + "sort_order" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_providers": { + "name": "custom_providers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "runtimes": { + "name": "runtimes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "auth": { + "name": "auth", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_custom_providers_sort_order": { + "name": "idx_custom_providers_sort_order", + "columns": [ + "sort_order" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "daily_model_usage": { + "name": "daily_model_usage", + "columns": { + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_amount": { + "name": "cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "cost_is_approximate": { + "name": "cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cache_create_tokens": { + "name": "cache_create_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "daily_model_usage_day_agent_kind_model_cost_currency_pk": { + "columns": [ + "day", + "agent_kind", + "model", + "cost_currency" + ], + "name": "daily_model_usage_day_agent_kind_model_cost_currency_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "daily_session_usage": { + "name": "daily_session_usage", + "columns": { + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tokens": { + "name": "tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "daily_session_usage_day_session_id_pk": { + "columns": [ + "day", + "session_id" + ], + "name": "daily_session_usage_day_session_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "daily_spend": { + "name": "daily_spend", + "columns": { + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_amount": { + "name": "cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "cost_is_approximate": { + "name": "cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "daily_spend_day_cost_currency_pk": { + "columns": [ + "day", + "cost_currency" + ], + "name": "daily_spend_day_cost_currency_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "device_link_ownership": { + "name": "device_link_ownership", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_pid": { + "name": "owner_pid", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_label": { + "name": "owner_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "embedding_jobs": { + "name": "embedding_jobs", + "columns": { + "rowid": { + "name": "rowid", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vec_table": { + "name": "vec_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locked_at": { + "name": "locked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_embedding_jobs_natural": { + "name": "uniq_embedding_jobs_natural", + "columns": [ + "source", + "source_id", + "chunk_index", + "model_id" + ], + "isUnique": true + }, + "idx_embedding_jobs_status_scheduled": { + "name": "idx_embedding_jobs_status_scheduled", + "columns": [ + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_embedding_jobs_source_id": { + "name": "idx_embedding_jobs_source_id", + "columns": [ + "source", + "source_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "embedding_meta": { + "name": "embedding_meta", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ghost_cards": { + "name": "ghost_cards", + "columns": { + "call_id": { + "name": "call_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "ghost_id": { + "name": "ghost_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "v": { + "name": "v", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "ghost_cards_updated_at_idx": { + "name": "ghost_cards_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "hook_group_context_cursors": { + "name": "hook_group_context_cursors", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cursor_key": { + "name": "cursor_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cursor_id": { + "name": "cursor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hook_group_context_cursors_updated_at_idx": { + "name": "hook_group_context_cursors_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "hook_group_context_cursors_provider_cursor_key_pk": { + "columns": [ + "provider", + "cursor_key" + ], + "name": "hook_group_context_cursors_provider_cursor_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "hook_group_message_stats": { + "name": "hook_group_message_stats", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text_bytes": { + "name": "text_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "hook_group_messages": { + "name": "hook_group_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_name": { + "name": "chat_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_bot": { + "name": "is_bot", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_names": { + "name": "file_names", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hook_group_messages_msg_idx": { + "name": "hook_group_messages_msg_idx", + "columns": [ + "provider", + "chat_id", + "thread_id", + "message_id" + ], + "isUnique": true + }, + "hook_group_messages_window_idx": { + "name": "hook_group_messages_window_idx", + "columns": [ + "provider", + "chat_id", + "thread_id", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "im_bindings": { + "name": "im_bindings", + "columns": { + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_context_id": { + "name": "bot_context_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "target_session_id": { + "name": "target_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attached_at": { + "name": "attached_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attached_via_card_message_id": { + "name": "attached_via_card_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_im_bindings_target": { + "name": "idx_im_bindings_target", + "columns": [ + "target_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "im_bindings_target_session_id_sessions_id_fk": { + "name": "im_bindings_target_session_id_sessions_id_fk", + "tableFrom": "im_bindings", + "tableTo": "sessions", + "columnsFrom": [ + "target_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "im_bindings_channel_bot_context_id_user_id_scope_key_pk": { + "columns": [ + "channel", + "bot_context_id", + "user_id", + "scope_key" + ], + "name": "im_bindings_channel_bot_context_id_user_id_scope_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "im_notification_origins": { + "name": "im_notification_origins", + "columns": { + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_context_id": { + "name": "bot_context_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "im_notification_origins_channel_bot_context_id_user_id_message_id_pk": { + "columns": [ + "channel", + "bot_context_id", + "user_id", + "message_id" + ], + "name": "im_notification_origins_channel_bot_context_id_user_id_message_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media_blobs": { + "name": "media_blobs", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "ext": { + "name": "ext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_cache": { + "name": "is_cache", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_access_at": { + "name": "last_access_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media_invocations": { + "name": "media_invocations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guide_revision": { + "name": "guide_revision", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guide_json": { + "name": "guide_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_json": { + "name": "response_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "media_invocations_owner_created_at_idx": { + "name": "media_invocations_owner_created_at_idx", + "columns": [ + "owner", + "created_at" + ], + "isUnique": false + }, + "media_invocations_owner_state_idx": { + "name": "media_invocations_owner_state_idx", + "columns": [ + "owner", + "state" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media_refs": { + "name": "media_refs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref_kind": { + "name": "ref_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "origin_session_id": { + "name": "origin_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "media_refs_hash_idx": { + "name": "media_refs_hash_idx", + "columns": [ + "hash" + ], + "isUnique": false + }, + "media_refs_ref_idx": { + "name": "media_refs_ref_idx", + "columns": [ + "ref_kind", + "ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "media_refs_hash_media_blobs_hash_fk": { + "name": "media_refs_hash_media_blobs_hash_fk", + "tableFrom": "media_refs", + "tableTo": "media_blobs", + "columnsFrom": [ + "hash" + ], + "columnsTo": [ + "hash" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_use_id": { + "name": "tool_use_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_meta": { + "name": "agent_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rewind_at": { + "name": "rewind_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_messages_session_client": { + "name": "uniq_messages_session_client", + "columns": [ + "session_id", + "client_id" + ], + "isUnique": true + }, + "idx_messages_session_created": { + "name": "idx_messages_session_created", + "columns": [ + "session_id", + "created_at" + ], + "isUnique": false + }, + "idx_messages_created_at": { + "name": "idx_messages_created_at", + "columns": [ + "created_at", + "id" + ], + "isUnique": false + }, + "idx_messages_rewind_at": { + "name": "idx_messages_rewind_at", + "columns": [ + "rewind_at" + ], + "isUnique": false + }, + "idx_messages_active_error_tail": { + "name": "idx_messages_active_error_tail", + "columns": [ + "session_id", + "created_at" + ], + "isUnique": false, + "where": "\"messages\".\"role\" = 'error' AND \"messages\".\"rewind_at\" IS NULL" + } + }, + "foreignKeys": { + "messages_session_id_sessions_id_fk": { + "name": "messages_session_id_sessions_id_fk", + "tableFrom": "messages", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages_fts_rows": { + "name": "messages_fts_rows", + "columns": { + "fts_rowid": { + "name": "fts_rowid", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "messages_fts_rows_message_id_idx": { + "name": "messages_fts_rows_message_id_idx", + "columns": [ + "message_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "migration_history": { + "name": "migration_history", + "columns": { + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "migration_meta": { + "name": "migration_meta", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_teams": { + "name": "orca_teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "lead_session_id": { + "name": "lead_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_active_team_per_lead": { + "name": "uniq_active_team_per_lead", + "columns": [ + "lead_session_id" + ], + "isUnique": true, + "where": "\"orca_teams\".\"status\" = 'active'" + }, + "idx_orca_teams_status": { + "name": "idx_orca_teams_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "orca_teams_lead_session_id_sessions_id_fk": { + "name": "orca_teams_lead_session_id_sessions_id_fk", + "tableFrom": "orca_teams", + "tableTo": "sessions", + "columnsFrom": [ + "lead_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_worker_creation_reservations": { + "name": "orca_worker_creation_reservations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_orca_worker_creation_reservations_team_label": { + "name": "uniq_orca_worker_creation_reservations_team_label", + "columns": [ + "team_id", + "lower(\"label\")" + ], + "isUnique": true + }, + "idx_orca_worker_creation_reservations_expires_at": { + "name": "idx_orca_worker_creation_reservations_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "orca_worker_creation_reservations_team_id_orca_teams_id_fk": { + "name": "orca_worker_creation_reservations_team_id_orca_teams_id_fk", + "tableFrom": "orca_worker_creation_reservations", + "tableTo": "orca_teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_workers": { + "name": "orca_workers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'idle'" + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_branch": { + "name": "worktree_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'developer'" + }, + "focused": { + "name": "focused", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "idle_since": { + "name": "idle_since", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_device_id": { + "name": "execution_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_session_id": { + "name": "remote_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_bridged_message_id": { + "name": "last_bridged_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_released_at": { + "name": "remote_released_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_orca_workers_session_id": { + "name": "uniq_orca_workers_session_id", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "uniq_orca_workers_team_label": { + "name": "uniq_orca_workers_team_label", + "columns": [ + "team_id", + "lower(\"label\")" + ], + "isUnique": true + }, + "uniq_orca_workers_focused_per_team": { + "name": "uniq_orca_workers_focused_per_team", + "columns": [ + "team_id" + ], + "isUnique": true, + "where": "\"orca_workers\".\"focused\" = true" + }, + "idx_orca_workers_team_id": { + "name": "idx_orca_workers_team_id", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "idx_orca_workers_status": { + "name": "idx_orca_workers_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "orca_workers_team_id_orca_teams_id_fk": { + "name": "orca_workers_team_id_orca_teams_id_fk", + "tableFrom": "orca_workers", + "tableTo": "orca_teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "orca_workers_session_id_sessions_id_fk": { + "name": "orca_workers_session_id_sessions_id_fk", + "tableFrom": "orca_workers", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_task_requests": { + "name": "plugin_task_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "plugin_task_request_key": { + "name": "plugin_task_request_key", + "columns": [ + "plugin_id", + "operation", + "target_id", + "request_key" + ], + "isUnique": true + }, + "plugin_task_target": { + "name": "plugin_task_target", + "columns": [ + "target_id", + "plugin_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_aliases": { + "name": "project_aliases", + "columns": { + "project_key": { + "name": "project_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_project_aliases_updated_at": { + "name": "idx_project_aliases_updated_at", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_automation_consents": { + "name": "project_automation_consents", + "columns": { + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "consented_at": { + "name": "consented_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_hash": { + "name": "config_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recent_workdirs": { + "name": "recent_workdirs", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_recent_workdirs_last_used_at": { + "name": "idx_recent_workdirs_last_used_at", + "columns": [ + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "right_sidebar_tabs": { + "name": "right_sidebar_tabs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "right_sidebar_tabs_session_idx": { + "name": "right_sidebar_tabs_session_idx", + "columns": [ + "session_id", + "position" + ], + "isUnique": false + }, + "right_sidebar_tabs_subagents_singleton_idx": { + "name": "right_sidebar_tabs_subagents_singleton_idx", + "columns": [ + "session_id" + ], + "isUnique": true, + "where": "\"right_sidebar_tabs\".\"kind\" = 'subagents'" + }, + "right_sidebar_tabs_bot_artifacts_singleton_idx": { + "name": "right_sidebar_tabs_bot_artifacts_singleton_idx", + "columns": [ + "session_id" + ], + "isUnique": true, + "where": "\"right_sidebar_tabs\".\"kind\" = 'bot-artifacts'" + } + }, + "foreignKeys": { + "right_sidebar_tabs_session_id_sessions_id_fk": { + "name": "right_sidebar_tabs_session_id_sessions_id_fk", + "tableFrom": "right_sidebar_tabs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_runs": { + "name": "schedule_runs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fired_at": { + "name": "fired_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_value_usd": { + "name": "estimated_value_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_amount": { + "name": "cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_value_amount": { + "name": "estimated_value_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_is_approximate": { + "name": "cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cost_attribution": { + "name": "cost_attribution", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'legacy'" + }, + "result_text": { + "name": "result_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pre_run_hook_result": { + "name": "pre_run_hook_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_schedule_runs_schedule": { + "name": "idx_schedule_runs_schedule", + "columns": [ + "schedule_id", + "fired_at" + ], + "isUnique": false + }, + "idx_schedule_runs_running_schedule": { + "name": "idx_schedule_runs_running_schedule", + "columns": [ + "schedule_id" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"status\" = 'running'" + }, + "idx_schedule_runs_running_heartbeat": { + "name": "idx_schedule_runs_running_heartbeat", + "columns": [ + "heartbeat_at" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"status\" = 'running' AND \"schedule_runs\".\"heartbeat_at\" IS NOT NULL" + }, + "idx_schedule_runs_running_legacy": { + "name": "idx_schedule_runs_running_legacy", + "columns": [ + "fired_at" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"status\" = 'running' AND \"schedule_runs\".\"heartbeat_at\" IS NULL" + }, + "idx_schedule_runs_unread_terminal": { + "name": "idx_schedule_runs_unread_terminal", + "columns": [ + "schedule_id", + "status", + "fired_at" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"read_at\" IS NULL AND \"schedule_runs\".\"status\" IN ('success', 'failed', 'aborted', 'interrupted')" + }, + "idx_schedule_runs_session_latest": { + "name": "idx_schedule_runs_session_latest", + "columns": [ + "session_id", + "fired_at", + "id" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"session_id\" IS NOT NULL" + } + }, + "foreignKeys": { + "schedule_runs_schedule_id_schedules_id_fk": { + "name": "schedule_runs_schedule_id_schedules_id_fk", + "tableFrom": "schedule_runs", + "tableTo": "schedules", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_runs_session_id_sessions_id_fk": { + "name": "schedule_runs_session_id_sessions_id_fk", + "tableFrom": "schedule_runs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_session_latest_runs": { + "name": "schedule_session_latest_runs", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fired_at": { + "name": "fired_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_schedule_session_latest_runs_run": { + "name": "idx_schedule_session_latest_runs_run", + "columns": [ + "run_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "schedule_session_latest_runs_session_id_sessions_id_fk": { + "name": "schedule_session_latest_runs_session_id_sessions_id_fk", + "tableFrom": "schedule_session_latest_runs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_session_latest_runs_run_id_schedule_runs_id_fk": { + "name": "schedule_session_latest_runs_run_id_schedule_runs_id_fk", + "tableFrom": "schedule_session_latest_runs", + "tableTo": "schedule_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedules": { + "name": "schedules", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_type": { + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'prompt'" + }, + "job_config": { + "name": "job_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_mode": { + "name": "execution_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'agent'" + }, + "script_config": { + "name": "script_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "project_config_id": { + "name": "project_config_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "legacy_session_fallback": { + "name": "legacy_session_fallback", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cron'" + }, + "cron_expr": { + "name": "cron_expr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recurring": { + "name": "recurring", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "manual": { + "name": "manual", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "interval_ms": { + "name": "interval_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_agent_kind": { + "name": "model_agent_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "effort": { + "name": "effort", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fast_mode": { + "name": "fast_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_kind": { + "name": "workspace_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'project'" + }, + "use_worktree": { + "name": "use_worktree", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "target_session_id": { + "name": "target_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persistent_session": { + "name": "persistent_session", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "silent_when_idle": { + "name": "silent_when_idle", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "pre_run_hook_command": { + "name": "pre_run_hook_command", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pre_run_hook_timeout_ms": { + "name": "pre_run_hook_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "skip_log_session_id": { + "name": "skip_log_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notify_desktop": { + "name": "notify_desktop", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "notify_feishu": { + "name": "notify_feishu", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_wecom_group": { + "name": "notify_wecom_group", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_finished_at": { + "name": "last_finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_fire_at": { + "name": "next_fire_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expire_at": { + "name": "expire_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_schedules_active_next": { + "name": "idx_schedules_active_next", + "columns": [ + "status", + "next_fire_at" + ], + "isUnique": false + }, + "idx_schedules_target_session": { + "name": "idx_schedules_target_session", + "columns": [ + "target_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "schedules_target_session_id_sessions_id_fk": { + "name": "schedules_target_session_id_sessions_id_fk", + "tableFrom": "schedules", + "tableTo": "sessions", + "columnsFrom": [ + "target_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "schedules_skip_log_session_id_sessions_id_fk": { + "name": "schedules_skip_log_session_id_sessions_id_fk", + "tableFrom": "schedules", + "tableTo": "sessions", + "columnsFrom": [ + "skip_log_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_goals": { + "name": "session_goals", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "objective": { + "name": "objective", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "budget_tokens": { + "name": "budget_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_turns": { + "name": "max_turns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "no_progress_limit": { + "name": "no_progress_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "turns_used": { + "name": "turns_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tokens_used": { + "name": "tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "no_progress_streak": { + "name": "no_progress_streak", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "usage_reset_at": { + "name": "usage_reset_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reason": { + "name": "last_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_session_goals_status": { + "name": "idx_session_goals_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_goals_session_id_sessions_id_fk": { + "name": "session_goals_session_id_sessions_id_fk", + "tableFrom": "session_goals", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_pr_refs": { + "name": "session_pr_refs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repo": { + "name": "repo", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_session_pr_refs": { + "name": "uniq_session_pr_refs", + "columns": [ + "session_id", + "owner", + "repo", + "pr_number" + ], + "isUnique": true + }, + "idx_session_pr_refs_session_last_seen": { + "name": "idx_session_pr_refs_session_last_seen", + "columns": [ + "session_id", + "last_seen_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_pr_refs_session_id_sessions_id_fk": { + "name": "session_pr_refs_session_id_sessions_id_fk", + "tableFrom": "session_pr_refs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_task_tags": { + "name": "session_task_tags", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_task_tags_tag_idx": { + "name": "session_task_tags_tag_idx", + "columns": [ + "tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_task_tags_session_id_sessions_id_fk": { + "name": "session_task_tags_session_id_sessions_id_fk", + "tableFrom": "session_task_tags", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_task_tags_tag_id_task_tags_id_fk": { + "name": "session_task_tags_tag_id_task_tags_id_fk", + "tableFrom": "session_task_tags", + "tableTo": "task_tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_task_tags_session_id_tag_id_pk": { + "columns": [ + "session_id", + "tag_id" + ], + "name": "session_task_tags_session_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'New Maker'" + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_kind": { + "name": "workspace_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'project'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'claude-sonnet-4-6'" + }, + "effort": { + "name": "effort", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'high'" + }, + "permission_mode": { + "name": "permission_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ask'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "sdk_session_id": { + "name": "sdk_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_token_usage": { + "name": "total_token_usage", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_usd": { + "name": "total_cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_amount": { + "name": "total_cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_currency": { + "name": "total_cost_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_cost_is_approximate": { + "name": "total_cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "context_tokens": { + "name": "context_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "context_window": { + "name": "context_window", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "context_window_runtime": { + "name": "context_window_runtime", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fast_mode": { + "name": "fast_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "plan_mode_enabled": { + "name": "plan_mode_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cleared_at": { + "name": "cleared_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_send_at": { + "name": "user_send_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cc'" + }, + "orca_role": { + "name": "orca_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "forked_at_message_id": { + "name": "forked_at_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_path": { + "name": "worktree_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'desktop'" + }, + "feishu_open_id": { + "name": "feishu_open_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feishu_bot_app_id": { + "name": "feishu_bot_app_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "im_bot_context_id": { + "name": "im_bot_context_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "im_user_id": { + "name": "im_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "im_default_route": { + "name": "im_default_route", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_project_context": { + "name": "used_project_context", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "codex_history_has_product_prompt": { + "name": "codex_history_has_product_prompt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "codex_plan_json": { + "name": "codex_plan_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "extra_dirs": { + "name": "extra_dirs", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "writable_dirs": { + "name": "writable_dirs", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "remote_host_id": { + "name": "remote_host_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_device_id": { + "name": "agent_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "orca_remote_lead": { + "name": "orca_remote_lead", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_turn_started_at": { + "name": "active_turn_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_turn_pid": { + "name": "active_turn_pid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_turn_ended_at": { + "name": "last_turn_ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_preview": { + "name": "list_preview", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_preview_role": { + "name": "list_preview_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_message_count": { + "name": "list_message_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sessions_updated_at": { + "name": "idx_sessions_updated_at", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "idx_sessions_user_send_at": { + "name": "idx_sessions_user_send_at", + "columns": [ + "user_send_at" + ], + "isUnique": false + }, + "idx_sessions_sdk_session_id": { + "name": "idx_sessions_sdk_session_id", + "columns": [ + "sdk_session_id" + ], + "isUnique": false + }, + "idx_sessions_workdir_created": { + "name": "idx_sessions_workdir_created", + "columns": [ + "working_dir", + "created_at", + "id" + ], + "isUnique": false + }, + "idx_sessions_created_at": { + "name": "idx_sessions_created_at", + "columns": [ + "created_at", + "id" + ], + "isUnique": false + }, + "idx_sessions_workspace_kind": { + "name": "idx_sessions_workspace_kind", + "columns": [ + "workspace_kind" + ], + "isUnique": false + }, + "idx_sessions_parent_session_id": { + "name": "idx_sessions_parent_session_id", + "columns": [ + "parent_session_id" + ], + "isUnique": false + }, + "idx_sessions_orca_role": { + "name": "idx_sessions_orca_role", + "columns": [ + "orca_role" + ], + "isUnique": false + }, + "idx_sessions_worktree_path": { + "name": "idx_sessions_worktree_path", + "columns": [ + "worktree_path" + ], + "isUnique": false + }, + "idx_sessions_feishu_lookup": { + "name": "idx_sessions_feishu_lookup", + "columns": [ + "source", + "feishu_bot_app_id", + "feishu_open_id" + ], + "isUnique": false + }, + "idx_sessions_im_lookup": { + "name": "idx_sessions_im_lookup", + "columns": [ + "source", + "im_bot_context_id", + "im_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_parent_session_id_sessions_id_fk": { + "name": "sessions_parent_session_id_sessions_id_fk", + "tableFrom": "sessions", + "tableTo": "sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_task_events": { + "name": "shared_task_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "shared_task_id": { + "name": "shared_task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "terminal": { + "name": "terminal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot": { + "name": "snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "shared_task_events_revision_idx": { + "name": "shared_task_events_revision_idx", + "columns": [ + "shared_task_id", + "kind", + "revision" + ], + "isUnique": true + }, + "shared_task_events_session_idx": { + "name": "shared_task_events_session_idx", + "columns": [ + "session_id", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_task_events_session_id_sessions_id_fk": { + "name": "shared_task_events_session_id_sessions_id_fk", + "tableFrom": "shared_task_events", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "skill_usage_exposures": { + "name": "skill_usage_exposures", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "analyzer_version": { + "name": "analyzer_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'6'" + }, + "raw_file_path": { + "name": "raw_file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "raw_line_no": { + "name": "raw_line_no", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sdk_session_id": { + "name": "sdk_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skill_name": { + "name": "skill_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skill_path": { + "name": "skill_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "skill_document_hash": { + "name": "skill_document_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exposure_content_hash": { + "name": "exposure_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_hash_source": { + "name": "document_hash_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_use_id": { + "name": "tool_use_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seen_at": { + "name": "seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_call_count": { + "name": "tool_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "repeated_tool_call_count": { + "name": "repeated_tool_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tool_error_count": { + "name": "tool_error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "command_call_count": { + "name": "command_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "command_failure_count": { + "name": "command_failure_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_skill_usage_exposures_skill_document_version": { + "name": "idx_skill_usage_exposures_skill_document_version", + "columns": [ + "analyzer_version", + "skill_name", + "skill_document_hash" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_skill_recent": { + "name": "idx_skill_usage_exposures_skill_recent", + "columns": [ + "skill_name", + "analyzer_version", + "seen_at" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_skill_recent_any_version": { + "name": "idx_skill_usage_exposures_skill_recent_any_version", + "columns": [ + "skill_name", + "seen_at" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_analyzer_recent_source": { + "name": "idx_skill_usage_exposures_analyzer_recent_source", + "columns": [ + "analyzer_version", + "seen_at", + "raw_file_path" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_session": { + "name": "idx_skill_usage_exposures_session", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_raw_file": { + "name": "idx_skill_usage_exposures_raw_file", + "columns": [ + "raw_file_path" + ], + "isUnique": false + } + }, + "foreignKeys": { + "skill_usage_exposures_raw_file_path_skill_usage_sources_raw_file_path_fk": { + "name": "skill_usage_exposures_raw_file_path_skill_usage_sources_raw_file_path_fk", + "tableFrom": "skill_usage_exposures", + "tableTo": "skill_usage_sources", + "columnsFrom": [ + "raw_file_path" + ], + "columnsTo": [ + "raw_file_path" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "skill_usage_sources": { + "name": "skill_usage_sources", + "columns": { + "raw_file_path": { + "name": "raw_file_path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "analyzer_version": { + "name": "analyzer_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'6'" + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sdk_session_id": { + "name": "sdk_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mtime_ms": { + "name": "mtime_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_scanned_at": { + "name": "last_scanned_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ok'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_skill_usage_sources_session": { + "name": "idx_skill_usage_sources_session", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_skill_usage_sources_sdk_session": { + "name": "idx_skill_usage_sources_sdk_session", + "columns": [ + "sdk_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subagent_run_aliases": { + "name": "subagent_run_aliases", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "subagent_run_aliases_lookup_idx": { + "name": "subagent_run_aliases_lookup_idx", + "columns": [ + "session_id", + "provider", + "alias", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "subagent_run_aliases_session_id_sessions_id_fk": { + "name": "subagent_run_aliases_session_id_sessions_id_fk", + "tableFrom": "subagent_run_aliases", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "subagent_run_aliases_run_id_subagent_runs_id_fk": { + "name": "subagent_run_aliases_run_id_subagent_runs_id_fk", + "tableFrom": "subagent_run_aliases", + "tableTo": "subagent_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "subagent_run_aliases_run_id_alias_pk": { + "columns": [ + "run_id", + "alias" + ], + "name": "subagent_run_aliases_run_id_alias_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subagent_runs": { + "name": "subagent_runs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logical_agent_id": { + "name": "logical_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_tool_use_id": { + "name": "parent_tool_use_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "provider_run_ids": { + "name": "provider_run_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'running'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "returned_result": { + "name": "returned_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "returned_result_empty": { + "name": "returned_result_empty", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "returned_result_truncated": { + "name": "returned_result_truncated", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reasoning_effort": { + "name": "reasoning_effort", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_uses": { + "name": "tool_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rewind_at": { + "name": "rewind_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "subagent_runs_logical_idx": { + "name": "subagent_runs_logical_idx", + "columns": [ + "session_id", + "provider", + "logical_agent_id" + ], + "isUnique": false + }, + "subagent_runs_session_idx": { + "name": "subagent_runs_session_idx", + "columns": [ + "session_id", + "rewind_at", + "deleted_at", + "started_at" + ], + "isUnique": false + }, + "subagent_runs_parent_tool_use_idx": { + "name": "subagent_runs_parent_tool_use_idx", + "columns": [ + "session_id", + "parent_tool_use_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "subagent_runs_session_id_sessions_id_fk": { + "name": "subagent_runs_session_id_sessions_id_fk", + "tableFrom": "subagent_runs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_tags": { + "name": "task_tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_customized": { + "name": "name_customized", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "favorite_order": { + "name": "favorite_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "task_tags_name_idx": { + "name": "task_tags_name_idx", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vec_table_meta": { + "name": "vec_table_meta", + "columns": { + "vec_table": { + "name": "vec_table", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dim": { + "name": "dim", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_file_attachments": { + "name": "wechat_file_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "abs_path": { + "name": "abs_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'staged'" + }, + "promoted_at": { + "name": "promoted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_wechat_file_attachments_task": { + "name": "idx_wechat_file_attachments_task", + "columns": [ + "binding_epoch", + "task_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "wechat_file_attachments_binding_epoch_wechat_sync_state_binding_epoch_fk": { + "name": "wechat_file_attachments_binding_epoch_wechat_sync_state_binding_epoch_fk", + "tableFrom": "wechat_file_attachments", + "tableTo": "wechat_sync_state", + "columnsFrom": [ + "binding_epoch" + ], + "columnsTo": [ + "binding_epoch" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_file_attachments_task_id_wechat_inbox_id_fk": { + "name": "wechat_file_attachments_task_id_wechat_inbox_id_fk", + "tableFrom": "wechat_file_attachments", + "tableTo": "wechat_inbox", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_file_attachments_session_id_sessions_id_fk": { + "name": "wechat_file_attachments_session_id_sessions_id_fk", + "tableFrom": "wechat_file_attachments", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_inbox": { + "name": "wechat_inbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform_message_id": { + "name": "platform_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform_seq": { + "name": "platform_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "peer_id": { + "name": "peer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform_created_at": { + "name": "platform_created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "lease_until": { + "name": "lease_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_epoch": { + "name": "conversation_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_nonce": { + "name": "context_nonce", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_ciphertext": { + "name": "context_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_tag": { + "name": "context_tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_wechat_inbox_platform_message": { + "name": "uniq_wechat_inbox_platform_message", + "columns": [ + "binding_epoch", + "platform_message_id" + ], + "isUnique": true + }, + "idx_wechat_inbox_queue": { + "name": "idx_wechat_inbox_queue", + "columns": [ + "binding_epoch", + "status", + "received_at" + ], + "isUnique": false + }, + "idx_wechat_inbox_lease": { + "name": "idx_wechat_inbox_lease", + "columns": [ + "binding_epoch", + "lease_until" + ], + "isUnique": false + }, + "idx_wechat_inbox_conversation": { + "name": "idx_wechat_inbox_conversation", + "columns": [ + "binding_epoch", + "peer_id", + "conversation_epoch" + ], + "isUnique": false + }, + "uniq_wechat_inbox_running_session": { + "name": "uniq_wechat_inbox_running_session", + "columns": [ + "binding_epoch", + "session_id" + ], + "isUnique": true, + "where": "\"wechat_inbox\".\"session_id\" IS NOT NULL AND \"wechat_inbox\".\"status\" IN ('dispatching', 'accepted_running', 'waiting_desktop', 'delivery_pending')" + } + }, + "foreignKeys": { + "wechat_inbox_binding_epoch_wechat_sync_state_binding_epoch_fk": { + "name": "wechat_inbox_binding_epoch_wechat_sync_state_binding_epoch_fk", + "tableFrom": "wechat_inbox", + "tableTo": "wechat_sync_state", + "columnsFrom": [ + "binding_epoch" + ], + "columnsTo": [ + "binding_epoch" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_inbox_session_id_sessions_id_fk": { + "name": "wechat_inbox_session_id_sessions_id_fk", + "tableFrom": "wechat_inbox", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_outbox": { + "name": "wechat_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "media_json": { + "name": "media_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_wechat_outbox_client_id": { + "name": "uniq_wechat_outbox_client_id", + "columns": [ + "binding_epoch", + "client_id" + ], + "isUnique": true + }, + "idx_wechat_outbox_delivery": { + "name": "idx_wechat_outbox_delivery", + "columns": [ + "binding_epoch", + "status", + "next_retry_at" + ], + "isUnique": false + }, + "idx_wechat_outbox_task": { + "name": "idx_wechat_outbox_task", + "columns": [ + "binding_epoch", + "task_id", + "chunk_index" + ], + "isUnique": false + } + }, + "foreignKeys": { + "wechat_outbox_binding_epoch_wechat_sync_state_binding_epoch_fk": { + "name": "wechat_outbox_binding_epoch_wechat_sync_state_binding_epoch_fk", + "tableFrom": "wechat_outbox", + "tableTo": "wechat_sync_state", + "columnsFrom": [ + "binding_epoch" + ], + "columnsTo": [ + "binding_epoch" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_outbox_task_id_wechat_inbox_id_fk": { + "name": "wechat_outbox_task_id_wechat_inbox_id_fk", + "tableFrom": "wechat_outbox", + "tableTo": "wechat_inbox", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_sync_state": { + "name": "wechat_sync_state", + "columns": { + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_cursor": { + "name": "sync_cursor", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "last_poll_at": { + "name": "last_poll_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_wechat_sync_active": { + "name": "uniq_wechat_sync_active", + "columns": [ + "is_active" + ], + "isUnique": true, + "where": "\"wechat_sync_state\".\"is_active\" = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uniq_orca_worker_creation_reservations_team_label": { + "columns": { + "lower(\"label\")": { + "isExpression": true + } + } + }, + "uniq_orca_workers_team_label": { + "columns": { + "lower(\"label\")": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/apps/desktop/drizzle/meta/0125_snapshot.json b/apps/desktop/drizzle/meta/0125_snapshot.json new file mode 100644 index 00000000000..0768e324fb6 --- /dev/null +++ b/apps/desktop/drizzle/meta/0125_snapshot.json @@ -0,0 +1,6930 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "713a8a25-ebe4-441d-abaf-13a07bd1bda9", + "prevId": "86f88929-384d-4831-bf8e-a29b3d83ec85", + "tables": { + "account_usage_snapshots": { + "name": "account_usage_snapshots", + "columns": { + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "snapshot": { + "name": "snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_input_queue_snapshots": { + "name": "agent_input_queue_snapshots", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_input_queue_snapshots_session_id_sessions_id_fk": { + "name": "agent_input_queue_snapshots_session_id_sessions_id_fk", + "tableFrom": "agent_input_queue_snapshots", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "auto_review_projections": { + "name": "auto_review_projections", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "lead_id": { + "name": "lead_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "projected_revision": { + "name": "projected_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": -1 + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "auto_review_projections_lead_idx": { + "name": "auto_review_projections_lead_idx", + "columns": [ + "lead_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "auto_review_projections_session_id_sessions_id_fk": { + "name": "auto_review_projections_session_id_sessions_id_fk", + "tableFrom": "auto_review_projections", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_review_projections_lead_id_sessions_id_fk": { + "name": "auto_review_projections_lead_id_sessions_id_fk", + "tableFrom": "auto_review_projections", + "tableTo": "sessions", + "columnsFrom": [ + "lead_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "auto_review_projections_session_id_lead_id_pk": { + "columns": [ + "session_id", + "lead_id" + ], + "name": "auto_review_projections_session_id_lead_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_delegations": { + "name": "bot_delegations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "requesting_bot_id": { + "name": "requesting_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_bot_id": { + "name": "target_bot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "child_session_id": { + "name": "child_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "objective": { + "name": "objective", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_refs_json": { + "name": "context_refs_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "artifact_refs_json": { + "name": "artifact_refs_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_snapshot_json": { + "name": "permission_snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "lineage_json": { + "name": "lineage_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "target_profile_version": { + "name": "target_profile_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "budget_tokens": { + "name": "budget_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tokens_used": { + "name": "tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'queued'" + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_artifacts_json": { + "name": "output_artifacts_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "pending_interaction_json": { + "name": "pending_interaction_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "run_sequence": { + "name": "run_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_delivered_at": { + "name": "completion_delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_delegations_requester_status": { + "name": "idx_bot_delegations_requester_status", + "columns": [ + "requesting_bot_id", + "status" + ], + "isUnique": false + }, + "idx_bot_delegations_target_status": { + "name": "idx_bot_delegations_target_status", + "columns": [ + "target_bot_id", + "status" + ], + "isUnique": false + }, + "idx_bot_delegations_parent_session": { + "name": "idx_bot_delegations_parent_session", + "columns": [ + "parent_session_id" + ], + "isUnique": false + }, + "uniq_bot_delegations_child_session": { + "name": "uniq_bot_delegations_child_session", + "columns": [ + "child_session_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "bot_delegations_requesting_bot_id_bot_profiles_id_fk": { + "name": "bot_delegations_requesting_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "bot_profiles", + "columnsFrom": [ + "requesting_bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_delegations_target_bot_id_bot_profiles_id_fk": { + "name": "bot_delegations_target_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "bot_profiles", + "columnsFrom": [ + "target_bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_delegations_parent_session_id_sessions_id_fk": { + "name": "bot_delegations_parent_session_id_sessions_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "bot_delegations_child_session_id_sessions_id_fk": { + "name": "bot_delegations_child_session_id_sessions_id_fk", + "tableFrom": "bot_delegations", + "tableTo": "sessions", + "columnsFrom": [ + "child_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_direct_message_threads": { + "name": "bot_direct_message_threads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_a_id": { + "name": "bot_a_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_b_id": { + "name": "bot_b_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "close_reason": { + "name": "close_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "max_messages": { + "name": "max_messages", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "blocked_until": { + "name": "blocked_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "closed_at": { + "name": "closed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_dm_threads_active_pair": { + "name": "uniq_bot_dm_threads_active_pair", + "columns": [ + "bot_a_id", + "bot_b_id" + ], + "isUnique": true, + "where": "\"bot_direct_message_threads\".\"status\" = 'active'" + }, + "idx_bot_dm_threads_pair_updated": { + "name": "idx_bot_dm_threads_pair_updated", + "columns": [ + "bot_a_id", + "bot_b_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_direct_messages": { + "name": "bot_direct_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sender_bot_id": { + "name": "sender_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_bot_id": { + "name": "recipient_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sender_session_id": { + "name": "sender_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_session_id": { + "name": "recipient_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delivery_status": { + "name": "delivery_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_name": { + "name": "recipient_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bridge_session_id": { + "name": "bridge_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_direct_messages_thread_sequence": { + "name": "uniq_bot_direct_messages_thread_sequence", + "columns": [ + "thread_id", + "sequence" + ], + "isUnique": true + }, + "idx_bot_direct_messages_thread_created": { + "name": "idx_bot_direct_messages_thread_created", + "columns": [ + "thread_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_direct_messages_thread_id_bot_direct_message_threads_id_fk": { + "name": "bot_direct_messages_thread_id_bot_direct_message_threads_id_fk", + "tableFrom": "bot_direct_messages", + "tableTo": "bot_direct_message_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_direct_messages_sender_session_id_sessions_id_fk": { + "name": "bot_direct_messages_sender_session_id_sessions_id_fk", + "tableFrom": "bot_direct_messages", + "tableTo": "sessions", + "columnsFrom": [ + "sender_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "bot_direct_messages_recipient_session_id_sessions_id_fk": { + "name": "bot_direct_messages_recipient_session_id_sessions_id_fk", + "tableFrom": "bot_direct_messages", + "tableTo": "sessions", + "columnsFrom": [ + "recipient_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_members": { + "name": "bot_group_members", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen_sequence": { + "name": "last_seen_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_group_members_bot": { + "name": "idx_bot_group_members_bot", + "columns": [ + "bot_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_group_members_group_id_bot_groups_id_fk": { + "name": "bot_group_members_group_id_bot_groups_id_fk", + "tableFrom": "bot_group_members", + "tableTo": "bot_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_group_members_bot_id_bot_profiles_id_fk": { + "name": "bot_group_members_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_group_members", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bot_group_members_group_id_bot_id_pk": { + "columns": [ + "group_id", + "bot_id" + ], + "name": "bot_group_members_group_id_bot_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_messages": { + "name": "bot_group_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'message'" + }, + "author_kind": { + "name": "author_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_bot_id": { + "name": "author_bot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "mentions_json": { + "name": "mentions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"all\":false,\"botIds\":[]}'" + }, + "notice_code": { + "name": "notice_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "files_json": { + "name": "files_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "attachments_json": { + "name": "attachments_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_group_messages_group_sequence": { + "name": "uniq_bot_group_messages_group_sequence", + "columns": [ + "group_id", + "sequence" + ], + "isUnique": true + }, + "uniq_bot_group_messages_group_client": { + "name": "uniq_bot_group_messages_group_client", + "columns": [ + "group_id", + "client_id" + ], + "isUnique": true, + "where": "\"bot_group_messages\".\"client_id\" IS NOT NULL" + } + }, + "foreignKeys": { + "bot_group_messages_group_id_bot_groups_id_fk": { + "name": "bot_group_messages_group_id_bot_groups_id_fk", + "tableFrom": "bot_group_messages", + "tableTo": "bot_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_plan_steps": { + "name": "bot_group_plan_steps", + "columns": { + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_name": { + "name": "bot_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "result_message_id": { + "name": "result_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "bot_group_plan_steps_plan_id_bot_group_plans_id_fk": { + "name": "bot_group_plan_steps_plan_id_bot_group_plans_id_fk", + "tableFrom": "bot_group_plan_steps", + "tableTo": "bot_group_plans", + "columnsFrom": [ + "plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bot_group_plan_steps_plan_id_position_pk": { + "columns": [ + "plan_id", + "position" + ], + "name": "bot_group_plan_steps_plan_id_position_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_group_plans": { + "name": "bot_group_plans", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_text": { + "name": "request_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments_json": { + "name": "attachments_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "organizer_bot_id": { + "name": "organizer_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organizer_name": { + "name": "organizer_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "current_step": { + "name": "current_step", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "work_dir": { + "name": "work_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_group_plans_group_created": { + "name": "idx_bot_group_plans_group_created", + "columns": [ + "group_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_group_plans_group_id_bot_groups_id_fk": { + "name": "bot_group_plans_group_id_bot_groups_id_fk", + "tableFrom": "bot_group_plans", + "tableTo": "bot_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_groups": { + "name": "bot_groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reply_mode": { + "name": "reply_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "speaking_mode": { + "name": "speaking_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "organizer_bot_id": { + "name": "organizer_bot_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_dir": { + "name": "project_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_groups_updated": { + "name": "idx_bot_groups_updated", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_lifecycle_events": { + "name": "bot_lifecycle_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_lifecycle_events_bot_created": { + "name": "idx_bot_lifecycle_events_bot_created", + "columns": [ + "bot_id", + "created_at" + ], + "isUnique": false + }, + "idx_bot_lifecycle_events_session_created": { + "name": "idx_bot_lifecycle_events_session_created", + "columns": [ + "session_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_lifecycle_events_bot_id_bot_profiles_id_fk": { + "name": "bot_lifecycle_events_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_lifecycle_events", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_lifecycle_events_session_id_sessions_id_fk": { + "name": "bot_lifecycle_events_session_id_sessions_id_fk", + "tableFrom": "bot_lifecycle_events", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_profile_versions": { + "name": "bot_profile_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "identity_source": { + "name": "identity_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "capabilities_json": { + "name": "capabilities_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_profile_versions_bot_version": { + "name": "uniq_bot_profile_versions_bot_version", + "columns": [ + "bot_id", + "version" + ], + "isUnique": true + }, + "idx_bot_profile_versions_bot_created": { + "name": "idx_bot_profile_versions_bot_created", + "columns": [ + "bot_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_profile_versions_bot_id_bot_profiles_id_fk": { + "name": "bot_profile_versions_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_profile_versions", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_profiles": { + "name": "bot_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'🤖'" + }, + "avatar_color": { + "name": "avatar_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'violet'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "hidden_at": { + "name": "hidden_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attention_reason": { + "name": "attention_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attention_at": { + "name": "attention_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "current_version": { + "name": "current_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "canonical_session_id": { + "name": "canonical_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_profiles_status_updated": { + "name": "idx_bot_profiles_status_updated", + "columns": [ + "status", + "updated_at" + ], + "isUnique": false + }, + "idx_bot_profiles_canonical_session": { + "name": "idx_bot_profiles_canonical_session", + "columns": [ + "canonical_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_profiles_canonical_session_id_sessions_id_fk": { + "name": "bot_profiles_canonical_session_id_sessions_id_fk", + "tableFrom": "bot_profiles", + "tableTo": "sessions", + "columnsFrom": [ + "canonical_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_runtime_snapshots": { + "name": "bot_runtime_snapshots", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_version": { + "name": "profile_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_scope_key": { + "name": "memory_scope_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "configured_json": { + "name": "configured_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "resolved_json": { + "name": "resolved_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_at": { + "name": "applied_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "failed_at": { + "name": "failed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "failure_json": { + "name": "failure_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_bot_runtime_snapshots_bot_prepared": { + "name": "idx_bot_runtime_snapshots_bot_prepared", + "columns": [ + "bot_id", + "prepared_at" + ], + "isUnique": false + }, + "idx_bot_runtime_snapshots_session_prepared": { + "name": "idx_bot_runtime_snapshots_session_prepared", + "columns": [ + "session_id", + "prepared_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_runtime_snapshots_bot_id_bot_profiles_id_fk": { + "name": "bot_runtime_snapshots_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_runtime_snapshots", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_runtime_snapshots_session_id_sessions_id_fk": { + "name": "bot_runtime_snapshots_session_id_sessions_id_fk", + "tableFrom": "bot_runtime_snapshots", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "bot_session_links": { + "name": "bot_session_links", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "profile_version": { + "name": "profile_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_bot_session_links_session": { + "name": "uniq_bot_session_links_session", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "uniq_bot_session_links_canonical_per_bot": { + "name": "uniq_bot_session_links_canonical_per_bot", + "columns": [ + "bot_id" + ], + "isUnique": true, + "where": "\"bot_session_links\".\"role\" = 'canonical'" + }, + "idx_bot_session_links_bot_role": { + "name": "idx_bot_session_links_bot_role", + "columns": [ + "bot_id", + "role" + ], + "isUnique": false + } + }, + "foreignKeys": { + "bot_session_links_bot_id_bot_profiles_id_fk": { + "name": "bot_session_links_bot_id_bot_profiles_id_fk", + "tableFrom": "bot_session_links", + "tableTo": "bot_profiles", + "columnsFrom": [ + "bot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_session_links_session_id_sessions_id_fk": { + "name": "bot_session_links_session_id_sessions_id_fk", + "tableFrom": "bot_session_links", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_mcp_servers": { + "name": "custom_mcp_servers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_custom_mcp_servers_sort_order": { + "name": "idx_custom_mcp_servers_sort_order", + "columns": [ + "sort_order" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_providers": { + "name": "custom_providers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "runtimes": { + "name": "runtimes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "auth": { + "name": "auth", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_custom_providers_sort_order": { + "name": "idx_custom_providers_sort_order", + "columns": [ + "sort_order" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "daily_model_usage": { + "name": "daily_model_usage", + "columns": { + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_amount": { + "name": "cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "cost_is_approximate": { + "name": "cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cache_create_tokens": { + "name": "cache_create_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "daily_model_usage_day_agent_kind_model_cost_currency_pk": { + "columns": [ + "day", + "agent_kind", + "model", + "cost_currency" + ], + "name": "daily_model_usage_day_agent_kind_model_cost_currency_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "daily_session_usage": { + "name": "daily_session_usage", + "columns": { + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tokens": { + "name": "tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "daily_session_usage_day_session_id_pk": { + "columns": [ + "day", + "session_id" + ], + "name": "daily_session_usage_day_session_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "daily_spend": { + "name": "daily_spend", + "columns": { + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_amount": { + "name": "cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "cost_is_approximate": { + "name": "cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "daily_spend_day_cost_currency_pk": { + "columns": [ + "day", + "cost_currency" + ], + "name": "daily_spend_day_cost_currency_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "device_link_ownership": { + "name": "device_link_ownership", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_pid": { + "name": "owner_pid", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_label": { + "name": "owner_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "embedding_jobs": { + "name": "embedding_jobs", + "columns": { + "rowid": { + "name": "rowid", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vec_table": { + "name": "vec_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locked_at": { + "name": "locked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_embedding_jobs_natural": { + "name": "uniq_embedding_jobs_natural", + "columns": [ + "source", + "source_id", + "chunk_index", + "model_id" + ], + "isUnique": true + }, + "idx_embedding_jobs_status_scheduled": { + "name": "idx_embedding_jobs_status_scheduled", + "columns": [ + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_embedding_jobs_source_id": { + "name": "idx_embedding_jobs_source_id", + "columns": [ + "source", + "source_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "embedding_meta": { + "name": "embedding_meta", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ghost_cards": { + "name": "ghost_cards", + "columns": { + "call_id": { + "name": "call_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "ghost_id": { + "name": "ghost_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "v": { + "name": "v", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "ghost_cards_updated_at_idx": { + "name": "ghost_cards_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "hook_group_context_cursors": { + "name": "hook_group_context_cursors", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cursor_key": { + "name": "cursor_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cursor_id": { + "name": "cursor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hook_group_context_cursors_updated_at_idx": { + "name": "hook_group_context_cursors_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "hook_group_context_cursors_provider_cursor_key_pk": { + "columns": [ + "provider", + "cursor_key" + ], + "name": "hook_group_context_cursors_provider_cursor_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "hook_group_message_stats": { + "name": "hook_group_message_stats", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text_bytes": { + "name": "text_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "hook_group_messages": { + "name": "hook_group_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_name": { + "name": "chat_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_bot": { + "name": "is_bot", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_names": { + "name": "file_names", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hook_group_messages_msg_idx": { + "name": "hook_group_messages_msg_idx", + "columns": [ + "provider", + "chat_id", + "thread_id", + "message_id" + ], + "isUnique": true + }, + "hook_group_messages_window_idx": { + "name": "hook_group_messages_window_idx", + "columns": [ + "provider", + "chat_id", + "thread_id", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "im_bindings": { + "name": "im_bindings", + "columns": { + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_context_id": { + "name": "bot_context_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "target_session_id": { + "name": "target_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attached_at": { + "name": "attached_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attached_via_card_message_id": { + "name": "attached_via_card_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_im_bindings_target": { + "name": "idx_im_bindings_target", + "columns": [ + "target_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "im_bindings_target_session_id_sessions_id_fk": { + "name": "im_bindings_target_session_id_sessions_id_fk", + "tableFrom": "im_bindings", + "tableTo": "sessions", + "columnsFrom": [ + "target_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "im_bindings_channel_bot_context_id_user_id_scope_key_pk": { + "columns": [ + "channel", + "bot_context_id", + "user_id", + "scope_key" + ], + "name": "im_bindings_channel_bot_context_id_user_id_scope_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "im_notification_origins": { + "name": "im_notification_origins", + "columns": { + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bot_context_id": { + "name": "bot_context_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "im_notification_origins_channel_bot_context_id_user_id_message_id_pk": { + "columns": [ + "channel", + "bot_context_id", + "user_id", + "message_id" + ], + "name": "im_notification_origins_channel_bot_context_id_user_id_message_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media_blobs": { + "name": "media_blobs", + "columns": { + "hash": { + "name": "hash", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "ext": { + "name": "ext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_cache": { + "name": "is_cache", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_access_at": { + "name": "last_access_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media_invocations": { + "name": "media_invocations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guide_revision": { + "name": "guide_revision", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guide_json": { + "name": "guide_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_json": { + "name": "response_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "media_invocations_owner_created_at_idx": { + "name": "media_invocations_owner_created_at_idx", + "columns": [ + "owner", + "created_at" + ], + "isUnique": false + }, + "media_invocations_owner_state_idx": { + "name": "media_invocations_owner_state_idx", + "columns": [ + "owner", + "state" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "media_refs": { + "name": "media_refs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref_kind": { + "name": "ref_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "origin_session_id": { + "name": "origin_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "media_refs_hash_idx": { + "name": "media_refs_hash_idx", + "columns": [ + "hash" + ], + "isUnique": false + }, + "media_refs_ref_idx": { + "name": "media_refs_ref_idx", + "columns": [ + "ref_kind", + "ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "media_refs_hash_media_blobs_hash_fk": { + "name": "media_refs_hash_media_blobs_hash_fk", + "tableFrom": "media_refs", + "tableTo": "media_blobs", + "columnsFrom": [ + "hash" + ], + "columnsTo": [ + "hash" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_use_id": { + "name": "tool_use_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_meta": { + "name": "agent_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rewind_at": { + "name": "rewind_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_messages_session_client": { + "name": "uniq_messages_session_client", + "columns": [ + "session_id", + "client_id" + ], + "isUnique": true + }, + "idx_messages_session_created": { + "name": "idx_messages_session_created", + "columns": [ + "session_id", + "created_at" + ], + "isUnique": false + }, + "idx_messages_created_at": { + "name": "idx_messages_created_at", + "columns": [ + "created_at", + "id" + ], + "isUnique": false + }, + "idx_messages_rewind_at": { + "name": "idx_messages_rewind_at", + "columns": [ + "rewind_at" + ], + "isUnique": false + }, + "idx_messages_active_error_tail": { + "name": "idx_messages_active_error_tail", + "columns": [ + "session_id", + "created_at" + ], + "isUnique": false, + "where": "\"messages\".\"role\" = 'error' AND \"messages\".\"rewind_at\" IS NULL" + } + }, + "foreignKeys": { + "messages_session_id_sessions_id_fk": { + "name": "messages_session_id_sessions_id_fk", + "tableFrom": "messages", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages_fts_rows": { + "name": "messages_fts_rows", + "columns": { + "fts_rowid": { + "name": "fts_rowid", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "messages_fts_rows_message_id_idx": { + "name": "messages_fts_rows_message_id_idx", + "columns": [ + "message_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "migration_history": { + "name": "migration_history", + "columns": { + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "applied_at": { + "name": "applied_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "migration_meta": { + "name": "migration_meta", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_remote_opens": { + "name": "orca_remote_opens", + "columns": { + "remote_session_id": { + "name": "remote_session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_teams": { + "name": "orca_teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "lead_session_id": { + "name": "lead_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_active_team_per_lead": { + "name": "uniq_active_team_per_lead", + "columns": [ + "lead_session_id" + ], + "isUnique": true, + "where": "\"orca_teams\".\"status\" = 'active'" + }, + "idx_orca_teams_status": { + "name": "idx_orca_teams_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "orca_teams_lead_session_id_sessions_id_fk": { + "name": "orca_teams_lead_session_id_sessions_id_fk", + "tableFrom": "orca_teams", + "tableTo": "sessions", + "columnsFrom": [ + "lead_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_worker_creation_reservations": { + "name": "orca_worker_creation_reservations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_orca_worker_creation_reservations_team_label": { + "name": "uniq_orca_worker_creation_reservations_team_label", + "columns": [ + "team_id", + "lower(\"label\")" + ], + "isUnique": true + }, + "idx_orca_worker_creation_reservations_expires_at": { + "name": "idx_orca_worker_creation_reservations_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "orca_worker_creation_reservations_team_id_orca_teams_id_fk": { + "name": "orca_worker_creation_reservations_team_id_orca_teams_id_fk", + "tableFrom": "orca_worker_creation_reservations", + "tableTo": "orca_teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orca_workers": { + "name": "orca_workers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'idle'" + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_branch": { + "name": "worktree_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'developer'" + }, + "focused": { + "name": "focused", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "idle_since": { + "name": "idle_since", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_device_id": { + "name": "execution_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_session_id": { + "name": "remote_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_bridged_message_id": { + "name": "last_bridged_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_released_at": { + "name": "remote_released_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pending_remote_report": { + "name": "pending_remote_report", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remote_stop_confirmed_at": { + "name": "remote_stop_confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_orca_workers_session_id": { + "name": "uniq_orca_workers_session_id", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "uniq_orca_workers_team_label": { + "name": "uniq_orca_workers_team_label", + "columns": [ + "team_id", + "lower(\"label\")" + ], + "isUnique": true + }, + "uniq_orca_workers_focused_per_team": { + "name": "uniq_orca_workers_focused_per_team", + "columns": [ + "team_id" + ], + "isUnique": true, + "where": "\"orca_workers\".\"focused\" = true" + }, + "idx_orca_workers_team_id": { + "name": "idx_orca_workers_team_id", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "idx_orca_workers_status": { + "name": "idx_orca_workers_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "orca_workers_team_id_orca_teams_id_fk": { + "name": "orca_workers_team_id_orca_teams_id_fk", + "tableFrom": "orca_workers", + "tableTo": "orca_teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "orca_workers_session_id_sessions_id_fk": { + "name": "orca_workers_session_id_sessions_id_fk", + "tableFrom": "orca_workers", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_task_requests": { + "name": "plugin_task_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "plugin_task_request_key": { + "name": "plugin_task_request_key", + "columns": [ + "plugin_id", + "operation", + "target_id", + "request_key" + ], + "isUnique": true + }, + "plugin_task_target": { + "name": "plugin_task_target", + "columns": [ + "target_id", + "plugin_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_aliases": { + "name": "project_aliases", + "columns": { + "project_key": { + "name": "project_key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_project_aliases_updated_at": { + "name": "idx_project_aliases_updated_at", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_automation_consents": { + "name": "project_automation_consents", + "columns": { + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "consented_at": { + "name": "consented_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_hash": { + "name": "config_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "recent_workdirs": { + "name": "recent_workdirs", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_recent_workdirs_last_used_at": { + "name": "idx_recent_workdirs_last_used_at", + "columns": [ + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "right_sidebar_tabs": { + "name": "right_sidebar_tabs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "right_sidebar_tabs_session_idx": { + "name": "right_sidebar_tabs_session_idx", + "columns": [ + "session_id", + "position" + ], + "isUnique": false + }, + "right_sidebar_tabs_subagents_singleton_idx": { + "name": "right_sidebar_tabs_subagents_singleton_idx", + "columns": [ + "session_id" + ], + "isUnique": true, + "where": "\"right_sidebar_tabs\".\"kind\" = 'subagents'" + }, + "right_sidebar_tabs_bot_artifacts_singleton_idx": { + "name": "right_sidebar_tabs_bot_artifacts_singleton_idx", + "columns": [ + "session_id" + ], + "isUnique": true, + "where": "\"right_sidebar_tabs\".\"kind\" = 'bot-artifacts'" + } + }, + "foreignKeys": { + "right_sidebar_tabs_session_id_sessions_id_fk": { + "name": "right_sidebar_tabs_session_id_sessions_id_fk", + "tableFrom": "right_sidebar_tabs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_runs": { + "name": "schedule_runs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fired_at": { + "name": "fired_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_value_usd": { + "name": "estimated_value_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_amount": { + "name": "cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_value_amount": { + "name": "estimated_value_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_is_approximate": { + "name": "cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cost_attribution": { + "name": "cost_attribution", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'legacy'" + }, + "result_text": { + "name": "result_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pre_run_hook_result": { + "name": "pre_run_hook_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_schedule_runs_schedule": { + "name": "idx_schedule_runs_schedule", + "columns": [ + "schedule_id", + "fired_at" + ], + "isUnique": false + }, + "idx_schedule_runs_running_schedule": { + "name": "idx_schedule_runs_running_schedule", + "columns": [ + "schedule_id" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"status\" = 'running'" + }, + "idx_schedule_runs_running_heartbeat": { + "name": "idx_schedule_runs_running_heartbeat", + "columns": [ + "heartbeat_at" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"status\" = 'running' AND \"schedule_runs\".\"heartbeat_at\" IS NOT NULL" + }, + "idx_schedule_runs_running_legacy": { + "name": "idx_schedule_runs_running_legacy", + "columns": [ + "fired_at" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"status\" = 'running' AND \"schedule_runs\".\"heartbeat_at\" IS NULL" + }, + "idx_schedule_runs_unread_terminal": { + "name": "idx_schedule_runs_unread_terminal", + "columns": [ + "schedule_id", + "status", + "fired_at" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"read_at\" IS NULL AND \"schedule_runs\".\"status\" IN ('success', 'failed', 'aborted', 'interrupted')" + }, + "idx_schedule_runs_session_latest": { + "name": "idx_schedule_runs_session_latest", + "columns": [ + "session_id", + "fired_at", + "id" + ], + "isUnique": false, + "where": "\"schedule_runs\".\"session_id\" IS NOT NULL" + } + }, + "foreignKeys": { + "schedule_runs_schedule_id_schedules_id_fk": { + "name": "schedule_runs_schedule_id_schedules_id_fk", + "tableFrom": "schedule_runs", + "tableTo": "schedules", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_runs_session_id_sessions_id_fk": { + "name": "schedule_runs_session_id_sessions_id_fk", + "tableFrom": "schedule_runs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_session_latest_runs": { + "name": "schedule_session_latest_runs", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fired_at": { + "name": "fired_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_schedule_session_latest_runs_run": { + "name": "idx_schedule_session_latest_runs_run", + "columns": [ + "run_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "schedule_session_latest_runs_session_id_sessions_id_fk": { + "name": "schedule_session_latest_runs_session_id_sessions_id_fk", + "tableFrom": "schedule_session_latest_runs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_session_latest_runs_run_id_schedule_runs_id_fk": { + "name": "schedule_session_latest_runs_run_id_schedule_runs_id_fk", + "tableFrom": "schedule_session_latest_runs", + "tableTo": "schedule_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedules": { + "name": "schedules", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_type": { + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'prompt'" + }, + "job_config": { + "name": "job_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "execution_mode": { + "name": "execution_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'agent'" + }, + "script_config": { + "name": "script_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "project_config_id": { + "name": "project_config_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "legacy_session_fallback": { + "name": "legacy_session_fallback", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cron'" + }, + "cron_expr": { + "name": "cron_expr", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recurring": { + "name": "recurring", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "manual": { + "name": "manual", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "interval_ms": { + "name": "interval_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_agent_kind": { + "name": "model_agent_kind", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "effort": { + "name": "effort", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fast_mode": { + "name": "fast_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_kind": { + "name": "workspace_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'project'" + }, + "use_worktree": { + "name": "use_worktree", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "target_session_id": { + "name": "target_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persistent_session": { + "name": "persistent_session", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "silent_when_idle": { + "name": "silent_when_idle", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "pre_run_hook_command": { + "name": "pre_run_hook_command", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pre_run_hook_timeout_ms": { + "name": "pre_run_hook_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "skip_log_session_id": { + "name": "skip_log_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notify_desktop": { + "name": "notify_desktop", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "notify_feishu": { + "name": "notify_feishu", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notify_wecom_group": { + "name": "notify_wecom_group", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_finished_at": { + "name": "last_finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "next_fire_at": { + "name": "next_fire_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expire_at": { + "name": "expire_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_schedules_active_next": { + "name": "idx_schedules_active_next", + "columns": [ + "status", + "next_fire_at" + ], + "isUnique": false + }, + "idx_schedules_target_session": { + "name": "idx_schedules_target_session", + "columns": [ + "target_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "schedules_target_session_id_sessions_id_fk": { + "name": "schedules_target_session_id_sessions_id_fk", + "tableFrom": "schedules", + "tableTo": "sessions", + "columnsFrom": [ + "target_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "schedules_skip_log_session_id_sessions_id_fk": { + "name": "schedules_skip_log_session_id_sessions_id_fk", + "tableFrom": "schedules", + "tableTo": "sessions", + "columnsFrom": [ + "skip_log_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_goals": { + "name": "session_goals", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "objective": { + "name": "objective", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "budget_tokens": { + "name": "budget_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_turns": { + "name": "max_turns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "no_progress_limit": { + "name": "no_progress_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "turns_used": { + "name": "turns_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tokens_used": { + "name": "tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "no_progress_streak": { + "name": "no_progress_streak", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "usage_reset_at": { + "name": "usage_reset_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reason": { + "name": "last_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_session_goals_status": { + "name": "idx_session_goals_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_goals_session_id_sessions_id_fk": { + "name": "session_goals_session_id_sessions_id_fk", + "tableFrom": "session_goals", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_pr_refs": { + "name": "session_pr_refs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repo": { + "name": "repo", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_session_pr_refs": { + "name": "uniq_session_pr_refs", + "columns": [ + "session_id", + "owner", + "repo", + "pr_number" + ], + "isUnique": true + }, + "idx_session_pr_refs_session_last_seen": { + "name": "idx_session_pr_refs_session_last_seen", + "columns": [ + "session_id", + "last_seen_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_pr_refs_session_id_sessions_id_fk": { + "name": "session_pr_refs_session_id_sessions_id_fk", + "tableFrom": "session_pr_refs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session_task_tags": { + "name": "session_task_tags", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_task_tags_tag_idx": { + "name": "session_task_tags_tag_idx", + "columns": [ + "tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_task_tags_session_id_sessions_id_fk": { + "name": "session_task_tags_session_id_sessions_id_fk", + "tableFrom": "session_task_tags", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_task_tags_tag_id_task_tags_id_fk": { + "name": "session_task_tags_tag_id_task_tags_id_fk", + "tableFrom": "session_task_tags", + "tableTo": "task_tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_task_tags_session_id_tag_id_pk": { + "columns": [ + "session_id", + "tag_id" + ], + "name": "session_task_tags_session_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'New Maker'" + }, + "working_dir": { + "name": "working_dir", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_kind": { + "name": "workspace_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'project'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'claude-sonnet-4-6'" + }, + "effort": { + "name": "effort", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'high'" + }, + "permission_mode": { + "name": "permission_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ask'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "sdk_session_id": { + "name": "sdk_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_token_usage": { + "name": "total_token_usage", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_usd": { + "name": "total_cost_usd", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_amount": { + "name": "total_cost_amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_cost_currency": { + "name": "total_cost_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_cost_is_approximate": { + "name": "total_cost_is_approximate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "context_tokens": { + "name": "context_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "context_window": { + "name": "context_window", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "context_window_runtime": { + "name": "context_window_runtime", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fast_mode": { + "name": "fast_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "plan_mode_enabled": { + "name": "plan_mode_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cleared_at": { + "name": "cleared_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_send_at": { + "name": "user_send_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cc'" + }, + "orca_role": { + "name": "orca_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "forked_at_message_id": { + "name": "forked_at_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_path": { + "name": "worktree_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'desktop'" + }, + "feishu_open_id": { + "name": "feishu_open_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feishu_bot_app_id": { + "name": "feishu_bot_app_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "im_bot_context_id": { + "name": "im_bot_context_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "im_user_id": { + "name": "im_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "im_default_route": { + "name": "im_default_route", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_project_context": { + "name": "used_project_context", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "codex_history_has_product_prompt": { + "name": "codex_history_has_product_prompt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "codex_plan_json": { + "name": "codex_plan_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "extra_dirs": { + "name": "extra_dirs", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "writable_dirs": { + "name": "writable_dirs", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "remote_host_id": { + "name": "remote_host_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_device_id": { + "name": "agent_device_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "orca_remote_lead": { + "name": "orca_remote_lead", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_turn_started_at": { + "name": "active_turn_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_turn_pid": { + "name": "active_turn_pid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_turn_ended_at": { + "name": "last_turn_ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_preview": { + "name": "list_preview", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_preview_role": { + "name": "list_preview_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "list_message_count": { + "name": "list_message_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sessions_updated_at": { + "name": "idx_sessions_updated_at", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "idx_sessions_user_send_at": { + "name": "idx_sessions_user_send_at", + "columns": [ + "user_send_at" + ], + "isUnique": false + }, + "idx_sessions_sdk_session_id": { + "name": "idx_sessions_sdk_session_id", + "columns": [ + "sdk_session_id" + ], + "isUnique": false + }, + "idx_sessions_workdir_created": { + "name": "idx_sessions_workdir_created", + "columns": [ + "working_dir", + "created_at", + "id" + ], + "isUnique": false + }, + "idx_sessions_created_at": { + "name": "idx_sessions_created_at", + "columns": [ + "created_at", + "id" + ], + "isUnique": false + }, + "idx_sessions_workspace_kind": { + "name": "idx_sessions_workspace_kind", + "columns": [ + "workspace_kind" + ], + "isUnique": false + }, + "idx_sessions_parent_session_id": { + "name": "idx_sessions_parent_session_id", + "columns": [ + "parent_session_id" + ], + "isUnique": false + }, + "idx_sessions_orca_role": { + "name": "idx_sessions_orca_role", + "columns": [ + "orca_role" + ], + "isUnique": false + }, + "idx_sessions_worktree_path": { + "name": "idx_sessions_worktree_path", + "columns": [ + "worktree_path" + ], + "isUnique": false + }, + "idx_sessions_feishu_lookup": { + "name": "idx_sessions_feishu_lookup", + "columns": [ + "source", + "feishu_bot_app_id", + "feishu_open_id" + ], + "isUnique": false + }, + "idx_sessions_im_lookup": { + "name": "idx_sessions_im_lookup", + "columns": [ + "source", + "im_bot_context_id", + "im_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_parent_session_id_sessions_id_fk": { + "name": "sessions_parent_session_id_sessions_id_fk", + "tableFrom": "sessions", + "tableTo": "sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shared_task_events": { + "name": "shared_task_events", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "shared_task_id": { + "name": "shared_task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "terminal": { + "name": "terminal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot": { + "name": "snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "shared_task_events_revision_idx": { + "name": "shared_task_events_revision_idx", + "columns": [ + "shared_task_id", + "kind", + "revision" + ], + "isUnique": true + }, + "shared_task_events_session_idx": { + "name": "shared_task_events_session_idx", + "columns": [ + "session_id", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "shared_task_events_session_id_sessions_id_fk": { + "name": "shared_task_events_session_id_sessions_id_fk", + "tableFrom": "shared_task_events", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "skill_usage_exposures": { + "name": "skill_usage_exposures", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "analyzer_version": { + "name": "analyzer_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'6'" + }, + "raw_file_path": { + "name": "raw_file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "raw_line_no": { + "name": "raw_line_no", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sdk_session_id": { + "name": "sdk_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skill_name": { + "name": "skill_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skill_path": { + "name": "skill_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "skill_document_hash": { + "name": "skill_document_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exposure_content_hash": { + "name": "exposure_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_hash_source": { + "name": "document_hash_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_use_id": { + "name": "tool_use_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seen_at": { + "name": "seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_call_count": { + "name": "tool_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "repeated_tool_call_count": { + "name": "repeated_tool_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tool_error_count": { + "name": "tool_error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "command_call_count": { + "name": "command_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "command_failure_count": { + "name": "command_failure_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_skill_usage_exposures_skill_document_version": { + "name": "idx_skill_usage_exposures_skill_document_version", + "columns": [ + "analyzer_version", + "skill_name", + "skill_document_hash" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_skill_recent": { + "name": "idx_skill_usage_exposures_skill_recent", + "columns": [ + "skill_name", + "analyzer_version", + "seen_at" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_skill_recent_any_version": { + "name": "idx_skill_usage_exposures_skill_recent_any_version", + "columns": [ + "skill_name", + "seen_at" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_analyzer_recent_source": { + "name": "idx_skill_usage_exposures_analyzer_recent_source", + "columns": [ + "analyzer_version", + "seen_at", + "raw_file_path" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_session": { + "name": "idx_skill_usage_exposures_session", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_skill_usage_exposures_raw_file": { + "name": "idx_skill_usage_exposures_raw_file", + "columns": [ + "raw_file_path" + ], + "isUnique": false + } + }, + "foreignKeys": { + "skill_usage_exposures_raw_file_path_skill_usage_sources_raw_file_path_fk": { + "name": "skill_usage_exposures_raw_file_path_skill_usage_sources_raw_file_path_fk", + "tableFrom": "skill_usage_exposures", + "tableTo": "skill_usage_sources", + "columnsFrom": [ + "raw_file_path" + ], + "columnsTo": [ + "raw_file_path" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "skill_usage_sources": { + "name": "skill_usage_sources", + "columns": { + "raw_file_path": { + "name": "raw_file_path", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "analyzer_version": { + "name": "analyzer_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'6'" + }, + "agent_kind": { + "name": "agent_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sdk_session_id": { + "name": "sdk_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mtime_ms": { + "name": "mtime_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_scanned_at": { + "name": "last_scanned_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ok'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_skill_usage_sources_session": { + "name": "idx_skill_usage_sources_session", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "idx_skill_usage_sources_sdk_session": { + "name": "idx_skill_usage_sources_sdk_session", + "columns": [ + "sdk_session_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subagent_run_aliases": { + "name": "subagent_run_aliases", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "subagent_run_aliases_lookup_idx": { + "name": "subagent_run_aliases_lookup_idx", + "columns": [ + "session_id", + "provider", + "alias", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "subagent_run_aliases_session_id_sessions_id_fk": { + "name": "subagent_run_aliases_session_id_sessions_id_fk", + "tableFrom": "subagent_run_aliases", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "subagent_run_aliases_run_id_subagent_runs_id_fk": { + "name": "subagent_run_aliases_run_id_subagent_runs_id_fk", + "tableFrom": "subagent_run_aliases", + "tableTo": "subagent_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "subagent_run_aliases_run_id_alias_pk": { + "columns": [ + "run_id", + "alias" + ], + "name": "subagent_run_aliases_run_id_alias_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "subagent_runs": { + "name": "subagent_runs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logical_agent_id": { + "name": "logical_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_tool_use_id": { + "name": "parent_tool_use_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "provider_run_ids": { + "name": "provider_run_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'running'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "returned_result": { + "name": "returned_result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "returned_result_empty": { + "name": "returned_result_empty", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "returned_result_truncated": { + "name": "returned_result_truncated", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reasoning_effort": { + "name": "reasoning_effort", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tool_uses": { + "name": "tool_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "activity": { + "name": "activity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rewind_at": { + "name": "rewind_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "subagent_runs_logical_idx": { + "name": "subagent_runs_logical_idx", + "columns": [ + "session_id", + "provider", + "logical_agent_id" + ], + "isUnique": false + }, + "subagent_runs_session_idx": { + "name": "subagent_runs_session_idx", + "columns": [ + "session_id", + "rewind_at", + "deleted_at", + "started_at" + ], + "isUnique": false + }, + "subagent_runs_parent_tool_use_idx": { + "name": "subagent_runs_parent_tool_use_idx", + "columns": [ + "session_id", + "parent_tool_use_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "subagent_runs_session_id_sessions_id_fk": { + "name": "subagent_runs_session_id_sessions_id_fk", + "tableFrom": "subagent_runs", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_tags": { + "name": "task_tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_customized": { + "name": "name_customized", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "favorite_order": { + "name": "favorite_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "task_tags_name_idx": { + "name": "task_tags_name_idx", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vec_table_meta": { + "name": "vec_table_meta", + "columns": { + "vec_table": { + "name": "vec_table", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dim": { + "name": "dim", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registered_at": { + "name": "registered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_file_attachments": { + "name": "wechat_file_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "abs_path": { + "name": "abs_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'staged'" + }, + "promoted_at": { + "name": "promoted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_wechat_file_attachments_task": { + "name": "idx_wechat_file_attachments_task", + "columns": [ + "binding_epoch", + "task_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "wechat_file_attachments_binding_epoch_wechat_sync_state_binding_epoch_fk": { + "name": "wechat_file_attachments_binding_epoch_wechat_sync_state_binding_epoch_fk", + "tableFrom": "wechat_file_attachments", + "tableTo": "wechat_sync_state", + "columnsFrom": [ + "binding_epoch" + ], + "columnsTo": [ + "binding_epoch" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_file_attachments_task_id_wechat_inbox_id_fk": { + "name": "wechat_file_attachments_task_id_wechat_inbox_id_fk", + "tableFrom": "wechat_file_attachments", + "tableTo": "wechat_inbox", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_file_attachments_session_id_sessions_id_fk": { + "name": "wechat_file_attachments_session_id_sessions_id_fk", + "tableFrom": "wechat_file_attachments", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_inbox": { + "name": "wechat_inbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform_message_id": { + "name": "platform_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform_seq": { + "name": "platform_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "peer_id": { + "name": "peer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform_created_at": { + "name": "platform_created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "lease_until": { + "name": "lease_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_epoch": { + "name": "conversation_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_nonce": { + "name": "context_nonce", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_ciphertext": { + "name": "context_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_tag": { + "name": "context_tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_wechat_inbox_platform_message": { + "name": "uniq_wechat_inbox_platform_message", + "columns": [ + "binding_epoch", + "platform_message_id" + ], + "isUnique": true + }, + "idx_wechat_inbox_queue": { + "name": "idx_wechat_inbox_queue", + "columns": [ + "binding_epoch", + "status", + "received_at" + ], + "isUnique": false + }, + "idx_wechat_inbox_lease": { + "name": "idx_wechat_inbox_lease", + "columns": [ + "binding_epoch", + "lease_until" + ], + "isUnique": false + }, + "idx_wechat_inbox_conversation": { + "name": "idx_wechat_inbox_conversation", + "columns": [ + "binding_epoch", + "peer_id", + "conversation_epoch" + ], + "isUnique": false + }, + "uniq_wechat_inbox_running_session": { + "name": "uniq_wechat_inbox_running_session", + "columns": [ + "binding_epoch", + "session_id" + ], + "isUnique": true, + "where": "\"wechat_inbox\".\"session_id\" IS NOT NULL AND \"wechat_inbox\".\"status\" IN ('dispatching', 'accepted_running', 'waiting_desktop', 'delivery_pending')" + } + }, + "foreignKeys": { + "wechat_inbox_binding_epoch_wechat_sync_state_binding_epoch_fk": { + "name": "wechat_inbox_binding_epoch_wechat_sync_state_binding_epoch_fk", + "tableFrom": "wechat_inbox", + "tableTo": "wechat_sync_state", + "columnsFrom": [ + "binding_epoch" + ], + "columnsTo": [ + "binding_epoch" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_inbox_session_id_sessions_id_fk": { + "name": "wechat_inbox_session_id_sessions_id_fk", + "tableFrom": "wechat_inbox", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_outbox": { + "name": "wechat_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "media_json": { + "name": "media_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uniq_wechat_outbox_client_id": { + "name": "uniq_wechat_outbox_client_id", + "columns": [ + "binding_epoch", + "client_id" + ], + "isUnique": true + }, + "idx_wechat_outbox_delivery": { + "name": "idx_wechat_outbox_delivery", + "columns": [ + "binding_epoch", + "status", + "next_retry_at" + ], + "isUnique": false + }, + "idx_wechat_outbox_task": { + "name": "idx_wechat_outbox_task", + "columns": [ + "binding_epoch", + "task_id", + "chunk_index" + ], + "isUnique": false + } + }, + "foreignKeys": { + "wechat_outbox_binding_epoch_wechat_sync_state_binding_epoch_fk": { + "name": "wechat_outbox_binding_epoch_wechat_sync_state_binding_epoch_fk", + "tableFrom": "wechat_outbox", + "tableTo": "wechat_sync_state", + "columnsFrom": [ + "binding_epoch" + ], + "columnsTo": [ + "binding_epoch" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wechat_outbox_task_id_wechat_inbox_id_fk": { + "name": "wechat_outbox_task_id_wechat_inbox_id_fk", + "tableFrom": "wechat_outbox", + "tableTo": "wechat_inbox", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "wechat_sync_state": { + "name": "wechat_sync_state", + "columns": { + "binding_epoch": { + "name": "binding_epoch", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sync_cursor": { + "name": "sync_cursor", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "last_poll_at": { + "name": "last_poll_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uniq_wechat_sync_active": { + "name": "uniq_wechat_sync_active", + "columns": [ + "is_active" + ], + "isUnique": true, + "where": "\"wechat_sync_state\".\"is_active\" = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uniq_orca_worker_creation_reservations_team_label": { + "columns": { + "lower(\"label\")": { + "isExpression": true + } + } + }, + "uniq_orca_workers_team_label": { + "columns": { + "lower(\"label\")": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/apps/desktop/drizzle/meta/_journal.json b/apps/desktop/drizzle/meta/_journal.json index 8bb39609fd5..a9a5bcb755a 100644 --- a/apps/desktop/drizzle/meta/_journal.json +++ b/apps/desktop/drizzle/meta/_journal.json @@ -869,6 +869,20 @@ "when": 1791260458468, "tag": "0123_wonderful_oracle", "breakpoints": true + }, + { + "idx": 124, + "version": "6", + "when": 1791526759742, + "tag": "0124_funny_glorian", + "breakpoints": true + }, + { + "idx": 125, + "version": "6", + "when": 1791563747047, + "tag": "0125_magical_night_thrasher", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/desktop/drizzle/scripts/0124_funny_glorian.ts b/apps/desktop/drizzle/scripts/0124_funny_glorian.ts new file mode 100644 index 00000000000..8352f0f7acc --- /dev/null +++ b/apps/desktop/drizzle/scripts/0124_funny_glorian.ts @@ -0,0 +1,34 @@ +import type Database from 'better-sqlite3'; + +function columnNames(db: Database.Database, tableName: string): Set { + return new Set( + db + .prepare(`PRAGMA table_info('${tableName}')`) + .all() + .map((row) => String((row as { name: unknown }).name)), + ); +} + +function addMissingColumns( + db: Database.Database, + tableName: string, + columns: Array<[string, string]>, +): void { + const existing = columnNames(db, tableName); + if (existing.size === 0) return; + for (const [name, type] of columns) { + if (!existing.has(name)) db.exec(`ALTER TABLE \`${tableName}\` ADD \`${name}\` ${type}`); + } +} + +function run(db: Database.Database): void { + addMissingColumns(db, 'orca_workers', [ + ['execution_device_id', 'text'], + ['remote_session_id', 'text'], + ['last_bridged_message_id', 'text'], + ['remote_released_at', 'integer'], + ]); + addMissingColumns(db, 'sessions', [['orca_remote_lead', 'text']]); +} + +module.exports = { run }; diff --git a/apps/desktop/drizzle/scripts/0125_magical_night_thrasher.ts b/apps/desktop/drizzle/scripts/0125_magical_night_thrasher.ts new file mode 100644 index 00000000000..a355dc984b7 --- /dev/null +++ b/apps/desktop/drizzle/scripts/0125_magical_night_thrasher.ts @@ -0,0 +1,19 @@ +import type Database from 'better-sqlite3'; + +function run(db: Database.Database): void { + const columns = new Set( + db + .prepare("PRAGMA table_info('orca_workers')") + .all() + .map((row) => String((row as { name: unknown }).name)), + ); + if (columns.size === 0) return; + for (const [name, type] of [ + ['pending_remote_report', 'text'], + ['remote_stop_confirmed_at', 'integer'], + ]) { + if (!columns.has(name)) db.exec('ALTER TABLE orca_workers ADD ' + name + ' ' + type); + } +} + +module.exports = { run }; diff --git a/apps/desktop/forge.config.ts b/apps/desktop/forge.config.ts index d7e740e641b..880d0fe1128 100644 --- a/apps/desktop/forge.config.ts +++ b/apps/desktop/forge.config.ts @@ -936,6 +936,8 @@ const MACOS_VOICE_HELPER_DEPLOYMENT_TARGET = 'macos10.15'; const MACOS_AGENT_ISLAND_HELPER_DEPLOYMENT_TARGET = 'macos14.0'; const MACOS_COMPUTER_PERMISSION_GUIDE_HELPER_DEPLOYMENT_TARGET = 'macos13.0'; const MACOS_SESSION_DRAG_RELEASE_HELPER_DEPLOYMENT_TARGET = 'macos10.15'; +// Swift Charts and the macOS 14 SwiftUI animation APIs. +const MACOS_MENUBAR_HELPER_DEPLOYMENT_TARGET = 'macos14.0'; const MACOS_XBOX_GAMEPAD_HELPER_DEPLOYMENT_TARGET = 'macos11.0'; const MACOS_REMOTE_DESKTOP_INPUT_DEPLOYMENT_TARGET = 'macos10.15'; @@ -1627,6 +1629,39 @@ function buildMacSessionDragReleaseHelper(platform: ForgePlatform, arch: ForgeAr ); } +function buildMacMenubarHelper(platform: ForgePlatform, arch: ForgeArch): void { + if (process.platform !== 'darwin' || !isMacForgePlatform(platform)) return; + const srcDir = path.join(__dirname, 'native', 'menubar'); + const src = path.join(srcDir, 'macos-menubar-helper.swift'); + const destDir = path.join(__dirname, 'resources', 'tools', 'menubar'); + const dest = path.join(destDir, 'xdt-macos-menubar-helper'); + const assets = ['wordmark-light.png', 'wordmark-dark.png']; + if (!fs.existsSync(src)) { + throw new Error(`[forge] macOS menu bar helper source missing at ${src}`); + } + for (const asset of assets) { + if (!fs.existsSync(path.join(srcDir, asset))) { + throw new Error(`[forge] macOS menu bar helper asset missing: ${asset}`); + } + } + fs.mkdirSync(destDir, { recursive: true }); + buildSwiftHelperForForgeArch( + src, + dest, + arch, + MACOS_MENUBAR_HELPER_DEPLOYMENT_TARGET, + // The helper uses @main, which requires library parsing mode. + ['-O', '-parse-as-library'], + 'menu bar helper', + ); + for (const asset of assets) fs.copyFileSync(path.join(srcDir, asset), path.join(destDir, asset)); + fs.chmodSync(dest, 0o755); + const sizeMb = (fs.statSync(dest).size / (1024 * 1024)).toFixed(2); + console.log( + `[forge:prePackage] macOS menu bar helper (${swiftArchLabel(arch, MACOS_MENUBAR_HELPER_DEPLOYMENT_TARGET)}) -> ${dest} (${sizeMb} MB)`, + ); +} + // MakerNSIS is Windows-only (native dependency), conditionally require to // avoid import errors on macOS / Linux. const makers: ForgeConfig['makers'] = [ @@ -1938,6 +1973,7 @@ const config: ForgeConfig = { buildMacAgentIslandHelper(platform, arch); buildMacComputerPermissionGuideHelper(platform, arch); buildMacSessionDragReleaseHelper(platform, arch); + buildMacMenubarHelper(platform, arch); buildRemoteDesktopInput(platform, arch); }, // packaged dir 产出后、makers 跑之前签内部 .exe。这样 NSIS 包出来的 diff --git a/apps/desktop/help-knowledge/appearance.md b/apps/desktop/help-knowledge/appearance.md index 13134141a40..316ef454f44 100644 --- a/apps/desktop/help-knowledge/appearance.md +++ b/apps/desktop/help-knowledge/appearance.md @@ -11,7 +11,8 @@ Settings > General controls how the app looks and how it notifies you. - **Theme**: light, dark, or system (follows your OS). - **Theme family**: pick from the registered theme families; each family supplies its own colors for light and dark. The app uses a VSCode-style token system (see docs/design-rules/cindy-design-system.md) so themes only override what they need. -- **Fonts and sidebar density**: the same section also has UI / code font-family pickers with font-size sliders, and a selector for the sidebar's session card mode. +- **Fonts**: choose fonts and sizes for interface text and code. Code font size changes code blocks, change comparisons, and editors; inline code, status numbers, badges, and path labels keep their size. +- **Pinned Style**: show pinned sessions as Text, Cards, or List. **List Style** controls unpinned sessions: Text uses one line; List adds a preview. - **Local theme brand identity**: this is an optional power-user feature configured in the local theme JSON. The settings page intentionally keeps only create-copy, open-folder, and refresh actions. New copies include self-explanatory example paths directly in the JSON. - **Export / open local theme files**: export the current theme's tokens to a file or open a local theme JSON for inspection / sharing. To replace the icon and logo used on the new-chat page: @@ -28,11 +29,11 @@ Settings > General controls how the app looks and how it notifies you. **Language:** -- Display language: System / English / 简体中文 / 繁體中文 / 日本語 / 한국어. Affects UI text only; agent replies follow your prompt and personalization, not this setting. +- Display language: System / English / 简体中文 / 繁體中文 / 日本語 / 한국어. Used for the interface and AI error reports. Other replies follow your message or language request. On mobile, the phone's display language is used for AI error reports for messages sent from that phone. **Notifications:** -- **Desktop notification on session finished** — OS-level ping when an agent completes its reply. +- **System Notifications** — notify when a session finishes, encounters an error, or needs a reply. Regular sessions notify only while the window is in the background. When Agent Island is enabled, it handles these alerts instead. - **FeiShu DM on session finished** — requires the FeiShu bot to be configured (see the FeiShu bot topic). **Notes:** diff --git a/apps/desktop/help-knowledge/feishu-bot.md b/apps/desktop/help-knowledge/feishu-bot.md index 207c2768dad..d5b9058ff45 100644 --- a/apps/desktop/help-knowledge/feishu-bot.md +++ b/apps/desktop/help-knowledge/feishu-bot.md @@ -19,7 +19,7 @@ Cindy can send DMs and take commands through a FeiShu (Lark) bot. (This is unrel **Notifications:** - Toggle **message notifications** on the same page to get bot DMs when sessions finish, or when the bot has updates for you. -- The desktop-OS-level "session finished" notification is a separate toggle in **Settings > General > Notifications**. +- **System Notifications** is a separate toggle in **Settings > General > Notifications**. It covers sessions finishing, encountering an error, or needing a reply. See the appearance topic for background-window and Agent Island behavior. **Group chats:** diff --git a/apps/desktop/native/menubar/macos-menubar-helper.swift b/apps/desktop/native/menubar/macos-menubar-helper.swift new file mode 100644 index 00000000000..a9bcdafa565 --- /dev/null +++ b/apps/desktop/native/menubar/macos-menubar-helper.swift @@ -0,0 +1,2927 @@ +// Cindy menu bar quota helper (macOS). +// +// Main (TypeScript) owns data, product wording and settings; it sends a complete view +// model as newline-delimited JSON on stdin. This process owns the NSStatusItem, the panel +// window and the SwiftUI pages, and reports user intent back on stdout. +// +// Built with `-parse-as-library` so SwiftUI state behaves normally (the top-level script +// mode used by the Agent Island helper drops `@State` initial values). + +import AppKit +import Charts +import Combine +import SwiftUI + +// MARK: - Protocol + +struct MBSettings: Codable, Equatable { + var enabled: Bool + var order: [String] + var hidden: [String] + var shown: [String] + var statusText: String + var lowAlert: Bool + var privacy: Bool + var scope: String +} + +struct MBCell: Decodable, Identifiable, Equatable { + let id: String + let kind: String + let logoSvg: String + let name: String + let value: String? + let valueUnit: String? + let valueLabel: String? + let level: String + let fill: Double? + let reserve: Bool + let emphasized: Bool + let when: String + let whenLevel: String + let accessibilityLabel: String +} + +struct MBRing: Decodable, Equatable { + let fraction: Double + let color: String +} + +struct MBFact: Decodable, Equatable, Hashable { + let label: String + let value: String + let sub: String +} + +struct MBAction: Decodable, Equatable { + let kind: String + let providerId: String? +} + +struct MBLegend: Decodable, Equatable, Hashable { + let label: String + let color: String +} + +struct MBDetail: Decodable, Equatable { + let id: String + let logoSvg: String + let title: String + let plan: String + let rings: [MBRing] + let bigValue: String + let bigUnit: String? + let bigColor: String? + let bigCompact: Bool + let bigCaption: String + let legend: [MBLegend] + let sayPrimary: String + let saySecondary: String? + let facts: [MBFact] + let actionLabel: String + let action: MBAction +} + +struct MBTask: Decodable, Identifiable, Equatable { + var id: String { sessionId } + let sessionId: String + let title: String + let harness: String + let logoSvg: String + let value: Double + let tokens: Double + let running: Bool + let startedAtMs: Double? + let idleStatus: String + let meta: String +} + +struct MBModelUsage: Decodable, Equatable { + let name: String + let harness: String + let money: Double + let tokens: Double +} + +struct MBDaily: Decodable, Equatable { + let money: [Double] + let paid: Double + let tokens: [Double] +} + +struct MBScopeData: Decodable, Equatable { + let daily: [MBDaily] + let models: [String: [MBModelUsage]] + let note: String +} + +struct MBUsage: Decodable, Equatable { + let days: [String] + let scopes: [String: MBScopeData] +} + +struct MBManageRow: Decodable, Identifiable, Equatable { + let id: String + let logoSvg: String + let title: String + let subtitle: String + let value: String + let hidden: Bool +} + +struct MBView: Decodable, Equatable { + let locale: String + let moneySymbol: String + let strings: [String: String] + let statusTextByMode: [String: String?] + let updatedAtMs: Double? + let headlinePrimary: String + let headlineSecondary: String? + let sources: [MBCell] + let details: [String: MBDetail] + let taskSummary: String + let tasks: [MBTask] + let usage: MBUsage + let manage: [MBManageRow] + let settings: MBSettings + /// "sign-in" until the user signs in or skips sign-in; nothing was read. + let gate: String? +} + +struct MBIncoming: Decodable { + let type: String + let view: MBView? +} + +enum Bridge { + private static let lock = NSLock() + + static func emit(_ payload: [String: Any]) { + lock.lock() + defer { lock.unlock() } + guard let data = try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]) else { + FileHandle.standardError.write(Data("menubar: could not encode event\n".utf8)) + return + } + FileHandle.standardOutput.write(data) + FileHandle.standardOutput.write(Data([0x0a])) + } + + static func emitSettings(_ patch: [String: Any]) { + emit(["type": "settings", "patch": patch]) + } +} + +// MARK: - Palette + +enum Palette { + private static func hex(_ value: UInt32, _ alpha: CGFloat = 1) -> NSColor { + NSColor( + srgbRed: CGFloat((value >> 16) & 0xFF) / 255, + green: CGFloat((value >> 8) & 0xFF) / 255, + blue: CGFloat(value & 0xFF) / 255, + alpha: alpha + ) + } + + private static func dynamic(_ light: NSColor, _ dark: NSColor) -> Color { + Color(nsColor: NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? dark : light + }) + } + + static let ink = dynamic(hex(0x0B0B0B), hex(0xF5F5F4)) + static let ink2 = dynamic(NSColor(white: 0, alpha: 0.52), NSColor(white: 1, alpha: 0.58)) + static let ink3 = dynamic(NSColor(white: 0, alpha: 0.32), NSColor(white: 1, alpha: 0.36)) + static let ink4 = dynamic(NSColor(white: 0, alpha: 0.14), NSColor(white: 1, alpha: 0.16)) + static let ink5 = dynamic(NSColor(white: 0, alpha: 0.06), NSColor(white: 1, alpha: 0.07)) + static let line = dynamic(NSColor(white: 0, alpha: 0.08), NSColor(white: 1, alpha: 0.08)) + static let tile = dynamic(NSColor(white: 1, alpha: 0.62), NSColor(white: 1, alpha: 0.06)) + static let edge = dynamic(NSColor(white: 1, alpha: 0.75), NSColor(white: 1, alpha: 0.16)) + static let fill = dynamic(hex(0x111111), hex(0xF5F5F4)) + /// Text on an `ink` pill. + static let onInk = dynamic(hex(0xFFFFFF), hex(0x111111)) + static let warn = Color(nsColor: hex(0xEA6B17)) + static let error = Color(nsColor: hex(0xD91F37)) + static let focus = Color(nsColor: hex(0x417CDD)) + static let claude = Color(nsColor: hex(0xD97757)) + static let claude2 = Color(nsColor: hex(0xE8A184)) + static let claude3 = Color(nsColor: hex(0xF2C9B6)) + static let codex = Color(nsColor: hex(0x7A9DFF)) + static let pi = dynamic(hex(0x14B8A6), hex(0x2DD4BF)) + + static func named(_ name: String?) -> Color { + switch name { + case "ink2": return ink2 + case "ink3": return ink3 + case "fill": return fill + case "warn": return warn + case "error": return error + case "claude": return claude + case "claude2": return claude2 + case "claude3": return claude3 + case "codex": return codex + case "pi": return pi + default: return ink + } + } + + static func level(_ level: String) -> Color? { + switch level { + case "warn": return warn + case "error": return error + default: return nil + } + } + + static func harness(_ harness: String) -> Color { + switch harness { + case "claude-code": return claude + case "pi": return pi + default: return codex + } + } +} + +/// The open/fill curve shared by tubes, rings, page slides and the panel width. +enum Motion { + static let fill = Animation.timingCurve(0.22, 1, 0.36, 1, duration: 0.5) + static let page = Animation.timingCurve(0.22, 1, 0.36, 1, duration: 0.5) + static let quick = Animation.easeOut(duration: 0.2) +} + +// MARK: - Formatting + +enum Fmt { + /// Currency symbol of the usage ledger ($ on Global, ¥ in mainland China); set by main. + @MainActor static var symbol = "$" + + @MainActor static func money(_ value: Double) -> String { + if value >= 10_000 { return symbol + String(format: "%.1fk", value / 1000) } + if value >= 100 { return symbol + grouped(value.rounded()) } + return symbol + String(format: "%.2f", value) + } + + static func tokens(_ value: Double) -> String { + let millions = value / 1_000_000 + if millions >= 1000 { return String(format: "%.1fB", millions / 1000) } + if millions >= 1 { return "\(Int(millions.rounded()))M" } + if value >= 1000 { return "\(Int((value / 1000).rounded()))K" } + return "\(Int(max(0, value).rounded()))" + } + + static func grouped(_ value: Double) -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.locale = Locale(identifier: "en_US") + formatter.maximumFractionDigits = 0 + return formatter.string(from: NSNumber(value: value)) ?? String(Int(value)) + } + + static func clock(_ seconds: Double) -> String { + let total = max(0, Int(seconds)) + return "\(total / 60):" + String(format: "%02d", total % 60) + } + + static func template(_ template: String, _ values: [String: String]) -> String { + var output = template + for (key, value) in values { + output = output.replacingOccurrences(of: "{{\(key)}}", with: value) + } + return output + } + + private static var dayCache: [String: Date] = [:] + + static func day(_ iso: String) -> Date { + if let cached = dayCache[iso] { return cached } + var components = DateComponents() + let parts = iso.split(separator: "-").compactMap { Int($0) } + if parts.count == 3 { + components.year = parts[0] + components.month = parts[1] + components.day = parts[2] + components.hour = 12 + } + let date = Calendar.current.date(from: components) ?? Date() + dayCache[iso] = date + return date + } + + static func monthDay(_ date: Date, locale: String) -> String { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: locale) + formatter.setLocalizedDateFormatFromTemplate("MMMd") + return formatter.string(from: date) + } + + static func weekday(_ date: Date, locale: String) -> String { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: locale) + formatter.setLocalizedDateFormatFromTemplate("EEE") + return formatter.string(from: date) + } + + static func clockTime(_ date: Date, locale: String) -> String { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: locale) + formatter.setLocalizedDateFormatFromTemplate("MMMd EEE HH:mm") + return formatter.string(from: date) + } +} + +// MARK: - Logos + +enum Logos { + private static var cache: [String: NSImage] = [:] + + /// Main sends this instead of the Cindy mark's long path; see `MENUBAR_CINDY_LOGO`. + static let cindySentinel = "cindy:mark" + + static func image(_ svg: String) -> NSImage? { + if let cached = cache[svg] { return cached } + let source = svg == cindySentinel + ? "" + : svg + guard let data = source.data(using: .utf8), let image = NSImage(data: data) else { return nil } + image.isTemplate = true + cache[svg] = image + return image + } + + /// Cindy "A" mark on an 18 pt canvas with 14 pt ink, the menu bar size from the brand kit. + /// The mark is fitted to 17pt of the 18pt canvas (its path bounds are x 2.4–21.5, + /// y 1.2–22 in a 24-unit box) so it matches the optical size of other menu bar extras. + static let statusIcon: NSImage? = { + let svg = """ + + """ + guard let data = svg.data(using: .utf8), let image = NSImage(data: data) else { return nil } + image.isTemplate = true + image.size = NSSize(width: 18, height: 18) + image.accessibilityDescription = "Cindy" + return image + }() +} + +struct LogoView: View { + let svg: String + var size: CGFloat = 13 + var color: Color = Palette.ink + + var body: some View { + if let image = Logos.image(svg) { + Image(nsImage: image) + .renderingMode(.template) + .resizable() + .interpolation(.high) + .aspectRatio(contentMode: .fit) + .frame(width: size, height: size) + .foregroundStyle(color) + } else { + Color.clear.frame(width: size, height: size) + } + } +} + +// MARK: - Store + +enum Page: Equatable { + case main + case detail(String) + case wide + case manage +} + +enum WideList: String { + case models + case tasks +} + +@MainActor +final class MenubarStore: ObservableObject { + @Published var view: MBView? + @Published var page: Page = .main + /// Bumped every time the panel opens; tubes and rings replay their fill from zero. + @Published var fillToken = 0 + @Published var refreshing = false + @Published var wideRange = 30 + @Published var wideMoney = true + @Published var wideScope = "all" + @Published var wideList: WideList = .models + /// Manage list order while the user drags, before main echoes the saved settings back. + @Published var draftOrder: [String]? + @Published var mainContentHeight: CGFloat = 0 + /// Width of the narrow pages; follows the number of tubes so a few don't sit in a wide panel. + @Published var narrowWidth: CGFloat = StatusController.narrowWidth + /// Natural height of the open detail page; the panel grows to fit it when main is shorter. + @Published var detailContentHeight: CGFloat = 0 + /// Natural height of the manage page; the panel takes it instead of the main page's height. + @Published var manageContentHeight: CGFloat = 0 + @Published var shakeId: String? + + var strings: [String: String] { view?.strings ?? [:] } + + func s(_ key: String) -> String { strings[key] ?? key } + + /// Fills `{{name}}` placeholders. A count in `n` picks the `_one` / `_other` variant when + /// main sent one, so languages with singular forms read naturally. + func s(_ key: String, _ values: [String: String]) -> String { + var resolved = key + if let n = values["n"], let count = Int(n) { + let variant = "\(key)_\(count == 1 ? "one" : "other")" + if strings[variant] != nil { resolved = variant } + } + return Fmt.template(s(resolved), values) + } + + var locale: String { view?.locale ?? "en" } + + func apply(_ next: MBView) { + let firstView = view == nil + Fmt.symbol = next.moneySymbol + view = next + if refreshing { refreshing = false } + if let draft = draftOrder, draft == next.settings.order || draftMatches(next) { + draftOrder = nil + } + if firstView { wideScope = next.settings.scope } + if case .detail(let id) = page, next.details[id] == nil { page = .main } + if next.gate != nil, page == .wide { page = .main } + let width = Self.narrowWidth(tubes: next.gate == nil ? next.sources.filter { $0.fill != nil }.count : 0) + if width != narrowWidth { narrowWidth = width } + } + + /// Five or more tubes use the full 404pt; fewer give width back, down to 340pt, which still + /// fits the captions, task rows and the month card. + static func narrowWidth(tubes: Int) -> CGFloat { + switch tubes { + case ...2: return 340 + case 3: return 356 + case 4: return 380 + default: return StatusController.narrowWidth + } + } + + private func draftMatches(_ next: MBView) -> Bool { + guard let draft = draftOrder else { return false } + return next.manage.map(\.id) == draft + } + + func go(_ next: Page) { + if next == .wide, let view { wideScope = view.settings.scope } + withAnimation(Motion.page) { page = next } + } + + func refresh() { + guard !refreshing else { return } + refreshing = true + Bridge.emit(["type": "refresh"]) + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { [weak self] in + self?.refreshing = false + } + } + + var manageRows: [MBManageRow] { + guard let view else { return [] } + guard let draft = draftOrder else { return view.manage } + let byId = Dictionary(uniqueKeysWithValues: view.manage.map { ($0.id, $0) }) + return draft.compactMap { byId[$0] } + } + + /// Live reorder while dragging: the dragged row takes the slot of the row it enters, so + /// entering the next row down moves it down by one (and up likewise). + func move(_ id: String, into target: String) { + var order = manageRows.map(\.id) + guard let from = order.firstIndex(of: id), let to = order.firstIndex(of: target), from != to else { return } + order.remove(at: from) + order.insert(id, at: to) + withAnimation(.spring(response: 0.3, dampingFraction: 0.86)) { draftOrder = order } + } + + func moveBy(_ id: String, offset: Int) { + var order = manageRows.map(\.id) + guard let from = order.firstIndex(of: id) else { return } + let to = from + offset + guard to >= 0, to < order.count else { return } + order.swapAt(from, to) + withAnimation(.spring(response: 0.3, dampingFraction: 0.86)) { draftOrder = order } + commitOrder() + } + + func commitOrder() { + guard let draft = draftOrder else { return } + Bridge.emitSettings(["order": draft]) + } + + func toggleHidden(_ row: MBManageRow) { + guard let view else { return } + var hidden = Set(view.settings.hidden) + var shown = Set(view.settings.shown) + if row.hidden { + hidden.remove(row.id) + shown.insert(row.id) + } else { + let visibleCount = view.manage.filter { !$0.hidden }.count + if visibleCount <= 1 { + shakeId = row.id + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in self?.shakeId = nil } + return + } + hidden.insert(row.id) + shown.remove(row.id) + } + Bridge.emitSettings(["hidden": Array(hidden).sorted(), "shown": Array(shown).sorted()]) + } +} + +// MARK: - Panel window + +final class MenubarPanel: NSPanel { + override var canBecomeKey: Bool { true } + override var canBecomeMain: Bool { false } +} + +/// Rounded, frosted backing for the panel. Uses the system popover material so the panel +/// reads like other menu bar extras in both appearances. +final class PanelBackgroundView: NSVisualEffectView { + static let radius: CGFloat = 26 + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + material = .popover + blendingMode = .behindWindow + state = .active + maskImage = PanelBackgroundView.mask(radius: PanelBackgroundView.radius) + } + + required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") } + + static func mask(radius: CGFloat) -> NSImage { + let edge = radius * 2 + 1 + let image = NSImage(size: NSSize(width: edge, height: edge), flipped: false) { rect in + NSColor.black.setFill() + NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius).fill() + return true + } + image.capInsets = NSEdgeInsets(top: radius, left: radius, bottom: radius, right: radius) + image.resizingMode = .stretch + return image + } +} + +@MainActor +final class StatusController: NSObject, NSWindowDelegate { + static let narrowWidth: CGFloat = 404 + static let wideWidth: CGFloat = 820 + private static let screenGap: CGFloat = 6 + private static let sideMargin: CGFloat = 12 + + let store: MenubarStore + private let statusItem: NSStatusItem + private var panel: MenubarPanel? + private var hosting: NSHostingView? + private var outsideMonitor: Any? + private var keyMonitor: Any? + private var cancellables = Set() + private(set) var isOpen = false + private var closing = false + private var openRetries = 0 + /** True while the open animation runs; a frame set directly then would be overwritten. */ + private var openAnimating = false + /** When a press on the status item closed the panel; that click's own action is then ignored. */ + private var closedByStatusPressAt: TimeInterval? + + init(store: MenubarStore) { + self.store = store + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + super.init() + if let button = statusItem.button { + button.image = Logos.statusIcon + button.imagePosition = .imageLeading + button.target = self + button.action = #selector(toggle) + button.sendAction(on: [.leftMouseUp, .rightMouseUp]) + button.setAccessibilityLabel("Cindy") + } + store.$view + .receive(on: RunLoop.main) + .sink { [weak self] _ in self?.updateStatusTitle() } + .store(in: &cancellables) + store.$page + .removeDuplicates() + .receive(on: RunLoop.main) + .sink { [weak self] page in self?.layoutPanel(for: page, animated: true) } + .store(in: &cancellables) + store.$mainContentHeight + .removeDuplicates() + .receive(on: RunLoop.main) + .sink { [weak self] _ in + guard let self, self.isOpen, self.store.page != .wide else { return } + self.layoutPanel(for: self.store.page, animated: false) + } + .store(in: &cancellables) + store.$narrowWidth + .removeDuplicates() + .dropFirst() + .receive(on: RunLoop.main) + .sink { [weak self] _ in + guard let self, self.isOpen, self.store.page != .wide else { return } + self.layoutPanel(for: self.store.page, animated: true) + } + .store(in: &cancellables) + store.$manageContentHeight + .removeDuplicates() + .receive(on: RunLoop.main) + .sink { [weak self] _ in + guard let self, self.isOpen, self.store.page == .manage else { return } + self.layoutPanel(for: .manage, animated: true) + } + .store(in: &cancellables) + store.$detailContentHeight + .removeDuplicates() + .receive(on: RunLoop.main) + .sink { [weak self] _ in + guard let self, self.isOpen, case .detail = self.store.page else { return } + self.layoutPanel(for: self.store.page, animated: true) + } + .store(in: &cancellables) + } + + private func updateStatusTitle() { + guard let button = statusItem.button else { return } + let mode = store.view?.settings.statusText ?? "icon" + let text = (store.view?.statusTextByMode[mode] ?? nil) ?? "" + if text.isEmpty { + button.attributedTitle = NSAttributedString(string: "") + button.imagePosition = .imageOnly + } else { + button.imagePosition = .imageLeading + button.attributedTitle = NSAttributedString( + string: " " + text, + attributes: [ + .font: NSFont.monospacedDigitSystemFont(ofSize: 12.5, weight: .semibold), + .baselineOffset: 0, + ] + ) + } + if let view = store.view { + button.setAccessibilityLabel(text.isEmpty ? "Cindy" : "Cindy, \(text)") + button.toolTip = view.headlinePrimary + } + } + + @objc func toggle() { + if let at = closedByStatusPressAt { + closedByStatusPressAt = nil + if ProcessInfo.processInfo.systemUptime - at < 0.6 { return } + } + isOpen ? close() : open() + } + + func open() { + guard !isOpen else { return } + // Right after launch the status item may not be placed in the menu bar yet; its rect is + // then empty or off every screen. Wait a few runloop turns before falling back. + if statusAnchor() == nil && openRetries < 20 { + openRetries += 1 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in self?.open() } + return + } + openRetries = 0 + isOpen = true + closing = false + let panel = ensurePanel() + store.page = .main + store.fillToken += 1 + layoutPanel(for: .main, animated: false) + let target = panel.frame + panel.setFrame(target.offsetBy(dx: 0, dy: 6), display: false) + panel.alphaValue = 0 + panel.makeKeyAndOrderFront(nil) + openAnimating = true + NSAnimationContext.runAnimationGroup({ context in + context.duration = 0.22 + context.timingFunction = CAMediaTimingFunction(controlPoints: 0.22, 1, 0.36, 1) + panel.animator().alphaValue = 1 + panel.animator().setFrame(target, display: true) + }, completionHandler: { [weak self] in + Task { @MainActor in + guard let self else { return } + self.openAnimating = false + // Opening asks main for fresh data, which often lands mid-animation and changes the + // content height; settle on the latest frame once the animation is done. + if self.isOpen { self.layoutPanel(for: self.store.page, animated: false) } + } + }) + statusItem.button?.highlight(true) + installMonitors() + Bridge.emit(["type": "panel", "open": true]) + } + + func close() { + guard isOpen, let panel else { return } + isOpen = false + closing = true + removeMonitors() + statusItem.button?.highlight(false) + NSAnimationContext.runAnimationGroup({ context in + context.duration = 0.16 + context.timingFunction = CAMediaTimingFunction(name: .easeIn) + panel.animator().alphaValue = 0 + }, completionHandler: { [weak self] in + Task { @MainActor in + guard let self, self.closing else { return } + panel.orderOut(nil) + self.closing = false + self.store.page = .main + self.store.draftOrder = nil + } + }) + Bridge.emit(["type": "panel", "open": false]) + } + + private func ensurePanel() -> MenubarPanel { + if let panel { return panel } + let panel = MenubarPanel( + contentRect: NSRect(x: 0, y: 0, width: Self.narrowWidth, height: 600), + styleMask: [.borderless, .nonactivatingPanel, .fullSizeContentView], + backing: .buffered, + defer: false + ) + panel.isFloatingPanel = true + panel.level = .popUpMenu + panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .transient, .ignoresCycle] + panel.isOpaque = false + panel.backgroundColor = .clear + panel.hasShadow = true + panel.hidesOnDeactivate = false + panel.isMovable = false + panel.animationBehavior = .none + panel.delegate = self + + let background = PanelBackgroundView(frame: panel.contentLayoutRect) + background.autoresizingMask = [.width, .height] + let hosting = NSHostingView(rootView: PanelRoot(store: store, controller: self)) + hosting.translatesAutoresizingMaskIntoConstraints = true + hosting.autoresizingMask = [.width, .height] + hosting.frame = background.bounds + hosting.sizingOptions = [] + background.addSubview(hosting) + panel.contentView = background + self.hosting = hosting + self.panel = panel + return panel + } + + /// Keeps the panel's right edge under the status item (inside the screen) and its top just + /// below the menu bar; the wide page grows to the left like the prototype. + /// The status button's rect on screen, or nil while the item is not placed yet. + private func statusAnchor() -> (rect: NSRect, screen: NSScreen)? { + guard let button = statusItem.button, let buttonWindow = button.window else { return nil } + let rect = buttonWindow.convertToScreen(button.convert(button.bounds, to: nil)) + guard rect.width > 0, rect.height > 0 else { return nil } + let center = NSPoint(x: rect.midX, y: rect.midY) + guard let screen = NSScreen.screens.first(where: { $0.frame.contains(center) }) else { return nil } + return (rect, screen) + } + + private func targetFrame(for page: Page) -> NSRect? { + let buttonRect: NSRect + let screen: NSScreen + if let anchor = statusAnchor() { + buttonRect = anchor.rect + screen = anchor.screen + } else { + // Not placed: anchor to the top-right of the main screen, where status items live. + guard let main = NSScreen.main ?? NSScreen.screens.first else { return nil } + screen = main + let frame = main.frame + buttonRect = NSRect(x: frame.maxX - store.narrowWidth / 2 - Self.sideMargin, y: main.visibleFrame.maxY, width: 0, height: 0) + } + let visible = screen.visibleFrame + let top = min(buttonRect.minY, visible.maxY) - Self.screenGap + let maxHeight = max(320, top - visible.minY - Self.sideMargin) + let wide = page == .wide + let width = min(wide ? Self.wideWidth : store.narrowWidth, visible.width - Self.sideMargin * 2) + let right = min(visible.maxX - Self.sideMargin, buttonRect.midX + store.narrowWidth / 2) + let x = max(visible.minX + Self.sideMargin, right - width) + let height: CGFloat + if wide { + height = min(maxHeight, 806) + } else { + var natural = store.mainContentHeight > 0 ? store.mainContentHeight : 640 + if case .detail = page { natural = max(natural, store.detailContentHeight) } + if page == .manage, store.manageContentHeight > 0 { natural = store.manageContentHeight } + height = min(maxHeight, natural) + } + return NSRect(x: x, y: top - height, width: width, height: height) + } + + func layoutPanel(for page: Page, animated: Bool) { + guard let panel, isOpen, let target = targetFrame(for: page) else { return } + if panel.frame.equalTo(target) { return } + if openAnimating { + // Retarget the running open animation instead of being overwritten by it. + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.18 + context.timingFunction = CAMediaTimingFunction(controlPoints: 0.22, 1, 0.36, 1) + panel.animator().setFrame(target, display: true) + } + return + } + let widthChanged = abs(panel.frame.width - target.width) > 0.5 + let heightChanged = abs(panel.frame.height - target.height) > 0.5 + if animated && (widthChanged || heightChanged) && !NSWorkspace.shared.accessibilityDisplayShouldReduceMotion { + NSAnimationContext.runAnimationGroup { context in + // Width changes slide with the page; a height-only change just settles the bottom edge. + context.duration = widthChanged ? 0.55 : 0.25 + context.timingFunction = CAMediaTimingFunction(controlPoints: 0.22, 1, 0.36, 1) + context.allowsImplicitAnimation = true + panel.animator().setFrame(target, display: true) + } + } else { + panel.setFrame(target, display: true) + } + } + + private func installMonitors() { + removeMonitors() + outsideMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] _ in + Task { @MainActor in + guard let self else { return } + // The menu bar reports a press on our own status item here too, and a quick tap may + // never reach the item's action. Close on the press and let `toggle` skip that click. + if self.statusAnchor()?.rect.contains(NSEvent.mouseLocation) == true { + guard self.isOpen else { return } + self.close() + self.closedByStatusPressAt = ProcessInfo.processInfo.systemUptime + return + } + self.close() + } + } + keyMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { [weak self] event in + guard let self, self.isOpen else { return event } + return self.handleKey(event) ? nil : event + } + } + + private func removeMonitors() { + if let outsideMonitor { NSEvent.removeMonitor(outsideMonitor) } + if let keyMonitor { NSEvent.removeMonitor(keyMonitor) } + outsideMonitor = nil + keyMonitor = nil + } + + private func handleKey(_ event: NSEvent) -> Bool { + let command = event.modifierFlags.contains(.command) + let key = event.charactersIgnoringModifiers?.lowercased() ?? "" + if event.keyCode == 53 { + if store.page == .main { close() } else { store.go(.main) } + return true + } + guard command else { return false } + switch key { + case "r": + store.refresh() + case "o": + openApp() + case ",": + store.go(.manage) + case "w": + close() + default: + return false + } + return true + } + + func openApp() { + Bridge.emit(["type": "open-app"]) + close() + } + + func emitAndClose(_ payload: [String: Any]) { + Bridge.emit(payload) + close() + } + + func windowDidResignKey(_ notification: Notification) { + // A press on our own status item takes focus first; its click then toggles the panel + // closed. Closing here too would let that click reopen it. + if let event = NSApp.currentEvent, + event.type == .leftMouseDown || event.type == .rightMouseDown, + let buttonWindow = statusItem.button?.window, + event.window === buttonWindow { + return + } + // Clicking another app: behave like a menu. + if isOpen { close() } + } + + /// Preview mode only: open without a click so screenshots show the panel. + func openForPreview(page: Page) { + open() + store.page = page + layoutPanel(for: page, animated: false) + } +} + +// MARK: - Root + +struct PanelRoot: View { + @ObservedObject var store: MenubarStore + let controller: StatusController + + var body: some View { + ZStack(alignment: .topTrailing) { + if let view = store.view { + pages(view) + } else { + ProgressView().controlSize(.small).frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + .overlay( + RoundedRectangle(cornerRadius: PanelBackgroundView.radius, style: .circular) + .strokeBorder(Palette.edge, lineWidth: 0.5) + .allowsHitTesting(false) + ) + .clipShape(RoundedRectangle(cornerRadius: PanelBackgroundView.radius, style: .circular)) + .foregroundStyle(Palette.ink) + .environment(\.locale, Locale(identifier: store.locale)) + } + + @ViewBuilder + private func pages(_ view: MBView) -> some View { + ZStack(alignment: .topTrailing) { + if store.page == .main, view.gate != nil { + GatePage(store: store, controller: controller) + .frame(width: store.narrowWidth) + .transition(.asymmetric( + insertion: .offset(x: -store.narrowWidth * 0.3).combined(with: .opacity), + removal: .offset(x: -store.narrowWidth * 0.3).combined(with: .opacity) + )) + } else if store.page == .main { + MainPage(store: store, view: view, controller: controller) + .frame(width: store.narrowWidth) + .transition(.asymmetric( + insertion: .offset(x: -store.narrowWidth * 0.3).combined(with: .opacity), + removal: .offset(x: -store.narrowWidth * 0.3).combined(with: .opacity) + )) + } + if case .detail(let id) = store.page, let detail = view.details[id] { + DetailPage(store: store, detail: detail, controller: controller) + .frame(width: store.narrowWidth) + .transition(.move(edge: .trailing).combined(with: .opacity)) + } + if store.page == .manage { + ManagePage(store: store, view: view, controller: controller) + .frame(width: store.narrowWidth) + .transition(.move(edge: .trailing).combined(with: .opacity)) + } + if store.page == .wide { + WidePage(store: store, view: view, controller: controller) + .frame(width: StatusController.wideWidth) + .transition(.move(edge: .trailing).combined(with: .opacity)) + } + } + } +} + +// MARK: - Shared pieces + +struct IconButton: View { + let systemName: String + let label: String + var help: String? = nil + var rotation: Double = 0 + let action: () -> Void + @State private var hovering = false + + var body: some View { + Button(action: action) { + Image(systemName: systemName) + .font(.system(size: 13, weight: .medium)) + .rotationEffect(.degrees(rotation)) + .frame(width: 32, height: 32) + .foregroundStyle(hovering ? Palette.ink : Palette.ink2) + .background(Circle().fill(hovering ? Palette.ink5 : .clear)) + .contentShape(Circle()) + } + .buttonStyle(.plain) + .onHover { hovering in withAnimation(Motion.quick) { self.hovering = hovering } } + .help(help ?? label) + .accessibilityLabel(label) + } +} + +struct InkPillButton: View { + let title: String + var shortcut: String? = nil + var height: CGFloat = 34 + var fullWidth = false + var disabled = false + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 8) { + Text(title).font(.system(size: 12.5, weight: .semibold)) + if let shortcut { + Text(shortcut).font(.system(size: 12.5, weight: .medium)).opacity(0.55) + } + } + .padding(.horizontal, 16) + .frame(height: height) + .frame(maxWidth: fullWidth ? .infinity : nil) + .foregroundStyle(Palette.onInk) + .background(Capsule().fill(Palette.ink)) + .contentShape(Capsule()) + } + .buttonStyle(PressScaleStyle()) + .disabled(disabled) + .opacity(disabled ? 0.4 : 1) + } +} + +struct PressScaleStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .scaleEffect(configuration.isPressed ? 0.97 : 1) + .animation(.easeOut(duration: 0.12), value: configuration.isPressed) + } +} + +struct Tile: View { + var radius: CGFloat = 20 + @ViewBuilder let content: Content + + var body: some View { + content + .background(RoundedRectangle(cornerRadius: radius, style: .circular).fill(Palette.tile)) + .overlay(RoundedRectangle(cornerRadius: radius, style: .circular).strokeBorder(Palette.line, lineWidth: 0.5)) + } +} + +/// Fixed bar above or below a scroll area; shows a hairline only while content runs under it. +struct EdgeLine: View { + let visible: Bool + var body: some View { + Rectangle().fill(Palette.line).frame(height: 0.5).opacity(visible ? 1 : 0) + .animation(Motion.quick, value: visible) + } +} + +private struct ScrollOffsetKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } +} + +private struct ContentHeightKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) } +} + +/// Scroll view that reports how far it is scrolled and how tall its content is. +struct TrackedScroll: View { + @Binding var offset: CGFloat + @Binding var contentHeight: CGFloat + @ViewBuilder let content: Content + private let space = UUID().uuidString + + var body: some View { + GeometryReader { outer in + ScrollView(.vertical, showsIndicators: false) { + content + .background(GeometryReader { inner in + Color.clear + .preference(key: ScrollOffsetKey.self, value: -inner.frame(in: .named(space)).minY) + .preference(key: ContentHeightKey.self, value: inner.size.height) + }) + } + .coordinateSpace(name: space) + .onPreferenceChange(ScrollOffsetKey.self) { offset = $0 } + .onPreferenceChange(ContentHeightKey.self) { contentHeight = $0 } + .frame(height: outer.size.height) + } + } +} + +// MARK: - Tube + +struct TubeView: View { + let fill: Double? + let level: String + let reserve: Bool + let emphasized: Bool + let index: Int + let fillToken: Int + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @State private var shown = true + + private static let height: CGFloat = 116 + private static let inset: CGFloat = 3 + private static let outerRadius: CGFloat = 15 + + var body: some View { + let wellHeight = Self.height - Self.inset * 2 + let fraction = max(0, min(1, fill ?? 0)) + let liquid = fraction > 0 ? max(3, fraction * wellHeight) : 0 + ZStack { + if fill == nil { + RoundedRectangle(cornerRadius: Self.outerRadius, style: .circular) + .strokeBorder(Palette.ink4, style: StrokeStyle(lineWidth: 1.5, dash: [4, 3])) + } else { + RoundedRectangle(cornerRadius: Self.outerRadius, style: .circular).fill(Palette.ink5) + if reserve { + HatchPattern() + .stroke(Palette.ink4, lineWidth: 2) + .clipShape(RoundedRectangle(cornerRadius: Self.outerRadius - Self.inset, style: .circular)) + .padding(Self.inset) + } + // Liquid lives in a well inset 3pt on every side; the well's rounded bottom shapes low + // levels, so the liquid never touches the tube wall and the top stays flat. + VStack(spacing: 0) { + Spacer(minLength: 0) + Rectangle() + .fill(Palette.level(level) ?? Palette.fill) + .frame(height: liquid) + .scaleEffect(x: 1, y: shown ? 1 : 0.001, anchor: .bottom) + } + .clipShape(RoundedRectangle(cornerRadius: Self.outerRadius - Self.inset, style: .circular)) + .padding(Self.inset) + .animation(Motion.fill, value: liquid) + RoundedRectangle(cornerRadius: Self.outerRadius, style: .circular) + .strokeBorder(emphasized ? Palette.ink : Palette.ink4, lineWidth: emphasized ? 1.5 : 0.5) + } + } + .frame(maxWidth: 46) + .frame(height: Self.height) + .onChange(of: fillToken) { replay() } + .onAppear { replay() } + } + + private func replay() { + guard !reduceMotion else { shown = true; return } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { shown = false } + DispatchQueue.main.async { + withAnimation(Motion.fill.delay(Double(index) * 0.025)) { shown = true } + } + } +} + +struct HatchPattern: Shape { + func path(in rect: CGRect) -> Path { + var path = Path() + let step: CGFloat = 7 + var x = -rect.height + while x < rect.width { + path.move(to: CGPoint(x: x, y: rect.maxY)) + path.addLine(to: CGPoint(x: x + rect.height, y: rect.minY)) + x += step + } + return path + } +} + +// MARK: - Main page + +struct MainPage: View { + @ObservedObject var store: MenubarStore + let view: MBView + let controller: StatusController + @State private var offset: CGFloat = 0 + @State private var contentHeight: CGFloat = 0 + @State private var topHeight: CGFloat = 0 + @State private var footHeight: CGFloat = 0 + @State private var viewportHeight: CGFloat = 0 + + var body: some View { + VStack(spacing: 0) { + topBar + .background(GeometryReader { g in Color.clear.onAppear { topHeight = g.size.height }.onChange(of: g.size.height) { _, h in topHeight = h } }) + EdgeLine(visible: offset > 2) + TrackedScroll(offset: $offset, contentHeight: $contentHeight) { + content + } + .background(GeometryReader { g in Color.clear.onAppear { viewportHeight = g.size.height }.onChange(of: g.size.height) { _, h in viewportHeight = h } }) + EdgeLine(visible: offset + viewportHeight < contentHeight - 2) + foot + .background(GeometryReader { g in Color.clear.onAppear { footHeight = g.size.height }.onChange(of: g.size.height) { _, h in footHeight = h } }) + } + .onChange(of: contentHeight) { report() } + .onChange(of: topHeight) { report() } + .onChange(of: footHeight) { report() } + } + + private func report() { + guard contentHeight > 0 else { return } + let natural = (topHeight + contentHeight + footHeight + 1).rounded(.up) + if abs(store.mainContentHeight - natural) > 0.5 { store.mainContentHeight = natural } + } + + private var topBar: some View { + HStack(spacing: 0) { + Wordmark().frame(height: 15) + TimelineView(.periodic(from: .now, by: 15)) { context in + Text(updatedText(now: context.date)) + .font(.system(size: 11.5)) + .foregroundStyle(Palette.ink3) + .padding(.leading, 10) + } + Spacer(minLength: 0) + RefreshButton(store: store) + } + .padding(EdgeInsets(top: 22, leading: 22, bottom: 10, trailing: 22)) + } + + private func updatedText(now: Date) -> String { + guard let updated = view.updatedAtMs else { return "" } + let seconds = max(0, now.timeIntervalSince1970 - updated / 1000) + if seconds < 60 { return store.s("updatedJustNow") } + if seconds < 3600 { return store.s("updatedMinutes", ["n": String(Int(seconds / 60))]) } + if seconds < 86400 { return store.s("updatedHours", ["n": String(Int(seconds / 3600))]) } + return store.s("updatedDays", ["n": String(Int(seconds / 86400))]) + } + + private var content: some View { + VStack(alignment: .leading, spacing: 0) { + Headline(primary: view.headlinePrimary, secondary: view.headlineSecondary) + .padding(EdgeInsets(top: 20, leading: 24, bottom: 2, trailing: 24)) + + // Only sources with a level get a tube; the rest (not read yet, pay-as-you-go) are + // listed as quiet rows so an empty tube never stands in for missing data. + let measured = view.sources.filter { $0.fill != nil } + let unmeasured = view.sources.filter { $0.fill == nil } + if !measured.isEmpty { + Cells(store: store, cells: measured) + .padding(.horizontal, 22) + .padding(.top, 22) + } + if !unmeasured.isEmpty { + UnmeasuredSources(store: store, cells: unmeasured) + .padding(.horizontal, 14) + .padding(.top, measured.isEmpty ? 18 : 8) + } + + Caption(title: store.s("tasks"), detail: view.taskSummary) { + TaskTotal(tasks: view.tasks, tokenUnit: store.s("tokenUnit"), showsTokens: store.narrowWidth >= 380) + } + .padding(EdgeInsets(top: 28, leading: 24, bottom: 12, trailing: 24)) + + TaskList(store: store, tasks: view.tasks, controller: controller) + .padding(.horizontal, 14) + + MonthCard(store: store, view: view) + .padding(EdgeInsets(top: 12, leading: 16, bottom: 14, trailing: 16)) + } + } + + private var foot: some View { + HStack(spacing: 6) { + InkPillButton(title: store.s("openApp"), shortcut: "⌘O") { controller.openApp() } + Spacer(minLength: 0) + IconButton(systemName: "gearshape", label: store.s("settings"), help: store.s("settings") + " ⌘,") { + store.go(.manage) + } + } + .padding(EdgeInsets(top: 12, leading: 18, bottom: 18, trailing: 18)) + } +} + +/// Shown before the user signs in or skips sign-in: nothing has been read, so there is +/// nothing to show but the way in. +struct GatePage: View { + @ObservedObject var store: MenubarStore + let controller: StatusController + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Wordmark().frame(height: 15) + .padding(EdgeInsets(top: 22, leading: 22, bottom: 0, trailing: 22)) + + Image(systemName: "person.crop.circle") + .font(.system(size: 30, weight: .regular)) + .foregroundStyle(Palette.ink3) + .accessibilityHidden(true) + .padding(EdgeInsets(top: 30, leading: 24, bottom: 14, trailing: 24)) + + Text(store.s("gateTitle")) + .font(.system(size: 22, weight: .semibold)) + .tracking(-0.4) + .fixedSize(horizontal: false, vertical: true) + .accessibilityAddTraits(.isHeader) + .padding(.horizontal, 24) + + Text(store.s("gateBody")) + .font(.system(size: 13)) + .lineSpacing(3) + .foregroundStyle(Palette.ink3) + .fixedSize(horizontal: false, vertical: true) + .padding(EdgeInsets(top: 8, leading: 24, bottom: 0, trailing: 24)) + + HStack(spacing: 6) { + InkPillButton(title: store.s("openApp"), shortcut: "⌘O") { controller.openApp() } + Spacer(minLength: 0) + IconButton(systemName: "gearshape", label: store.s("settings"), help: store.s("settings") + " ⌘,") { + store.go(.manage) + } + } + .padding(EdgeInsets(top: 26, leading: 18, bottom: 18, trailing: 18)) + } + .frame(maxWidth: .infinity, alignment: .topLeading) + .fixedSize(horizontal: false, vertical: true) + .background(GeometryReader { g in + Color.clear + .onAppear { report(g.size.height) } + .onChange(of: g.size.height) { _, h in report(h) } + }) + .frame(maxHeight: .infinity, alignment: .top) + } + + private func report(_ height: CGFloat) { + let natural = (height + 1).rounded(.up) + if natural > 0, abs(store.mainContentHeight - natural) > 0.5 { store.mainContentHeight = natural } + } +} + +struct Wordmark: View { + @Environment(\.colorScheme) private var colorScheme + + var body: some View { + if let image = WordmarkAsset.image(dark: colorScheme == .dark) { + Image(nsImage: image) + .resizable() + .interpolation(.high) + .aspectRatio(contentMode: .fit) + .accessibilityLabel("Cindy") + } else { + Text("Cindy").font(.system(size: 15, weight: .bold)).foregroundStyle(Palette.ink) + } + } +} + +/// The wordmark keeps its red ">" and dot, so it is a full-color pair, not a template image. +enum WordmarkAsset { + private static let dir: String = ProcessInfo.processInfo.environment["CINDY_MENUBAR_ASSET_DIR"] + ?? (CommandLine.arguments.first.map { ($0 as NSString).deletingLastPathComponent } ?? "") + private static let light = NSImage(contentsOfFile: (dir as NSString).appendingPathComponent("wordmark-light.png")) + private static let dark = NSImage(contentsOfFile: (dir as NSString).appendingPathComponent("wordmark-dark.png")) + + static func image(dark isDark: Bool) -> NSImage? { + isDark ? (dark ?? light) : (light ?? dark) + } +} + +struct RefreshButton: View { + @ObservedObject var store: MenubarStore + @State private var angle: Double = 0 + + var body: some View { + IconButton(systemName: "arrow.clockwise", label: store.s("refresh"), help: store.s("refresh") + " ⌘R", rotation: angle) { + store.refresh() + } + .onChange(of: store.refreshing) { _, refreshing in + guard refreshing else { return } + withAnimation(.timingCurve(0.4, 0.1, 0.3, 1, duration: 0.9)) { angle += 360 } + } + } +} + +struct Headline: View { + let primary: String + let secondary: String? + + var body: some View { + VStack(alignment: .leading, spacing: 3) { + Text(primary) + .font(.system(size: 26, weight: .semibold)) + .tracking(-0.6) + .lineSpacing(2) + if let secondary { + Text(secondary) + .font(.system(size: 20, weight: .semibold)) + .tracking(-0.4) + .foregroundStyle(Palette.ink3) + } + } + .fixedSize(horizontal: false, vertical: true) + .accessibilityElement(children: .combine) + .accessibilityAddTraits(.isHeader) + } +} + +struct Caption: View { + let title: String + let detail: String + @ViewBuilder let trailing: Trailing + + var body: some View { + HStack(alignment: .firstTextBaseline, spacing: 8) { + Text(title).font(.system(size: 13, weight: .semibold)).tracking(-0.1) + Text(detail).font(.system(size: 12)).foregroundStyle(Palette.ink3).monospacedDigit().lineLimit(1) + Spacer(minLength: 4) + trailing + } + } +} + +struct Cells: View { + @ObservedObject var store: MenubarStore + let cells: [MBCell] + + var body: some View { + HStack(alignment: .top, spacing: cells.count > 6 ? 6 : 10) { + ForEach(Array(cells.enumerated()), id: \.element.id) { index, cell in + CellView(cell: cell, index: index, fillToken: store.fillToken) { + store.go(.detail(cell.id)) + } + } + } + } +} + +/// Sources without a level: logo, name and why there is no tube, opening the detail page. +struct UnmeasuredSources: View { + @ObservedObject var store: MenubarStore + let cells: [MBCell] + + var body: some View { + VStack(spacing: 2) { + ForEach(cells) { cell in + UnmeasuredRow(cell: cell) { store.go(.detail(cell.id)) } + } + } + } +} + +struct UnmeasuredRow: View { + let cell: MBCell + let action: () -> Void + @State private var hovering = false + + private var status: String { + [cell.valueLabel, cell.when] + .compactMap { $0 } + .filter { !$0.isEmpty && $0 != "—" } + .joined(separator: " · ") + } + + var body: some View { + Button(action: action) { + HStack(spacing: 10) { + LogoView(svg: cell.logoSvg, size: 13) + .frame(width: 26, height: 26) + .background(RoundedRectangle(cornerRadius: 8, style: .circular).fill(Palette.ink5)) + Text(cell.name) + .font(.system(size: 13, weight: .semibold)) + .tracking(-0.1) + .lineLimit(1) + Spacer(minLength: 8) + Text(status) + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + .lineLimit(1) + Image(systemName: "chevron.right") + .font(.system(size: 10, weight: .semibold)) + .foregroundStyle(Palette.ink4) + } + .padding(EdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 12)) + .background(RoundedRectangle(cornerRadius: 12, style: .circular).fill(hovering ? Palette.ink5 : .clear)) + .contentShape(RoundedRectangle(cornerRadius: 12, style: .circular)) + } + .buttonStyle(.plain) + .onHover { hovering in withAnimation(.easeOut(duration: 0.15)) { self.hovering = hovering } } + .accessibilityLabel(cell.accessibilityLabel) + } +} + +struct CellView: View { + let cell: MBCell + let index: Int + let fillToken: Int + let action: () -> Void + @State private var hovering = false + + var body: some View { + Button(action: action) { + VStack(spacing: 9) { + valueLabel.frame(height: 20) + TubeView( + fill: cell.kind == "payg" ? nil : cell.fill, + level: cell.level, + reserve: cell.reserve, + emphasized: cell.emphasized, + index: index, + fillToken: fillToken + ) + VStack(spacing: 5) { + LogoView(svg: cell.logoSvg, size: 13) + Text(cell.name) + .font(.system(size: 11)) + .foregroundStyle(Palette.ink2) + .lineLimit(1) + .truncationMode(.tail) + } + Text(cell.when) + .font(.system(size: 10.5, weight: .medium)) + .foregroundStyle(Palette.level(cell.whenLevel) ?? Palette.ink2) + .lineLimit(1) + .frame(height: 15) + .padding(.top, -4) + } + .padding(.top, 6) + .padding(.bottom, 4) + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + .offset(y: hovering ? -3 : 0) + } + .buttonStyle(.plain) + .onHover { hovering in + withAnimation(.timingCurve(0.22, 1, 0.36, 1, duration: 0.25)) { self.hovering = hovering } + } + .accessibilityLabel(cell.accessibilityLabel) + } + + @ViewBuilder + private var valueLabel: some View { + if let value = cell.value { + HStack(alignment: .firstTextBaseline, spacing: 1) { + Text(value) + .font(.system(size: cell.kind == "balance" ? 13 : 15, weight: .semibold)) + .tracking(cell.kind == "balance" ? -0.2 : -0.3) + if let unit = cell.valueUnit { + Text(unit).font(.system(size: 10, weight: .semibold)).foregroundStyle(Palette.ink3) + } + } + .monospacedDigit() + .foregroundStyle(Palette.level(cell.level) ?? Palette.ink) + } else if let label = cell.valueLabel { + Text(label) + .font(.system(size: 11, weight: .semibold)) + .foregroundStyle(Palette.ink2) + .padding(.top, 3) + } + } +} + +// MARK: - Tasks + +struct TaskTotal: View { + let tasks: [MBTask] + let tokenUnit: String + /// A narrow panel keeps just the money; each task row still shows its tokens. + var showsTokens = true + + var body: some View { + let money = tasks.reduce(0) { $0 + $1.value } + let tokens = tasks.reduce(0) { $0 + $1.tokens } + (Text(Fmt.money(money)).foregroundColor(Palette.ink).fontWeight(.semibold) + + Text(showsTokens ? " · \(Fmt.tokens(tokens)) \(tokenUnit)" : "")) + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + .monospacedDigit() + .lineLimit(1) + } +} + +struct TaskList: View { + @ObservedObject var store: MenubarStore + let tasks: [MBTask] + let controller: StatusController + + var body: some View { + let sorted = tasks.sorted { $0.value > $1.value } + let total = max(0.0001, sorted.reduce(0) { $0 + $1.value }) + let top = Array(sorted.prefix(3)) + let rest = Array(sorted.dropFirst(3)) + VStack(spacing: 2) { + if sorted.isEmpty { + Text(store.s("noTasksToday")) + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 10) + .padding(.vertical, 6) + } + ForEach(top) { task in + TaskRow(store: store, task: task, share: task.value / total) { + controller.emitAndClose(["type": "open-session", "sessionId": task.sessionId]) + } + } + if !rest.isEmpty { + MoreRow( + label: store.s("moreTasks", ["n": String(rest.count)]), + value: Fmt.money(rest.reduce(0) { $0 + $1.value }) + ) { + store.wideList = .tasks + store.go(.wide) + } + .padding(.horizontal, 10) + } + } + } +} + +struct TaskRow: View { + @ObservedObject var store: MenubarStore + let task: MBTask + let share: Double + let action: () -> Void + @State private var hovering = false + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + var body: some View { + Button(action: action) { + HStack(alignment: .center, spacing: 11) { + TaskIcon(svg: task.logoSvg, running: task.running) + VStack(alignment: .leading, spacing: 1) { + Text(task.title) + .font(.system(size: 13, weight: .semibold)) + .tracking(-0.1) + .lineLimit(1) + metaLine + } + .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .trailing, spacing: 1) { + Text(Fmt.money(task.value)) + .font(.system(size: 15, weight: .semibold)) + .tracking(-0.3) + Text("\(Int((share * 100).rounded()))%") + .font(.system(size: 10.5, weight: .medium)) + .foregroundStyle(Palette.ink3) + } + .monospacedDigit() + } + .padding(EdgeInsets(top: 9, leading: 10, bottom: 11, trailing: 10)) + .background(alignment: .bottomLeading) { shareBar } + .background(RoundedRectangle(cornerRadius: 16, style: .circular).fill(hovering ? Palette.ink5 : .clear)) + .contentShape(RoundedRectangle(cornerRadius: 16, style: .circular)) + } + .buttonStyle(.plain) + .onHover { hovering in withAnimation(.easeOut(duration: 0.15)) { self.hovering = hovering } } + .help(task.title) + .accessibilityLabel("\(task.title), \(Fmt.money(task.value))") + } + + private var shareBar: some View { + GeometryReader { geometry in + let width = geometry.size.width - 55 - 10 + ZStack(alignment: .leading) { + Capsule().fill(Palette.ink5) + Capsule().fill(Palette.ink3).frame(width: max(4, width * share)) + } + .frame(width: max(0, width), height: 2) + .offset(x: 55, y: geometry.size.height - 4 - 2) + } + } + + @ViewBuilder + private var metaLine: some View { + if task.running, let started = task.startedAtMs { + TimelineView(.periodic(from: .now, by: 1)) { context in + HStack(spacing: 0) { + BreathingDot(animate: !reduceMotion) + .padding(.trailing, 5) + Text(store.s("running") + " " + Fmt.clock(context.date.timeIntervalSince1970 - started / 1000)) + .fontWeight(.semibold) + .foregroundStyle(Palette.ink2) + Text(" · " + task.meta).foregroundStyle(Palette.ink3) + } + .font(.system(size: 11)) + .monospacedDigit() + .lineLimit(1) + } + } else { + Text(task.idleStatus + " · " + task.meta) + .font(.system(size: 11)) + .foregroundStyle(Palette.ink3) + .monospacedDigit() + .lineLimit(1) + } + } +} + +struct TaskIcon: View { + let svg: String + let running: Bool + @State private var pulse = false + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 12, style: .circular).fill(Palette.ink5) + LogoView(svg: svg, size: 15) + if running && !reduceMotion { + RoundedRectangle(cornerRadius: 14, style: .circular) + .strokeBorder(Palette.ink3, lineWidth: 1) + .padding(-3) + .scaleEffect(pulse ? 1.12 : 0.92) + .opacity(pulse ? 0 : 0.7) + .onAppear { + withAnimation(.easeOut(duration: 2.2).repeatForever(autoreverses: false)) { pulse = true } + } + } + } + .frame(width: 34, height: 34) + } +} + +struct BreathingDot: View { + let animate: Bool + @State private var dim = false + + var body: some View { + Circle() + .fill(Palette.ink2) + .frame(width: 6, height: 6) + .opacity(dim ? 0.2 : 1) + .onAppear { + guard animate else { return } + withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) { dim = true } + } + } +} + +struct MoreRow: View { + let label: String + let value: String + let action: () -> Void + @State private var hovering = false + + var body: some View { + Button(action: action) { + HStack { + Text(label) + Spacer() + Text(value + " ›").monospacedDigit() + } + .font(.system(size: 12)) + .foregroundStyle(hovering ? Palette.ink : Palette.ink3) + .padding(.vertical, 6) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .onHover { hovering in withAnimation(Motion.quick) { self.hovering = hovering } } + } +} + +// MARK: - Month card + +struct MonthCard: View { + @ObservedObject var store: MenubarStore + let view: MBView + @State private var hovering = false + + var body: some View { + let scope = view.settings.scope + let data = view.usage.scopes[scope] ?? view.usage.scopes["all"] + let last = Array((data?.daily ?? []).suffix(30)) + let days = Array(view.usage.days.suffix(30)) + let total = last.reduce(0) { $0 + $1.money.reduce(0, +) } + Button { + store.wideList = .models + store.go(.wide) + } label: { + HStack(spacing: 14) { + VStack(alignment: .leading, spacing: 0) { + Text(store.s(scope == "local" ? "monthScopeLocal" : "monthScopeAll")) + .font(.system(size: 11)) + .foregroundStyle(Palette.ink3) + Text(Fmt.money(total)) + .font(.system(size: 20, weight: .bold)) + .tracking(-0.6) + .monospacedDigit() + } + UsageChart(days: days, daily: last, money: true, compact: true, selection: .constant(nil)) + .frame(height: 34) + HStack(spacing: 5) { + Text(store.s("expand")) + Image(systemName: "arrow.up.right").font(.system(size: 9, weight: .semibold)) + } + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(Palette.ink2) + } + .padding(EdgeInsets(top: 12, leading: 14, bottom: 12, trailing: 14)) + .background(RoundedRectangle(cornerRadius: 18, style: .circular).fill(Palette.tile)) + .overlay(RoundedRectangle(cornerRadius: 18, style: .circular).strokeBorder(Palette.line, lineWidth: 0.5)) + .contentShape(RoundedRectangle(cornerRadius: 18, style: .circular)) + .offset(y: hovering ? -1 : 0) + } + .buttonStyle(.plain) + .onHover { hovering in withAnimation(Motion.quick) { self.hovering = hovering } } + .accessibilityLabel(store.s("expandUsage")) + } +} + +// MARK: - Usage chart (Swift Charts) + +struct UsagePoint: Identifiable { + let id: String + let date: Date + let harness: String + let value: Double +} + +/// Harness stacking order, bottom to top: Codex, Claude Code, Pi. +let harnessOrder: [(index: Int, key: String)] = [(1, "codex"), (0, "claude-code"), (2, "pi")] + +struct UsageChart: View { + let days: [String] + let daily: [MBDaily] + let money: Bool + let compact: Bool + @Binding var selection: Int? + var axisLabels: [(Int, String)] = [] + + var body: some View { + let count = min(days.count, daily.count) + let dates = days.suffix(count).map(Fmt.day) + let rows = Array(daily.suffix(count)) + let points = harnessOrder.flatMap { harness in + (0.. LinearGradient { + LinearGradient(colors: [color.opacity(0.95), color.opacity(0.55)], startPoint: .top, endPoint: .bottom) + } + + private func nearestIndex(dates: [Date], to date: Date) -> Int { + var best = 0 + var bestDistance = Double.greatestFiniteMagnitude + for (i, candidate) in dates.enumerated() { + let distance = abs(candidate.timeIntervalSince(date)) + if distance < bestDistance { + best = i + bestDistance = distance + } + } + return best + } +} + +// MARK: - Detail page + +struct RingArc: Shape { + var progress: Double + let lineWidth: CGFloat + + var animatableData: Double { + get { progress } + set { progress = newValue } + } + + /// The visible arc including both round caps spans exactly 0...progress of the circle: + /// the stroke starts half a line width after 12 o'clock and ends half a line width early. + /// A full ring uses butt caps so two round caps never meet; zero draws nothing. + func path(in rect: CGRect) -> Path { + let radius = min(rect.width, rect.height) / 2 - lineWidth / 2 + let center = CGPoint(x: rect.midX, y: rect.midY) + guard progress > 0.0005, radius > 0 else { return Path() } + var arc = Path() + if progress >= 0.9995 { + arc.addArc(center: center, radius: radius, startAngle: .degrees(-90), endAngle: .degrees(270), clockwise: false) + return arc.strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: .butt)) + } + let cap = Double(lineWidth / 2 / radius) + let start = -Double.pi / 2 + cap + let end = max(start + 0.0001, -Double.pi / 2 + 2 * Double.pi * progress - cap) + arc.addArc(center: center, radius: radius, startAngle: .radians(start), endAngle: .radians(end), clockwise: false) + return arc.strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: .round)) + } +} + +struct RingsView: View { + let rings: [MBRing] + let fillToken: Int + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @State private var shown = true + + var body: some View { + let lineWidth: CGFloat = rings.count > 1 ? 14 : 16 + let gap: CGFloat = 4 + ZStack { + ForEach(Array(rings.enumerated()), id: \.offset) { index, ring in + let inset = 4 + CGFloat(index) * (lineWidth + gap) + ZStack { + Circle() + .strokeBorder(Palette.ink5, lineWidth: lineWidth) + RingArc(progress: shown ? max(0, min(1, ring.fraction)) : 0, lineWidth: lineWidth) + .fill(Palette.named(ring.color)) + .animation(Motion.fill.delay(Double(index) * 0.06), value: shown) + .animation(Motion.fill, value: ring.fraction) + } + .padding(inset) + } + } + .frame(width: 240, height: 240) + .onAppear { replay() } + .onChange(of: fillToken) { replay() } + } + + private func replay() { + guard !reduceMotion else { shown = true; return } + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { shown = false } + DispatchQueue.main.async { shown = true } + } +} + +struct DetailPage: View { + @ObservedObject var store: MenubarStore + let detail: MBDetail + let controller: StatusController + + var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 8) { + IconButton(systemName: "chevron.left", label: store.s("back")) { store.go(.main) } + LogoView(svg: detail.logoSvg, size: 15) + Text(detail.title).font(.system(size: 14, weight: .semibold)) + Text(detail.plan).font(.system(size: 11, weight: .medium)).foregroundStyle(Palette.ink3) + Spacer(minLength: 0) + } + .padding(EdgeInsets(top: 18, leading: 18, bottom: 0, trailing: 18)) + + ZStack { + RingsView(rings: detail.rings, fillToken: store.fillToken) + VStack(spacing: 6) { + HStack(alignment: .firstTextBaseline, spacing: 2) { + Text(detail.bigValue) + .font(.system(size: detail.bigCompact ? 46 : 64, weight: .light)) + .tracking(detail.bigCompact ? -2 : -3) + if let unit = detail.bigUnit { + Text(unit).font(.system(size: 22)).foregroundStyle(Palette.ink3) + } + } + .monospacedDigit() + .foregroundStyle(detail.bigColor.map(Palette.named) ?? Palette.ink) + Text(detail.bigCaption).font(.system(size: 12)).foregroundStyle(Palette.ink3) + } + } + .frame(maxWidth: .infinity) + .padding(.top, 20) + .accessibilityElement(children: .combine) + + if !detail.legend.isEmpty { + HStack(spacing: 16) { + ForEach(detail.legend, id: \.self) { item in + HStack(spacing: 6) { + Circle().fill(Palette.named(item.color)).frame(width: 8, height: 8) + Text(item.label) + } + } + } + .font(.system(size: 11.5)) + .foregroundStyle(Palette.ink2) + .monospacedDigit() + .frame(maxWidth: .infinity) + .padding(.top, 14) + } + + sayText + .font(.system(size: 19, weight: .semibold)) + .tracking(-0.4) + .lineSpacing(3) + .fixedSize(horizontal: false, vertical: true) + .padding(EdgeInsets(top: 22, leading: 28, bottom: 0, trailing: 28)) + + LazyVGrid(columns: [GridItem(.flexible(), spacing: 10), GridItem(.flexible(), spacing: 10)], spacing: 10) { + ForEach(detail.facts, id: \.self) { fact in + FactCard(fact: fact) + } + } + .padding(EdgeInsets(top: 22, leading: 22, bottom: 24, trailing: 22)) + + InkPillButton(title: detail.actionLabel, height: 40, fullWidth: true, disabled: detail.action.kind == "none") { + controller.emitAndClose(["type": "detail-action", "sourceId": detail.id]) + } + .padding(EdgeInsets(top: 0, leading: 22, bottom: 24, trailing: 22)) + } + .background(GeometryReader { inner in + Color.clear + .onAppear { report(inner.size.height) } + .onChange(of: inner.size.height) { _, height in report(height) } + }) + } + } + + private func report(_ height: CGFloat) { + let natural = (height + 1).rounded(.up) + if abs(store.detailContentHeight - natural) > 0.5 { store.detailContentHeight = natural } + } + + private var sayText: Text { + var text = Text(detail.sayPrimary) + if let secondary = detail.saySecondary { + text = text + Text(secondary).foregroundColor(Palette.ink3) + } + return text + } +} + +struct FactCard: View { + let fact: MBFact + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + Text(fact.label).font(.system(size: 11)).foregroundStyle(Palette.ink3) + Text(fact.value) + .font(.system(size: fact.value.count > 14 ? 13 : 17, weight: .semibold)) + .tracking(fact.value.count > 14 ? 0 : -0.4) + .monospacedDigit() + .lineLimit(1) + .minimumScaleFactor(0.8) + .padding(.top, fact.value.count > 14 ? 5 : 3) + Text(fact.sub).font(.system(size: 11)).foregroundStyle(Palette.ink3).lineLimit(1).padding(.top, 1) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(EdgeInsets(top: 13, leading: 14, bottom: 13, trailing: 14)) + .background(RoundedRectangle(cornerRadius: 16, style: .circular).fill(Palette.tile)) + .overlay(RoundedRectangle(cornerRadius: 16, style: .circular).strokeBorder(Palette.line, lineWidth: 0.5)) + } +} + +// MARK: - Wide usage page + +struct WidePage: View { + @ObservedObject var store: MenubarStore + let view: MBView + let controller: StatusController + @State private var selection: Int? + + private var scopeData: MBScopeData? { view.usage.scopes[store.wideScope] ?? view.usage.scopes["all"] } + + var body: some View { + let data = scopeData + let range = store.wideRange + let allDays = view.usage.days + let days = Array(allDays.suffix(range)) + let daily = Array((data?.daily ?? []).suffix(range)) + let byHarness = harnessTotals(daily) + let total = byHarness.reduce(0, +) + HStack(alignment: .top, spacing: 0) { + sidebar(days: days, daily: daily, byHarness: byHarness, total: total, note: data?.note ?? "") + .frame(width: 260) + .frame(maxHeight: .infinity, alignment: .top) + .overlay(alignment: .trailing) { Rectangle().fill(Palette.line).frame(width: 0.5) } + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: 0) { + controls + UsageChart( + days: days, + daily: daily, + money: store.wideMoney, + compact: false, + selection: $selection, + axisLabels: axisLabels(days: days) + ) + .frame(height: 306) + .padding(.top, 22) + Picker("", selection: $store.wideList) { + Text(store.s("listModels", ["n": String(range)])).tag(WideList.models) + Text(store.s("listTasks")).tag(WideList.tasks) + } + .pickerStyle(.segmented) + .labelsHidden() + .fixedSize() + .padding(.top, 22) + if store.wideList == .models { + modelList(data?.models[String(range)] ?? []) + } else { + taskList + } + } + .padding(EdgeInsets(top: 20, leading: 24, bottom: 22, trailing: 24)) + } + } + .onChange(of: store.wideRange) { selection = nil } + } + + private func harnessTotals(_ daily: [MBDaily]) -> [Double] { + var totals = [0.0, 0.0, 0.0] + for day in daily { + for j in 0..<3 { + totals[j] += store.wideMoney ? day.money[j] : day.tokens[j] + } + } + return totals + } + + private func format(_ value: Double) -> String { + store.wideMoney ? Fmt.money(value) : Fmt.tokens(value) + } + + @ViewBuilder + private func sidebar(days: [String], daily: [MBDaily], byHarness: [Double], total: Double, note: String) -> some View { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 6) { + IconButton(systemName: "chevron.left", label: store.s("back")) { store.go(.main) } + Text(store.s("usageTitle")).font(.system(size: 14, weight: .bold)) + } + if let selection, selection < daily.count { + selectedSummary(day: days[selection], row: daily[selection]) + } else { + Text(format(total)) + .font(.system(size: 52, weight: .semibold)) + .tracking(-2.4) + .monospacedDigit() + .lineLimit(1) + .minimumScaleFactor(0.6) + .padding(.top, 26) + Text(store.s(store.wideMoney ? "wideCaptionMoney" : "wideCaptionTokens", ["n": String(store.wideRange)]) + + "\n" + store.s(store.wideScope == "local" ? "scopeLocal" : "scopeAll")) + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + .frame(minHeight: 36, alignment: .topLeading) + .padding(.top, 8) + } + subStats(daily: daily, total: total).padding(.top, 22) + harnessList(byHarness: byHarness, total: total).padding(.top, 28) + Spacer(minLength: 20) + Text(note) + .font(.system(size: 11)) + .foregroundStyle(Palette.ink3) + .lineSpacing(4) + .fixedSize(horizontal: false, vertical: true) + } + .padding(EdgeInsets(top: 20, leading: 22, bottom: 22, trailing: 22)) + } + + @ViewBuilder + private func selectedSummary(day: String, row: MBDaily) -> some View { + let values = store.wideMoney ? row.money : row.tokens + Text(format(values.reduce(0, +))) + .font(.system(size: 52, weight: .semibold)) + .tracking(-2.4) + .monospacedDigit() + .lineLimit(1) + .minimumScaleFactor(0.6) + .padding(.top, 26) + VStack(alignment: .leading, spacing: 2) { + let date = Fmt.day(day) + Text(Fmt.monthDay(date, locale: store.locale) + " " + Fmt.weekday(date, locale: store.locale)) + .fontWeight(.semibold) + .foregroundStyle(Palette.ink) + let present = harnessOrder.filter { values[$0.index] > 0 } + if present.isEmpty { + Text(store.s("noUsage")) + } + ForEach(present, id: \.index) { harness in + HStack(spacing: 4) { + Circle().fill(Palette.harness(harness.key)).frame(width: 7, height: 7) + Text("\(store.s("harness." + harness.key)) \(format(values[harness.index]))") + } + } + } + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + .monospacedDigit() + .frame(minHeight: 36, alignment: .topLeading) + .padding(.top, 8) + } + + @ViewBuilder + private func subStats(daily: [MBDaily], total: Double) -> some View { + let range = Double(max(1, store.wideRange)) + HStack(alignment: .top, spacing: 18) { + if store.wideMoney { + stat(store.s("dailyAverage"), Fmt.money(total / range)) + stat(store.s("paid"), Fmt.money(daily.reduce(0) { $0 + $1.paid })) + stat(store.s("tokenUnitTitle"), Fmt.tokens(daily.reduce(0) { $0 + $1.tokens.reduce(0, +) })) + } else { + stat(store.s("dailyAverage"), Fmt.tokens(total / range)) + stat(store.s("peakDay"), Fmt.tokens(daily.map { $0.tokens.reduce(0, +) }.max() ?? 0)) + } + } + } + + private func stat(_ label: String, _ value: String) -> some View { + VStack(alignment: .leading, spacing: 2) { + Text(label).font(.system(size: 11)).foregroundStyle(Palette.ink3) + Text(value).font(.system(size: 16, weight: .semibold)).tracking(-0.3).monospacedDigit() + } + } + + @ViewBuilder + private func harnessList(byHarness: [Double], total: Double) -> some View { + VStack(alignment: .leading, spacing: 0) { + HarnessBar(values: harnessOrder.map { ($0.key, byHarness[$0.index]) }) + .frame(height: 6) + .padding(.top, 10) + ForEach(harnessOrder, id: \.index) { harness in + HStack(spacing: 10) { + Circle().fill(Palette.harness(harness.key)).frame(width: 9, height: 9) + Text(store.s("harness." + harness.key)).frame(maxWidth: .infinity, alignment: .leading) + Text("\(Int((byHarness[harness.index] / max(total, 0.0001) * 100).rounded()))%") + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + Text(format(byHarness[harness.index])) + .fontWeight(.semibold) + .frame(width: 70, alignment: .trailing) + } + .font(.system(size: 13)) + .monospacedDigit() + .padding(.vertical, 9) + } + } + } + + private var controls: some View { + HStack(spacing: 8) { + Picker("", selection: $store.wideRange) { + Text(store.s("range7")).tag(7) + Text(store.s("range30")).tag(30) + Text(store.s("range90")).tag(90) + } + .pickerStyle(.segmented) + .labelsHidden() + .fixedSize() + Picker("", selection: $store.wideMoney) { + Text(store.s("metricMoney")).tag(true) + Text(store.s("metricTokens")).tag(false) + } + .pickerStyle(.segmented) + .labelsHidden() + .fixedSize() + Picker("", selection: $store.wideScope) { + Text(store.s("scopeLocal")).tag("local") + Text(store.s("scopeAll")).tag("all") + } + .pickerStyle(.segmented) + .labelsHidden() + .fixedSize() + } + } + + private func axisLabels(days: [String]) -> [(Int, String)] { + let every = store.wideRange == 7 ? 1 : store.wideRange == 30 ? 7 : 21 + var labels: [(Int, String)] = [] + for (i, day) in days.enumerated() where (days.count - 1 - i) % every == 0 { + let date = Fmt.day(day) + let label: String + if i == days.count - 1 { + label = store.s("today") + } else if store.wideRange == 7 { + label = Fmt.weekday(date, locale: store.locale) + } else { + label = Fmt.monthDay(date, locale: store.locale) + } + labels.append((i, label)) + } + return labels + } + + @ViewBuilder + private func modelList(_ models: [MBModelUsage]) -> some View { + let rows = models + .map { ($0, store.wideMoney ? $0.money : $0.tokens) } + .filter { $0.1 > 0 } + .sorted { $0.1 > $1.1 } + .prefix(8) + LazyVGrid(columns: [GridItem(.flexible(), spacing: 24), GridItem(.flexible(), spacing: 24)], alignment: .leading, spacing: 4) { + ForEach(Array(rows.enumerated()), id: \.offset) { index, row in + HStack(spacing: 10) { + Text("\(index + 1)").font(.system(size: 11)).foregroundStyle(Palette.ink3).frame(width: 16, alignment: .leading) + Circle().fill(Palette.harness(row.0.harness)).frame(width: 7, height: 7) + Text(row.0.name).lineLimit(1).truncationMode(.tail).frame(maxWidth: .infinity, alignment: .leading) + Text(format(row.1)).fontWeight(.semibold) + } + .font(.system(size: 12.5)) + .monospacedDigit() + .padding(.vertical, 7) + .overlay(alignment: .bottom) { Rectangle().fill(Palette.line).frame(height: 0.5) } + } + } + .padding(.top, 22) + .animation(Motion.quick, value: store.wideMoney) + } + + private var taskList: some View { + let tasks = view.tasks.sorted { $0.value > $1.value } + return VStack(spacing: 4) { + if tasks.isEmpty { + Text(store.s("noTasksToday")).font(.system(size: 12)).foregroundStyle(Palette.ink3).frame(maxWidth: .infinity, alignment: .leading) + } + ForEach(Array(tasks.enumerated()), id: \.element.id) { index, task in + Button { + controller.emitAndClose(["type": "open-session", "sessionId": task.sessionId]) + } label: { + WideTaskRow(store: store, index: index, task: task) + } + .buttonStyle(.plain) + .help(task.title) + } + } + .padding(.top, 22) + } +} + +struct WideTaskRow: View { + @ObservedObject var store: MenubarStore + let index: Int + let task: MBTask + + var body: some View { + HStack(spacing: 10) { + Text("\(index + 1)").font(.system(size: 11)).foregroundStyle(Palette.ink3).frame(width: 16, alignment: .leading) + Circle().fill(Palette.harness(task.harness)).frame(width: 7, height: 7) + HStack(spacing: 5) { + if task.running { BreathingDot(animate: true) } + Text(task.title).lineLimit(1).truncationMode(.tail) + } + .frame(maxWidth: .infinity, alignment: .leading) + TimelineView(.periodic(from: .now, by: 1)) { context in + let status = task.running && task.startedAtMs != nil + ? store.s("running") + " " + Fmt.clock(context.date.timeIntervalSince1970 - (task.startedAtMs ?? 0) / 1000) + : task.idleStatus + Text(status + " · " + task.meta).font(.system(size: 11.5)).foregroundStyle(Palette.ink3).lineLimit(1) + } + Text(Fmt.money(task.value)).fontWeight(.semibold).frame(width: 64, alignment: .trailing) + } + .font(.system(size: 12.5)) + .monospacedDigit() + .padding(.vertical, 7) + .contentShape(Rectangle()) + .overlay(alignment: .bottom) { Rectangle().fill(Palette.line).frame(height: 0.5) } + } +} + +/// Capsule segments in harness order. Each segment is its own capsule, so rounding comes from +/// a single shape per segment and tiny shares keep a readable minimum width. +struct HarnessBar: View { + let values: [(String, Double)] + + var body: some View { + GeometryReader { geometry in + let present = values.filter { $0.1 > 0 } + let total = max(present.reduce(0) { $0 + $1.1 }, 0.0001) + let gaps = CGFloat(max(0, present.count - 1)) * 2 + let available = max(0, geometry.size.width - gaps) + let minimum: CGFloat = 6 + let raw = present.map { CGFloat($0.1 / total) * available } + let deficit = raw.reduce(0) { $0 + max(0, minimum - $1) } + let flexible = raw.reduce(0) { $0 + ($1 > minimum ? $1 : 0) } + HStack(spacing: 2) { + ForEach(Array(present.enumerated()), id: \.offset) { index, item in + let width = raw[index] <= minimum ? minimum : raw[index] - deficit * (raw[index] / max(flexible, 0.0001)) + Capsule().fill(Palette.harness(item.0)).frame(width: max(minimum, width)) + } + } + } + } +} + +// MARK: - Manage page + +struct ManagePage: View { + @ObservedObject var store: MenubarStore + let view: MBView + let controller: StatusController + @State private var offset: CGFloat = 0 + @State private var contentHeight: CGFloat = 0 + @State private var headerHeight: CGFloat = 0 + @State private var dragging: String? + + var body: some View { + VStack(spacing: 0) { + HStack(spacing: 6) { + IconButton(systemName: "chevron.left", label: store.s("back")) { store.go(.main) } + Text(store.s("manageTitle")).font(.system(size: 14, weight: .bold)) + Spacer(minLength: 0) + Button { store.go(.main) } label: { + Text(store.s("done")) + .font(.system(size: 12.5, weight: .semibold)) + .foregroundStyle(Palette.onInk) + .padding(.horizontal, 14) + .frame(height: 30) + .background(Capsule().fill(Palette.ink)) + } + .buttonStyle(PressScaleStyle()) + .keyboardShortcut(.defaultAction) + } + .padding(EdgeInsets(top: 18, leading: 18, bottom: 10, trailing: 18)) + .background(GeometryReader { g in + Color.clear + .onAppear { headerHeight = g.size.height } + .onChange(of: g.size.height) { _, h in headerHeight = h } + }) + EdgeLine(visible: offset > 2) + TrackedScroll(offset: $offset, contentHeight: $contentHeight) { + content + } + } + .onChange(of: contentHeight) { report() } + .onChange(of: headerHeight) { report() } + } + + /// Sizes the panel to the whole page; it only scrolls when the screen is shorter. + private func report() { + guard contentHeight > 0 else { return } + let natural = (headerHeight + contentHeight + 1).rounded(.up) + if abs(store.manageContentHeight - natural) > 0.5 { store.manageContentHeight = natural } + } + + private var content: some View { + VStack(alignment: .leading, spacing: 0) { + VStack(alignment: .leading, spacing: 6) { + Text(store.s("manageHero")) + .font(.system(size: 24, weight: .semibold)) + .tracking(-0.5) + .fixedSize(horizontal: false, vertical: true) + Text(store.s("manageHeroSub")) + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(Palette.ink3) + .lineSpacing(3) + .fixedSize(horizontal: false, vertical: true) + } + .padding(EdgeInsets(top: 18, leading: 24, bottom: 0, trailing: 24)) + + section(store.s("sources"), store.s("sourcesShown", [ + "a": String(view.manage.filter { !$0.hidden }.count), + "b": String(view.manage.count), + ])) + sourceList + + section(store.s("statusIcon"), store.s("statusIconSub")) + StatusPreview(store: store, view: view) + .padding(.horizontal, 16) + Picker("", selection: Binding( + get: { view.settings.statusText }, + set: { Bridge.emitSettings(["statusText": $0]) } + )) { + Text(store.s("statusIconMode.icon")).tag("icon") + Text(store.s("statusIconMode.lowest")).tag("lowest") + Text(store.s("statusIconMode.today")).tag("today") + Text(store.s("statusIconMode.task")).tag("task") + } + .pickerStyle(.segmented) + .labelsHidden() + .padding(EdgeInsets(top: 12, leading: 16, bottom: 0, trailing: 16)) + + section(store.s("other"), nil) + Tile { + VStack(spacing: 0) { + settingRow(store.s("lowAlert"), store.s("lowAlertSub")) { + Toggle("", isOn: Binding(get: { view.settings.lowAlert }, set: { Bridge.emitSettings(["lowAlert": $0]) })) + .toggleStyle(.switch).controlSize(.small).labelsHidden() + } + Divider().overlay(Palette.line) + settingRow(store.s("privacy"), store.s("privacySub")) { + Toggle("", isOn: Binding(get: { view.settings.privacy }, set: { Bridge.emitSettings(["privacy": $0]) })) + .toggleStyle(.switch).controlSize(.small).labelsHidden() + } + Divider().overlay(Palette.line) + settingRow(store.s("usageScope"), nil) { + Picker("", selection: Binding(get: { view.settings.scope }, set: { Bridge.emitSettings(["scope": $0]) })) { + Text(store.s("scopeLocal")).tag("local") + Text(store.s("scopeAll")).tag("all") + } + .pickerStyle(.segmented) + .labelsHidden() + .controlSize(.small) + .fixedSize() + } + } + .padding(.horizontal, 14) + .padding(.vertical, 2) + } + .padding(.horizontal, 16) + + Button { controller.emitAndClose(["type": "open-provider-settings"]) } label: { + Text(store.s("manageFoot")) + .font(.system(size: 11.5)) + .foregroundStyle(Palette.ink3) + .lineSpacing(4) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + } + .buttonStyle(.plain) + .padding(EdgeInsets(top: 20, leading: 24, bottom: 22, trailing: 24)) + } + } + + private func section(_ title: String, _ detail: String?) -> some View { + HStack(alignment: .firstTextBaseline) { + Text(title).font(.system(size: 13, weight: .semibold)) + Spacer() + if let detail { + Text(detail).font(.system(size: 11.5)).foregroundStyle(Palette.ink3).monospacedDigit() + } + } + .padding(EdgeInsets(top: 26, leading: 24, bottom: 10, trailing: 24)) + } + + private func settingRow(_ title: String, _ sub: String?, @ViewBuilder control: () -> Control) -> some View { + HStack(spacing: 12) { + VStack(alignment: .leading, spacing: 1) { + Text(title).font(.system(size: 13)) + if let sub { + Text(sub).font(.system(size: 11)).foregroundStyle(Palette.ink3).fixedSize(horizontal: false, vertical: true) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + control() + } + .padding(.vertical, 11) + } + + private var sourceList: some View { + let rows = store.manageRows + return Tile { + VStack(spacing: 0) { + ForEach(Array(rows.enumerated()), id: \.element.id) { index, row in + ManageRow( + store: store, + row: row, + showDivider: index > 0, + dragging: dragging == row.id + ) + .onDrag { + dragging = row.id + return NSItemProvider(object: row.id as NSString) + } + .onDrop(of: [.text], delegate: ReorderDropDelegate(target: row.id, store: store, dragging: $dragging)) + } + } + .padding(4) + } + .padding(.horizontal, 16) + } +} + +struct ReorderDropDelegate: DropDelegate { + let target: String + let store: MenubarStore + @Binding var dragging: String? + + func dropEntered(info: DropInfo) { + guard let dragging, dragging != target else { return } + Task { @MainActor in store.move(dragging, into: target) } + } + + func dropUpdated(info: DropInfo) -> DropProposal? { DropProposal(operation: .move) } + + func performDrop(info: DropInfo) -> Bool { + Task { @MainActor in + store.commitOrder() + dragging = nil + } + return true + } + + func dropExited(info: DropInfo) {} +} + +struct ManageRow: View { + @ObservedObject var store: MenubarStore + let row: MBManageRow + let showDivider: Bool + let dragging: Bool + @State private var hovering = false + @State private var shake: CGFloat = 0 + + var body: some View { + HStack(spacing: 12) { + Image(systemName: "line.3.horizontal") + .font(.system(size: 11, weight: .semibold)) + .foregroundStyle(Palette.ink3) + .frame(width: 16, height: 28) + .accessibilityHidden(true) + ZStack { + RoundedRectangle(cornerRadius: 10, style: .circular).fill(Palette.ink5) + LogoView(svg: row.logoSvg, size: 15) + } + .frame(width: 30, height: 30) + .opacity(row.hidden ? 0.4 : 1) + VStack(alignment: .leading, spacing: 0) { + Text(row.title).font(.system(size: 13, weight: .semibold)).lineLimit(1) + Text(row.subtitle).font(.system(size: 11)).foregroundStyle(Palette.ink3).lineLimit(1) + } + .frame(maxWidth: .infinity, alignment: .leading) + .opacity(row.hidden ? 0.4 : 1) + Text(row.value) + .font(.system(size: 12)) + .foregroundStyle(Palette.ink3) + .monospacedDigit() + .padding(.trailing, 4) + Toggle("", isOn: Binding(get: { !row.hidden }, set: { _ in store.toggleHidden(row) })) + .toggleStyle(.switch) + .controlSize(.small) + .labelsHidden() + .offset(x: shake) + .accessibilityLabel(store.s("showSource", ["name": row.title])) + } + .padding(EdgeInsets(top: 9, leading: 6, bottom: 9, trailing: 10)) + .background(RoundedRectangle(cornerRadius: 16, style: .circular).fill(hovering ? Palette.ink5 : .clear)) + .overlay(alignment: .top) { + if showDivider { + Rectangle().fill(Palette.line).frame(height: 0.5).padding(.leading, 58).padding(.trailing, 12) + } + } + .opacity(dragging ? 0.35 : 1) + .contentShape(Rectangle()) + .onHover { hovering in withAnimation(.easeOut(duration: 0.15)) { self.hovering = hovering } } + .accessibilityElement(children: .contain) + .accessibilityLabel(row.title) + .accessibilityHint(store.s("reorderHint")) + .accessibilityAction(named: store.s("moveUp")) { store.moveBy(row.id, offset: -1) } + .accessibilityAction(named: store.s("moveDown")) { store.moveBy(row.id, offset: 1) } + .contextMenu { + Button(store.s("moveUp")) { store.moveBy(row.id, offset: -1) } + Button(store.s("moveDown")) { store.moveBy(row.id, offset: 1) } + } + .onChange(of: store.shakeId) { _, id in + guard id == row.id else { return } + withAnimation(.easeInOut(duration: 0.06).repeatCount(4, autoreverses: true)) { shake = 4 } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { withAnimation(.easeOut(duration: 0.05)) { shake = 0 } } + } + } +} + +/// A slice of a menu bar over a wallpaper, showing exactly what the status item will look like. +struct StatusPreview: View { + @ObservedObject var store: MenubarStore + let view: MBView + @Environment(\.colorScheme) private var colorScheme + + var body: some View { + let text = (view.statusTextByMode[view.settings.statusText] ?? nil) ?? "" + let dark = colorScheme == .dark + ZStack(alignment: .topLeading) { + wallpaper(dark: dark) + HStack(spacing: 4) { + Spacer() + Image(systemName: "wifi").font(.system(size: 13, weight: .semibold)).opacity(0.5).padding(.horizontal, 8) + HStack(spacing: 6) { + if let icon = Logos.statusIcon { + Image(nsImage: icon).renderingMode(.template).resizable().frame(width: 18, height: 18) + } + if !text.isEmpty { + Text(text).font(.system(size: 12.5, weight: .semibold)).monospacedDigit() + } + } + .padding(.horizontal, 8) + .frame(height: 24) + .background(RoundedRectangle(cornerRadius: 7, style: .continuous).fill(dark ? Color.white.opacity(0.18) : Color.black.opacity(0.11))) + TimelineView(.periodic(from: .now, by: 30)) { context in + Text(Fmt.clockTime(context.date, locale: store.locale)) + .font(.system(size: 13, weight: .medium)) + .monospacedDigit() + .opacity(0.8) + .padding(.horizontal, 8) + } + } + .foregroundStyle(dark ? Color(white: 0.957) : Color(white: 0.063)) + .padding(.horizontal, 14) + .frame(height: 30) + .background(.ultraThinMaterial) + .padding(.top, 22) + } + .frame(height: 96) + .clipShape(RoundedRectangle(cornerRadius: 20, style: .circular)) + .overlay(RoundedRectangle(cornerRadius: 20, style: .circular).strokeBorder(Palette.line, lineWidth: 0.5)) + .animation(Motion.quick, value: text) + .accessibilityElement(children: .ignore) + .accessibilityLabel(text.isEmpty ? "Cindy" : "Cindy \(text)") + } + + private func wallpaper(dark: Bool) -> some View { + ZStack { + (dark ? Color(red: 0.055, green: 0.059, blue: 0.071) : Color(red: 0.89, green: 0.882, blue: 0.859)) + RadialGradient(colors: [dark ? Color(red: 0.165, green: 0.137, blue: 0.251) : Color(red: 0.965, green: 0.937, blue: 0.894), .clear], center: UnitPoint(x: 0.15, y: 0.15), startRadius: 0, endRadius: 220) + RadialGradient(colors: [dark ? Color(red: 0.29, green: 0.165, blue: 0.11) : Color(red: 0.941, green: 0.788, blue: 0.651), .clear], center: UnitPoint(x: 0.85, y: 0.25), startRadius: 0, endRadius: 200) + RadialGradient(colors: [dark ? Color(red: 0.071, green: 0.231, blue: 0.227) : Color(red: 0.663, green: 0.765, blue: 0.776), .clear], center: UnitPoint(x: 0.7, y: 1), startRadius: 0, endRadius: 220) + } + } +} + +// MARK: - App + +@MainActor +final class AppDelegate: NSObject, NSApplicationDelegate { + let store = MenubarStore() + var controller: StatusController? + private let decoder = JSONDecoder() + + func applicationDidFinishLaunching(_ notification: Notification) { + controller = StatusController(store: store) + let args = CommandLine.arguments + if let index = args.firstIndex(of: "--preview"), index + 1 < args.count { + startPreview(path: args[index + 1], args: args) + return + } + startReadingStdin() + Bridge.emit(["type": "ready"]) + } + + /// Development preview: render a saved view model and open the panel without main. + private func startPreview(path: String, args: [String]) { + guard let data = FileManager.default.contents(atPath: path) else { + FileHandle.standardError.write(Data("menubar: preview file missing\n".utf8)) + NSApp.terminate(nil) + return + } + do { + let view = try decoder.decode(MBView.self, from: data) + store.apply(view) + } catch { + FileHandle.standardError.write(Data("menubar: preview decode failed: \(error)\n".utf8)) + NSApp.terminate(nil) + return + } + if let index = args.firstIndex(of: "--appearance"), index + 1 < args.count { + NSApp.appearance = NSAppearance(named: args[index + 1] == "dark" ? .darkAqua : .aqua) + } + var page: Page = .main + if let index = args.firstIndex(of: "--page"), index + 1 < args.count { + let value = args[index + 1] + if value == "wide" { page = .wide } + if value == "manage" { page = .manage } + if value.hasPrefix("detail:") { page = .detail(String(value.dropFirst(7))) } + } + if let index = args.firstIndex(of: "--list"), index + 1 < args.count, args[index + 1] == "tasks" { + store.wideList = .tasks + } + var snapshotPath: String? + if let index = args.firstIndex(of: "--snapshot"), index + 1 < args.count { + snapshotPath = args[index + 1] + } + var snapshotDelay = 1.2 + if let index = args.firstIndex(of: "--delay"), index + 1 < args.count, let value = Double(args[index + 1]) { + snapshotDelay = value + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in + self?.controller?.openForPreview(page: page) + guard let window = NSApp.windows.first(where: { $0 is MenubarPanel }) else { return } + FileHandle.standardError.write(Data("menubar: preview window \(window.windowNumber)\n".utf8)) + guard let snapshotPath else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + snapshotDelay) { + Self.writeSnapshot(of: window, to: snapshotPath) + NSApp.terminate(nil) + } + } + } + + /// Renders the panel into a PNG without screen recording access. The frosted material + /// cannot be sampled this way, so the backdrop is a flat stand-in for it. + private static func writeSnapshot(of window: NSWindow, to path: String) { + guard let view = window.contentView else { return } + let scale = window.backingScaleFactor + let size = view.bounds.size + guard let rep = NSBitmapImageRep( + bitmapDataPlanes: nil, pixelsWide: Int(size.width * scale), pixelsHigh: Int(size.height * scale), + bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, + colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0) + else { return } + rep.size = size + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) + let dark = NSApp.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua + (dark ? NSColor(white: 0.17, alpha: 1) : NSColor(white: 0.93, alpha: 1)).setFill() + NSBezierPath(roundedRect: view.bounds, xRadius: PanelBackgroundView.radius, yRadius: PanelBackgroundView.radius).fill() + NSGraphicsContext.restoreGraphicsState() + view.cacheDisplay(in: view.bounds, to: rep) + try? rep.representation(using: .png, properties: [:])?.write(to: URL(fileURLWithPath: path)) + } + + private func startReadingStdin() { + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + while let line = readLine(strippingNewline: true) { + self?.handle(line) + } + DispatchQueue.main.async { NSApp.terminate(nil) } + } + } + + nonisolated private func handle(_ line: String) { + guard let data = line.data(using: .utf8) else { return } + let decoder = JSONDecoder() + do { + let message = try decoder.decode(MBIncoming.self, from: data) + switch message.type { + case "shutdown": + DispatchQueue.main.async { NSApp.terminate(nil) } + case "update": + guard let view = message.view else { return } + DispatchQueue.main.async { [weak self] in + MainActor.assumeIsolated { self?.store.apply(view) } + } + case "open": + DispatchQueue.main.async { [weak self] in + MainActor.assumeIsolated { self?.controller?.open() } + } + default: + break + } + } catch { + Bridge.emit(["type": "error", "message": "Could not decode menu bar update: \(error)"]) + } + } +} + +@main +enum MenubarHelperMain { + static func main() { + let app = NSApplication.shared + let delegate = AppDelegate() + app.delegate = delegate + app.setActivationPolicy(.accessory) + withExtendedLifetime(delegate) { app.run() } + } +} + +// The Cindy "A" mark from the brand kit (24pt viewBox, 20pt ink, even-odd `>` cut-out). +let cindyMarkPath = "M 18.163,3.549 C 18.561,3.879 18.876,4.238 19.198,4.64 C 19.268,4.71 19.339,4.78 19.411,4.852 C 19.492,4.934 19.574,5.015 19.658,5.099 C 19.707,5.148 19.756,5.196 19.807,5.246 C 20.975,6.474 21.548,8.384 21.521,10.044 C 21.495,10.615 21.368,11.261 20.977,11.697 C 20.936,11.732 20.894,11.768 20.852,11.805 C 20.852,11.562 20.852,11.32 20.852,11.07 C 20.633,11.931 20.637,12.526 20.946,13.369 C 21.317,14.501 21.213,15.792 20.72,16.867 C 20.364,17.549 20.364,17.549 20.025,17.775 C 20.039,17.703 20.054,17.631 20.068,17.557 C 20.077,17.511 20.077,17.511 20.123,17.275 C 20.141,17.183 20.159,17.09 20.178,16.994 C 20.188,16.919 20.198,16.843 20.209,16.764 C 20.178,16.734 20.148,16.704 20.117,16.673 C 20.107,16.753 20.098,16.833 20.088,16.915 C 19.896,17.855 19.189,18.653 18.463,19.244 C 17.965,19.568 17.495,19.757 16.902,19.796 C 17.054,19.644 17.205,19.492 17.361,19.336 C 17.086,19.336 17.086,19.336 16.81,19.428 C 16.621,19.435 16.431,19.436 16.242,19.434 C 16.142,19.433 16.042,19.432 15.939,19.431 C 15.863,19.43 15.787,19.429 15.708,19.428 C 15.799,19.368 15.89,19.307 15.983,19.244 C 15.898,19.247 15.813,19.249 15.725,19.251 C 15.611,19.253 15.498,19.254 15.381,19.256 C 15.269,19.258 15.157,19.26 15.042,19.262 C 14.677,19.243 14.4,19.174 14.055,19.061 C 14.149,18.786 14.248,18.513 14.347,18.24 C 14.374,18.162 14.401,18.084 14.429,18.003 C 14.638,17.438 14.638,17.438 14.908,17.298 C 15.231,17.321 15.513,17.399 15.823,17.494 C 16.353,17.646 16.809,17.746 17.361,17.683 C 17.713,17.485 17.801,17.281 17.918,16.902 C 18.096,16.333 18.096,16.333 18.372,16.122 C 18.432,16.122 18.493,16.122 18.555,16.122 C 18.55,16.051 18.544,15.981 18.538,15.909 C 18.555,15.662 18.555,15.662 18.693,15.533 C 18.87,15.345 18.853,15.256 18.848,15.002 C 18.875,14.596 19.004,14.442 19.302,14.17 C 19.37,14.114 19.439,14.059 19.509,14.002 C 19.658,13.825 19.658,13.825 19.651,13.601 C 19.559,13.348 19.446,13.159 19.29,12.941 C 18.966,12.484 18.91,12.168 18.897,11.606 C 18.895,11.541 18.894,11.477 18.892,11.41 C 18.886,11.203 18.882,10.996 18.877,10.788 C 18.858,9.96 18.837,9.138 18.739,8.314 C 18.678,8.314 18.618,8.314 18.555,8.314 C 18.538,8.426 18.521,8.538 18.504,8.653 C 18.133,10.262 16.849,11.349 15.68,12.413 C 14.855,13.168 14.222,13.835 14.115,14.994 C 14.099,15.675 14.158,16.228 14.422,16.856 C 14.109,16.759 13.87,16.672 13.595,16.489 C 13.563,16.603 13.531,16.716 13.498,16.833 C 13.279,17.493 12.925,18.134 12.467,18.657 C 12.309,18.877 12.309,18.877 12.315,19.07 C 12.413,19.269 12.522,19.396 12.677,19.554 C 12.985,19.893 13.235,20.25 13.481,20.634 C 13.515,20.688 13.55,20.741 13.585,20.797 C 14.146,21.686 14.146,21.686 14.146,22 C 13.75,21.874 13.374,21.722 12.993,21.558 C 11.378,20.9 9.722,20.714 7.992,20.622 C 8.028,20.575 8.064,20.528 8.101,20.479 C 8.37,20.121 8.637,19.761 8.894,19.394 C 8.947,19.317 9.001,19.241 9.056,19.162 C 9.187,18.969 9.187,18.969 9.278,18.785 C 9.245,18.78 9.245,18.78 9.077,18.756 C 8.797,18.688 8.6,18.576 8.36,18.418 C 8.36,18.478 8.36,18.539 8.36,18.602 C 7.479,18.463 6.695,17.922 6.155,17.224 C 5.981,16.926 5.832,16.623 5.696,16.305 C 5.74,16.453 5.784,16.6 5.828,16.747 C 5.853,16.829 5.877,16.911 5.903,16.996 C 5.954,17.164 6.008,17.332 6.064,17.499 C 5.299,17.281 4.772,16.857 4.318,16.213 C 3.714,15.033 3.82,13.901 4.043,12.631 C 3.577,13.432 3.387,14.102 3.308,15.019 C 2.824,14.591 2.572,13.903 2.481,13.274 C 2.397,11.547 3.913,10.126 4.944,8.882 C 5.479,8.227 5.879,7.592 6.224,6.822 C 6.567,6.064 6.997,5.455 7.533,4.824 C 7.593,4.75 7.652,4.675 7.713,4.599 C 10.235,1.556 15.063,1.158 18.163,3.549 Z M 11.024,12.539 L 13.412,14.009 L 13.412,14.56 L 11.024,16.03 L 11.024,15.203 L 12.585,14.284 L 11.024,13.366 L 11.024,12.539 Z" diff --git a/apps/desktop/native/menubar/wordmark-dark.png b/apps/desktop/native/menubar/wordmark-dark.png new file mode 100644 index 00000000000..61ab6b7b60f Binary files /dev/null and b/apps/desktop/native/menubar/wordmark-dark.png differ diff --git a/apps/desktop/native/menubar/wordmark-light.png b/apps/desktop/native/menubar/wordmark-light.png new file mode 100644 index 00000000000..1f34d34fcc6 Binary files /dev/null and b/apps/desktop/native/menubar/wordmark-light.png differ diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 42de49e6f70..9517c882476 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -26,7 +26,7 @@ "gen:help-kb": "node scripts/gen-help-kb.mjs", "gen:help-kb:check": "node scripts/help-kb-guard.mjs", "lint": "eslint src/", - "typecheck": "cross-env NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit -p tsconfig.json", + "typecheck": "cross-env NODE_OPTIONS=--max-old-space-size=12288 tsc --noEmit -p tsconfig.json", "format": "prettier --write src/", "ensure-deps": "node ../../scripts/ensure-deps.mjs", "ensure-dev-runtime-assets": "node ../../scripts/ensure-dev-runtime-assets.mjs", diff --git a/apps/desktop/scripts/check-mermaid-copy.mjs b/apps/desktop/scripts/check-mermaid-copy.mjs new file mode 100644 index 00000000000..f821ff23e18 --- /dev/null +++ b/apps/desktop/scripts/check-mermaid-copy.mjs @@ -0,0 +1,313 @@ +#!/usr/bin/env node +/* global process, URL, window, Image, Blob, document, Buffer, console */ +// Real production components + Mermaid + browser decoding/canvas. Only the +// native clipboard sink is replaced. No Electron, account, disk media or network. +// node apps/desktop/scripts/check-mermaid-copy.mjs [Chromium path] [source file] [evidence directory] +import assert from 'node:assert/strict'; +import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import esbuild from 'esbuild'; +import postcss from 'postcss'; +import tailwind from 'tailwindcss'; +import loadConfig from 'tailwindcss/loadConfig.js'; +import { chromium } from 'playwright-core'; + +const desktop = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const renderer = path.join(desktop, 'src/renderer'); +const temp = mkdtempSync(path.join(os.tmpdir(), 'cindy-mermaid-copy-')); +const sourceOverride = process.argv[3] ? readFileSync(process.argv[3], 'utf8').trim() : undefined; +const evidence = process.argv[4]; +if (evidence) mkdirSync(evidence, { recursive: true }); +let browser; +try { + const config = loadConfig(path.join(desktop, 'tailwind.config.ts')); + config.content = [ + path.join(renderer, 'components/chat/*.tsx'), + path.join(renderer, 'components/ui/*.tsx'), + ]; + const css = ( + await postcss([tailwind(config)]).process( + readFileSync(path.join(renderer, 'styles/generated/tokens.css'), 'utf8') + + '\n' + + readFileSync(path.join(renderer, 'styles/globals.css'), 'utf8').replace( + /^@import.*$/gm, + '', + ), + { from: undefined }, + ) + ).css; + const bundle = await esbuild.build({ + entryPoints: [path.join(desktop, 'scripts/fixtures/mermaid-copy/mermaid-copy-fixture.tsx')], + write: false, + outfile: path.join(temp, 'mermaid-copy.js'), + bundle: true, + format: 'esm', + platform: 'browser', + jsx: 'automatic', + tsconfig: path.join(desktop, 'tsconfig.json'), + loader: { + '.svg': 'dataurl', + '.png': 'dataurl', + '.woff2': 'dataurl', + '.woff': 'dataurl', + '.ttf': 'dataurl', + }, + define: { 'import.meta.env.PROD': 'true', 'import.meta.env.DEV': 'false' }, + }); + const script = bundle.outputFiles.find((file) => file.path.endsWith('.js')).text; + const styles = + css + + bundle.outputFiles + .filter((file) => file.path.endsWith('.css')) + .map((file) => file.text) + .join('\n'); + browser = await chromium.launch({ + headless: true, + ...(process.argv[2] ? { executablePath: process.argv[2] } : {}), + }); + const page = await browser.newPage({ viewport: { width: 1100, height: 1000 } }); + const pageErrors = []; + page.on('pageerror', (error) => pageErrors.push(error.message)); + // This is stricter than production network policy; same data: image allowance, + // no inline scripts and no bypassCSP / disable-web-security / clipboard grants. + await page.route('**/*', (route) => { + const url = new URL(route.request().url()); + if (url.origin !== 'http://fixture.local') return route.abort(); + if (url.pathname === '/mermaid-copy.js') + return route.fulfill({ contentType: 'text/javascript', body: script }); + if (url.pathname === '/mermaid-copy.css') + return route.fulfill({ contentType: 'text/css', body: styles }); + return route.fulfill({ + contentType: 'text/html', + headers: { + 'Content-Security-Policy': + "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'none'; object-src 'none'; frame-src 'none'", + }, + body: '
', + }); + }); + await page.goto('http://fixture.local/'); + await page.waitForFunction(() => Boolean(window.fixture)); + const raw = sourceOverride ?? (await page.evaluate(() => window.fixture.source)); + const diagram = '#root svg[viewBox]:not([class*="lucide"])'; + const label = (key) => page.evaluate((k) => window.fixture.label(k), key); + async function mount(simplified = false, dark = false, locale = 'zh-CN') { + await page.evaluate((args) => window.fixture.render(...args), [raw, simplified, dark, locale]); + await page.waitForSelector(`${diagram} .nodes foreignObject`); + } + async function copyCount(count) { + await page.waitForFunction( + (expected) => + window.fixture.copies.length === expected || + window.fixture.getToastSnapshot().some((item) => !item.exiting && item.variant === 'error'), + count, + ); + assert.equal( + await page.evaluate(() => window.fixture.copies.length), + count, + 'The production copy button must reach the native sink without a failure toast', + ); + } + async function validatePng(selector, copyIndex = 0) { + const result = await page.evaluate( + async ({ selector, copyIndex }) => { + const { png, plainText } = window.fixture.copies[copyIndex]; + const image = new Image(); + const url = URL.createObjectURL(new Blob([new Uint8Array(png)], { type: 'image/png' })); + try { + image.src = url; + await image.decode(); + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0); + const svg = document.querySelector(selector); + const svgRect = svg.getBoundingClientRect(); + // Each HTML label's interior must contain actual rendered glyphs, not + // just an empty rectangle. Compare pixel variation inside its bounds. + const labelPixels = [...svg.querySelectorAll('.nodes foreignObject')].map((node) => { + const r = node.getBoundingClientRect(); + const x = Math.max( + 0, + Math.floor(((r.left - svgRect.left) / svgRect.width) * canvas.width), + ); + const y = Math.max( + 0, + Math.floor(((r.top - svgRect.top) / svgRect.height) * canvas.height), + ); + const w = Math.max(1, Math.floor((r.width / svgRect.width) * canvas.width)); + const h = Math.max(1, Math.floor((r.height / svgRect.height) * canvas.height)); + const pixels = ctx.getImageData(x, y, w, h).data; + let varied = 0; + for (let i = 0; i < pixels.length; i += 4) { + if ( + Math.abs(pixels[i] - pixels[0]) + + Math.abs(pixels[i + 1] - pixels[1]) + + Math.abs(pixels[i + 2] - pixels[2]) > + 60 + ) + varied++; + } + return varied; + }); + return { + signature: png.slice(0, 8), + bytes: png.length, + width: canvas.width, + height: canvas.height, + labelPixels, + plainText, + }; + } finally { + URL.revokeObjectURL(url); + } + }, + { selector, copyIndex }, + ); + assert.deepEqual(result.signature, [137, 80, 78, 71, 13, 10, 26, 10]); + assert(result.bytes > 1000); + assert(result.width > 100 && result.height > 100); + assert(result.width <= 4096 && result.height <= 4096); + assert( + result.labelPixels.length >= 5 && result.labelPixels.every((count) => count > 20), + JSON.stringify(result), + ); + assert.equal(result.plainText, selector.startsWith('#editor') ? raw : raw + '\n'); + return result; + } + const results = []; + for (const simplified of [false, true]) { + for (const dark of [false, true]) { + await mount(simplified, dark); + // Independent control: the old raw HTML-as-SVG algorithm fails before + // clipboard access. No mocked Mermaid/image/canvas success path. + const oldFailure = await page.evaluate(async (selector) => { + const rawSvg = document.querySelector(selector).outerHTML; + const image = new Image(); + image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(rawSvg)}`; + try { + await image.decode(); + return null; + } catch (error) { + return error.name; + } + }, diagram); + assert.equal(oldFailure, 'EncodingError'); + await page + .getByRole('button', { name: await label('chat.mermaid.copy'), exact: true }) + .click(); + await copyCount(1); + const png = await validatePng(diagram); + await page + .getByRole('button', { name: await label('chat.mermaid.copied'), exact: true }) + .waitFor(); + await page.locator(diagram).click({ button: 'right' }); + await page + .getByRole('menuitem', { name: await label('chat.media.copyImage'), exact: true }) + .click(); + await copyCount(2); + await page.getByRole('menuitem').waitFor({ state: 'hidden' }); + await validatePng(diagram, 1); + if (evidence && simplified) { + const suffix = dark ? 'dark' : 'light'; + await page.screenshot({ path: path.join(evidence, `mermaid-panel-${suffix}.png`) }); + const bytes = await page.evaluate(() => window.fixture.copies[0].png); + writeFileSync(path.join(evidence, `mermaid-export-${suffix}.png`), Buffer.from(bytes)); + } + await page + .getByRole('button', { name: await label('chat.mermaid.zoom'), exact: true }) + .click(); + await page + .getByRole('button', { name: await label('chat.mermaid.copy'), exact: true }) + .last() + .click(); + await copyCount(3); + await validatePng(diagram, 2); + results.push({ + simplified, + dark, + bytes: png.bytes, + size: [png.width, png.height], + labelPixels: png.labelPixels, + }); + await page.keyboard.press('Escape'); + await page.waitForFunction(() => document.body.style.overflow !== 'hidden'); + } + } + // Native failures remain localized, never become false copied feedback. + for (const locale of ['zh-CN', 'zh-TW', 'en', 'ja', 'ko']) { + await mount(true, false, locale); + await page.evaluate(() => window.fixture.setRejectCopy(true)); + await page.getByRole('button', { name: await label('chat.mermaid.copy'), exact: true }).click(); + await page.waitForFunction(() => + window.fixture + .getToastSnapshot() + .some( + (item) => !item.exiting && item.message === window.fixture.label('ipcError.INTERNAL'), + ), + ); + assert.equal(await page.evaluate(() => window.fixture.copies.length), 0); + assert.equal( + await page + .getByRole('button', { name: await label('chat.mermaid.copied'), exact: true }) + .count(), + 0, + ); + assert( + await page.evaluate(() => + window.fixture.logs.some((entry) => String(entry[2]).includes('"stage":"clipboard"')), + ), + ); + } + await mount(); + await page.evaluate(() => window.fixture.setDecodeFailure(true)); + await page.getByRole('button', { name: await label('chat.mermaid.copy'), exact: true }).click(); + await page.waitForFunction(() => + window.fixture + .getToastSnapshot() + .some( + (item) => !item.exiting && item.message === window.fixture.label('chat.media.copyFailed'), + ), + ); + assert.equal(await page.evaluate(() => window.fixture.copies.length), 0); + assert( + await page.evaluate(() => + window.fixture.logs.some((entry) => String(entry[2]).includes('"stage":"rasterize"')), + ), + ); + await page.evaluate(() => window.fixture.setDecodeFailure(false)); + // The third shared consumer: actual CodeMirror Mermaid preview toolbar. + await mount(); + await page.evaluate((raw) => window.fixture.renderEditor(raw), raw); + await page.waitForSelector('#editor svg .nodes foreignObject'); + await page + .locator('#editor') + .getByRole('button', { + name: await label('ccAgent.workdirBrowse.mermaidEditor.toolbarCopy'), + exact: true, + }) + .click(); + await copyCount(1); + await validatePng('#editor svg[viewBox]:not([class*="lucide"])'); + assert.deepEqual(pageErrors, []); + console.log( + JSON.stringify( + { + passed: true, + browser: browser.version(), + results, + localizedFailures: 5, + rasterizeFailure: true, + editorCopy: true, + }, + null, + 2, + ), + ); +} finally { + await browser?.close(); + rmSync(temp, { recursive: true, force: true }); +} diff --git a/apps/desktop/scripts/fixtures/cindy-device-picker/device-picker-fixture.tsx b/apps/desktop/scripts/fixtures/cindy-device-picker/device-picker-fixture.tsx new file mode 100644 index 00000000000..f18f59ba4b0 --- /dev/null +++ b/apps/desktop/scripts/fixtures/cindy-device-picker/device-picker-fixture.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react'; +import { createRoot } from 'react-dom/client'; +import i18next from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import { MemoryRouter, useLocation } from 'react-router-dom'; +import zh from '../../../src/renderer/i18n/locales/zh-CN/common.json'; +import '../../../src/renderer/themes/colors'; +import { ThemeService } from '../../../src/renderer/themes/theme-service'; +import { defaultLight } from '../../../src/renderer/themes/builtin/default-light'; +import { defaultDark } from '../../../src/renderer/themes/builtin/default-dark'; +import { CindyDeviceRow } from '../../../src/renderer/features/bots/CindyDeviceRow'; +import { BotSessionContentHeader } from '../../../src/renderer/features/bots/BotSessionContentHeader'; +import { cindyDeviceOptions } from '../../../src/renderer/features/bots/cindyDeviceRoster'; +import { Select } from '../../../src/renderer/components/ui/select'; + +const query = new URLSearchParams(location.search); +const dark = query.get('theme') === 'dark'; +new ThemeService().applyTheme(dark ? defaultDark : defaultLight); +document.documentElement.classList.toggle('dark', dark); +await i18next.use(initReactI18next).init({ + lng: 'zh-CN', + resources: { 'zh-CN': { translation: zh } }, +}); + +// Only roster hooks are stubbed by the runner; geometry, Radix and routing are real. +const data = (window as any).devicePickerFixture; +const options = cindyDeviceOptions(data.bots, data.remoteBots, data.devices, data.unread, '本机'); +function App() { + const [current, setCurrent] = useState(options[query.has('long') ? 1 : 0]); + const [opens, setOpens] = useState(0); + const [selections, setSelections] = useState(0); + const route = useLocation(); + return ( + <> +

+ {query.get('stage')} · {dark ? 'Dark' : 'Light'} · 生产组件浏览器验证(非实机) +

+
+ +
+
+ +
+
+ {opens} + {selections} + {route.pathname} +
+ { + const enabled = event.currentTarget.checked; + // One explicit media owner per provider; other chat routes and keys stay intact. + setRtSynced((prev) => Object.fromEntries( + Object.entries(prev).map(([agent, runtime]) => [agent, { + ...runtime, + imageApi: agent === activeTab && enabled + ? 'openai-images' + : enabled || agent === activeTab ? undefined : runtime.imageApi, + }]), + ) as Record); + }} + className="h-4 w-4 shrink-0 cursor-pointer accent-[var(--settings-menu-text-selected)]" + /> + {t('settings.providers.custom.fields.openaiImages')} + +

+ {t('settings.providers.custom.fields.openaiImagesHint')} +

+
+ )} + + {authMode !== 'oauth' && ( +
+ +

+ {t('settings.providers.custom.fields.xaiVideosHint')} +

+
+ )} + {/* Codex Responses Provider 级能力。放在自定义请求头之后,默认收起; 同一张说明卡片支持 hover/focus 临时预览和 click/tap 固定。 */} {canShowImageGenerationAdvanced && ( diff --git a/apps/desktop/src/renderer/components/settings/ProvidersSection.tsx b/apps/desktop/src/renderer/components/settings/ProvidersSection.tsx index 284995a962a..547937408e4 100644 --- a/apps/desktop/src/renderer/components/settings/ProvidersSection.tsx +++ b/apps/desktop/src/renderer/components/settings/ProvidersSection.tsx @@ -1,3 +1,8 @@ +import { refreshLocalCapabilities } from '@/hooks/useAgentCapabilities'; +import { useCodexRateLimits } from '@/hooks/useCodexRateLimits'; +import { useCodexRateLimitReset } from '@/hooks/useCodexRateLimitReset'; +import { CodexResetCredits } from '../status/CodexResetCredits'; +import { providerWeeklyQuotaSource } from '../new-chat/useProviderWeeklyQuota'; /** * ProvidersSection —— 设置 → 模型供应商页(2026-07 重构:双栏管理)。 * @@ -87,7 +92,13 @@ import { useProviderSubscriptionCard } from './useProviderSubscriptionCard'; import { QuotaHoverCard } from '../status/QuotaHoverCard'; import { ProviderConnectionDialog } from './ProviderConnectionDialog'; import { AddProviderWizard, type WizardEntry } from './AddProviderWizard'; +import { CursorProviderSetup } from './CursorProviderSetup'; import { OllamaProviderDetail } from './OllamaProviderDetail'; +import { + OwnRemoteProviderDetail, + OwnRemoteProviderRows, + useOwnRemoteProviderList, +} from './OwnRemoteProviders'; import { LlamaCppProviderDetail } from './LlamaCppProviderDetail'; import { MANAGED_LLAMACPP_PROVIDER_ID } from '../../../shared/llamaCpp'; import { @@ -105,7 +116,9 @@ import { XDIncMark } from '@/components/icons/XDIncMark'; import { hasProviderLogo, ProviderLogoMark } from '@/components/icons/ProviderLogoMark'; import { SortableList } from '@/components/sidebar/SortableList'; import { ProviderShareEntryButton } from '@/features/provider-share/ProviderShareEntryButton'; +import { useLocalProviderGroupsState } from '@/features/provider-group/useLocalProviderGroups'; import { ProviderShareManagePage } from '@/features/provider-share/ProviderShareManagePage'; +import { ProviderSharePasteButton } from '@/features/provider-share/ProviderSharePasteDialog'; import { ProviderShareReceivedDetail, ProviderShareReceivedRailGroup, @@ -155,10 +168,13 @@ function writeProviderDisabled(providerId: string, disabled: boolean, errorText: } /** - * 「允许被远程调用」(供应商级远程 Agent 授权)。默认关闭。能力逐级开启(产品规则 + * 「允许被远程调用」(供应商级远程 Agent 授权)与「远程与分享」入口合成的一行。默认关闭。能力逐级开启(产品规则 * docs/product-rules/provider-sharing.md §3):允许远程控制 → 允许被远程调用 → 分享。 - * 前两级任一未开启时整行仍显示,但开关或分享入口不可点击,并说明先开启哪一级。 + * 前两级任一未开启时整行仍显示,但开关不可用,并说明先开启哪一级。 * 成功后 main 广播 PROVIDER_CHANGED 刷新快照。 + * + * 供应商组不另占一行(2026-10-11 用户反馈详情太长):组的状态写在开关左侧的入口按钮上,组设置在入口进去的 + * 「远程与分享」页。组状态取本机组设置快照,第一次读完之前整行不出现,免得先显示「没有组」再跳成组。 */ function RemoteProviderAccessRow({ provider, @@ -173,20 +189,23 @@ function RemoteProviderAccessRow({ }) { const { t } = useTranslation(); const navigate = useNavigate(); + const localGroups = useLocalProviderGroupsState(); const [enabled, setEnabled] = useState(provider.remoteInvocationEnabled === true); const [busy, setBusy] = useState(false); useEffect(() => { setEnabled(provider.remoteInvocationEnabled === true); }, [provider.remoteInvocationEnabled]); + if (!localGroups.ready) return null; + const group = localGroups.groups[provider.id] ?? null; const gate = providerShareGate({ remoteControlEnabled, invocationEnabled: enabled }); return (
-
+
{t('settings.providers.detail.remoteAccess.label')} @@ -204,13 +223,20 @@ function RemoteProviderAccessRow({ ) : gate === 'invocation-off' ? ( t('providerShare.entry.descriptionOff') + ) : group ? ( + // 有组时 Agent 由组选一台运行,不再是「在这台电脑上运行」。 + t('settings.providers.detail.remoteAccess.descriptionGroup') ) : ( t('settings.providers.detail.remoteAccess.description') )}
-
- +
+ (null); const hasDetail = Boolean(detail); useLayoutEffect(() => { @@ -678,6 +707,8 @@ function DetailHeader({ agent: provider.agents[0] === 'claude-code' ? 'Claude Code' + : provider.agents[0] === 'cursor' + ? 'Cursor' : provider.agents[0] === 'pi' ? 'Pi' : 'Codex', @@ -866,7 +897,11 @@ function DetailHeader({ ? () => void window.electronAPI.openExternal('https://grok.com') : undefined } - /> + > + {resetEnabled ? ( + void manualReset.reset(credit)} /> + ) : null} +
))} {children} @@ -1041,7 +1076,6 @@ function OpenAiHeader({ refresh, triggerLogin, cancelLogin, - logout, } = useCodexAuth(); const reconnectRequired = state.kind === 'reconnect-required'; const loggingIn = state.kind === 'login-pending'; @@ -2409,17 +2443,33 @@ export function ProvidersSection() { const openaiReconnectRequired = codexAuth.state.kind === 'reconnect-required'; const [selectedId, setSelectedId] = useState(null); - // 左栏「分享给我的」里选中的一条;选中自己的供应商或登录引导时清空。 + // 左栏里选中的另一台电脑上的供应商:「分享给我的」一条,或自己其他电脑上的一个(两者互斥); + // 选中本机供应商或登录引导时清空。 const [selectedShareId, setSelectedShareId] = useState(null); + const [selectedRemoteKey, setSelectedRemoteKey] = useState(null); const { received: receivedShares } = useProviderShareReceived(); const selectedShare = selectedShareId ? receivedShares.find((share) => share.shareId === selectedShareId) ?? null : null; + const { entries: ownRemoteProviders, hiddenShareIds: groupedShareIds } = useOwnRemoteProviderList(); + const selectedRemote = selectedRemoteKey + ? ownRemoteProviders.find((entry) => entry.key === selectedRemoteKey) ?? null + : null; + const otherDeviceSelected = selectedShare !== null || selectedRemote !== null; // 用户或深链、向导完成、导入完成等「打开某个供应商」时,右栏回到那个供应商(即使它就是当前选中项)。 const selectProvider = useCallback((providerId: string) => { setSelectedShareId(null); + setSelectedRemoteKey(null); setSelectedId(providerId); }, []); + const selectShare = useCallback((shareId: string) => { + setSelectedRemoteKey(null); + setSelectedShareId(shareId); + }, []); + const selectRemote = useCallback((key: string) => { + setSelectedShareId(null); + setSelectedRemoteKey(key); + }, []); const [pendingProviderOrder, setPendingProviderOrder] = useState<{ dataOwnerId: string | null; ids: string[]; @@ -2528,6 +2578,8 @@ export function ProvidersSection() { const openaiHasImageCap = p.id === 'openai' && (p.imageModels?.length ?? 0) > 0; if ( p.id === 'xd' || + // Native installation remains manageable even when model discovery has not succeeded. + (p.id === 'cursor' && detections.some((d) => d.providerId === 'cursor' && d.installed)) || p.connected || p.removed === false || (p.id === 'openai' && openaiReconnectRequired) || @@ -2553,7 +2605,7 @@ export function ProvidersSection() { } } return rows; - }, [providers, openaiReconnectRequired]); + }, [providers, openaiReconnectRequired, detections]); const orderedVisibleProviders = useMemo( () => applyProviderOrder(visibleProviders, providerOrder), @@ -2721,7 +2773,7 @@ export function ProvidersSection() { const model = searchParams.get('model')?.trim() || null; const agentParam = searchParams.get('agent'); const agent = - agentParam === 'claude-code' || agentParam === 'codex' || agentParam === 'pi' + agentParam === 'claude-code' || agentParam === 'codex' || agentParam === 'pi' || agentParam === 'cursor' ? agentParam : undefined; const importId = searchParams.get('import'); @@ -2892,6 +2944,7 @@ export function ProvidersSection() { if (!isBuiltinRefreshableProviderId(p.id) || !beginProviderRefresh(p.id)) return; try { await window.electronAPI.maker.refreshBuiltinProviderModels(p.id); + await refreshLocalCapabilities(); toast.success(t('settings.providers.models.refreshDone')); refetch(); } catch (err) { @@ -2902,7 +2955,9 @@ export function ProvidersSection() { if (ipcError?.code === 'MODEL_CATALOG_FETCH_DISABLED') { toast.info(t('settings.providers.models.refreshFetchDisabled')); } else { - toast.error(t('settings.providers.models.refreshFailed')); + toast.error(t(p.id === 'cursor' + ? 'settings.providers.cursor.refreshFailed' + : 'settings.providers.models.refreshFailed')); } } finally { finishProviderRefresh(p.id); @@ -2950,6 +3005,14 @@ export function ProvidersSection() { if (p.id === 'openai') return ; if (p.id === 'xai') return ; + if (p.id === 'cursor') return ( + : undefined} /> + ); if (p.source === 'builtin' && p.auth.method === 'apiKey' && isBuiltinApiKeyProviderId(p.id)) { return ( @@ -3011,16 +3074,20 @@ export function ProvidersSection() { return (
{shareManagePage ?? (<> -
-

- {t('settings.providers.title')} -

-

- {t('settings.providers.subtitle')} -

+
+
+

+ {t('settings.providers.title')} +

+

+ {t('settings.providers.subtitle')} +

+
+ {/* 收到别人的供应商分享链接时从这里输入(加入网页唤起 Cindy 失败时也指引到这里)。 */} +
{/* 先取数据再渲染卡片(规则 7:首帧即终态高度,不出现连接态翻转的跳变帧)。 @@ -3046,7 +3113,7 @@ export function ProvidersSection() {
{showCindySignin && ( { selectProvider(CINDY_SIGNIN_ID); }} @@ -3066,7 +3133,7 @@ export function ProvidersSection() { renderItem={(provider, index) => ( {orderAnnouncement} - {/* 分享给我的供应商(受邀者):与自己的供应商同列,单独成组。 */} + {/* 自己其他电脑上能用的供应商:接在本机供应商后面,图标带远程角标。 */} + { + setFocusedModel(null); + selectRemote(key); + }} + /> + {/* 分享给我的供应商(受邀者):单独成组,没有分享时不显示。 */} { setFocusedModel(null); - setSelectedShareId(shareId); + selectShare(shareId); }} /> {suggestions.length > 0 && ( @@ -3139,11 +3216,19 @@ export function ProvidersSection() { {/* 右栏身份固定;说明、资产和模型共用 DetailHeader 的滚动区。 */}
{selectedShare ? ( + ) : selectedRemote ? ( + ) : cindySigninActive ? ( /* 登录引导是 brand-scale surface(DESIGN §3):48px 标识 → 24px 名字 → 一行价值主张 → 赠送余额徽标 → 黑 CTA,间距走 8px 系统。底部留白比 @@ -3249,8 +3334,9 @@ export function ProvidersSection() { />
)} - {/* 允许被远程调用 + 管理分享:供应商已连接且能跑 Agent 时出现。本机未允许 - 远程控制时整行仍显示,但开关与分享入口不可用(provider-sharing.md §3)。 */} + {/* 允许被远程调用 + 「远程与分享」入口(含供应商组):供应商已连接且能跑 Agent 时出现。 + 本机未允许远程控制时整行仍显示,但开关不可用(provider-sharing.md §3);入口始终可用, + 只给本机用时也可以建组(provider-groups.md §10)。 */} {remoteControlEnabled !== null && !effectiveSelected.suspended && effectiveSelected.connected && diff --git a/apps/desktop/src/renderer/components/settings/ReadOnlyProviderModels.tsx b/apps/desktop/src/renderer/components/settings/ReadOnlyProviderModels.tsx new file mode 100644 index 00000000000..076961e11c2 --- /dev/null +++ b/apps/desktop/src/renderer/components/settings/ReadOnlyProviderModels.tsx @@ -0,0 +1,90 @@ +/** + * 另一台电脑上的供应商(自己其他电脑的远程供应商、别人分享给我的供应商)在设置右栏里的 + * 只读模型清单:开关与排序都由那台电脑决定,这里只列出它开放的模型。 + */ +import type { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; + +import type { AgentKind, ProviderView } from '@cindy/model-providers'; + +import { isDeviceModelVisible } from '@/lib/providerModels'; + +export interface ReadOnlyProviderModel { + id: string; + name: string; +} + +const AGENT_ORDER: readonly AgentKind[] = ['claude-code', 'codex', 'pi']; + +/** 那台电脑上这个供应商开放的模型:去掉停用与隐藏的,按 Agent 顺序去重。 */ +export function readOnlyProviderModels( + provider: ProviderView | null, + overrides: Record | undefined, +): ReadOnlyProviderModel[] { + if (!provider) return []; + const seen = new Set(); + const rows: ReadOnlyProviderModel[] = []; + for (const agent of AGENT_ORDER) { + for (const model of provider.models[agent] ?? []) { + if (model.disabled || seen.has(model.id)) continue; + if (!isDeviceModelVisible(overrides, agent, provider.id, model)) continue; + seen.add(model.id); + rows.push({ id: model.id, name: model.name || model.id }); + } + } + return rows; +} + +/** 「可用的模型」一节;note 非空时以一句说明代替清单(读取中、读不到、暂停等),action 跟在说明下面。 */ +export function ReadOnlyProviderModelSection({ + title, + models, + note, + action, + testId, +}: { + title: string; + models: readonly ReadOnlyProviderModel[]; + note: string | null; + action?: ReactNode; + testId?: string; +}) { + const { t } = useTranslation(); + return ( +
+
+ + {title} + + {note === null && ( + + {t('settings.providers.models.modelCount', { count: models.length })} + + )} +
+ {note !== null ? ( +
+

+ {note} +

+ {action} +
+ ) : ( +
    + {models.map((model) => ( +
  • + {model.name} +
  • + ))} +
+ )} +
+ ); +} diff --git a/apps/desktop/src/renderer/components/settings/RemoteSection.tsx b/apps/desktop/src/renderer/components/settings/RemoteSection.tsx index 2110486ab98..e9f4c9e6142 100644 --- a/apps/desktop/src/renderer/components/settings/RemoteSection.tsx +++ b/apps/desktop/src/renderer/components/settings/RemoteSection.tsx @@ -50,6 +50,7 @@ interface RowProps { onDisconnect: () => void; onRemove: () => void; onSetupKey: () => void; + onReviewHostKey: () => void; onEdit: () => void; onToggleAutoConnect: (next: boolean) => void; } @@ -81,6 +82,7 @@ function HostRow({ onDisconnect, onRemove, onSetupKey, + onReviewHostKey, onEdit, onToggleAutoConnect, }: RowProps) { @@ -99,7 +101,10 @@ function HostRow({ const via = snap.lastAuthLabel ? ` · ${t('settings.remote.viaAuth', { label: snap.lastAuthLabel })}` : ''; - const subtitle = snap.lastError && snap.status === 'failed' + const keyMismatch = snap.status === 'failed' ? snap.hostKeyMismatch : undefined; + const subtitle = keyMismatch + ? t('settings.remote.hostKey.explanation', { host: keyMismatch.host }) + : snap.lastError && snap.status === 'failed' ? snap.lastError : `${snap.config.user}@${snap.config.hostname}:${snap.config.port}${via}`; @@ -199,7 +204,12 @@ function HostRow({ /> {t('settings.remote.button.autoConnect')} - {snap.status === 'failed' && ( + {keyMismatch && ( + + )} + {snap.status === 'failed' && !keyMismatch && ( +
+ ) : null} + {!canReset && !retryOnly && availableCount > 0 ? ( +

{t('codexResets.availableAtLimit')}

+ ) : null} + {available.length > 0 && availableCount > 0 ? ( +
    + {available.map((credit, index) => ( +
  • +
    +
    {credit.title || t('codexResets.fullReset')}
    +
    {formatExpiry(credit.expiresAt)}
    +
    + +
  • + ))} +
+ ) : null} + {!retryOnly && (availableCount === 0 ? ( +

{t('codexResets.noCredit')}

+ ) : ( + <> + {available.length < availableCount && ( +

{t('codexResets.detailsUnavailable')}

+ )} + {available.length === 0 ? ( + + ) : null} + + ))} + + ); + + return ( +
+ {embedded ? ( + <> + + + {content} + + + ) : ( + <> +
+

{t('codexResets.title')}

+ {count} +
+ {content} + + )} +
+ ); +} diff --git a/apps/desktop/src/renderer/components/status/QuotaHoverCard.tsx b/apps/desktop/src/renderer/components/status/QuotaHoverCard.tsx index a6387e22508..582978b08f5 100644 --- a/apps/desktop/src/renderer/components/status/QuotaHoverCard.tsx +++ b/apps/desktop/src/renderer/components/status/QuotaHoverCard.tsx @@ -45,6 +45,10 @@ export interface QuotaHoverCardSessionUsage { } export interface QuotaHoverCardProps { + /** One compact provider action beside the last quota window’s reset countdown. */ + windowAction?: React.ReactNode; + /** Provider-specific manual actions, inside the existing scrollable card. */ + children?: React.ReactNode; /** Embedded settings surface shares content without a second card frame. */ variant?: 'popover' | 'embedded'; /** The settings identity row already names this subscription and plan. */ @@ -121,6 +125,7 @@ function WindowBlock({ breakdown, showAbsoluteReset = false, compact = false, + action, nowMs, paceNowMs, locale, @@ -134,6 +139,7 @@ function WindowBlock({ breakdown?: UsageCardWindow['breakdown']; showAbsoluteReset?: boolean; compact?: boolean; + action?: React.ReactNode; nowMs: number; paceNowMs: number | null; locale: string | undefined; @@ -224,14 +230,17 @@ function WindowBlock({ {bar}
{percentText} - {resetCountdown !== null ? ( - - {resetCountdown} - {showAbsoluteReset && resetAt !== null && ( - {t('quotaCard.resetAt', { at: resetAt })} - )} - - ) : null} +
+ {resetCountdown !== null ? ( + + {resetCountdown} + {showAbsoluteReset && resetAt !== null && ( + {t('quotaCard.resetAt', { at: resetAt })} + )} + + ) : null} + {action} +
{detail ?
{detail}
: null} @@ -435,6 +444,8 @@ function SessionUsageSection({ /** 套餐、配额、任务合计与本轮明细按同一信息层级渲染;供应商差异只来自 account。 */ export function QuotaHoverCard({ + windowAction, + children, variant = 'popover', hideIdentity = false, account, @@ -449,18 +460,28 @@ export function QuotaHoverCard({ const embedded = variant === 'embedded'; // 测试可只注入 t;运行时再优先跟随应用当前语言格式化日期。 const locale = i18n?.resolvedLanguage ?? i18n?.language; - const { title, planLabel, windows, details = [], notices = [], emptyText, updatedAt } = account; + const { + title, + identity, + planLabel, + windows, + details = [], + notices = [], + emptyText, + updatedAt, + } = account; // Use observation time for pace so a stale snapshot cannot drift as the card renders. const paceNowMs = typeof updatedAt === 'number' && Number.isFinite(updatedAt) ? updatedAt : null; const staleMinutes = paceNowMs !== null && nowMs - paceNowMs > STALE_AFTER_MS ? Math.floor((nowMs - paceNowMs) / 60_000) : null; - const windowBlocks = windows.map(({ key, ...displayWindow }) => ( + const windowBlocks = windows.map(({ key, ...displayWindow }, index) => ( @@ -492,11 +515,18 @@ export function QuotaHoverCard({ {title && (!embedded || !hideIdentity) ? ( <>
- {title} +
+ {title} + {identity ? ( + + {identity} + + ) : null} +
{planLabel ? ( {planLabel} @@ -518,6 +548,9 @@ export function QuotaHoverCard({ ) : ( windowBlocks )} + {!embedded && windows.length === 0 && windowAction ? ( +
{windowAction}
+ ) : null} {emptyText ? (
{emptyText}
) : null} @@ -553,6 +586,8 @@ export function QuotaHoverCard({ ) : null} + {children} + {sessionUsage ? ( <> diff --git a/apps/desktop/src/renderer/components/status/TodaySpendChip.tsx b/apps/desktop/src/renderer/components/status/TodaySpendChip.tsx index 40b5448e7aa..848e7b80714 100644 --- a/apps/desktop/src/renderer/components/status/TodaySpendChip.tsx +++ b/apps/desktop/src/renderer/components/status/TodaySpendChip.tsx @@ -1,9 +1,11 @@ +import { useCodexRateLimitReset } from '@/hooks/useCodexRateLimitReset'; +import { CodexResetCredits } from './CodexResetCredits'; import { FIVE_HOUR_WINDOW_MINUTES, formatCompactTimeUntilReset, WEEKLY_WINDOW_MINUTES, } from '@/lib/compactQuotaCountdown'; -import { isOpenAiSubscriptionProvider } from '@cindy/model-providers'; +import { isCodexGatewayWireModel, isOpenAiSubscriptionProvider } from '@cindy/model-providers'; import { useDeviceProviders } from '@/hooks/useDeviceProviders'; import { useProviders } from '@/hooks/useProviders'; /** @@ -15,11 +17,16 @@ import { useProviders } from '@/hooks/useProviders'; */ import React from 'react'; +import * as Dialog from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; +import { Tip } from '@/components/ui/tooltip'; +import { Button } from '@/components/ui/button'; import { useTranslation } from 'react-i18next'; import type { TFunction } from 'i18next'; import { summarizeCodexRateLimitReset } from '@cindy/maker-shared/session-controls'; import { cn } from '@/lib/utils'; +import { TASK_USAGE_ACCOUNT, type UsageAccountLocation } from '@/lib/usageAccountLocation'; import { DAILY_SOFT_LIMIT_FACTOR, formatCompactMoney, @@ -722,7 +729,7 @@ function renderSegmentedLabel(segments: React.ReactNode[]): React.ReactNode { } interface TodaySpendChipProps { - vendorKey?: 'cc' | 'codex' | 'pi'; + vendorKey?: 'cc' | 'codex' | 'pi' | 'cursor'; /** 当前会话模型;codex/ 折扣 GPT 恒走 gateway API, 即使 oauth-bearer spawn 也按 API 形态显示。 */ modelId?: string | null; /** @@ -748,6 +755,11 @@ interface TodaySpendChipProps { * 必须抑制本地账号读取(否则 chip 显示的是控制端账号的用量,张冠李戴)。 */ deviceLinkDeviceId?: string | null; + /** + * 本轮消耗哪台电脑的账号额度(远程 Agent 让 Agent 在另一台电脑运行,见 usageAccountLocation)。 + * 缺省 = 任务所在电脑:本机任务读本机,deviceLinkDeviceId 非空时读被控端。 + */ + usageAccount?: UsageAccountLocation; } export function TodaySpendChip({ @@ -760,12 +772,24 @@ export function TodaySpendChip({ sessionInitialTokens, remoteHostId, deviceLinkDeviceId, + usageAccount = TASK_USAGE_ACCOUNT, }: TodaySpendChipProps) { const { t, i18n } = useTranslation(); const formatterLocale = i18n.resolvedLanguage ?? i18n.language; - // device-link 远程会话:turn 跑在被控端、消耗被控端账号,计费形态(订阅/网关)与账号 - // 余量的事实都在被控端 —— 本机的 route 观察 / 账号快照与之无关,一律不读、不据此分类。 - const isDeviceLinkRemote = Boolean(deviceLinkDeviceId); + // 账号余量从哪台电脑读:Agent 在任务所在电脑时就是那台(本机,或 deviceLinkDeviceId 的被控端); + // 远程 Agent 在另一台电脑时是那台(经 device-link 读它的镜像,与模型选择器同一份)。 + // 读不到那份账号(别人分享的供应商等)时本机与远端都不读,只显示任务价值。 + const accountDeviceId = + usageAccount.kind === 'device' + ? usageAccount.deviceId + : usageAccount.kind === 'task' + ? (deviceLinkDeviceId ?? null) + : null; + const accountUnreadable = usageAccount.kind === 'unreadable'; + // 账号不在本机(device-link 远程会话 / 远程 Agent):turn 消耗另一台电脑的账号,计费形态 + // (订阅/网关)与账号余量的事实都在那台 —— 本机的 route 观察 / 账号快照与之无关,一律不读、 + // 不据此分类。 + const isDeviceLinkRemote = Boolean(accountDeviceId) || accountUnreadable; const { authInjection: codexAuthInjection } = useCodexRuntimeRoute({ enabled: vendorKey === 'codex' && !isDeviceLinkRemote, refreshKey: sessionId, @@ -798,20 +822,22 @@ export function TodaySpendChip({ observedClaudeRoute == null && (gatewayKeyReconciling || (!hasGatewayKey && claudeOAuthConnected == null)); const { providers: localQuotaProviders } = useProviders(); - const { providers: deviceQuotaProviders } = useDeviceProviders(deviceLinkDeviceId ?? undefined); + const { providers: deviceQuotaProviders } = useDeviceProviders(accountDeviceId ?? undefined); const quotaProviders = isDeviceLinkRemote ? deviceQuotaProviders : localQuotaProviders; const selectedQuotaProvider = quotaProviders.find((provider) => provider.id === providerId); const isClaudeAccount = providerId === 'anthropic' || selectedQuotaProvider?.auth?.native === 'claude'; const isXaiAccount = providerId === 'xai' || selectedQuotaProvider?.auth?.native === 'xai'; - // Remote provider ids belong to the controlled device. Reuse its existing provider + // Remote provider ids belong to the device running the Agent. Reuse its existing provider // catalog (also used by the model selector), never the controller's same-named account. const isDeviceLinkRemoteBridgeModel = typeof modelId === 'string' && (modelId.startsWith(CHATGPT_MODEL_PREFIX) || modelId.startsWith(XAI_MODEL_PREFIX)); + // 默认路由只在被控端自己跑 Agent 时有观察值:远程 Agent 必带来源(没来源按读不到处理), + // 走不到这里。 const remoteClaudeRoute = useRemoteClaudeSessionRoute( isDeviceLinkRemote && vendorKey === 'cc' && providerId == null && !isDeviceLinkRemoteBridgeModel - ? (deviceLinkDeviceId ?? null) + ? accountDeviceId : null, sessionId, ); @@ -855,7 +881,7 @@ export function TodaySpendChip({ (isXaiAccount || (providerId == null && isXaiPrefixedModel)); const isSubscriptionBridge = isChatgptBridge || isXaiBridge; const isRemoteCodexSession = vendorKey === 'codex' && Boolean(remoteHostId); - const isCodexBudgetModel = typeof modelId === 'string' && modelId.startsWith('codex/'); + const isCodexBudgetModel = typeof modelId === 'string' && isCodexGatewayWireModel(modelId); const isCodexGatewayBudgetModel = isCodexBudgetModel && (providerId == null || providerId === 'xd'); const isCodexXaiProvider = @@ -893,9 +919,9 @@ export function TodaySpendChip({ // codex-oauth 与 cc+chatgpt/ bridge 共用同一 ChatGPT 账户 → 同一套限额窗口 chip 渲染。 const usesCodexQuotaForm = isCodexOauth || isChatgptBridge; const usesXaiQuotaForm = isCodexXaiProvider || isXaiBridge; - // 远程会话不读本机账户快照 —— 额度事实在远端:SSH 用 remoteHostId 判,device-link 用 - // deviceLinkDeviceId 判(两者互斥,任一非空即远程,turn 消耗的是远端账号的额度)。 - const isAnyRemoteSession = Boolean(remoteHostId) || Boolean(deviceLinkDeviceId); + // 远程会话不读本机账户快照 —— 额度事实在远端:SSH 用 remoteHostId 判,device-link 与 + // 远程 Agent 用 isDeviceLinkRemote 判(任一成立即远程,turn 消耗的是远端账号的额度)。 + const isAnyRemoteSession = Boolean(remoteHostId) || isDeviceLinkRemote; // Model Access 配额只属于实际走 XD/Cindy AI Gateway 的本地会话。显式自定义供应商即使 // 复用了 env-key / oauth-bearer host,也不能据 host 的启动凭证把 /v2/user/info 串进来。 const isClaudeGateway = @@ -946,7 +972,8 @@ export function TodaySpendChip({ isCodexApi || isCodexSubscription || isSubscriptionBridge || - isDeviceLinkRemote + isDeviceLinkRemote || + Boolean(deviceLinkDeviceId) ? sessionId : undefined, sessionInitialTokens, @@ -963,10 +990,10 @@ export function TodaySpendChip({ modelId, providerId ?? 'openai', ); - // device-link 远程 codex / chatgpt-bridge:被控端权威组合 payload 镜像,选槽 / 选桶 + // device-link 远程 codex / chatgpt-bridge:账号所在电脑的权威组合 payload 镜像,选槽 / 选桶 // 与本机同口径在消费点执行(selectRemoteCodexAccountUsage)。 const remoteCodexAccountPayload = useRemoteCodexAccountUsage( - isDeviceLinkRemote && usesCodexQuotaForm ? (deviceLinkDeviceId ?? null) : null, + isDeviceLinkRemote && usesCodexQuotaForm ? accountDeviceId : null, providerId ?? 'openai', ); const accountUsage = isDeviceLinkRemote @@ -980,11 +1007,11 @@ export function TodaySpendChip({ usesCodexQuotaForm && !isAnyRemoteSession, providerId ?? 'openai', ); + const manualReset = useCodexRateLimitReset(codexRateLimits, refreshCodexRateLimits, providerId ?? 'openai'); // xAI 限流快照同为本机 main 抓的 —— SSH 远程仍抑制回落价值估算;device-link 远程 // 走被控端镜像(订阅周用量 invoke + push,限流头 push-only,与本机同语义降级)。 // 本机侧按所选供应商账号取(多账号供应商,#4197 / #4246:限流 hook 同样带 providerId)。 - const remoteXaiDeviceId = - isDeviceLinkRemote && usesXaiQuotaForm ? (deviceLinkDeviceId ?? null) : null; + const remoteXaiDeviceId = isDeviceLinkRemote && usesXaiQuotaForm ? accountDeviceId : null; const localXaiRateLimit = useXaiRateLimit( usesXaiQuotaForm && !isAnyRemoteSession, providerId ?? 'xai', @@ -1006,7 +1033,7 @@ export function TodaySpendChip({ // 均只展示各自的额度/本地会话统计,不读取 Model Access 账号配额。 const localClaudeQuota = useClaudeAccountUsage(usesGatewayQuota); const remoteClaudeQuota = useRemoteClaudeAccountUsage( - isDeviceLinkRemoteGateway ? (deviceLinkDeviceId ?? null) : null, + isDeviceLinkRemoteGateway ? accountDeviceId : null, ); const claudeQuota = isDeviceLinkRemote ? remoteClaudeQuota : localClaudeQuota; // 个人租户的额度事实在 Gateway 三池账本里(推理入口不提供管理面接口), 与上面的 @@ -1014,16 +1041,14 @@ export function TodaySpendChip({ const creditUsage = useModelAccessCreditUsage(usesGatewayQuota); const creditTotals = React.useMemo(() => resolveCreditTotals(creditUsage), [creditUsage]); // Claude 订阅账号余量 (5h/周/分模型窗口, 端点 + proxy 旁路 headers 双源)。bridge 模型形态 - // 优先(不消耗 Claude 订阅额度),此时不读。device-link 远程会话读被控端镜像, - // 本机快照与其无关(额度事实在被控端),两个 hook 的 enabled 互斥。 + // 优先(不消耗 Claude 订阅额度),此时不读。device-link 远程会话 / 远程 Agent 读账号所在 + // 电脑的镜像,本机快照与其无关(额度事实在那台),两个 hook 的 enabled 互斥。 const localClaudeSubscriptionUsage = useClaudeSubscriptionUsage( isClaudeSubscription && !isSubscriptionBridge && !isDeviceLinkRemote, providerId ?? 'anthropic', ); const remoteClaudeSubscriptionUsage = useRemoteClaudeSubscriptionUsage( - isDeviceLinkRemoteClaudeSubscription && !isSubscriptionBridge - ? (deviceLinkDeviceId ?? null) - : null, + isDeviceLinkRemoteClaudeSubscription && !isSubscriptionBridge ? accountDeviceId : null, providerId ?? 'anthropic', ); const claudeSubscriptionUsage = isDeviceLinkRemote @@ -1033,6 +1058,7 @@ export function TodaySpendChip({ const quotaCardSessionUsage = toQuotaHoverCardSessionUsage(sessionUsage, sessionTokens); const quotaCardTurnUsage = toQuotaHoverCardTurnUsage(latestTurnUsage, t); const [quotaPopoverOpen, setQuotaPopoverOpen] = React.useState(false); + const [resetListOpen, setResetListOpen] = React.useState(false); const quotaPopoverOpenTimerRef = React.useRef(null); const quotaPopoverCloseTimerRef = React.useRef(null); const quotaPopoverPointerInsideRef = React.useRef(false); @@ -1126,7 +1152,8 @@ export function TodaySpendChip({ modelId, vendorKey, remoteHostId, - deviceLinkDeviceId, + accountDeviceId, + accountUnreadable, usesCodexQuotaForm, usesXaiQuotaForm, isClaudeSubscription, @@ -1141,7 +1168,8 @@ export function TodaySpendChip({ modelId, vendorKey, remoteHostId, - deviceLinkDeviceId, + accountDeviceId, + accountUnreadable, usesCodexQuotaForm, usesXaiQuotaForm, isClaudeSubscription, @@ -1152,6 +1180,7 @@ export function TodaySpendChip({ identity, }; if (!contextInvalidated) return; + setResetListOpen(false); // provider / model 或任务切换会原地复用组件;在新 chip 节点挂载后同步收口旧弹窗, // 清掉 hover / focus 残态与悬空 timer,并把卡片接管的键盘焦点交还给当前 chip。 @@ -1165,7 +1194,8 @@ export function TodaySpendChip({ modelId, vendorKey, remoteHostId, - deviceLinkDeviceId, + accountDeviceId, + accountUnreadable, usesCodexQuotaForm, usesXaiQuotaForm, isClaudeSubscription, @@ -1291,7 +1321,7 @@ export function TodaySpendChip({ // 礼花按供应商共享去重,不依赖任务组件的挂载或数字滚动状态。 const chipRef = React.useRef(null); const celebrationProvider = JSON.stringify([ - isDeviceLinkRemote ? deviceLinkDeviceId : 'local', + isDeviceLinkRemote ? accountDeviceId : 'local', providerId ?? (usesCodexQuotaForm ? 'openai' : usesXaiQuotaForm ? 'xai' : 'anthropic'), ]); const quotaSnapshotUpdatedAt = usesCodexQuotaForm @@ -1364,25 +1394,25 @@ export function TodaySpendChip({ usesXaiQuotaForm && !isXaiWeeklyUsageCurrent(xaiSubscriptionUsage, windowLabelNowMs); React.useEffect(() => { if (!hasPendingResetWindow && !xaiNeedsWeeklyRefresh) return; - // 悬念期催刷按会话来源分路:device-link 远程催被控端(经隧道,被控端节流兜底), - // 本机催本机(按所选供应商账号,#4197);不得拿本机通道替远程会话催刷(账号不同)。 + // 悬念期催刷按账号所在分路:device-link 远程 / 远程 Agent 催账号所在电脑(经隧道,那台 + // 节流兜底),本机催本机(按所选供应商账号,#4197);不得拿本机通道替远程会话催刷(账号不同)。 if (isChatgptBridge || (isDeviceLinkRemote && usesCodexQuotaForm)) { if (isDeviceLinkRemote) { - if (deviceLinkDeviceId) - requestRemoteCodexAccountRefresh(deviceLinkDeviceId, providerId ?? 'openai'); + if (accountDeviceId) + requestRemoteCodexAccountRefresh(accountDeviceId, providerId ?? 'openai'); } else { requestCodexAccountRefresh(providerId ?? undefined); } } else if (usesXaiQuotaForm) { if (isDeviceLinkRemote) { - if (deviceLinkDeviceId) - requestRemoteXaiSubscriptionRefresh(deviceLinkDeviceId, providerId ?? 'xai'); + if (accountDeviceId) + requestRemoteXaiSubscriptionRefresh(accountDeviceId, providerId ?? 'xai'); } else { requestXaiSubscriptionRefresh(providerId ?? 'xai'); } } else if (isClaudeSubscription && !usesCodexQuotaForm) { - if (isDeviceLinkRemoteClaudeSubscription && deviceLinkDeviceId) { - requestRemoteClaudeSubscriptionRefresh(deviceLinkDeviceId, providerId ?? 'anthropic'); + if (isDeviceLinkRemoteClaudeSubscription && accountDeviceId) { + requestRemoteClaudeSubscriptionRefresh(accountDeviceId, providerId ?? 'anthropic'); } else if (!isDeviceLinkRemote) { requestClaudeSubscriptionRefresh(providerId ?? 'anthropic'); } @@ -1394,7 +1424,7 @@ export function TodaySpendChip({ isClaudeSubscription, isDeviceLinkRemote, isDeviceLinkRemoteClaudeSubscription, - deviceLinkDeviceId, + accountDeviceId, usesCodexQuotaForm, usesXaiQuotaForm, providerId, @@ -1407,11 +1437,13 @@ export function TodaySpendChip({ void window.electronAPI.openExternal(usageDashboardUrl); }; - // device-link 远程形态已解析 = 走与本机一致的对应分支(数据全部来自被控端镜像); - // 未解析(默认路由无观察值 / 老被控端)才落占位分支。 + // device-link 远程形态已解析 = 走与本机一致的对应分支(数据全部来自账号所在电脑的镜像); + // 未解析(默认路由无观察值 / 老被控端)或那份账号读不到(分享来的供应商等)才落占位分支。 const deviceLinkRemoteFormResolved = - isDeviceLinkRemoteClaudeSubscription || - (isDeviceLinkRemote && (usesCodexQuotaForm || usesXaiQuotaForm || isDeviceLinkRemoteGateway)); + !accountUnreadable && + (isDeviceLinkRemoteClaudeSubscription || + (isDeviceLinkRemote && + (usesCodexQuotaForm || usesXaiQuotaForm || isDeviceLinkRemoteGateway))); let labelNode: React.ReactNode; let account: UsageCardAccount = { title: t('quotaCard.usageTitle'), windows: [] }; if (isDeviceLinkRemote && !deviceLinkRemoteFormResolved) { @@ -1437,10 +1469,25 @@ export function TodaySpendChip({ // accountUsage / xai / claude 快照均已在上方切到被控端镜像,与本机同一套卡片渲染。 if (!remoteHostId) { account = usesCodexQuotaForm - ? buildCodexUsageCard(accountUsage, codexResetSummary, t, windowLabelNowMs, formatterLocale) + ? buildCodexUsageCard( + accountUsage, + !isAnyRemoteSession && codexRateLimits?.rateLimitResetCredits ? null : codexResetSummary, + t, + windowLabelNowMs, + formatterLocale, + ) : usesXaiQuotaForm ? buildXaiUsageCard(xaiSubscriptionUsage, xaiRateLimit, t, windowLabelNowMs) : buildClaudeUsageCard(claudeSubscriptionUsage, t); + // Identity and quota must come from the same provider on the same execution device. + const identityProvider = quotaProviders.find( + (provider) => + provider.id === + (providerId ?? (usesCodexQuotaForm ? 'openai' : usesXaiQuotaForm ? 'xai' : 'anthropic')), + ); + account.identity = usesCodexQuotaForm + ? identityProvider?.openAiAccount?.identity + : identityProvider?.subscriptionAccount?.identity; } } else { const slots = computeMetricSlots(claudeQuota, creditTotals, sessionSegment, t); @@ -1575,7 +1622,7 @@ export function TodaySpendChip({ onFocusCapture={refreshCodexRateLimits} > { // 打开只由 hover / focus 驱动;Radix 的 outside / Escape 仍可请求关闭。 if (!open) closeQuotaPopoverImmediately(); @@ -1656,7 +1703,7 @@ export function TodaySpendChip({ quotaPopoverOpenSourceRef.current = null; scheduleQuotaPopoverClose(); }} - className="w-[340px] max-w-[calc(100vw-16px)] border-0 bg-transparent p-0 shadow-none" + className="w-auto max-w-[calc(100vw-16px)] border-0 bg-transparent p-0 shadow-none" > { + quotaPopoverFocusTakenRef.current = false; + closeQuotaPopoverImmediately(); + setResetListOpen(true); + }}> + {manualReset.canRetry ? t('codexResets.retryReset') : t('codexResets.available', { count: codexRateLimits?.rateLimitResetCredits?.availableCount ?? 0 })} + + ) : null} /> + + + + event.preventDefault()} + className="modal-panel fixed left-1/2 top-1/2 z-[10000] w-[400px] max-w-[calc(100vw-32px)] -translate-x-1/2 -translate-y-1/2 p-4" + onCloseAutoFocus={(event) => { + event.preventDefault(); + // The confirmation owns focus when it opens after this dialog closes. + if (!manualReset.busy) { + quotaPopoverFocusTakenRef.current = true; + restoreQuotaPopoverFocus(); + } + }} + > + {t('codexResets.title')} +
+ { + setResetListOpen(false); + const context = quotaPopoverContextRef.current.identity; + void manualReset.reset(credit).finally(() => { + if (quotaPopoverContextRef.current.identity !== context) return; + quotaPopoverFocusTakenRef.current = true; + restoreQuotaPopoverFocus(); + }); + }} /> +
+ + + + + +
+
+
{confettiBurst && ( ({ snapshot: null as MobileCodexRateLimitsResult | null, reset: vi.fn(), canReset: true, busy: false })); +vi.mock('react-i18next', () => ({ useTranslation: () => ({ + i18n: { language: 'en' }, + t: (key: string, values?: Record) => `${key}${values?.count !== undefined ? ` ${values.count}` : ''}${values?.at ? ` ${values.at}` : ''}`, +}) })); + +beforeEach(() => { + mocks.canReset = true; mocks.busy = false; mocks.reset.mockReset(); + mocks.snapshot = { + account: { email: null, accountId: null, planType: null }, rateLimits: {}, rateLimitsByLimitId: null, resetOffer: null, + rateLimitResetCredits: { availableCount: 2, credits: [ + { status: 'available', resetType: 'codexRateLimits', grantedAt: 1, expiresAt: Math.floor(Date.now() / 1000) + 100_000, title: 'Later reset', description: null, resetOffer: { idempotencyKey: 'later', expiresAt: null, validUntil: Date.now() + 60_000 } }, + { status: 'available', resetType: 'codexRateLimits', grantedAt: 1, expiresAt: Math.floor(Date.now() / 1000) + 50_000, title: 'Earlier reset', description: null, resetOffer: { idempotencyKey: 'earlier', expiresAt: null, validUntil: Date.now() + 60_000 } }, + ] }, + }; +}); +afterEach(cleanup); + +describe('available reset list', () => { + it('sorts rows by expiry and passes the clicked row to its own action', () => { + render(); + expect(screen.getAllByRole('listitem').map(row => row.textContent)).toEqual([ + expect.stringContaining('Earlier reset'), expect.stringContaining('Later reset'), + ]); + expect(screen.getAllByRole('button')).toHaveLength(2); + fireEvent.click(screen.getAllByRole('button', { name: 'codexResets.useReset' })[1]); + expect(mocks.reset).toHaveBeenCalledExactlyOnceWith(mocks.snapshot!.rateLimitResetCredits!.credits![0]); + }); + + it.each([true, false])('offers only the frozen retry even with remaining rows: %s', withRows => { + mocks.snapshot!.resetOffer = { idempotencyKey: 'frozen', expiresAt: null, validUntil: Date.now() + 60_000, retryOnly: true }; + if (!withRows) mocks.snapshot!.rateLimitResetCredits = null; + render(); + for (const button of screen.queryAllByRole('button', { name: 'codexResets.useReset' }) as HTMLButtonElement[]) { + expect(button.disabled).toBe(true); + fireEvent.click(button); + } + expect(mocks.reset).not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole('button', { name: 'codexResets.retryReset' })); + expect(mocks.reset).toHaveBeenCalledExactlyOnceWith(); + }); + + it('keeps provider settings collapsed until its header is opened', async () => { + render(); + const header = screen.getByRole('button', { name: /codexResets.title/ }); + expect(header.getAttribute('aria-expanded')).toBe('false'); + expect(screen.queryByRole('list')).toBeNull(); + fireEvent.click(header); + expect(header.getAttribute('aria-expanded')).toBe('true'); + expect(await screen.findAllByRole('listitem')).toHaveLength(2); + fireEvent.click(header); + expect(header.getAttribute('aria-expanded')).toBe('false'); + await waitFor(() => expect(screen.queryByRole('list')).toBeNull()); + }); + + it('shows a count-only response honestly without inventing rows', () => { + mocks.snapshot!.rateLimitResetCredits!.credits = null; + render(); + expect(screen.getByText('codexResets.available 2')).toBeTruthy(); + expect(screen.getByText('codexResets.detailsUnavailable')).toBeTruthy(); + expect(screen.queryByRole('list')).toBeNull(); + }); + + it('does not substitute a different credit when an older host omits row offers', () => { + for (const credit of mocks.snapshot!.rateLimitResetCredits!.credits!) delete credit.resetOffer; + render(); + for (const button of screen.getAllByRole('button') as HTMLButtonElement[]) { + expect(button.disabled).toBe(true); + fireEvent.click(button); + } + expect(mocks.reset).not.toHaveBeenCalled(); + }); + + it('shows zero availability without a reset action', () => { + mocks.snapshot!.rateLimitResetCredits = { availableCount: 0, credits: [] }; + render(); + expect(screen.getByText('codexResets.available 0')).toBeTruthy(); + expect(screen.queryByRole('button')).toBeNull(); + }); + + it('hides the section when reset availability was not returned', () => { + mocks.snapshot!.rateLimitResetCredits = null; + const view = render(); + expect(view.container.textContent).toBe(''); + }); + + it('disables reset until a limit is reached', () => { + mocks.canReset = false; + render(); + for (const button of screen.getAllByRole('button') as HTMLButtonElement[]) { + expect(button.disabled).toBe(true); + fireEvent.click(button); + } + expect(mocks.reset).not.toHaveBeenCalled(); + }); + + it('does not label already-used or expired credits as available', () => { + mocks.snapshot!.rateLimitResetCredits!.credits![0].status = 'redeemed'; + mocks.snapshot!.rateLimitResetCredits!.credits![1].expiresAt = 1; + render(); + expect(screen.queryByRole('list')).toBeNull(); + expect(screen.getByText('codexResets.detailsUnavailable')).toBeTruthy(); + }); +}); diff --git a/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.deviceLink.test.tsx b/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.deviceLink.test.tsx index d28f1624f78..e5d0191e90e 100644 --- a/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.deviceLink.test.tsx +++ b/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.deviceLink.test.tsx @@ -8,15 +8,27 @@ * - 默认路由(providerId=null)的远程会话:不做本机启发式猜测,维持占位显示。 */ -import { act, cleanup, fireEvent, render, screen, within } from '@testing-library/react'; +import { act, cleanup, fireEvent, render as renderUI, screen, within } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { ReactElement } from 'react'; +import { ConfirmDialogProvider } from '@/components/ui/confirm-dialog-provider'; + +function render(ui: ReactElement) { + return renderUI(ui, { wrapper: ConfirmDialogProvider }); +} + import type { ClaudeSubscriptionUsageSnapshot } from '../../../../shared/claudeSubscriptionUsage'; import type { SessionUsageMoney } from '@/hooks/useSessionUsageMoney'; +import type { ProviderView } from '@cindy/model-providers'; + +type TestProvider = Pick & { + auth: { native: string }; +}; const mocks = vi.hoisted(() => ({ - localProviders: [] as Array<{ id: string; auth: { native: string } }>, - deviceProviders: [] as Array<{ id: string; auth: { native: string } }>, + localProviders: [] as TestProvider[], + deviceProviders: [] as TestProvider[], remoteProviderIds: [] as Array, localClaudeSnapshot: null as ClaudeSubscriptionUsageSnapshot | null, remoteClaudeSnapshot: null as ClaudeSubscriptionUsageSnapshot | null, @@ -300,6 +312,20 @@ describe('TodaySpendChip device-link remote sessions', () => { }); it('远程 codex 会话按订阅形态渲染被控端账号窗口(组合 payload 选桶)', () => { + mocks.localProviders = [ + { + id: 'openai', + auth: { native: 'codex' }, + openAiAccount: { source: 'oauth', identity: 'local@example.com' }, + }, + ]; + mocks.deviceProviders = [ + { + id: 'openai', + auth: { native: 'codex' }, + openAiAccount: { source: 'oauth', identity: 'remote@example.com' }, + }, + ]; mocks.remoteCodexPayload = { source: 'codex-app-server', limitId: 'codex', @@ -329,6 +355,10 @@ describe('TodaySpendChip device-link remote sessions', () => { expect(container.textContent).toContain('5h 剩余 88%'); expect(container.textContent).toContain('7天 剩余 66%'); expect(mocks.remoteCodexDeviceIds).toContain('device-abc'); + fireEvent.mouseEnter(screen.getByRole('button')); + act(() => vi.advanceTimersByTime(300)); + expect(screen.getByText('remote@example.com')).toBeTruthy(); + expect(screen.queryByText('local@example.com')).toBeNull(); }); it('窗口刚重置时倒计时以窗口长度封顶,不显示「8天」', () => { @@ -427,6 +457,129 @@ describe('TodaySpendChip device-link remote sessions', () => { }); +// 远程 Agent:Agent 在另一台电脑上用那台的登录运行,余量读那台(2026-10-09 用户反馈:远程供应商 +// 的任务一直「等待配额数据更新」—— chip 拿任务所在电脑的同名账号去读)。 +describe('TodaySpendChip remote Agent account', () => { + const agentSnapshot: ClaudeSubscriptionUsageSnapshot = { + source: 'oauth-endpoint', + fiveHour: { utilization: 12 }, + sevenDay: { utilization: 25 }, + }; + + it('本机任务的 Agent 在另一台电脑:读那台的镜像,不读本机同名账号', () => { + mocks.localClaudeSnapshot = { source: 'oauth-endpoint', fiveHour: { utilization: 90 } }; + mocks.remoteClaudeSnapshot = agentSnapshot; + + const { container } = render( + , + ); + + expect(container.textContent).toContain('5h 剩余 88%'); + expect(container.textContent).not.toContain('剩余 10%'); + expect(mocks.remoteHookDeviceIds).toContain('device-agent'); + }); + + it('远程控制的任务把 Agent 放在第三台电脑:读第三台,不读被控电脑', () => { + mocks.remoteClaudeSnapshot = agentSnapshot; + + const { container } = render( + , + ); + + expect(container.textContent).toContain('5h 剩余 88%'); + expect(mocks.remoteHookDeviceIds.filter(Boolean)).toEqual( + expect.arrayContaining(['device-agent']), + ); + expect(mocks.remoteHookDeviceIds).not.toContain('device-abc'); + }); + + it('远程 Agent 的 codex 账号同样读那台', () => { + mocks.remoteCodexPayload = { + source: 'codex-app-server', + limitId: 'codex', + primary: { usedPercent: 12, windowMinutes: 300 }, + appServerBuckets: { + codex: { + source: 'codex-app-server', + limitId: 'codex', + primary: { usedPercent: 12, windowMinutes: 300 }, + }, + }, + webSnapshot: null, + }; + + const { container } = render( + , + ); + + expect(container.textContent).toContain('剩余 88%'); + expect(mocks.remoteCodexDeviceIds).toContain('device-agent'); + }); + + it('远程控制的任务把 Agent 放在本机:读本机账号', () => { + mocks.localProviders = [{ id: 'anthropic', auth: { native: 'claude' } }]; + mocks.localClaudeSnapshot = agentSnapshot; + mocks.remoteClaudeSnapshot = { source: 'oauth-endpoint', fiveHour: { utilization: 90 } }; + + const { container } = render( + , + ); + + expect(container.textContent).toContain('5h 剩余 88%'); + expect(mocks.remoteHookDeviceIds.filter(Boolean)).toHaveLength(0); + }); + + it('读不到那份账号(分享来的供应商等):只显示任务价值,不等数据、不拿别的账号凑数', () => { + mocks.localClaudeSnapshot = agentSnapshot; + mocks.remoteClaudeSnapshot = agentSnapshot; + + const { container } = render( + , + ); + + expect(container.textContent).not.toContain('剩余'); + expect(mocks.remoteHookDeviceIds.filter(Boolean)).toHaveLength(0); + const trigger = screen.getByRole('button', { name: 'quotaCard.usageTitle' }); + fireEvent.mouseEnter(trigger); + act(() => vi.advanceTimersByTime(300)); + const card = screen.getByTestId('quota-hover-card'); + expect(card.textContent).not.toContain('等待额度数据'); + expect(within(card).queryAllByRole('progressbar')).toHaveLength(0); + }); +}); + describe('existing UI with remote account data', () => { it('renders the same subscription label and classes for identical local and remote data', () => { const snapshot: ClaudeSubscriptionUsageSnapshot = { diff --git a/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.popover.test.tsx b/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.popover.test.tsx index b1d3fb40404..4f75371b2b0 100644 --- a/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.popover.test.tsx +++ b/apps/desktop/src/renderer/components/status/__tests__/TodaySpendChip.popover.test.tsx @@ -1,8 +1,16 @@ // @vitest-environment jsdom -import { act, cleanup, fireEvent, render, screen, within } from '@testing-library/react'; +import { act, cleanup, fireEvent, render as renderUI, screen, within } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { ReactElement } from 'react'; +import type { MobileCodexRateLimitsResult } from '@cindy/maker-shared/device-link-contract'; +import { ConfirmDialogProvider } from '@/components/ui/confirm-dialog-provider'; + +function render(ui: ReactElement) { + return renderUI(ui, { wrapper: ConfirmDialogProvider }); +} + import type { ClaudeSubscriptionUsageSnapshot } from '../../../../shared/claudeSubscriptionUsage'; import type { RateLimitSnapshot } from '@/hooks/useAccountUsage'; import type { XaiSubscriptionUsageSnapshot } from '../../../../shared/xaiSubscriptionUsage'; @@ -30,6 +38,8 @@ const mocks = vi.hoisted(() => ({ } as SessionUsageMoney, openExternal: vi.fn(() => Promise.resolve()), refreshCodexRateLimits: vi.fn(), + resetSnapshot: null as MobileCodexRateLimitsResult | null, + consumeReset: vi.fn(), })); vi.mock('react-i18next', () => ({ @@ -134,7 +144,7 @@ vi.mock('@/hooks/useCodexRuntimeRoute', () => ({ })); vi.mock('@/hooks/useCodexRateLimits', () => ({ useCodexRateLimits: () => ({ - snapshot: null, + snapshot: mocks.resetSnapshot, refresh: mocks.refreshCodexRateLimits, }), })); @@ -248,6 +258,8 @@ beforeEach(() => { sevenDay: { utilization: 34, resetsAt: Date.now() / 1000 + 86_400 }, }; mocks.codexSnapshot = null; + mocks.resetSnapshot = null; + mocks.consumeReset.mockReset(); mocks.xaiSnapshot = null; mocks.gatewaySnapshot = null; mocks.sessionTokens = null; @@ -256,7 +268,7 @@ beforeEach(() => { mocks.accountSnapshots = {}; Object.defineProperty(window, 'electronAPI', { configurable: true, - value: { openExternal: mocks.openExternal }, + value: { openExternal: mocks.openExternal, maker: { usage: { consumeCodexRateLimitReset: mocks.consumeReset } } }, }); }); @@ -442,6 +454,99 @@ describe('TodaySpendChip Claude subscription popover', () => { expect(trigger.querySelector('[class*="error-fg"]')).toBeNull(); }); + it.each([true, false])('returns focus after reset confirmation accepted=%s', async accepted => { + mocks.codexAuthInjection = 'oauth-bearer'; + mocks.codexSnapshot = { primary: { usedPercent: 100, windowMinutes: 300 } }; + mocks.resetSnapshot = { + account: { email: 'te***@example.test', accountId: '…123456', planType: 'plus' }, + rateLimits: { primary: { usedPercent: 100 } }, rateLimitsByLimitId: null, + rateLimitResetCredits: { availableCount: 2, credits: null }, + resetOffer: { idempotencyKey: '00000000-0000-4000-8000-000000000001', expiresAt: null, validUntil: Date.now() + 60_000 }, + }; + mocks.consumeReset.mockResolvedValue({ outcome: 'reset', rateLimits: null }); + render(); + fireEvent.mouseEnter(screen.getByRole('button', { name: '打开 Codex 用量页面' })); + act(() => vi.advanceTimersByTime(300)); + expect(screen.queryByRole('button', { name: 'codexResets.useReset' })).toBeNull(); + fireEvent.click(screen.getByRole('button', { name: 'codexResets.title' })); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + expect(screen.getByRole('dialog')).toBeTruthy(); + fireEvent.click(screen.getByRole('button', { name: 'codexResets.useReset' })); + expect(mocks.consumeReset).not.toHaveBeenCalled(); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + const dialog = screen.getByRole('alertdialog'); + expect(dialog.contains(document.activeElement)).toBe(true); + await act(async () => fireEvent.click(within(dialog).getByRole('button', { name: accepted ? 'codexResets.consumeOnce' : 'commonUi.confirmDialog.cancel' }))); + act(() => vi.advanceTimersByTime(250)); + expect(document.activeElement).toBe(screen.getByRole('button', { name: '打开 Codex 用量页面' })); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + if (accepted) { + expect(mocks.consumeReset).toHaveBeenCalledExactlyOnceWith(mocks.resetSnapshot.resetOffer!.idempotencyKey, 'openai'); + expect(mocks.refreshCodexRateLimits).toHaveBeenCalled(); + } else { + expect(mocks.consumeReset).not.toHaveBeenCalled(); + } + }); + + it('closes the reset list with X and restores focus without reopening the quota card', async () => { + mocks.codexAuthInjection = 'oauth-bearer'; + mocks.codexSnapshot = { primary: { usedPercent: 100, windowMinutes: 300 } }; + mocks.resetSnapshot = { + account: { email: 'te***@example.test', accountId: '…123456', planType: 'plus' }, + rateLimits: { primary: { usedPercent: 100 } }, rateLimitsByLimitId: null, + rateLimitResetCredits: { availableCount: 2, credits: null }, + resetOffer: { idempotencyKey: '00000000-0000-4000-8000-000000000001', expiresAt: null, validUntil: Date.now() + 60_000 }, + }; + mocks.consumeReset.mockResolvedValue({ outcome: 'reset', rateLimits: null }); + render(); + const trigger = screen.getByRole('button', { name: '打开 Codex 用量页面' }); + act(() => trigger.focus()); + act(() => vi.advanceTimersByTime(300)); + expect(screen.queryByRole('button', { name: 'codexResets.useReset' })).toBeNull(); + fireEvent.click(screen.getByRole('button', { name: 'codexResets.title' })); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + expect(screen.getByRole('dialog')).toBeTruthy(); + fireEvent.click(screen.getByRole('button', { name: 'common.dismiss' })); + act(() => vi.advanceTimersByTime(0)); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + expect(screen.queryByRole('dialog')).toBeNull(); + expect(document.activeElement).toBe(trigger); + expect(mocks.consumeReset).not.toHaveBeenCalled(); + }); + + it('uses the clicked reset row and closes the list before confirming', async () => { + mocks.codexAuthInjection = 'oauth-bearer'; + mocks.codexSnapshot = { primary: { usedPercent: 100, windowMinutes: 300 } }; + mocks.resetSnapshot = { + account: { email: 'te***@example.test', accountId: '…123456', planType: 'plus' }, + rateLimits: { primary: { usedPercent: 100 } }, rateLimitsByLimitId: null, + rateLimitResetCredits: { availableCount: 2, credits: [ + { status: 'available', resetType: 'codexRateLimits', grantedAt: 1, expiresAt: Date.now() / 1000 + 100_000, + title: 'Later', description: null, resetOffer: { idempotencyKey: '00000000-0000-4000-8000-000000000002', expiresAt: null, validUntil: Date.now() + 60_000 } }, + { status: 'available', resetType: 'codexRateLimits', grantedAt: 1, expiresAt: Date.now() / 1000 + 50_000, + title: 'Earlier', description: null, resetOffer: { idempotencyKey: '00000000-0000-4000-8000-000000000001', expiresAt: null, validUntil: Date.now() + 60_000 } }, + ] }, + resetOffer: { idempotencyKey: '00000000-0000-4000-8000-000000000001', expiresAt: null, validUntil: Date.now() + 60_000 }, + }; + mocks.consumeReset.mockResolvedValue({ outcome: 'reset', rateLimits: null }); + render(); + fireEvent.mouseEnter(screen.getByRole('button', { name: '打开 Codex 用量页面' })); + act(() => vi.advanceTimersByTime(300)); + expect(screen.queryByRole('button', { name: 'codexResets.useReset' })).toBeNull(); + fireEvent.click(screen.getByRole('button', { name: 'codexResets.title' })); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + expect(screen.getByRole('dialog')).toBeTruthy(); + expect(screen.getByRole('button', { name: 'common.dismiss' })).toBeTruthy(); + fireEvent.click(screen.getAllByRole('button', { name: 'codexResets.useReset' })[1]); + expect(mocks.consumeReset).not.toHaveBeenCalled(); + expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + const dialog = screen.getByRole('alertdialog'); + expect(dialog.contains(document.activeElement)).toBe(true); + await act(async () => fireEvent.click(within(dialog).getByRole('button', { name: 'codexResets.consumeOnce' }))); + expect(mocks.consumeReset).toHaveBeenCalledExactlyOnceWith(mocks.resetSnapshot.rateLimitResetCredits!.credits![0].resetOffer!.idempotencyKey, 'openai'); + expect(mocks.refreshCodexRateLimits).toHaveBeenCalled(); + }); + it('完整渲染 Codex app-server 的两个权威窗口', () => { mocks.codexAuthInjection = 'oauth-bearer'; mocks.codexSnapshot = { @@ -962,6 +1067,7 @@ describe('TodaySpendChip Claude subscription popover', () => { ({ id, auth: { method: 'oauth', native: 'codex' }, + openAiAccount: { source: 'oauth', identity: `${id}@example.com` }, }) as ProviderView, ); mocks.accountSnapshots = { @@ -981,6 +1087,8 @@ describe('TodaySpendChip Claude subscription popover', () => { ); act(() => screen.getByRole('button', { name: '打开 Codex 用量页面' }).focus()); expect(within(screen.getByTestId('quota-hover-card')).getByText('Pro')).toBeTruthy(); + expect(screen.getByText('account-a@example.com')).toBeTruthy(); + expect(screen.queryByText('account-b@example.com')).toBeNull(); view.rerender( { ); expect(screen.queryByTestId('quota-hover-card')).toBeNull(); + fireEvent.mouseEnter(screen.getByRole('button', { name: '打开 Codex 用量页面' })); + act(() => vi.advanceTimersByTime(300)); + expect(screen.getByText('account-b@example.com')).toBeTruthy(); + expect(screen.queryByText('account-a@example.com')).toBeNull(); + view.rerender( { } }); + it.each([ + ['codex', 'openai', 'gpt-6-astra', 'openAiAccount'], + ['cc', 'anthropic', 'claude-opus-5', 'subscriptionAccount'], + ['pi', 'xai', 'xai/grok-4.6', 'subscriptionAccount'], + ] as const)('默认 %s 订阅显示对应账号身份', (vendorKey, id, modelId, accountField) => { + mocks.codexAuthInjection = 'oauth-bearer'; + mocks.providers = [ + { + id, + name: id, + source: 'builtin', + connected: true, + agents: [], + auth: { method: 'oauth' }, + routing: {}, + models: {}, + [accountField]: { source: 'oauth', identity: `${id}@example.com` }, + }, + ]; + render(); + fireEvent.mouseEnter(screen.getByRole('button')); + act(() => vi.advanceTimersByTime(300)); + expect(screen.getByText(`${id}@example.com`)).toBeTruthy(); + }); + it('ChatGPT 动态窗口和套餐渲染为与 Claude 相同的进度条', () => { mocks.codexAuthInjection = 'oauth-bearer'; mocks.codexSnapshot = { diff --git a/apps/desktop/src/renderer/components/status/usageCardModel.ts b/apps/desktop/src/renderer/components/status/usageCardModel.ts index a8530e328a0..0cc1de72d95 100644 --- a/apps/desktop/src/renderer/components/status/usageCardModel.ts +++ b/apps/desktop/src/renderer/components/status/usageCardModel.ts @@ -34,6 +34,7 @@ export interface UsageCardWindow { export interface UsageCardAccount { title?: string; + identity?: string | null; planLabel?: string | null; windows: UsageCardWindow[]; details?: string[]; diff --git a/apps/desktop/src/renderer/components/ui/select.tsx b/apps/desktop/src/renderer/components/ui/select.tsx index 2bc77ed2d4f..138a6bce4cd 100644 --- a/apps/desktop/src/renderer/components/ui/select.tsx +++ b/apps/desktop/src/renderer/components/ui/select.tsx @@ -23,6 +23,8 @@ export interface SelectProps { onOpenChange?(open: boolean): void; disabled?: boolean; className?: string; + /** Size a compact trigger's menu independently; ordinary fields keep equal width. */ + contentClassName?: string; 'aria-describedby'?: string; 'aria-invalid'?: AriaAttributes['aria-invalid']; 'aria-required'?: AriaAttributes['aria-required']; @@ -44,6 +46,7 @@ export function Select({ onOpenChange, disabled, className, + contentClassName, 'aria-describedby': describedBy, 'aria-invalid': invalid, 'aria-required': required, @@ -91,7 +94,10 @@ export function Select({ align="end" sideOffset={4} collisionPadding={8} - className="z-[10010] w-[var(--radix-select-trigger-width)] max-h-[var(--radix-select-content-available-height)] overflow-hidden rounded-xl border border-[var(--border-default)] bg-[var(--surface-elevated)] p-1.5 text-13 text-[var(--text-primary)] [-webkit-app-region:no-drag]" + className={cn( + 'z-[10010] w-[var(--radix-select-trigger-width)] max-h-[var(--radix-select-content-available-height)] overflow-hidden rounded-xl border border-[var(--border-default)] bg-[var(--surface-elevated)] p-1.5 text-13 text-[var(--text-primary)] [-webkit-app-region:no-drag]', + contentClassName, + )} >
); } - -function SessionTaskCardBody({ - meta, - sessionId, -}: { - meta: BotCollaborationMeta; - sessionId?: string; -}) { - const { t } = useTranslation(); - const navigate = useNavigate(); - const parentSessionId = sessionId ?? meta.parentSessionId ?? null; - const [sourceDeviceId] = useState(() => - parentSessionId ? remoteProjectsStore.getSessionDeviceId(parentSessionId) : undefined, - ); - const remoteBots = useRemoteBots(); - const online = - !sourceDeviceId || remoteBots.some((bot) => bot.deviceId === sourceDeviceId && bot.online); - const { row, resolved, stale } = useBotDelegation(parentSessionId, meta.delegationId); - const [pending, setPending] = useState(false); - const [actionError, setActionError] = useState(null); - const [now, setNow] = useState(() => Date.now()); - - const { ref: observeCard, visible } = useElementVisible(); - const active = row ? isActiveDelegationStatus(row.status) : false; - const childSessionId = row?.childSessionId ?? meta.childSessionId; - const { registerPrConsumer, invalidateRemotePrRefs } = usePrActions(); - const pullRequests = usePrRefsForSession(childSessionId ?? '').slice(0, MAX_STATUS_QUERIES); - const { statuses, successfulStatuses, refreshError } = usePrStatuses(childSessionId ?? ''); - useEffect(() => { - if (!visible || !childSessionId) return; - return registerPrConsumer(childSessionId, sourceDeviceId); - }, [visible, childSessionId, sourceDeviceId, registerPrConsumer]); - useEffect(() => { - if (visible && childSessionId && sourceDeviceId && row?.updatedAt !== undefined) { - invalidateRemotePrRefs(childSessionId); - } - }, [visible, childSessionId, sourceDeviceId, row?.updatedAt, invalidateRemotePrRefs]); - const prIcon = (ref: (typeof pullRequests)[number]) => { - const result = statuses.get(prStatusKey(ref)); - const confirmed = result?.ok ? result : successfulStatuses.get(prStatusKey(ref)); - const kind = confirmed?.ok ? confirmed.status : undefined; - const Icon = kind ? PR_STATUS_ICON[kind] : GitPullRequest; - return ( -