2335 lines · 79.7 KB · Zig
RawDownload
// Host contract:// - fields: command_output_blocks, folded_command_blocks,//   command_output_display, command_output_render,//   entries, transcript, transcript_band_dirty, replaceable_last_line,//   replaceable_start, footer_viewport, viewport_top_row, cursor_row,//   cursor_col, layout// - methods: assertCanMutateTranscript, appendRawBytesEntryClassified,//   attachCommandOutputEntryForLifecycle,//   writeRecordedCommandOutputChunkAtomic,//   flushRecordedCommandOutputSummaryAtomic,//   rebuildTranscriptCacheFromEntries,//   rebuildTranscriptCacheAfterStructuredRewrite,//   recomputeCursorFromTranscript, enforceStructuredRetention,//   enforceStructuredRetentionAndReport const std = @import("std");const debug_trace = @import("../../core/shared/debug_trace.zig");const display_width = @import("../../core/shared/display_width.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 render_engine = @import("../render_engine.zig");const assistant_wrap = @import("../render_engine/assistant_wrap.zig"); const Allocator = std.mem.Allocator;const Metrics = types.Metrics;const transcript_blocks = render_engine.transcript_blocks; const Styles = transcript_blocks.Styles; fn requestTranscriptPaint(shell: anytype, entry_id: ?u32) void {    const Runtime = @TypeOf(shell.*);    if (entry_id) |dirty_entry_id| {        if (comptime @hasDecl(Runtime, "markTranscriptCommandOutputDirtyFrom")) {            shell.markTranscriptCommandOutputDirtyFrom(dirty_entry_id);            return;        }    }    if (comptime @hasDecl(Runtime, "markTranscriptCommandOutputDirty")) {        shell.markTranscriptCommandOutputDirty();    } else if (comptime @hasDecl(Runtime, "markTranscriptContentDirty")) {        shell.markTranscriptContentDirty();    } else if (comptime @hasDecl(Runtime, "markTranscriptDirty")) {        shell.markTranscriptDirty();    } else {        shell.transcript_band_dirty = true;    }} fn commandBlockDirtyEntryId(block: CommandOutputBlock) ?u32 {    if (block.entry_id) |entry_id| return entry_id;    if (block.live_entry_ids.items.len > 0) return block.live_entry_ids.items[0];    if (block.source_entry_ids.items.len > 0) return block.source_entry_ids.items[0];    for (block.lines.items) |line| {        if (line.entry_id) |entry_id| return entry_id;    }    return null;} pub fn openCommandOutputDirtyEntryId(shell: anytype) ?u32 {    const index = shell.command_output_display.open_command_block orelse return null;    if (index >= shell.command_output_blocks.items.len) return null;    return commandBlockDirtyEntryId(shell.command_output_blocks.items[index]);} pub const CommandOutputLine = struct {    stream: command_output_content.Stream,    text: []u8,    record_ordinal: usize = 0,    entry_id: ?u32 = null,    terminated: bool = false,    visible: bool = true,}; pub const CommandOutputPrunedRange = struct {    anchor_entry_id: u32,    start_record: usize,    end_record: usize,}; const command_output_line_descriptor_bytes = @sizeOf(CommandOutputLine);const command_output_entry_descriptor_bytes = @sizeOf(transcript_blocks.TranscriptEntry);const command_output_source_id_descriptor_bytes = @sizeOf(u32);const command_output_pruned_range_descriptor_bytes = @sizeOf(CommandOutputPrunedRange); pub const CommandOutputBlock = struct {    entry_id: ?u32 = null,    lifecycle_id: ?types.ToolLifecycleId = null,    total_lines: usize = 0,    retained_text_bytes: usize = 0,    lines: std.ArrayList(CommandOutputLine) = .empty,    decoders: [2]command_output_content.Decoder = .{ .{}, .{} },    open_line_indices: [2]?usize = .{ null, null },    overflow_open_records: [2]bool = .{ false, false },    overflow_record_visible: [2]bool = .{ false, false },    retention_overflow: bool = false,    /// First retained record whose text became incomplete at the structured    /// cap. Records before this index are stable for active Ctrl-O projection.    overflow_line_index: ?usize = null,    pruned_ranges: std.ArrayList(CommandOutputPrunedRange) = .empty,    live_entry_ids: std.ArrayList(u32) = .empty,    source_entry_ids: std.ArrayList(u32) = .empty,     pub fn deinit(self: *CommandOutputBlock, alloc: Allocator) void {        if (self.lifecycle_id) |id| alloc.free(@constCast(id.call_id));        for (self.lines.items) |line| alloc.free(line.text);        self.lines.deinit(alloc);        self.pruned_ranges.deinit(alloc);        self.live_entry_ids.deinit(alloc);        self.source_entry_ids.deinit(alloc);    }     pub fn canReconstructEntries(self: CommandOutputBlock) bool {        return self.pruned_ranges.items.len == 0 and            self.lines.items.len >= self.live_entry_ids.items.len and            self.lines.items.len >= self.source_entry_ids.items.len;    }}; pub const FoldedCommandBlock = struct {    summary_transcript_index: usize = 0,    summary_entry_id: ?u32 = null,    lines: std.ArrayList(CommandOutputLine) = .empty,     pub fn deinit(self: *FoldedCommandBlock, alloc: Allocator) void {        for (self.lines.items) |line| alloc.free(line.text);        self.lines.deinit(alloc);    }}; pub const CommandOutputRenderPolicy = struct {    styles: Styles = .{        .system_notice_label_style = "",        .system_notice_text_style = "",        .reset_style = "",        .dim_style = "",        .red_style = "",    },}; test "command output render policy has no presentation mode" {    try std.testing.expect(!@hasField(CommandOutputRenderPolicy, "output_level"));} pub const compact_output_row_limit: usize = 5; pub const CommandOutputEntryProjection = struct {    entry_id: u32,    byte_start: usize,    byte_end: usize,}; pub const CommandOutputProjection = struct {    bytes: std.ArrayList(u8) = .empty,    entries: std.ArrayList(CommandOutputEntryProjection) = .empty,     pub fn deinit(self: *CommandOutputProjection, alloc: Allocator) void {        self.bytes.deinit(alloc);        self.entries.deinit(alloc);        self.* = undefined;    }     pub fn bytesForEntry(self: *const CommandOutputProjection, entry_id: u32) ?[]const u8 {        for (self.entries.items) |entry| {            if (entry.entry_id != entry_id) continue;            return self.bytes.items[entry.byte_start..entry.byte_end];        }        return null;    }}; pub fn foldedBlockRetainedBytes(block: FoldedCommandBlock) usize {    var total: usize = 0;    for (block.lines.items) |line| total += line.text.len;    return total;} pub fn commandOutputBlockRetainedBytes(block: CommandOutputBlock) usize {    var total = block.retained_text_bytes;    for (block.lines.items) |line| {        total +|= command_output_line_descriptor_bytes;        if (line.entry_id != null) total +|= command_output_entry_descriptor_bytes;    }    total +|= block.pruned_ranges.items.len *|        (command_output_pruned_range_descriptor_bytes +| command_output_entry_descriptor_bytes);    total +|= (block.live_entry_ids.items.len +| block.source_entry_ids.items.len) *|        command_output_source_id_descriptor_bytes;    return total;} pub fn commandOutputLineRetainedBytes(text_bytes: usize) usize {    return text_bytes +| command_output_line_descriptor_bytes +|        command_output_entry_descriptor_bytes +|        command_output_source_id_descriptor_bytes;} pub fn commandOutputAdmissionBytes(raw: []const u8) usize {    var decoder: command_output_content.Decoder = .{};    var sink: CommandOutputAdmissionSink = .{};    if (raw.len > 0) sink.beginRecord() catch return std.math.maxInt(usize);    decoder.append(raw, &sink) catch return std.math.maxInt(usize);    return sink.text_bytes +|        sink.record_count *| commandOutputLineRetainedBytes(0);} const CommandOutputAdmissionSink = struct {    text_bytes: usize = 0,    current_record_bytes: usize = 0,    record_count: usize = 0,    open: bool = false,     pub fn beginRecord(self: *CommandOutputAdmissionSink) error{}!void {        if (self.open) return;        self.open = true;        self.current_record_bytes = 0;        self.record_count +|= 1;    }     pub fn appendText(self: *CommandOutputAdmissionSink, bytes: []const u8) error{}!void {        try self.beginRecord();        self.text_bytes +|= bytes.len;        self.current_record_bytes +|= bytes.len;    }     pub fn finishLine(self: *CommandOutputAdmissionSink) error{}!void {        try self.beginRecord();        self.open = false;        self.current_record_bytes = 0;    }     pub fn replaceLine(self: *CommandOutputAdmissionSink) error{}!void {        try self.beginRecord();        self.text_bytes -|= self.current_record_bytes;        self.current_record_bytes = 0;    }}; pub fn commandArtifactHandleFromResult(result: []const u8) ?[]const u8 {    const prefix = "output_file=";    var truncated = false;    var has_stdout_bytes = false;    var has_stderr_bytes = false;    var lines = std.mem.splitScalar(u8, result, '\n');    while (lines.next()) |raw_line| {        const line = std.mem.trimEnd(u8, raw_line, "\r");        if (std.mem.eql(u8, line, "<stdout>") or std.mem.eql(u8, line, "<stderr>")) return null;        if (std.mem.eql(u8, line, "truncated=true")) {            truncated = true;            continue;        }        if (std.mem.startsWith(u8, line, "stdout_bytes=")) {            has_stdout_bytes = true;            continue;        }        if (std.mem.startsWith(u8, line, "stderr_bytes=")) {            has_stderr_bytes = true;            continue;        }        if (!std.mem.startsWith(u8, line, prefix)) continue;        if (!truncated or !has_stdout_bytes or !has_stderr_bytes) return null;         const path = line[prefix.len..];        const handle = std.fs.path.basename(path);        if (std.mem.startsWith(u8, handle, "fx-command-") and            std.mem.endsWith(u8, handle, ".log") and            !std.mem.endsWith(u8, handle, ".stdout.log") and            !std.mem.endsWith(u8, handle, ".stderr.log"))        {            return handle;        }        return null;    }    return null;} pub fn removeFoldedCommandBlock(shell: anytype, alloc: Allocator, index: usize) void {    var block = shell.folded_command_blocks.orderedRemove(index);    block.deinit(alloc);     if (shell.command_output_display.open_block) |open| {        if (open == index) {            shell.command_output_display.open_block = null;        } else if (open > index) {            shell.command_output_display.open_block = open - 1;        }    }} pub fn removeCommandOutputBlock(shell: anytype, alloc: Allocator, index: usize) void {    var block = shell.command_output_blocks.orderedRemove(index);    block.deinit(alloc);     if (shell.command_output_display.open_command_block) |open| {        if (open == index) {            shell.command_output_display.open_command_block = null;        } else if (open > index) {            shell.command_output_display.open_command_block = open - 1;        }    }} pub fn resetCommandOutputDisplay(shell: anytype, alloc: Allocator, reason: []const u8) void {    if (shell.command_output_display.open_command_block) |index| {        if (index < shell.command_output_blocks.items.len and shell.command_output_blocks.items[index].entry_id == null) {            debug_trace.logf("command_output", "dropping buffered command output lines={d} reason={s}", .{ shell.command_output_blocks.items[index].total_lines, reason });            removeCommandOutputBlock(shell, alloc, index);        }    }    shell.command_output_display = .{};} pub fn setCommandOutputRenderPolicy(shell: anytype, styles: Styles) void {    shell.command_output_render = .{        .styles = styles,    };} const PreparedCanonicalRecord = struct {    text: []u8,    placeholder: []u8,}; const PreparedCanonicalBatch = struct {    block_index: usize,    stream: command_output_content.Stream,    total_record_count: usize,    retained_bytes_after: usize,    records: std.ArrayList(PreparedCanonicalRecord) = .empty,     fn deinit(self: *PreparedCanonicalBatch, alloc: Allocator) void {        for (self.records.items) |record| {            alloc.free(record.text);            alloc.free(record.placeholder);        }        self.records.deinit(alloc);        self.* = undefined;    }}; /// One owner-level decision for both direct and recorded command-output writes./// Prepared canonical buffers remain owned by this value until apply transfers/// them into the transcript block.pub const PreparedCommandOutputMutation = union(enum) {    decode,    overflow_fragment: usize,    count_only: struct {        block_index: usize,        record_count: usize,    },    canonical_batch: PreparedCanonicalBatch,     pub fn deinit(self: *PreparedCommandOutputMutation, alloc: Allocator) void {        switch (self.*) {            .canonical_batch => |*batch| batch.deinit(alloc),            .decode, .overflow_fragment, .count_only => {},        }        self.* = undefined;    }     pub fn requiresRecordedShadow(self: PreparedCommandOutputMutation) bool {        return switch (self) {            .decode => true,            .overflow_fragment, .count_only, .canonical_batch => false,        };    }}; pub fn prepareCommandOutputMutation(    shell: anytype,    alloc: Allocator,    lifecycle_id: ?types.ToolLifecycleId,    stream: command_output_content.Stream,    text: []const u8,    record: bool,) !PreparedCommandOutputMutation {    const block_index = shell.command_output_display.open_command_block orelse        return .decode;    const block = &shell.command_output_blocks.items[block_index];    if (!sameLifecycleId(block.lifecycle_id, lifecycle_id)) {        return error.CommandOutputLifecycleMismatch;    }     const stream_index = commandStreamIndex(stream);    if (record and        block.overflow_open_records[stream_index] and        block.decoders[stream_index].isIdle() and        std.mem.indexOfAny(u8, text, "\r\n\x1b") == null)    {        return .{ .overflow_fragment = block_index };    }     if (record and        block.retention_overflow and        block.open_line_indices[stream_index] == null and        !block.overflow_open_records[stream_index] and        text.len > 0 and text[text.len - 1] == '\n' and        isCountOnlyCommandOutput(text))    {        const record_count = std.mem.count(u8, text, "\n");        if (record_count > 0) {            return .{ .count_only = .{                .block_index = block_index,                .record_count = record_count,            } };        }    }     if (!record or        block.total_lines < compact_output_row_limit + 1 or        block.open_line_indices[stream_index] != null or        block.overflow_open_records[stream_index])    {        return .decode;    }     var canonical: command_output_content.CanonicalOutput = .{};    defer canonical.deinit(alloc);    try canonical.append(        alloc,        switch (stream) {            .stdout => .stdout,            .stderr => .stderr,        },        text,    );    if (canonical.records.items.len == 0) return .decode;    for (canonical.records.items) |canonical_record| {        if (!canonical_record.terminated) return .decode;    }     const cap = commandOutputRetentionCap(shell);    var retained = retainedCommandOutputBytes(shell, block_index);    var retained_count: usize = 0;    if (!block.retention_overflow) {        for (canonical.records.items) |canonical_record| {            const footprint = commandOutputLineRetainedBytes(                canonical_record.text.items.len,            );            if (retained > cap or footprint > cap - retained) break;            retained += footprint;            retained_count += 1;        }    }     var batch = PreparedCanonicalBatch{        .block_index = block_index,        .stream = stream,        .total_record_count = canonical.records.items.len,        .retained_bytes_after = retained,    };    errdefer batch.deinit(alloc);    try batch.records.ensureTotalCapacityPrecise(alloc, retained_count);    for (canonical.records.items[0..retained_count]) |canonical_record| {        const owned_text = try alloc.dupe(u8, canonical_record.text.items);        errdefer alloc.free(owned_text);        const placeholder = try alloc.alloc(u8, 0);        batch.records.appendAssumeCapacity(.{            .text = owned_text,            .placeholder = placeholder,        });    }    return .{ .canonical_batch = batch };} fn isCountOnlyCommandOutput(text: []const u8) bool {    for (text) |byte| {        if (byte == '\n' or byte == '\t' or (byte >= 0x20 and byte < 0x7f)) continue;        return false;    }    return true;} pub fn applyPreparedCommandOutputMutation(    shell: anytype,    alloc: Allocator,    metrics: *Metrics,    styles: Styles,    lifecycle_id: ?types.ToolLifecycleId,    stream: command_output_content.Stream,    text: []const u8,    record: bool,    mutation: *PreparedCommandOutputMutation,) !?u32 {    return applyPreparedCommandOutputMutationAt(        shell,        alloc,        metrics,        styles,        lifecycle_id,        stream,        text,        record,        mutation,        null,    );} fn applyPreparedCommandOutputMutationAt(    shell: anytype,    alloc: Allocator,

Lines 1–500 of 2335.