4448 lines · 166.7 KB · Zig
RawDownload
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

Lines 1–500 of 4448.