const std = @import("std");const question_answer = @import("../agent/question_answer.zig");const config_runtime = @import("../config/config_runtime.zig");const model_capabilities = @import("../config/model_capabilities.zig");const assistant_presentation = @import("../agent/assistant_presentation.zig");const tool_presentation = @import("../agent/runtime/tool_presentation.zig");const debug_trace = @import("../shared/debug_trace.zig");const runtime_profile = @import("../hosts/runtime_profile.zig");const host_target = @import("../hosts/target.zig");const diff = @import("../output/diff.zig");const diagnostics = @import("../workspace/diagnostics.zig");const app_lifecycle = @import("app_lifecycle.zig");const input_completion_runtime = @import("input_completion_runtime.zig");const input_queue_runtime = @import("input_queue_runtime.zig");const image_attachments = @import("../images/image_attachments.zig");const core_input_runtime = @import("../input/runtime.zig");const io_mod = @import("../shared/io.zig");const list_window = @import("../shared/list_window.zig");const session_runtime = @import("../session/session.zig");const session_catalog = @import("../session/session_catalog.zig");const session_codec = @import("../session/session_codec.zig");const js_host_session_store = @import("../session/js_host_session_store.zig");const session_event = @import("../session/session_event.zig");const session_usage = @import("../session/session_usage.zig");const session_child_store = @import("../session/session_child_store.zig");const result_store = @import("../session/result_store.zig");const command_replay_store = @import("../session/command_replay_store.zig");const command_output_content = @import("../tooling/command_output_content.zig");const captured_command = @import("../tooling/captured_command.zig");const session_display_metadata = @import("../session/session_display_metadata.zig");const session_log = @import("../session/session_log.zig");const session_resume_view = @import("../session/session_resume_view.zig");const session_store = @import("../session/session_store.zig");const session_summary_codec = @import("../session/session_summary_codec.zig");const subagent_tool_host = @import("../subagent/tool_host.zig");const subagent_authority = @import("../subagent/authority.zig");const subagent_resume_admission = @import("../subagent/resume_admission.zig");const tool_set_contract = @import("../tooling/tool_set.zig");const builtin_tools = @import("../../builtins/tools.zig");const types = @import("../shared/types.zig");const permissions = @import("../permissions/permissions.zig");const session_permission_state = @import("../permissions/session_permission_state.zig");const mcp_access = @import("../mcp/access_policy.zig");const shell_runtime = @import("../../ui/shell_runtime.zig");const transcript_runtime = @import("../../ui/transcript/runtime.zig");const ui_input = @import("../../ui/input/runtime.zig");const ui_render = @import("../../ui/render.zig"); const Allocator = std.mem.Allocator; const BackgroundSessionPolicy = enum { carry_forward, stop_forget,}; fn nextImageIdForResumedHistory( alloc: Allocator, history: []const types.HistoryTurn,) !usize { const restored_catalog = try session_runtime.collect_image_catalog( alloc, history, &.{}, ); defer types.freeImageAttachmentSlice(alloc, restored_catalog); const bounds = try image_attachments.calculate_next_image_id(restored_catalog); return bounds.next_id;} pub const SessionPickerScope = session_catalog.Scope; pub const HistoryAppendOutcome = enum { uncommitted, committed, committed_degraded,}; pub const ResumeHandoffIntent = enum { none, requested, upgrade_requested,}; const ResumeNotice = union(enum) { session, upgrade: []const u8,}; pub const ResumeViewStage = union(enum) { none, ready: u32,}; pub const ResumeHandoffState = struct { intent: ResumeHandoffIntent, has_writable_session: bool, is_pristine: bool, has_unresolved_degraded_tail: bool,}; pub fn shouldCreateResumeHandoff(state: ResumeHandoffState) bool { return state.intent != .none and state.has_writable_session and (!state.is_pristine or state.intent == .upgrade_requested) and !state.has_unresolved_degraded_tail;} test "resume handoff policy requires requested durable non-pristine state" { const cases = [_]struct { state: ResumeHandoffState, expected: bool, }{ .{ .state = .{ .intent = .requested, .has_writable_session = true, .is_pristine = false, .has_unresolved_degraded_tail = false, }, .expected = true, }, .{ .state = .{ .intent = .none, .has_writable_session = true, .is_pristine = false, .has_unresolved_degraded_tail = false, }, .expected = false, }, .{ .state = .{ .intent = .requested, .has_writable_session = false, .is_pristine = false, .has_unresolved_degraded_tail = false, }, .expected = false, }, .{ .state = .{ .intent = .upgrade_requested, .has_writable_session = true, .is_pristine = true, .has_unresolved_degraded_tail = false, }, .expected = true, }, .{ .state = .{ .intent = .requested, .has_writable_session = true, .is_pristine = true, .has_unresolved_degraded_tail = false, }, .expected = false, }, .{ .state = .{ .intent = .requested, .has_writable_session = true, .is_pristine = false, .has_unresolved_degraded_tail = true, }, .expected = false, }, }; for (cases) |case| { try std.testing.expectEqual(case.expected, shouldCreateResumeHandoff(case.state)); }} /// Owns `session_id`; callers must release it with `deinit`.pub const ResumeHandoff = struct { session_id: []u8, pub fn deinit(self: *ResumeHandoff, alloc: Allocator) void { alloc.free(self.session_id); self.* = undefined; }}; pub const SessionPreferencePatch = struct { model: ?[]const u8 = null, effort: ?types.ReasoningEffort = null, fast_mode: ?bool = null, fn userSettingsPatch(self: SessionPreferencePatch) config_runtime.UserSettingsPatch { return .{ .model = self.model, .effort = self.effort, .fast_mode = self.fast_mode, }; }}; pub const PreferenceCommitResult = struct { settings_outcome: ?config_runtime.CommitOutcome = null, settings_error: ?anyerror = null, settings_failure_cleanup: config_runtime.LegacyCleanup = .{}, session_error: ?anyerror = null, pub fn deinit(self: *PreferenceCommitResult, alloc: Allocator) void { if (self.settings_outcome) |*outcome| outcome.deinit(alloc); self.settings_failure_cleanup.deinit(alloc); self.* = undefined; }}; pub const SessionPicker = struct { active: bool = false, load_state: session_catalog.LoadState = .loading, generation: u64 = 0, has_more: bool = false, loading_more: bool = false, summaries: std.ArrayList(session_store.SessionSummary) = .empty, selected: usize = 0, window_start: usize = 0, scope: SessionPickerScope = .current_workspace, query_buf: [256]u8 = undefined, query_len: usize = 0, selection_failure: ?session_catalog.ResumeFailure = null, pub fn deinit(self: *SessionPicker, alloc: Allocator) void { for (self.summaries.items) |*summary| summary.deinit(alloc); self.summaries.deinit(alloc); self.* = .{}; } fn appendSummary( self: *SessionPicker, alloc: Allocator, source: *const session_store.SessionSummary, ) !void { if (source.history_len == 0 and !source.has_managed_children) return; var summary = try session_summary_codec.cloneSessionSummary(alloc, source.*); errdefer summary.deinit(alloc); try self.summaries.append(alloc, summary); } pub fn selectedId(self: *const SessionPicker) ?[]const u8 { if (!self.active or self.load_state != .ready) return null; if (self.selected >= self.filteredItemCount()) return null; const summary = session_catalog.summaryAt(self.summaries.items, self.query(), self.selected) orelse return null; return summary.id; } fn isLoading(self: *const SessionPicker) bool { return self.active and self.load_state == .loading; } pub fn query(self: *const SessionPicker) []const u8 { return self.query_buf[0..self.query_len]; } pub fn setQuery(self: *SessionPicker, query_text: []const u8) void { const len = @min(query_text.len, self.query_buf.len); if (len > 0) std.mem.copyForwards(u8, self.query_buf[0..len], query_text[0..len]); self.query_len = len; self.selected = 0; self.window_start = 0; self.selection_failure = null; } pub fn filteredItemCount(self: *const SessionPicker) usize { return session_catalog.filteredCount(self.summaries.items, self.query()); } pub fn navigationItemCount(self: *const SessionPicker) usize { return self.filteredItemCount() + @intFromBool(self.has_more); } fn isLoadMoreSelected(self: *const SessionPicker) bool { return self.active and self.load_state == .ready and self.has_more and self.selected == self.filteredItemCount(); } fn moveVisibleItems(self: *SessionPicker, delta: i32, visible_items: u16) bool { if (!self.active or self.load_state != .ready) return false; self.selection_failure = null; const count = self.navigationItemCount(); if (count == 0) return true; // Clamp at both ends instead of wrapping: at the top the selection // stays on the first session, at the bottom on the last row. const current: i32 = @intCast(self.selected % count); var next = current + delta; if (next < 0) next = 0; if (next >= @as(i32, @intCast(count))) next = @as(i32, @intCast(count)) - 1; self.selected = @intCast(next); self.syncWindowStart(visible_items); return true; } pub fn syncWindowStart(self: *SessionPicker, visible_items: u16) void { const session_count = self.filteredItemCount(); const action_items: u16 = @intFromBool(self.has_more and visible_items > 0); const visible_sessions = visible_items -| action_items; if (session_count == 0 or visible_sessions == 0) { self.window_start = 0; return; } if (self.selected >= session_count) { self.window_start = session_count -| visible_sessions; return; } self.window_start = list_window.updateEdgeStart( self.window_start, session_count, self.selected, visible_sessions, ); } fn appendPage( self: *SessionPicker, alloc: Allocator, summaries: []const session_store.SessionSummary, ) !void { const summary_len = self.summaries.items.len; errdefer { while (self.summaries.items.len > summary_len) { if (self.summaries.pop()) |summary_value| { var summary = summary_value; summary.deinit(alloc); } } } for (summaries) |*summary| try self.appendSummary(alloc, summary); }}; const session_picker_cache_ttl_ns: i128 = 5 * std.time.ns_per_s; const SessionPickerPageCache = struct { ready: bool = false, active_id: ?[]u8 = null, limit: usize = 0, loaded_at_ns: i128 = 0, page: session_store.ResumableSessionPage = .{}, fn deinit(self: *SessionPickerPageCache) void { const alloc = std.heap.c_allocator; if (self.active_id) |id| alloc.free(id); self.page.deinit(alloc); self.* = .{}; } fn matches( self: *const SessionPickerPageCache, active_id: ?[]const u8, limit: usize, ) bool { return self.ready and self.limit >= limit and optionalStringEql(self.active_id, active_id); } fn isFresh(self: *const SessionPickerPageCache, now_ns: i128) bool { return self.ready and now_ns >= self.loaded_at_ns and now_ns - self.loaded_at_ns <= session_picker_cache_ttl_ns; } fn install( self: *SessionPickerPageCache, source: *const session_store.ResumableSessionPage, active_id: ?[]const u8, limit: usize, loaded_at_ns: i128, ) !void { const alloc = std.heap.c_allocator; var replacement: SessionPickerPageCache = .{ .ready = true, .active_id = if (active_id) |id| try alloc.dupe(u8, id) else null, .limit = limit, .loaded_at_ns = loaded_at_ns, .page = .{ .has_more = source.has_more }, }; errdefer replacement.deinit(); for (source.summaries.items) |summary| { var copied = try session_summary_codec.cloneSessionSummary(alloc, summary); replacement.page.summaries.append(alloc, copied) catch |err| { copied.deinit(alloc); return err; }; } self.deinit(); self.* = replacement; }}; fn optionalStringEql(a: ?[]const u8, b: ?[]const u8) bool { if (a == null or b == null) return a == null and b == null; return std.mem.eql(u8, a.?, b.?);} fn sessionPickerCacheForScope( persistence: *Persistence, scope: SessionPickerScope,) *SessionPickerPageCache { return switch (scope) { .current_workspace => &persistence.session_picker_current_cache, .all_workspaces => &persistence.session_picker_all_cache, };} fn replaceSessionPickerPage( picker: *SessionPicker, alloc: Allocator, source: *const session_store.ResumableSessionPage,) !void { var replacement: std.ArrayList(session_store.SessionSummary) = .empty; errdefer { for (replacement.items) |*summary| summary.deinit(alloc); replacement.deinit(alloc); } for (source.summaries.items) |*summary| { if (summary.history_len == 0 and !summary.has_managed_children) continue; var copied = try session_summary_codec.cloneSessionSummary(alloc, summary.*); replacement.append(alloc, copied) catch |err| { copied.deinit(alloc); return err; }; } for (picker.summaries.items) |*summary| summary.deinit(alloc); picker.summaries.deinit(alloc); picker.summaries = replacement;} fn selectionAfterLoadedPage( previous_filtered_count: usize, filtered_count: usize, has_more: bool,) usize { if (filtered_count > previous_filtered_count) return previous_filtered_count; if (has_more) return filtered_count; return filtered_count -| 1;} const SessionPickerLoad = struct { const Continuation = struct { updated_at_ms: i64, id: []u8, fn deinit(self: *Continuation) void { std.heap.c_allocator.free(self.id); self.* = undefined; } }; const PageRequest = struct { generation: u64, scope: SessionPickerScope, active_id: ?[]u8 = null, continuation: ?Continuation = null, limit: usize = session_store.default_resume_page_limit, fn init( generation: u64, scope: SessionPickerScope, active_id: ?[]const u8, continuation: ?session_store.ResumableSessionContinuation, limit: usize, ) !PageRequest { const alloc = std.heap.c_allocator; var request: PageRequest = .{ .generation = generation, .scope = scope, .limit = limit }; errdefer request.deinit(); if (active_id) |id| request.active_id = try alloc.dupe(u8, id); if (continuation) |position| { request.continuation = .{ .updated_at_ms = position.updated_at_ms, .id = try alloc.dupe(u8, position.id), }; } return request; } fn deinit(self: *PageRequest) void { if (self.active_id) |id| std.heap.c_allocator.free(id); if (self.continuation) |*position| position.deinit(); self.* = undefined; } fn continuationValue(self: *const PageRequest) ?session_store.ResumableSessionContinuation { const continuation = self.continuation orelse return null; return .{ .updated_at_ms = continuation.updated_at_ms, .id = continuation.id, }; } }; const Task = struct { thread: ?std.Thread = null, done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), home_dir: []u8, workspace_root: []u8, request: PageRequest, page: ?session_store.ResumableSessionPage = null, failure: ?anyerror = null, Lines 1–500 of 9395. Load more
const std = @import("std"); const question_answer = @import("../agent/question_answer.zig"); const config_runtime = @import("../config/config_runtime.zig"); const model_capabilities = @import("../config/model_capabilities.zig"); const assistant_presentation = @import("../agent/assistant_presentation.zig"); const tool_presentation = @import("../agent/runtime/tool_presentation.zig"); const debug_trace = @import("../shared/debug_trace.zig"); const runtime_profile = @import("../hosts/runtime_profile.zig"); const host_target = @import("../hosts/target.zig"); const diff = @import("../output/diff.zig"); const diagnostics = @import("../workspace/diagnostics.zig"); const app_lifecycle = @import("app_lifecycle.zig"); const input_completion_runtime = @import("input_completion_runtime.zig"); const input_queue_runtime = @import("input_queue_runtime.zig"); const image_attachments = @import("../images/image_attachments.zig"); const core_input_runtime = @import("../input/runtime.zig"); const io_mod = @import("../shared/io.zig"); const list_window = @import("../shared/list_window.zig"); const session_runtime = @import("../session/session.zig"); const session_catalog = @import("../session/session_catalog.zig"); const session_codec = @import("../session/session_codec.zig"); const js_host_session_store = @import("../session/js_host_session_store.zig"); const session_event = @import("../session/session_event.zig"); const session_usage = @import("../session/session_usage.zig"); const session_child_store = @import("../session/session_child_store.zig"); const result_store = @import("../session/result_store.zig"); const command_replay_store = @import("../session/command_replay_store.zig"); const command_output_content = @import("../tooling/command_output_content.zig"); const captured_command = @import("../tooling/captured_command.zig"); const session_display_metadata = @import("../session/session_display_metadata.zig"); const session_log = @import("../session/session_log.zig"); const session_resume_view = @import("../session/session_resume_view.zig"); const session_store = @import("../session/session_store.zig"); const session_summary_codec = @import("../session/session_summary_codec.zig"); const subagent_tool_host = @import("../subagent/tool_host.zig"); const subagent_authority = @import("../subagent/authority.zig"); const subagent_resume_admission = @import("../subagent/resume_admission.zig"); const tool_set_contract = @import("../tooling/tool_set.zig"); const builtin_tools = @import("../../builtins/tools.zig"); const types = @import("../shared/types.zig"); const permissions = @import("../permissions/permissions.zig"); const session_permission_state = @import("../permissions/session_permission_state.zig"); const mcp_access = @import("../mcp/access_policy.zig"); const shell_runtime = @import("../../ui/shell_runtime.zig"); const transcript_runtime = @import("../../ui/transcript/runtime.zig"); const ui_input = @import("../../ui/input/runtime.zig"); const ui_render = @import("../../ui/render.zig"); const Allocator = std.mem.Allocator; const BackgroundSessionPolicy = enum { carry_forward, stop_forget, }; fn nextImageIdForResumedHistory( alloc: Allocator, history: []const types.HistoryTurn, ) !usize { const restored_catalog = try session_runtime.collect_image_catalog( alloc, history, &.{}, ); defer types.freeImageAttachmentSlice(alloc, restored_catalog); const bounds = try image_attachments.calculate_next_image_id(restored_catalog); return bounds.next_id; } pub const SessionPickerScope = session_catalog.Scope; pub const HistoryAppendOutcome = enum { uncommitted, committed, committed_degraded, }; pub const ResumeHandoffIntent = enum { none, requested, upgrade_requested, }; const ResumeNotice = union(enum) { session, upgrade: []const u8, }; pub const ResumeViewStage = union(enum) { none, ready: u32, }; pub const ResumeHandoffState = struct { intent: ResumeHandoffIntent, has_writable_session: bool, is_pristine: bool, has_unresolved_degraded_tail: bool, }; pub fn shouldCreateResumeHandoff(state: ResumeHandoffState) bool { return state.intent != .none and state.has_writable_session and (!state.is_pristine or state.intent == .upgrade_requested) and !state.has_unresolved_degraded_tail; } test "resume handoff policy requires requested durable non-pristine state" { const cases = [_]struct { state: ResumeHandoffState, expected: bool, }{ .{ .state = .{ .intent = .requested, .has_writable_session = true, .is_pristine = false, .has_unresolved_degraded_tail = false, }, .expected = true, }, .{ .state = .{ .intent = .none, .has_writable_session = true, .is_pristine = false, .has_unresolved_degraded_tail = false, }, .expected = false, }, .{ .state = .{ .intent = .requested, .has_writable_session = false, .is_pristine = false, .has_unresolved_degraded_tail = false, }, .expected = false, }, .{ .state = .{ .intent = .upgrade_requested, .has_writable_session = true, .is_pristine = true, .has_unresolved_degraded_tail = false, }, .expected = true, }, .{ .state = .{ .intent = .requested, .has_writable_session = true, .is_pristine = true, .has_unresolved_degraded_tail = false, }, .expected = false, }, .{ .state = .{ .intent = .requested, .has_writable_session = true, .is_pristine = false, .has_unresolved_degraded_tail = true, }, .expected = false, }, }; for (cases) |case| { try std.testing.expectEqual(case.expected, shouldCreateResumeHandoff(case.state)); } } /// Owns `session_id`; callers must release it with `deinit`. pub const ResumeHandoff = struct { session_id: []u8, pub fn deinit(self: *ResumeHandoff, alloc: Allocator) void { alloc.free(self.session_id); self.* = undefined; } }; pub const SessionPreferencePatch = struct { model: ?[]const u8 = null, effort: ?types.ReasoningEffort = null, fast_mode: ?bool = null, fn userSettingsPatch(self: SessionPreferencePatch) config_runtime.UserSettingsPatch { return .{ .model = self.model, .effort = self.effort, .fast_mode = self.fast_mode, }; } }; pub const PreferenceCommitResult = struct { settings_outcome: ?config_runtime.CommitOutcome = null, settings_error: ?anyerror = null, settings_failure_cleanup: config_runtime.LegacyCleanup = .{}, session_error: ?anyerror = null, pub fn deinit(self: *PreferenceCommitResult, alloc: Allocator) void { if (self.settings_outcome) |*outcome| outcome.deinit(alloc); self.settings_failure_cleanup.deinit(alloc); self.* = undefined; } }; pub const SessionPicker = struct { active: bool = false, load_state: session_catalog.LoadState = .loading, generation: u64 = 0, has_more: bool = false, loading_more: bool = false, summaries: std.ArrayList(session_store.SessionSummary) = .empty, selected: usize = 0, window_start: usize = 0, scope: SessionPickerScope = .current_workspace, query_buf: [256]u8 = undefined, query_len: usize = 0, selection_failure: ?session_catalog.ResumeFailure = null, pub fn deinit(self: *SessionPicker, alloc: Allocator) void { for (self.summaries.items) |*summary| summary.deinit(alloc); self.summaries.deinit(alloc); self.* = .{}; } fn appendSummary( self: *SessionPicker, alloc: Allocator, source: *const session_store.SessionSummary, ) !void { if (source.history_len == 0 and !source.has_managed_children) return; var summary = try session_summary_codec.cloneSessionSummary(alloc, source.*); errdefer summary.deinit(alloc); try self.summaries.append(alloc, summary); } pub fn selectedId(self: *const SessionPicker) ?[]const u8 { if (!self.active or self.load_state != .ready) return null; if (self.selected >= self.filteredItemCount()) return null; const summary = session_catalog.summaryAt(self.summaries.items, self.query(), self.selected) orelse return null; return summary.id; } fn isLoading(self: *const SessionPicker) bool { return self.active and self.load_state == .loading; } pub fn query(self: *const SessionPicker) []const u8 { return self.query_buf[0..self.query_len]; } pub fn setQuery(self: *SessionPicker, query_text: []const u8) void { const len = @min(query_text.len, self.query_buf.len); if (len > 0) std.mem.copyForwards(u8, self.query_buf[0..len], query_text[0..len]); self.query_len = len; self.selected = 0; self.window_start = 0; self.selection_failure = null; } pub fn filteredItemCount(self: *const SessionPicker) usize { return session_catalog.filteredCount(self.summaries.items, self.query()); } pub fn navigationItemCount(self: *const SessionPicker) usize { return self.filteredItemCount() + @intFromBool(self.has_more); } fn isLoadMoreSelected(self: *const SessionPicker) bool { return self.active and self.load_state == .ready and self.has_more and self.selected == self.filteredItemCount(); } fn moveVisibleItems(self: *SessionPicker, delta: i32, visible_items: u16) bool { if (!self.active or self.load_state != .ready) return false; self.selection_failure = null; const count = self.navigationItemCount(); if (count == 0) return true; // Clamp at both ends instead of wrapping: at the top the selection // stays on the first session, at the bottom on the last row. const current: i32 = @intCast(self.selected % count); var next = current + delta; if (next < 0) next = 0; if (next >= @as(i32, @intCast(count))) next = @as(i32, @intCast(count)) - 1; self.selected = @intCast(next); self.syncWindowStart(visible_items); return true; } pub fn syncWindowStart(self: *SessionPicker, visible_items: u16) void { const session_count = self.filteredItemCount(); const action_items: u16 = @intFromBool(self.has_more and visible_items > 0); const visible_sessions = visible_items -| action_items; if (session_count == 0 or visible_sessions == 0) { self.window_start = 0; return; } if (self.selected >= session_count) { self.window_start = session_count -| visible_sessions; return; } self.window_start = list_window.updateEdgeStart( self.window_start, session_count, self.selected, visible_sessions, ); } fn appendPage( self: *SessionPicker, alloc: Allocator, summaries: []const session_store.SessionSummary, ) !void { const summary_len = self.summaries.items.len; errdefer { while (self.summaries.items.len > summary_len) { if (self.summaries.pop()) |summary_value| { var summary = summary_value; summary.deinit(alloc); } } } for (summaries) |*summary| try self.appendSummary(alloc, summary); } }; const session_picker_cache_ttl_ns: i128 = 5 * std.time.ns_per_s; const SessionPickerPageCache = struct { ready: bool = false, active_id: ?[]u8 = null, limit: usize = 0, loaded_at_ns: i128 = 0, page: session_store.ResumableSessionPage = .{}, fn deinit(self: *SessionPickerPageCache) void { const alloc = std.heap.c_allocator; if (self.active_id) |id| alloc.free(id); self.page.deinit(alloc); self.* = .{}; } fn matches( self: *const SessionPickerPageCache, active_id: ?[]const u8, limit: usize, ) bool { return self.ready and self.limit >= limit and optionalStringEql(self.active_id, active_id); } fn isFresh(self: *const SessionPickerPageCache, now_ns: i128) bool { return self.ready and now_ns >= self.loaded_at_ns and now_ns - self.loaded_at_ns <= session_picker_cache_ttl_ns; } fn install( self: *SessionPickerPageCache, source: *const session_store.ResumableSessionPage, active_id: ?[]const u8, limit: usize, loaded_at_ns: i128, ) !void { const alloc = std.heap.c_allocator; var replacement: SessionPickerPageCache = .{ .ready = true, .active_id = if (active_id) |id| try alloc.dupe(u8, id) else null, .limit = limit, .loaded_at_ns = loaded_at_ns, .page = .{ .has_more = source.has_more }, }; errdefer replacement.deinit(); for (source.summaries.items) |summary| { var copied = try session_summary_codec.cloneSessionSummary(alloc, summary); replacement.page.summaries.append(alloc, copied) catch |err| { copied.deinit(alloc); return err; }; } self.deinit(); self.* = replacement; } }; fn optionalStringEql(a: ?[]const u8, b: ?[]const u8) bool { if (a == null or b == null) return a == null and b == null; return std.mem.eql(u8, a.?, b.?); } fn sessionPickerCacheForScope( persistence: *Persistence, scope: SessionPickerScope, ) *SessionPickerPageCache { return switch (scope) { .current_workspace => &persistence.session_picker_current_cache, .all_workspaces => &persistence.session_picker_all_cache, }; } fn replaceSessionPickerPage( picker: *SessionPicker, alloc: Allocator, source: *const session_store.ResumableSessionPage, ) !void { var replacement: std.ArrayList(session_store.SessionSummary) = .empty; errdefer { for (replacement.items) |*summary| summary.deinit(alloc); replacement.deinit(alloc); } for (source.summaries.items) |*summary| { if (summary.history_len == 0 and !summary.has_managed_children) continue; var copied = try session_summary_codec.cloneSessionSummary(alloc, summary.*); replacement.append(alloc, copied) catch |err| { copied.deinit(alloc); return err; }; } for (picker.summaries.items) |*summary| summary.deinit(alloc); picker.summaries.deinit(alloc); picker.summaries = replacement; } fn selectionAfterLoadedPage( previous_filtered_count: usize, filtered_count: usize, has_more: bool, ) usize { if (filtered_count > previous_filtered_count) return previous_filtered_count; if (has_more) return filtered_count; return filtered_count -| 1; } const SessionPickerLoad = struct { const Continuation = struct { updated_at_ms: i64, id: []u8, fn deinit(self: *Continuation) void { std.heap.c_allocator.free(self.id); self.* = undefined; } }; const PageRequest = struct { generation: u64, scope: SessionPickerScope, active_id: ?[]u8 = null, continuation: ?Continuation = null, limit: usize = session_store.default_resume_page_limit, fn init( generation: u64, scope: SessionPickerScope, active_id: ?[]const u8, continuation: ?session_store.ResumableSessionContinuation, limit: usize, ) !PageRequest { const alloc = std.heap.c_allocator; var request: PageRequest = .{ .generation = generation, .scope = scope, .limit = limit }; errdefer request.deinit(); if (active_id) |id| request.active_id = try alloc.dupe(u8, id); if (continuation) |position| { request.continuation = .{ .updated_at_ms = position.updated_at_ms, .id = try alloc.dupe(u8, position.id), }; } return request; } fn deinit(self: *PageRequest) void { if (self.active_id) |id| std.heap.c_allocator.free(id); if (self.continuation) |*position| position.deinit(); self.* = undefined; } fn continuationValue(self: *const PageRequest) ?session_store.ResumableSessionContinuation { const continuation = self.continuation orelse return null; return .{ .updated_at_ms = continuation.updated_at_ms, .id = continuation.id, }; } }; const Task = struct { thread: ?std.Thread = null, done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), home_dir: []u8, workspace_root: []u8, request: PageRequest, page: ?session_store.ResumableSessionPage = null, failure: ?anyerror = null,