// Host contract:// - fields: transcript, entries, next_entry_id, last_rendered_cols,// transcript_cache_origin_untrimmed,// max_transcript_bytes, max_retained_transcript_bytes,// folded_command_blocks, command_output_blocks, command_output_display,// tool_details,// full_transcript, replaceable_last_line, replaceable_start,// replaceable_row, transcript_band_dirty, layout, cursor_row, cursor_col,// owned_top_row, viewport_top_row, pending_scroll_compact,// has_painted_transcript, last_viewport_selection,// last_visible_transcript_top_row, last_visible_transcript_start_line,// last_visible_transcript_partial_skip_rows, last_visible_transcript_split_active,// last_visible_transcript_split_prefix_lines,// last_visible_transcript_split_suffix_start_line// - methods: assertCanMutateTranscript, advanceCursor, invalidateTranscriptAnchor,// recomputeCursorFromTranscript, writeTranscriptBytes,// writeTranscriptClassified const std = @import("std");const debug_trace = @import("../../core/shared/debug_trace.zig");const io_mod = @import("../../core/shared/io.zig");const types = @import("../../core/shared/types.zig");const command_output_content = @import("../../core/tooling/command_output_content.zig");const command_output_runtime = @import("command_output_runtime.zig");const render_engine = @import("../render_engine.zig");const source_preparation = @import("source_preparation.zig");const user_message_card = @import("../assistant/user_message_card.zig");const input_visual_layout = @import("../input/visual_layout.zig"); const Allocator = std.mem.Allocator;const Metrics = types.Metrics;const transcript_blocks = render_engine.transcript_blocks;const assistant_presentation = @import("../../core/agent/assistant_presentation.zig"); const AssistantTurnSegments = transcript_blocks.AssistantTurnSegments;const RawEntryClass = transcript_blocks.RawEntryClass;const Styles = transcript_blocks.Styles;const ToolDetailRecord = transcript_blocks.ToolDetailRecord;const TranscriptEntry = transcript_blocks.TranscriptEntry;const SkillTokenSpan = input_visual_layout.SkillTokenSpan; pub const TranscriptSourceRewriteMode = enum { strict, preserve_same_epoch,}; pub const LifecycleEntryUpdate = struct { entry_id: u32, bytes: []const u8,}; noinline fn requestTranscriptPaint(self: anytype) void { const Runtime = @TypeOf(self.*); if (comptime @hasDecl(Runtime, "markTranscriptContentDirty")) { self.markTranscriptContentDirty(); } else if (comptime @hasDecl(Runtime, "markTranscriptDirty")) { self.markTranscriptDirty(); } else { self.transcript_band_dirty = true; }} noinline fn requestTranscriptPaintFrom(self: anytype, entry_id: u32) void { const Runtime = @TypeOf(self.*); if (comptime @hasDecl(Runtime, "markTranscriptContentDirtyFrom")) { self.markTranscriptContentDirtyFrom(entry_id); } else { requestTranscriptPaint(self); }} fn reconcileTranscriptAnchorAfterSourceChange( self: anytype, force_rebase: bool, reason: []const u8, mode: TranscriptSourceRewriteMode,) void { reconcileTranscriptAnchorAfterSourceChangeUsingSource( self, self.transcript.items, force_rebase, reason, mode, );} fn reconcileTranscriptAnchorAfterSourceChangeUsingSource( self: anytype, reconciliation_source: []const u8, force_rebase: bool, reason: []const u8, mode: TranscriptSourceRewriteMode,) void { const Runtime = @TypeOf(self.*); if (comptime @hasDecl(Runtime, "reconcileTranscriptCommitSourceWithRewriteMode")) { self.reconcileTranscriptCommitSourceWithRewriteMode( reconciliation_source, force_rebase, reason, mode, ); } else if (comptime @hasDecl(Runtime, "reconcileTranscriptCommitSource")) { self.reconcileTranscriptCommitSource( reconciliation_source, force_rebase, reason, ); } else if (comptime @hasDecl(Runtime, "stableTranscriptProjectionForFlow") and @hasDecl(Runtime, "invalidateTranscriptAnchor")) { if (force_rebase or self.stableTranscriptProjectionForFlow(reconciliation_source) == null) { self.invalidateTranscriptAnchor(reason); } }} /// Preserve an already committed prefix while an owner-level detached build/// replaces the transcript state. The complete source is used only for/// reconciliation; retained entries remain authoritative after publication.pub fn reconcileDetachedInstallSource( self: anytype, source: []const u8,) void { reconcileTranscriptAnchorAfterSourceChangeUsingSource( self, source, false, "resume_projection_install", .strict, );} pub fn initBacking(self: anytype, alloc: Allocator) !void { try self.transcript.ensureTotalCapacity(alloc, self.max_transcript_bytes);} pub fn ensurePaintReservation(self: anytype, alloc: Allocator) !void { if (self.transcript.capacity < self.max_transcript_bytes) { try self.transcript.ensureTotalCapacity(alloc, self.max_transcript_bytes); }} pub fn rebuildTranscriptFromRendered(self: anytype, alloc: Allocator, bytes: []const u8, label: []const u8) !void { const start = cappedTailStart(bytes, self.max_transcript_bytes); try self.transcript.ensureTotalCapacity(alloc, bytes.len - start); self.transcript_cache_origin_untrimmed = false; self.transcript.clearRetainingCapacity(); if (start > 0) { debug_trace.logf("transcript_cap", "{s} trimmed {d} leading bytes (bytes.len={d} cap={d})", .{ label, start, bytes.len, self.max_transcript_bytes }); } self.transcript.appendSliceAssumeCapacity(bytes[start..]); self.transcript_cache_origin_untrimmed = start == 0; self.last_rendered_cols = if (self.layout.cols > 0) self.layout.cols else 80;} pub fn retainedStructuredBytes(self: anytype) usize { var total: usize = 0; for (self.entries.items) |entry| total += entryRetainedBytes(entry); for (self.folded_command_blocks.items) |block| total += command_output_runtime.foldedBlockRetainedBytes(block); for (self.command_output_blocks.items) |block| total += command_output_runtime.commandOutputBlockRetainedBytes(block); return total;} fn entryRetainedBytes(entry: TranscriptEntry) usize { return switch (entry) { .raw_bytes => |e| e.bytes.len, .semantic_notice => |e| e.topic.len + e.body.len, .user_turn => |e| blk: { var total = e.turn.text.len; for (e.turn.images) |img| { total += img.path.len; total += img.media_type.len; } for (e.skill_tokens) |token| { total += token.name.len; total += token.path.len; } break :blk total; }, .assistant_turn => |e| e.segments.text.items.len, .assistant_table => |e| tableRetainedBytes(e.table), .assistant_code_block => |e| e.block.language.len + e.block.code.len, .assistant_thematic_rule => 0, };} fn dupeSkillTokenSpans(alloc: Allocator, skill_tokens: []const SkillTokenSpan) ![]SkillTokenSpan { if (skill_tokens.len == 0) return &.{}; const copy = try alloc.alloc(SkillTokenSpan, skill_tokens.len); var filled: usize = 0; errdefer { var i: usize = 0; while (i < filled) : (i += 1) { alloc.free(@constCast(copy[i].name)); alloc.free(@constCast(copy[i].path)); } alloc.free(copy); } while (filled < skill_tokens.len) : (filled += 1) { const name = try alloc.dupe(u8, skill_tokens[filled].name); errdefer alloc.free(name); copy[filled] = .{ .raw_start = skill_tokens[filled].raw_start, .raw_end = skill_tokens[filled].raw_end, .name = name, .path = try alloc.dupe(u8, skill_tokens[filled].path), .display_source = skill_tokens[filled].display_source, .owns_trailing_separator = skill_tokens[filled].owns_trailing_separator, }; } return copy;} fn freeSkillTokenSpans(alloc: Allocator, skill_tokens: []SkillTokenSpan) void { for (skill_tokens) |token| { alloc.free(@constCast(token.name)); alloc.free(@constCast(token.path)); } if (skill_tokens.len > 0) alloc.free(skill_tokens);} fn tableRetainedBytes(table: assistant_presentation.TablePayload) usize { var total: usize = table.alignments.len * @sizeOf(assistant_presentation.TableColumnAlign); for (table.rows) |row| { for (row.cells) |cell| total += cell.len; } return total;} pub fn replaceableEntryId(self: anytype) ?u32 { if (!self.replaceable_last_line or self.entries.items.len == 0) return null; const tail = self.entries.items[self.entries.items.len - 1]; return switch (tail) { .raw_bytes => |e| e.id, else => null, };} fn protectedPreviousUserTurnId(self: anytype, protected_id: u32) ?u32 { for (self.entries.items, 0..) |entry, index| { if (entry.id() != protected_id) continue; if (entry != .assistant_turn or index == 0) return null; const previous = self.entries.items[index - 1]; return if (previous == .user_turn) previous.user_turn.id else null; } return null;} fn isProtectedEntry(self: anytype, entry_id: u32, protected_id: ?u32) bool { if (isLifecyclePinned(self, entry_id)) return true; for (self.command_output_blocks.items) |block| { if (command_output_runtime.isPrunedRangeAnchor(block, entry_id)) return true; } if (protected_id) |id| { if (entry_id == id) return true; if (protectedPreviousUserTurnId(self, id)) |user_id| { if (entry_id == user_id) return true; } } if (replaceableEntryId(self)) |id| { if (entry_id == id) return true; } return false;} pub fn isLifecyclePinned(self: anytype, entry_id: u32) bool { for (self.entries.items) |entry| { if (entry != .raw_bytes or entry.raw_bytes.id != entry_id) continue; return entry.raw_bytes.lifecycle_pinned; } return false;} pub fn toolStatusEntryLabel(self: anytype, entry_id: u32) ?[]const u8 { for (self.entries.items) |entry| { if (entry != .raw_bytes or entry.raw_bytes.id != entry_id) continue; return std.mem.trimEnd(u8, entry.raw_bytes.bytes, "\r\n"); } return null;} pub fn lifecyclePinCount(self: anytype) usize { var count: usize = 0; for (self.entries.items) |entry| { if (entry == .raw_bytes and entry.raw_bytes.lifecycle_pinned) count += 1; } return count;} pub const EntryIdSet = std.AutoHashMapUnmanaged(u32, void); fn rebuildEntryIdSet(self: anytype, alloc: Allocator, entry_ids: *EntryIdSet) !void { entry_ids.clearRetainingCapacity(); for (self.entries.items) |entry| { try entry_ids.put(alloc, entry.id(), {}); }} fn compactEntriesToRetainedSet( self: anytype, alloc: Allocator, retained_entry_ids: *const EntryIdSet,) void { var write_index: usize = 0; for (self.entries.items, 0..) |entry, read_index| { if (!retained_entry_ids.contains(entry.id())) { var removed = entry; removed.deinit(alloc); continue; } if (write_index != read_index) { self.entries.items[write_index] = entry; } write_index += 1; } self.entries.items.len = write_index;} fn buildProtectedEntryIdSet( self: anytype, alloc: Allocator, protected_id: ?u32,) !EntryIdSet { var protected_entry_ids: EntryIdSet = .empty; errdefer protected_entry_ids.deinit(alloc); for (self.entries.items) |entry| { if (entry == .raw_bytes and entry.raw_bytes.lifecycle_pinned) { try protected_entry_ids.put(alloc, entry.raw_bytes.id, {}); } } for (self.command_output_blocks.items) |block| { for (block.pruned_ranges.items) |range| { try protected_entry_ids.put(alloc, range.anchor_entry_id, {}); } } if (protected_id) |id| { try protected_entry_ids.put(alloc, id, {}); if (protectedPreviousUserTurnId(self, id)) |user_id| { try protected_entry_ids.put(alloc, user_id, {}); } } if (replaceableEntryId(self)) |id| { try protected_entry_ids.put(alloc, id, {}); } return protected_entry_ids;} fn pruneOrphanedFoldedCommandBlocks( self: anytype, alloc: Allocator, entry_ids: *const EntryIdSet,) bool { var changed = false; var i: usize = 0; while (i < self.folded_command_blocks.items.len) { const summary_id = self.folded_command_blocks.items[i].summary_entry_id; if (summary_id) |id| { if (!entry_ids.contains(id)) { command_output_runtime.removeFoldedCommandBlock(self, alloc, i); changed = true; continue; } } i += 1; } return changed;} fn firstExistingCommandOutputSourceEntryId( entry_ids: *const EntryIdSet, block: command_output_runtime.CommandOutputBlock,) ?u32 { for (block.pruned_ranges.items) |range| { if (entry_ids.contains(range.anchor_entry_id)) return range.anchor_entry_id; } for (block.source_entry_ids.items) |entry_id| { if (entry_ids.contains(entry_id)) return entry_id; } return null;} fn retargetCommandOutputDetails(self: anytype, old_entry_id: u32, new_entry_id: u32) void { if (comptime !@hasField(@TypeOf(self.*), "tool_details")) return; for (self.tool_details.items) |*detail| { if (detail.command_output_entry_id == old_entry_id) { detail.command_output_entry_id = new_entry_id; } }} fn commandOutputBlockHasDetail( self: anytype, block: command_output_runtime.CommandOutputBlock,) bool { if (comptime !@hasField(@TypeOf(self.*), "tool_details")) return false; for (self.tool_details.items) |detail| { if (detail.command_output_entry_id) |entry_id| { if (command_output_runtime.commandBlockOwnsEntry(block, entry_id)) { return true; } } if (detail.lifecycle_id != null and block.lifecycle_id != null and command_output_runtime.sameLifecycleId(detail.lifecycle_id, block.lifecycle_id)) { return true; } } return false;} fn removePrunedRangeAnchorEntries( self: anytype, alloc: Allocator, block: command_output_runtime.CommandOutputBlock, entry_ids: *EntryIdSet,) void { var entry_index: usize = 0; while (entry_index < self.entries.items.len) { const entry_id = self.entries.items[entry_index].id(); if (!command_output_runtime.isPrunedRangeAnchor(block, entry_id)) { entry_index += 1; continue; } var removed = self.entries.orderedRemove(entry_index); _ = entry_ids.remove(entry_id); removed.deinit(alloc); }} fn pruneOrphanedCommandOutputBlocks( self: anytype, alloc: Allocator, entry_ids: *EntryIdSet,) bool { var changed = false; var i: usize = 0; while (i < self.command_output_blocks.items.len) { if (self.command_output_display.open_command_block) |open| { if (open == i) { i += 1; continue; } } if (self.command_output_blocks.items[i].lines.items.len == 0 and self.command_output_blocks.items[i].pruned_ranges.items.len > 0 and !commandOutputBlockHasDetail(self, self.command_output_blocks.items[i])) { removePrunedRangeAnchorEntries( self, alloc, self.command_output_blocks.items[i], entry_ids, ); command_output_runtime.removeCommandOutputBlock(self, alloc, i); changed = true; continue; } const entry_id = self.command_output_blocks.items[i].entry_id; if (entry_id) |id| { if (!entry_ids.contains(id)) { if (firstExistingCommandOutputSourceEntryId(entry_ids, self.command_output_blocks.items[i])) |replacement| { self.command_output_blocks.items[i].entry_id = replacement; retargetCommandOutputDetails(self, id, replacement); changed = true; i += 1; continue; } command_output_runtime.removeCommandOutputBlock(self, alloc, i); changed = true; continue; } } else { if (firstExistingCommandOutputSourceEntryId(entry_ids, self.command_output_blocks.items[i])) |replacement| { self.command_output_blocks.items[i].entry_id = replacement; changed = true; i += 1; continue; } command_output_runtime.removeCommandOutputBlock(self, alloc, i); changed = true; continue; } i += 1; } return changed;} fn coalesceCommandOutputPrunedRanges(self: anytype, alloc: Allocator) bool { var changed = false; for (0..self.command_output_blocks.items.len) |block_index| { if (command_output_runtime.coalesceCommandOutputPrunedRanges( self, alloc, block_index, )) changed = true;Lines 1–500 of 3281. Load more
// Host contract: // - fields: transcript, entries, next_entry_id, last_rendered_cols, // transcript_cache_origin_untrimmed, // max_transcript_bytes, max_retained_transcript_bytes, // folded_command_blocks, command_output_blocks, command_output_display, // tool_details, // full_transcript, replaceable_last_line, replaceable_start, // replaceable_row, transcript_band_dirty, layout, cursor_row, cursor_col, // owned_top_row, viewport_top_row, pending_scroll_compact, // has_painted_transcript, last_viewport_selection, // last_visible_transcript_top_row, last_visible_transcript_start_line, // last_visible_transcript_partial_skip_rows, last_visible_transcript_split_active, // last_visible_transcript_split_prefix_lines, // last_visible_transcript_split_suffix_start_line // - methods: assertCanMutateTranscript, advanceCursor, invalidateTranscriptAnchor, // recomputeCursorFromTranscript, writeTranscriptBytes, // writeTranscriptClassified const std = @import("std"); const debug_trace = @import("../../core/shared/debug_trace.zig"); const io_mod = @import("../../core/shared/io.zig"); const types = @import("../../core/shared/types.zig"); const command_output_content = @import("../../core/tooling/command_output_content.zig"); const command_output_runtime = @import("command_output_runtime.zig"); const render_engine = @import("../render_engine.zig"); const source_preparation = @import("source_preparation.zig"); const user_message_card = @import("../assistant/user_message_card.zig"); const input_visual_layout = @import("../input/visual_layout.zig"); const Allocator = std.mem.Allocator; const Metrics = types.Metrics; const transcript_blocks = render_engine.transcript_blocks; const assistant_presentation = @import("../../core/agent/assistant_presentation.zig"); const AssistantTurnSegments = transcript_blocks.AssistantTurnSegments; const RawEntryClass = transcript_blocks.RawEntryClass; const Styles = transcript_blocks.Styles; const ToolDetailRecord = transcript_blocks.ToolDetailRecord; const TranscriptEntry = transcript_blocks.TranscriptEntry; const SkillTokenSpan = input_visual_layout.SkillTokenSpan; pub const TranscriptSourceRewriteMode = enum { strict, preserve_same_epoch, }; pub const LifecycleEntryUpdate = struct { entry_id: u32, bytes: []const u8, }; noinline fn requestTranscriptPaint(self: anytype) void { const Runtime = @TypeOf(self.*); if (comptime @hasDecl(Runtime, "markTranscriptContentDirty")) { self.markTranscriptContentDirty(); } else if (comptime @hasDecl(Runtime, "markTranscriptDirty")) { self.markTranscriptDirty(); } else { self.transcript_band_dirty = true; } } noinline fn requestTranscriptPaintFrom(self: anytype, entry_id: u32) void { const Runtime = @TypeOf(self.*); if (comptime @hasDecl(Runtime, "markTranscriptContentDirtyFrom")) { self.markTranscriptContentDirtyFrom(entry_id); } else { requestTranscriptPaint(self); } } fn reconcileTranscriptAnchorAfterSourceChange( self: anytype, force_rebase: bool, reason: []const u8, mode: TranscriptSourceRewriteMode, ) void { reconcileTranscriptAnchorAfterSourceChangeUsingSource( self, self.transcript.items, force_rebase, reason, mode, ); } fn reconcileTranscriptAnchorAfterSourceChangeUsingSource( self: anytype, reconciliation_source: []const u8, force_rebase: bool, reason: []const u8, mode: TranscriptSourceRewriteMode, ) void { const Runtime = @TypeOf(self.*); if (comptime @hasDecl(Runtime, "reconcileTranscriptCommitSourceWithRewriteMode")) { self.reconcileTranscriptCommitSourceWithRewriteMode( reconciliation_source, force_rebase, reason, mode, ); } else if (comptime @hasDecl(Runtime, "reconcileTranscriptCommitSource")) { self.reconcileTranscriptCommitSource( reconciliation_source, force_rebase, reason, ); } else if (comptime @hasDecl(Runtime, "stableTranscriptProjectionForFlow") and @hasDecl(Runtime, "invalidateTranscriptAnchor")) { if (force_rebase or self.stableTranscriptProjectionForFlow(reconciliation_source) == null) { self.invalidateTranscriptAnchor(reason); } } } /// Preserve an already committed prefix while an owner-level detached build /// replaces the transcript state. The complete source is used only for /// reconciliation; retained entries remain authoritative after publication. pub fn reconcileDetachedInstallSource( self: anytype, source: []const u8, ) void { reconcileTranscriptAnchorAfterSourceChangeUsingSource( self, source, false, "resume_projection_install", .strict, ); } pub fn initBacking(self: anytype, alloc: Allocator) !void { try self.transcript.ensureTotalCapacity(alloc, self.max_transcript_bytes); } pub fn ensurePaintReservation(self: anytype, alloc: Allocator) !void { if (self.transcript.capacity < self.max_transcript_bytes) { try self.transcript.ensureTotalCapacity(alloc, self.max_transcript_bytes); } } pub fn rebuildTranscriptFromRendered(self: anytype, alloc: Allocator, bytes: []const u8, label: []const u8) !void { const start = cappedTailStart(bytes, self.max_transcript_bytes); try self.transcript.ensureTotalCapacity(alloc, bytes.len - start); self.transcript_cache_origin_untrimmed = false; self.transcript.clearRetainingCapacity(); if (start > 0) { debug_trace.logf("transcript_cap", "{s} trimmed {d} leading bytes (bytes.len={d} cap={d})", .{ label, start, bytes.len, self.max_transcript_bytes }); } self.transcript.appendSliceAssumeCapacity(bytes[start..]); self.transcript_cache_origin_untrimmed = start == 0; self.last_rendered_cols = if (self.layout.cols > 0) self.layout.cols else 80; } pub fn retainedStructuredBytes(self: anytype) usize { var total: usize = 0; for (self.entries.items) |entry| total += entryRetainedBytes(entry); for (self.folded_command_blocks.items) |block| total += command_output_runtime.foldedBlockRetainedBytes(block); for (self.command_output_blocks.items) |block| total += command_output_runtime.commandOutputBlockRetainedBytes(block); return total; } fn entryRetainedBytes(entry: TranscriptEntry) usize { return switch (entry) { .raw_bytes => |e| e.bytes.len, .semantic_notice => |e| e.topic.len + e.body.len, .user_turn => |e| blk: { var total = e.turn.text.len; for (e.turn.images) |img| { total += img.path.len; total += img.media_type.len; } for (e.skill_tokens) |token| { total += token.name.len; total += token.path.len; } break :blk total; }, .assistant_turn => |e| e.segments.text.items.len, .assistant_table => |e| tableRetainedBytes(e.table), .assistant_code_block => |e| e.block.language.len + e.block.code.len, .assistant_thematic_rule => 0, }; } fn dupeSkillTokenSpans(alloc: Allocator, skill_tokens: []const SkillTokenSpan) ![]SkillTokenSpan { if (skill_tokens.len == 0) return &.{}; const copy = try alloc.alloc(SkillTokenSpan, skill_tokens.len); var filled: usize = 0; errdefer { var i: usize = 0; while (i < filled) : (i += 1) { alloc.free(@constCast(copy[i].name)); alloc.free(@constCast(copy[i].path)); } alloc.free(copy); } while (filled < skill_tokens.len) : (filled += 1) { const name = try alloc.dupe(u8, skill_tokens[filled].name); errdefer alloc.free(name); copy[filled] = .{ .raw_start = skill_tokens[filled].raw_start, .raw_end = skill_tokens[filled].raw_end, .name = name, .path = try alloc.dupe(u8, skill_tokens[filled].path), .display_source = skill_tokens[filled].display_source, .owns_trailing_separator = skill_tokens[filled].owns_trailing_separator, }; } return copy; } fn freeSkillTokenSpans(alloc: Allocator, skill_tokens: []SkillTokenSpan) void { for (skill_tokens) |token| { alloc.free(@constCast(token.name)); alloc.free(@constCast(token.path)); } if (skill_tokens.len > 0) alloc.free(skill_tokens); } fn tableRetainedBytes(table: assistant_presentation.TablePayload) usize { var total: usize = table.alignments.len * @sizeOf(assistant_presentation.TableColumnAlign); for (table.rows) |row| { for (row.cells) |cell| total += cell.len; } return total; } pub fn replaceableEntryId(self: anytype) ?u32 { if (!self.replaceable_last_line or self.entries.items.len == 0) return null; const tail = self.entries.items[self.entries.items.len - 1]; return switch (tail) { .raw_bytes => |e| e.id, else => null, }; } fn protectedPreviousUserTurnId(self: anytype, protected_id: u32) ?u32 { for (self.entries.items, 0..) |entry, index| { if (entry.id() != protected_id) continue; if (entry != .assistant_turn or index == 0) return null; const previous = self.entries.items[index - 1]; return if (previous == .user_turn) previous.user_turn.id else null; } return null; } fn isProtectedEntry(self: anytype, entry_id: u32, protected_id: ?u32) bool { if (isLifecyclePinned(self, entry_id)) return true; for (self.command_output_blocks.items) |block| { if (command_output_runtime.isPrunedRangeAnchor(block, entry_id)) return true; } if (protected_id) |id| { if (entry_id == id) return true; if (protectedPreviousUserTurnId(self, id)) |user_id| { if (entry_id == user_id) return true; } } if (replaceableEntryId(self)) |id| { if (entry_id == id) return true; } return false; } pub fn isLifecyclePinned(self: anytype, entry_id: u32) bool { for (self.entries.items) |entry| { if (entry != .raw_bytes or entry.raw_bytes.id != entry_id) continue; return entry.raw_bytes.lifecycle_pinned; } return false; } pub fn toolStatusEntryLabel(self: anytype, entry_id: u32) ?[]const u8 { for (self.entries.items) |entry| { if (entry != .raw_bytes or entry.raw_bytes.id != entry_id) continue; return std.mem.trimEnd(u8, entry.raw_bytes.bytes, "\r\n"); } return null; } pub fn lifecyclePinCount(self: anytype) usize { var count: usize = 0; for (self.entries.items) |entry| { if (entry == .raw_bytes and entry.raw_bytes.lifecycle_pinned) count += 1; } return count; } pub const EntryIdSet = std.AutoHashMapUnmanaged(u32, void); fn rebuildEntryIdSet(self: anytype, alloc: Allocator, entry_ids: *EntryIdSet) !void { entry_ids.clearRetainingCapacity(); for (self.entries.items) |entry| { try entry_ids.put(alloc, entry.id(), {}); } } fn compactEntriesToRetainedSet( self: anytype, alloc: Allocator, retained_entry_ids: *const EntryIdSet, ) void { var write_index: usize = 0; for (self.entries.items, 0..) |entry, read_index| { if (!retained_entry_ids.contains(entry.id())) { var removed = entry; removed.deinit(alloc); continue; } if (write_index != read_index) { self.entries.items[write_index] = entry; } write_index += 1; } self.entries.items.len = write_index; } fn buildProtectedEntryIdSet( self: anytype, alloc: Allocator, protected_id: ?u32, ) !EntryIdSet { var protected_entry_ids: EntryIdSet = .empty; errdefer protected_entry_ids.deinit(alloc); for (self.entries.items) |entry| { if (entry == .raw_bytes and entry.raw_bytes.lifecycle_pinned) { try protected_entry_ids.put(alloc, entry.raw_bytes.id, {}); } } for (self.command_output_blocks.items) |block| { for (block.pruned_ranges.items) |range| { try protected_entry_ids.put(alloc, range.anchor_entry_id, {}); } } if (protected_id) |id| { try protected_entry_ids.put(alloc, id, {}); if (protectedPreviousUserTurnId(self, id)) |user_id| { try protected_entry_ids.put(alloc, user_id, {}); } } if (replaceableEntryId(self)) |id| { try protected_entry_ids.put(alloc, id, {}); } return protected_entry_ids; } fn pruneOrphanedFoldedCommandBlocks( self: anytype, alloc: Allocator, entry_ids: *const EntryIdSet, ) bool { var changed = false; var i: usize = 0; while (i < self.folded_command_blocks.items.len) { const summary_id = self.folded_command_blocks.items[i].summary_entry_id; if (summary_id) |id| { if (!entry_ids.contains(id)) { command_output_runtime.removeFoldedCommandBlock(self, alloc, i); changed = true; continue; } } i += 1; } return changed; } fn firstExistingCommandOutputSourceEntryId( entry_ids: *const EntryIdSet, block: command_output_runtime.CommandOutputBlock, ) ?u32 { for (block.pruned_ranges.items) |range| { if (entry_ids.contains(range.anchor_entry_id)) return range.anchor_entry_id; } for (block.source_entry_ids.items) |entry_id| { if (entry_ids.contains(entry_id)) return entry_id; } return null; } fn retargetCommandOutputDetails(self: anytype, old_entry_id: u32, new_entry_id: u32) void { if (comptime !@hasField(@TypeOf(self.*), "tool_details")) return; for (self.tool_details.items) |*detail| { if (detail.command_output_entry_id == old_entry_id) { detail.command_output_entry_id = new_entry_id; } } } fn commandOutputBlockHasDetail( self: anytype, block: command_output_runtime.CommandOutputBlock, ) bool { if (comptime !@hasField(@TypeOf(self.*), "tool_details")) return false; for (self.tool_details.items) |detail| { if (detail.command_output_entry_id) |entry_id| { if (command_output_runtime.commandBlockOwnsEntry(block, entry_id)) { return true; } } if (detail.lifecycle_id != null and block.lifecycle_id != null and command_output_runtime.sameLifecycleId(detail.lifecycle_id, block.lifecycle_id)) { return true; } } return false; } fn removePrunedRangeAnchorEntries( self: anytype, alloc: Allocator, block: command_output_runtime.CommandOutputBlock, entry_ids: *EntryIdSet, ) void { var entry_index: usize = 0; while (entry_index < self.entries.items.len) { const entry_id = self.entries.items[entry_index].id(); if (!command_output_runtime.isPrunedRangeAnchor(block, entry_id)) { entry_index += 1; continue; } var removed = self.entries.orderedRemove(entry_index); _ = entry_ids.remove(entry_id); removed.deinit(alloc); } } fn pruneOrphanedCommandOutputBlocks( self: anytype, alloc: Allocator, entry_ids: *EntryIdSet, ) bool { var changed = false; var i: usize = 0; while (i < self.command_output_blocks.items.len) { if (self.command_output_display.open_command_block) |open| { if (open == i) { i += 1; continue; } } if (self.command_output_blocks.items[i].lines.items.len == 0 and self.command_output_blocks.items[i].pruned_ranges.items.len > 0 and !commandOutputBlockHasDetail(self, self.command_output_blocks.items[i])) { removePrunedRangeAnchorEntries( self, alloc, self.command_output_blocks.items[i], entry_ids, ); command_output_runtime.removeCommandOutputBlock(self, alloc, i); changed = true; continue; } const entry_id = self.command_output_blocks.items[i].entry_id; if (entry_id) |id| { if (!entry_ids.contains(id)) { if (firstExistingCommandOutputSourceEntryId(entry_ids, self.command_output_blocks.items[i])) |replacement| { self.command_output_blocks.items[i].entry_id = replacement; retargetCommandOutputDetails(self, id, replacement); changed = true; i += 1; continue; } command_output_runtime.removeCommandOutputBlock(self, alloc, i); changed = true; continue; } } else { if (firstExistingCommandOutputSourceEntryId(entry_ids, self.command_output_blocks.items[i])) |replacement| { self.command_output_blocks.items[i].entry_id = replacement; changed = true; i += 1; continue; } command_output_runtime.removeCommandOutputBlock(self, alloc, i); changed = true; continue; } i += 1; } return changed; } fn coalesceCommandOutputPrunedRanges(self: anytype, alloc: Allocator) bool { var changed = false; for (0..self.command_output_blocks.items.len) |block_index| { if (command_output_runtime.coalesceCommandOutputPrunedRanges( self, alloc, block_index, )) changed = true;