UpdateSession updates an existing session's metadata, or creates it if it doesn't exist (upsert). Only metadata is modified - use AddMessage, AddSubSession, AddSummary for items. Messages are persisted separately via events to avoid duplication.
(ctx context.Context, session *Session)
| 913 | // Only metadata is modified - use AddMessage, AddSubSession, AddSummary for items. |
| 914 | // Messages are persisted separately via events to avoid duplication. |
| 915 | func (s *SQLiteSessionStore) UpdateSession(ctx context.Context, session *Session) error { |
| 916 | if session.ID == "" { |
| 917 | return ErrEmptyID |
| 918 | } |
| 919 | |
| 920 | // Snapshot the persisted fields under session.mu so the reads below |
| 921 | // don't race with concurrent writers on the runtime stream goroutine |
| 922 | // (SetUsage / ApplyCompaction update InputTokens/OutputTokens while a |
| 923 | // stream is running). Mirrors InMemorySessionStore.UpdateSession. |
| 924 | // MAINTENANCE: when adding new persisted fields to Session, add them here too. |
| 925 | session.mu.RLock() |
| 926 | snapshot := &Session{ |
| 927 | ID: session.ID, |
| 928 | Title: session.Title, |
| 929 | CreatedAt: session.CreatedAt, |
| 930 | ToolsApproved: session.ToolsApproved, |
| 931 | HideToolResults: session.HideToolResults, |
| 932 | WorkingDir: session.WorkingDir, |
| 933 | SendUserMessage: session.SendUserMessage, |
| 934 | MaxIterations: session.MaxIterations, |
| 935 | Starred: session.Starred, |
| 936 | InputTokens: session.InputTokens, |
| 937 | OutputTokens: session.OutputTokens, |
| 938 | Cost: session.Cost, |
| 939 | Permissions: clonePermissionsConfig(session.Permissions), |
| 940 | AgentModelOverrides: cloneStringMap(session.AgentModelOverrides), |
| 941 | CustomModelsUsed: cloneStringSlice(session.CustomModelsUsed), |
| 942 | ParentID: session.ParentID, |
| 943 | } |
| 944 | session.mu.RUnlock() |
| 945 | |
| 946 | fields, err := sessionPersistedFieldsOf(snapshot) |
| 947 | if err != nil { |
| 948 | return err |
| 949 | } |
| 950 | |
| 951 | // Use a transaction |
| 952 | tx, err := s.db.BeginTx(ctx, nil) |
| 953 | if err != nil { |
| 954 | return err |
| 955 | } |
| 956 | defer func() { _ = tx.Rollback() }() |
| 957 | |
| 958 | // Use INSERT OR REPLACE for upsert behavior - creates if not exists, updates if exists |
| 959 | _, err = tx.ExecContext(ctx, |
| 960 | `INSERT INTO sessions ( |
| 961 | id, tools_approved, input_tokens, output_tokens, title, cost, send_user_message, |
| 962 | max_iterations, working_dir, created_at, starred, permissions, agent_model_overrides, |
| 963 | custom_models_used, thinking, parent_id |
| 964 | ) |
| 965 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| 966 | ON CONFLICT(id) DO UPDATE SET |
| 967 | title = excluded.title, |
| 968 | tools_approved = excluded.tools_approved, |
| 969 | input_tokens = excluded.input_tokens, |
| 970 | output_tokens = excluded.output_tokens, |
| 971 | cost = excluded.cost, |
| 972 | send_user_message = excluded.send_user_message, |