const std = @import("std");const build_checkpoint = @import("build_checkpoint.zig");const debug_trace = @import("../../core/shared/debug_trace.zig");const display_width = @import("../../core/shared/display_width.zig");const types = @import("../../core/shared/types.zig");const command_output_content = @import("../../core/tooling/command_output_content.zig");const assistant_wrap = @import("assistant_wrap.zig");const transcript_measure = @import("transcript_measure.zig");const user_message_card = @import("../assistant/user_message_card.zig");const presentation_mode = @import("../../core/config/presentation_mode.zig");const input_visual_layout = @import("../input/visual_layout.zig");const vt_emulator = @import("../../core/terminal/engine.zig");const assistant_presentation = @import("../../core/agent/assistant_presentation.zig");const code_highlight = @import("code_highlight.zig");const code_highlight_languages = @import("code_highlight_languages.zig"); const Allocator = std.mem.Allocator; pub const WalkResult = transcript_measure.WalkResult;pub const walkText = transcript_measure.walkText;pub const nextCursorColForLine = transcript_measure.nextCursorColForLine;pub const skipVisualRowsInLine = transcript_measure.skipVisualRowsInLine;pub const visualRowsForLine = transcript_measure.visualRowsForLine; pub const CommandOutputDisplayState = struct { open_block: ?usize = null, open_command_block: ?usize = null,}; pub const CodeHighlightTheme = code_highlight.Theme; pub const Styles = struct { system_notice_label_style: []const u8 = "", system_notice_text_style: []const u8 = "", reset_style: []const u8 = "", dim_style: []const u8 = "", red_style: []const u8 = "", cancelled_text_style: []const u8 = "", notice_information_style: []const u8 = "", notice_success_style: []const u8 = "", notice_warning_style: []const u8 = "", notice_error_style: []const u8 = "", notice_cancelled_style: []const u8 = "", code_highlight_theme: CodeHighlightTheme = .dark,}; pub const ToolFallbackDisposition = enum { completion_unreported, not_executed,}; pub const ToolDetailRecord = struct { entry_id: u32, tool_name: []u8, captured_command: bool = false, activity_kind: ?types.ToolActivityKind = null, arguments_json: ?[]u8 = null, result: ?[]u8 = null, result_handle: ?[]u8 = null, command_artifact_handle: ?[]u8 = null, command_output_replay: ?types.CommandOutputReplay = null, command_process_presentation: ?types.CommandProcessPresentation = null, outcome: ?types.ToolOutcomeKind = null, fallback_disposition: ?ToolFallbackDisposition = null, lifecycle_id: ?types.ToolLifecycleId = null, presentation_group_id: ?types.ToolPresentationGroupId = null, command_output_entry_id: ?u32 = null, pub fn isCapturedCommand(self: ToolDetailRecord) bool { // Hand-built details and restored records may predate the explicit // classifier. Preserve their historical presentation contract. return self.captured_command or std.mem.eql(u8, self.tool_name, "run_command"); } pub fn deinit(self: *ToolDetailRecord, alloc: std.mem.Allocator) void { alloc.free(self.tool_name); if (self.arguments_json) |value| alloc.free(value); if (self.result) |value| alloc.free(value); if (self.result_handle) |value| alloc.free(value); if (self.command_artifact_handle) |value| alloc.free(value); if (self.command_output_replay) |replay| { types.freeCommandOutputReplay(alloc, replay); } if (self.lifecycle_id) |id| alloc.free(@constCast(id.call_id)); self.* = undefined; }}; pub const FullDetailAppend = struct { attached: bool = false, ends_with_newline: bool = false,}; pub const FullPresentationSink = struct { context: *anyopaque, skip_entry: *const fn (context: *anyopaque, entry: TranscriptEntry, index: usize) bool, override_kind: *const fn (context: *anyopaque, entry: TranscriptEntry) ?TranscriptBlockKind, append_override: *const fn (context: *anyopaque, entry: TranscriptEntry, out: *std.Io.Writer.Allocating) anyerror!bool, before_entry: *const fn (context: *anyopaque, entry_id: u32, out: *std.Io.Writer.Allocating) anyerror!void, append_detail: *const fn (context: *anyopaque, entry_id: u32, out: *std.Io.Writer.Allocating) anyerror!FullDetailAppend,}; pub const AssistantTurnSegments = struct { /// Raw streamed text from the pacer, with inline SGR runs. No /// injected wraps. Paint-time reflow decides wrap boundaries /// for the current cols and reasserts SGR at each boundary so /// bold and colour never leak into continuation rows. text: std.ArrayList(u8) = .empty, pub fn deinit(self: *AssistantTurnSegments, alloc: Allocator) void { self.text.deinit(alloc); }}; pub const RawEntryClass = enum { welcome, turn_summary, tool_status, command_output, diff_block, question_resolution, subagent_status, unknown_raw,}; pub const TranscriptBlockKind = enum { user_turn, assistant_turn, assistant_table, assistant_code_block, assistant_thematic_rule, turn_summary, welcome, tool_status, command_output, diff_block, system_notice, error_notice, cancel_notice, subagent_status, unknown_raw,}; pub const TranscriptEntryClass = enum { user_turn, assistant_turn, assistant_table, assistant_code_block, assistant_thematic_rule, welcome, turn_summary, tool_status, command_output, diff_block, system_notice, context_notice, error_notice, cancel_notice, subagent_status, unknown_raw,}; pub const LineProvenance = union(enum) { entry: struct { entry_id: u32, entry_class: TranscriptEntryClass, }, block_separator, boundary_blank, capped_continuation, folded_command_output: struct { entry_id: ?u32, stream: command_output_content.Stream, }, unattributed, empty_transcript,}; pub const RowProvenance = struct { row: u16, source: LineProvenance,}; pub fn blockKindForRawClass(class: RawEntryClass) TranscriptBlockKind { return switch (class) { .welcome => .welcome, .turn_summary => .turn_summary, .tool_status => .tool_status, .command_output => .command_output, .diff_block => .diff_block, .question_resolution => .cancel_notice, .subagent_status => .subagent_status, .unknown_raw => .unknown_raw, };} pub fn blockKindForNoticeTone(tone: types.NoticeTone) TranscriptBlockKind { return switch (tone) { .information, .success, .warning, .neutral => .system_notice, .@"error" => .error_notice, .cancelled => .cancel_notice, };} pub fn blockGapRowsBetween(prev: TranscriptBlockKind, next: TranscriptBlockKind) u16 { return default_block_gap_policy.gapBetween(prev, next);} fn blockKindForEntry(entry: TranscriptEntry) TranscriptBlockKind { return switch (entry) { .raw_bytes => |e| blockKindForRawClass(e.class), .semantic_notice => |e| blockKindForNoticeTone(e.tone), .user_turn => .user_turn, .assistant_turn => .assistant_turn, .assistant_table => .assistant_table, .assistant_code_block => .assistant_code_block, .assistant_thematic_rule => .assistant_thematic_rule, };} pub fn isEntryVisibleInCompactPresentation(entry: TranscriptEntry) bool { return switch (entry) { .raw_bytes => true, .semantic_notice => |notice| notice.visibility == .compact_and_full, else => true, };} pub fn entryClassForEntry(entry: TranscriptEntry) TranscriptEntryClass { return switch (entry) { .user_turn => .user_turn, .assistant_turn => .assistant_turn, .assistant_table => .assistant_table, .assistant_code_block => .assistant_code_block, .assistant_thematic_rule => .assistant_thematic_rule, .semantic_notice => |notice| switch (notice.tone) { .information, .success, .warning, .neutral => if (std.ascii.eqlIgnoreCase(notice.topic, "context")) .context_notice else .system_notice, .@"error" => .error_notice, .cancelled => .cancel_notice, }, .raw_bytes => |raw| switch (raw.class) { .welcome => .welcome, .turn_summary => .turn_summary, .tool_status => .tool_status, .command_output => .command_output, .diff_block => .diff_block, .question_resolution => .cancel_notice, .subagent_status => .subagent_status, .unknown_raw => .unknown_raw, }, };} pub const BlockGapPolicy = struct { default_gap_rows: u16 = 1, pub fn gapBetween(self: BlockGapPolicy, prev: TranscriptBlockKind, next: TranscriptBlockKind) u16 { if (prev == .tool_status and next == .command_output) return 0; if (prev == .command_output and next == .command_output) return 0; if (prev == .subagent_status and next == .subagent_status) return 0; return self.default_gap_rows; }}; pub const EntryRenderOverride = struct { entry_id: u32, kind: TranscriptBlockKind, bytes: []const u8,}; pub const EntryRenderAction = union(enum) { keep, hide, override: struct { kind: TranscriptBlockKind, bytes: []const u8, },}; pub const default_block_gap_policy: BlockGapPolicy = .{}; pub const TranscriptEntry = union(enum) { /// Banners, command-output summaries, system notices, spinners: /// content with a fixed byte shape that does not reshape across /// widths. Entry owns the byte slice; freed with the entry. raw_bytes: RawBytesEntry, /// One retained semantic notice. Topic and body are owned by the entry; /// paint-time rendering supplies theme, width, marker, and spacing. semantic_notice: SemanticNoticeEntry, /// One user prompt. Stored as its logical text plus images and optional /// display-only skill spans; the bordered card is rebuilt at paint time /// at the current cols so reshape is correct at any width. user_turn: UserTurnEntry, /// One assistant response, populated incrementally as the pacer /// emits tokens. Reflowed at paint time for the current cols. assistant_turn: AssistantTurnEntry, /// One completed Markdown table, retained as cells so it can be /// re-laid out at the active terminal width. assistant_table: AssistantTableEntry, /// One completed Markdown code block, retained as literal source so it /// can be re-laid out at the active terminal width. assistant_code_block: AssistantCodeBlockEntry, /// One completed Markdown thematic rule, retained semantically so it /// can fill the active terminal width. assistant_thematic_rule: AssistantThematicRuleEntry, pub const RawBytesEntry = struct { id: u32, created_at_ms: i64 = 0, bytes: []const u8, class: RawEntryClass = .unknown_raw, lifecycle_pinned: bool = false, }; pub const SemanticNoticeEntry = struct { id: u32, created_at_ms: i64 = 0, topic: []const u8, tone: types.NoticeTone, body: []const u8, visibility: types.NoticeVisibility, // The producer will replace this entry in place; its bytes are not // final and must not be released into terminal history. pending_replacement: bool = false, }; pub const UserTurnEntry = struct { id: u32, created_at_ms: i64 = 0, turn: types.UserTurn, skill_tokens: []input_visual_layout.SkillTokenSpan = &.{}, }; pub const AssistantTurnEntry = struct { id: u32, created_at_ms: i64 = 0, segments: AssistantTurnSegments, }; pub const AssistantTableEntry = struct { id: u32, created_at_ms: i64 = 0, table: assistant_presentation.TablePayload, }; pub const AssistantCodeBlockEntry = struct { id: u32, created_at_ms: i64 = 0, block: assistant_presentation.CodeBlockPayload, }; pub const AssistantThematicRuleEntry = struct { id: u32, created_at_ms: i64 = 0, }; pub fn id(self: TranscriptEntry) u32 { return switch (self) { .raw_bytes => |e| e.id, .semantic_notice => |e| e.id, .user_turn => |e| e.id, .assistant_turn => |e| e.id, .assistant_table => |e| e.id, .assistant_code_block => |e| e.id, .assistant_thematic_rule => |e| e.id, }; } pub fn createdAtMs(self: TranscriptEntry) i64 { return switch (self) { .raw_bytes => |e| e.created_at_ms, .semantic_notice => |e| e.created_at_ms, .user_turn => |e| e.created_at_ms, .assistant_turn => |e| e.created_at_ms, .assistant_table => |e| e.created_at_ms, .assistant_code_block => |e| e.created_at_ms, .assistant_thematic_rule => |e| e.created_at_ms, }; } pub fn deinit(self: *TranscriptEntry, alloc: Allocator) void { switch (self.*) { .raw_bytes => |e| { alloc.free(e.bytes); }, .semantic_notice => |e| { alloc.free(e.topic); alloc.free(e.body); }, .user_turn => |*e| { types.freeUserTurn(alloc, e.turn); for (e.skill_tokens) |token| { alloc.free(@constCast(token.name)); alloc.free(@constCast(token.path)); } if (e.skill_tokens.len > 0) alloc.free(e.skill_tokens); }, .assistant_turn => |*e| e.segments.deinit(alloc), .assistant_table => |*e| e.table.deinit(alloc), .assistant_code_block => |*e| e.block.deinit(alloc), .assistant_thematic_rule => {}, } }}; /// Reflow assistant text to fit within `cols` while preserving inline/// SGR attributes and OSC 8 links across wrap boundaries. Paragraphs wrap at whitespace/// and move a preceding word down when that avoids a single-word tail./// Pre-existing hard newlines in `text` are honoured. The returned slice is/// owned by the caller and freed with `alloc`.pub fn wrapAssistantText(alloc: Allocator, text: []const u8, cols: u16) ![]u8 { return assistant_wrap.wrapAssistantText(alloc, text, cols);} fn renderThematicRuleForTranscript(alloc: Allocator, cols: u16) ![]u8 { if (cols == 0) return alloc.dupe(u8, ""); const gutter = assistant_wrap.gutterWidth(cols); const rule_width = cols -| gutter; var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); try out.appendNTimes(alloc, ' ', gutter); try out.appendSlice(alloc, "\x1b[2m"); var i: u16 = 0; while (i < rule_width) : (i += 1) try out.appendSlice(alloc, "\xe2\x94\x80"); try out.appendSlice(alloc, "\x1b[22m"); return out.toOwnedSlice(alloc);} fn toolStatusOmissionMarker(cols: u16) []const u8 { return switch (cols) { 0 => "", 1 => ".", 2 => "..", else => "...", };} const ToolStatusLine = struct { end: usize, next: usize, hard_break: bool = false, omitted_rune: bool = false,}; fn skipToolStatusWrapWhitespace(text: []const u8, start: usize) usize { var index = start; while (index < text.len and (text[index] == ' ' or text[index] == '\t')) : (index += 1) {} return index;} fn toolStatusTokenExceedsWidth(text: []const u8, start: usize, max_width: usize) bool { if (max_width == 0) return false; var index = start; var width: usize = 0; while (index < text.len) { const byte = text[index]; if (byte == 0x1b) { const end = display_width.ansiSequenceEnd(text, index); if (end <= index) return false; index = end; continue; } if (byte == ' ' or byte == '\t' or byte == '\n' or byte == '\r') return false; if (byte < 32) { index += 1; continue; } const unit = display_width.displayUnitAt(text, index); const cell_width = unit.cell_width; if (width + cell_width > max_width) return true; width += cell_width; index += unit.byte_len; } return false;} fn scanToolStatusLine(text: []const u8, start: usize, max_width: usize) ToolStatusLine { if (start >= text.len or max_width == 0) return .{ .end = start, .next = start }; var index = start; var width: usize = 0; var last_wrap_end: ?usize = null; var last_wrap_next: usize = start; while (index < text.len) { const byte = text[index]; if (byte == 0x1b) { const end = display_width.ansiSequenceEnd(text, index); if (end <= index) return .{ .end = index, .next = text.len, .omitted_rune = true }; index = end; continue; } if (byte == '\n' or byte == '\r') { const next = if (byte == '\r' and index + 1 < text.len and text[index + 1] == '\n') index + 2Lines 1–500 of 4448. Load more
const std = @import("std"); const build_checkpoint = @import("build_checkpoint.zig"); const debug_trace = @import("../../core/shared/debug_trace.zig"); const display_width = @import("../../core/shared/display_width.zig"); const types = @import("../../core/shared/types.zig"); const command_output_content = @import("../../core/tooling/command_output_content.zig"); const assistant_wrap = @import("assistant_wrap.zig"); const transcript_measure = @import("transcript_measure.zig"); const user_message_card = @import("../assistant/user_message_card.zig"); const presentation_mode = @import("../../core/config/presentation_mode.zig"); const input_visual_layout = @import("../input/visual_layout.zig"); const vt_emulator = @import("../../core/terminal/engine.zig"); const assistant_presentation = @import("../../core/agent/assistant_presentation.zig"); const code_highlight = @import("code_highlight.zig"); const code_highlight_languages = @import("code_highlight_languages.zig"); const Allocator = std.mem.Allocator; pub const WalkResult = transcript_measure.WalkResult; pub const walkText = transcript_measure.walkText; pub const nextCursorColForLine = transcript_measure.nextCursorColForLine; pub const skipVisualRowsInLine = transcript_measure.skipVisualRowsInLine; pub const visualRowsForLine = transcript_measure.visualRowsForLine; pub const CommandOutputDisplayState = struct { open_block: ?usize = null, open_command_block: ?usize = null, }; pub const CodeHighlightTheme = code_highlight.Theme; pub const Styles = struct { system_notice_label_style: []const u8 = "", system_notice_text_style: []const u8 = "", reset_style: []const u8 = "", dim_style: []const u8 = "", red_style: []const u8 = "", cancelled_text_style: []const u8 = "", notice_information_style: []const u8 = "", notice_success_style: []const u8 = "", notice_warning_style: []const u8 = "", notice_error_style: []const u8 = "", notice_cancelled_style: []const u8 = "", code_highlight_theme: CodeHighlightTheme = .dark, }; pub const ToolFallbackDisposition = enum { completion_unreported, not_executed, }; pub const ToolDetailRecord = struct { entry_id: u32, tool_name: []u8, captured_command: bool = false, activity_kind: ?types.ToolActivityKind = null, arguments_json: ?[]u8 = null, result: ?[]u8 = null, result_handle: ?[]u8 = null, command_artifact_handle: ?[]u8 = null, command_output_replay: ?types.CommandOutputReplay = null, command_process_presentation: ?types.CommandProcessPresentation = null, outcome: ?types.ToolOutcomeKind = null, fallback_disposition: ?ToolFallbackDisposition = null, lifecycle_id: ?types.ToolLifecycleId = null, presentation_group_id: ?types.ToolPresentationGroupId = null, command_output_entry_id: ?u32 = null, pub fn isCapturedCommand(self: ToolDetailRecord) bool { // Hand-built details and restored records may predate the explicit // classifier. Preserve their historical presentation contract. return self.captured_command or std.mem.eql(u8, self.tool_name, "run_command"); } pub fn deinit(self: *ToolDetailRecord, alloc: std.mem.Allocator) void { alloc.free(self.tool_name); if (self.arguments_json) |value| alloc.free(value); if (self.result) |value| alloc.free(value); if (self.result_handle) |value| alloc.free(value); if (self.command_artifact_handle) |value| alloc.free(value); if (self.command_output_replay) |replay| { types.freeCommandOutputReplay(alloc, replay); } if (self.lifecycle_id) |id| alloc.free(@constCast(id.call_id)); self.* = undefined; } }; pub const FullDetailAppend = struct { attached: bool = false, ends_with_newline: bool = false, }; pub const FullPresentationSink = struct { context: *anyopaque, skip_entry: *const fn (context: *anyopaque, entry: TranscriptEntry, index: usize) bool, override_kind: *const fn (context: *anyopaque, entry: TranscriptEntry) ?TranscriptBlockKind, append_override: *const fn (context: *anyopaque, entry: TranscriptEntry, out: *std.Io.Writer.Allocating) anyerror!bool, before_entry: *const fn (context: *anyopaque, entry_id: u32, out: *std.Io.Writer.Allocating) anyerror!void, append_detail: *const fn (context: *anyopaque, entry_id: u32, out: *std.Io.Writer.Allocating) anyerror!FullDetailAppend, }; pub const AssistantTurnSegments = struct { /// Raw streamed text from the pacer, with inline SGR runs. No /// injected wraps. Paint-time reflow decides wrap boundaries /// for the current cols and reasserts SGR at each boundary so /// bold and colour never leak into continuation rows. text: std.ArrayList(u8) = .empty, pub fn deinit(self: *AssistantTurnSegments, alloc: Allocator) void { self.text.deinit(alloc); } }; pub const RawEntryClass = enum { welcome, turn_summary, tool_status, command_output, diff_block, question_resolution, subagent_status, unknown_raw, }; pub const TranscriptBlockKind = enum { user_turn, assistant_turn, assistant_table, assistant_code_block, assistant_thematic_rule, turn_summary, welcome, tool_status, command_output, diff_block, system_notice, error_notice, cancel_notice, subagent_status, unknown_raw, }; pub const TranscriptEntryClass = enum { user_turn, assistant_turn, assistant_table, assistant_code_block, assistant_thematic_rule, welcome, turn_summary, tool_status, command_output, diff_block, system_notice, context_notice, error_notice, cancel_notice, subagent_status, unknown_raw, }; pub const LineProvenance = union(enum) { entry: struct { entry_id: u32, entry_class: TranscriptEntryClass, }, block_separator, boundary_blank, capped_continuation, folded_command_output: struct { entry_id: ?u32, stream: command_output_content.Stream, }, unattributed, empty_transcript, }; pub const RowProvenance = struct { row: u16, source: LineProvenance, }; pub fn blockKindForRawClass(class: RawEntryClass) TranscriptBlockKind { return switch (class) { .welcome => .welcome, .turn_summary => .turn_summary, .tool_status => .tool_status, .command_output => .command_output, .diff_block => .diff_block, .question_resolution => .cancel_notice, .subagent_status => .subagent_status, .unknown_raw => .unknown_raw, }; } pub fn blockKindForNoticeTone(tone: types.NoticeTone) TranscriptBlockKind { return switch (tone) { .information, .success, .warning, .neutral => .system_notice, .@"error" => .error_notice, .cancelled => .cancel_notice, }; } pub fn blockGapRowsBetween(prev: TranscriptBlockKind, next: TranscriptBlockKind) u16 { return default_block_gap_policy.gapBetween(prev, next); } fn blockKindForEntry(entry: TranscriptEntry) TranscriptBlockKind { return switch (entry) { .raw_bytes => |e| blockKindForRawClass(e.class), .semantic_notice => |e| blockKindForNoticeTone(e.tone), .user_turn => .user_turn, .assistant_turn => .assistant_turn, .assistant_table => .assistant_table, .assistant_code_block => .assistant_code_block, .assistant_thematic_rule => .assistant_thematic_rule, }; } pub fn isEntryVisibleInCompactPresentation(entry: TranscriptEntry) bool { return switch (entry) { .raw_bytes => true, .semantic_notice => |notice| notice.visibility == .compact_and_full, else => true, }; } pub fn entryClassForEntry(entry: TranscriptEntry) TranscriptEntryClass { return switch (entry) { .user_turn => .user_turn, .assistant_turn => .assistant_turn, .assistant_table => .assistant_table, .assistant_code_block => .assistant_code_block, .assistant_thematic_rule => .assistant_thematic_rule, .semantic_notice => |notice| switch (notice.tone) { .information, .success, .warning, .neutral => if (std.ascii.eqlIgnoreCase(notice.topic, "context")) .context_notice else .system_notice, .@"error" => .error_notice, .cancelled => .cancel_notice, }, .raw_bytes => |raw| switch (raw.class) { .welcome => .welcome, .turn_summary => .turn_summary, .tool_status => .tool_status, .command_output => .command_output, .diff_block => .diff_block, .question_resolution => .cancel_notice, .subagent_status => .subagent_status, .unknown_raw => .unknown_raw, }, }; } pub const BlockGapPolicy = struct { default_gap_rows: u16 = 1, pub fn gapBetween(self: BlockGapPolicy, prev: TranscriptBlockKind, next: TranscriptBlockKind) u16 { if (prev == .tool_status and next == .command_output) return 0; if (prev == .command_output and next == .command_output) return 0; if (prev == .subagent_status and next == .subagent_status) return 0; return self.default_gap_rows; } }; pub const EntryRenderOverride = struct { entry_id: u32, kind: TranscriptBlockKind, bytes: []const u8, }; pub const EntryRenderAction = union(enum) { keep, hide, override: struct { kind: TranscriptBlockKind, bytes: []const u8, }, }; pub const default_block_gap_policy: BlockGapPolicy = .{}; pub const TranscriptEntry = union(enum) { /// Banners, command-output summaries, system notices, spinners: /// content with a fixed byte shape that does not reshape across /// widths. Entry owns the byte slice; freed with the entry. raw_bytes: RawBytesEntry, /// One retained semantic notice. Topic and body are owned by the entry; /// paint-time rendering supplies theme, width, marker, and spacing. semantic_notice: SemanticNoticeEntry, /// One user prompt. Stored as its logical text plus images and optional /// display-only skill spans; the bordered card is rebuilt at paint time /// at the current cols so reshape is correct at any width. user_turn: UserTurnEntry, /// One assistant response, populated incrementally as the pacer /// emits tokens. Reflowed at paint time for the current cols. assistant_turn: AssistantTurnEntry, /// One completed Markdown table, retained as cells so it can be /// re-laid out at the active terminal width. assistant_table: AssistantTableEntry, /// One completed Markdown code block, retained as literal source so it /// can be re-laid out at the active terminal width. assistant_code_block: AssistantCodeBlockEntry, /// One completed Markdown thematic rule, retained semantically so it /// can fill the active terminal width. assistant_thematic_rule: AssistantThematicRuleEntry, pub const RawBytesEntry = struct { id: u32, created_at_ms: i64 = 0, bytes: []const u8, class: RawEntryClass = .unknown_raw, lifecycle_pinned: bool = false, }; pub const SemanticNoticeEntry = struct { id: u32, created_at_ms: i64 = 0, topic: []const u8, tone: types.NoticeTone, body: []const u8, visibility: types.NoticeVisibility, // The producer will replace this entry in place; its bytes are not // final and must not be released into terminal history. pending_replacement: bool = false, }; pub const UserTurnEntry = struct { id: u32, created_at_ms: i64 = 0, turn: types.UserTurn, skill_tokens: []input_visual_layout.SkillTokenSpan = &.{}, }; pub const AssistantTurnEntry = struct { id: u32, created_at_ms: i64 = 0, segments: AssistantTurnSegments, }; pub const AssistantTableEntry = struct { id: u32, created_at_ms: i64 = 0, table: assistant_presentation.TablePayload, }; pub const AssistantCodeBlockEntry = struct { id: u32, created_at_ms: i64 = 0, block: assistant_presentation.CodeBlockPayload, }; pub const AssistantThematicRuleEntry = struct { id: u32, created_at_ms: i64 = 0, }; pub fn id(self: TranscriptEntry) u32 { return switch (self) { .raw_bytes => |e| e.id, .semantic_notice => |e| e.id, .user_turn => |e| e.id, .assistant_turn => |e| e.id, .assistant_table => |e| e.id, .assistant_code_block => |e| e.id, .assistant_thematic_rule => |e| e.id, }; } pub fn createdAtMs(self: TranscriptEntry) i64 { return switch (self) { .raw_bytes => |e| e.created_at_ms, .semantic_notice => |e| e.created_at_ms, .user_turn => |e| e.created_at_ms, .assistant_turn => |e| e.created_at_ms, .assistant_table => |e| e.created_at_ms, .assistant_code_block => |e| e.created_at_ms, .assistant_thematic_rule => |e| e.created_at_ms, }; } pub fn deinit(self: *TranscriptEntry, alloc: Allocator) void { switch (self.*) { .raw_bytes => |e| { alloc.free(e.bytes); }, .semantic_notice => |e| { alloc.free(e.topic); alloc.free(e.body); }, .user_turn => |*e| { types.freeUserTurn(alloc, e.turn); for (e.skill_tokens) |token| { alloc.free(@constCast(token.name)); alloc.free(@constCast(token.path)); } if (e.skill_tokens.len > 0) alloc.free(e.skill_tokens); }, .assistant_turn => |*e| e.segments.deinit(alloc), .assistant_table => |*e| e.table.deinit(alloc), .assistant_code_block => |*e| e.block.deinit(alloc), .assistant_thematic_rule => {}, } } }; /// Reflow assistant text to fit within `cols` while preserving inline /// SGR attributes and OSC 8 links across wrap boundaries. Paragraphs wrap at whitespace /// and move a preceding word down when that avoids a single-word tail. /// Pre-existing hard newlines in `text` are honoured. The returned slice is /// owned by the caller and freed with `alloc`. pub fn wrapAssistantText(alloc: Allocator, text: []const u8, cols: u16) ![]u8 { return assistant_wrap.wrapAssistantText(alloc, text, cols); } fn renderThematicRuleForTranscript(alloc: Allocator, cols: u16) ![]u8 { if (cols == 0) return alloc.dupe(u8, ""); const gutter = assistant_wrap.gutterWidth(cols); const rule_width = cols -| gutter; var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); try out.appendNTimes(alloc, ' ', gutter); try out.appendSlice(alloc, "\x1b[2m"); var i: u16 = 0; while (i < rule_width) : (i += 1) try out.appendSlice(alloc, "\xe2\x94\x80"); try out.appendSlice(alloc, "\x1b[22m"); return out.toOwnedSlice(alloc); } fn toolStatusOmissionMarker(cols: u16) []const u8 { return switch (cols) { 0 => "", 1 => ".", 2 => "..", else => "...", }; } const ToolStatusLine = struct { end: usize, next: usize, hard_break: bool = false, omitted_rune: bool = false, }; fn skipToolStatusWrapWhitespace(text: []const u8, start: usize) usize { var index = start; while (index < text.len and (text[index] == ' ' or text[index] == '\t')) : (index += 1) {} return index; } fn toolStatusTokenExceedsWidth(text: []const u8, start: usize, max_width: usize) bool { if (max_width == 0) return false; var index = start; var width: usize = 0; while (index < text.len) { const byte = text[index]; if (byte == 0x1b) { const end = display_width.ansiSequenceEnd(text, index); if (end <= index) return false; index = end; continue; } if (byte == ' ' or byte == '\t' or byte == '\n' or byte == '\r') return false; if (byte < 32) { index += 1; continue; } const unit = display_width.displayUnitAt(text, index); const cell_width = unit.cell_width; if (width + cell_width > max_width) return true; width += cell_width; index += unit.byte_len; } return false; } fn scanToolStatusLine(text: []const u8, start: usize, max_width: usize) ToolStatusLine { if (start >= text.len or max_width == 0) return .{ .end = start, .next = start }; var index = start; var width: usize = 0; var last_wrap_end: ?usize = null; var last_wrap_next: usize = start; while (index < text.len) { const byte = text[index]; if (byte == 0x1b) { const end = display_width.ansiSequenceEnd(text, index); if (end <= index) return .{ .end = index, .next = text.len, .omitted_rune = true }; index = end; continue; } if (byte == '\n' or byte == '\r') { const next = if (byte == '\r' and index + 1 < text.len and text[index + 1] == '\n') index + 2