9395 lines · 354.2 KB · Zig
RawDownload
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.