4391 lines · 172.5 KB · Zig
RawDownload
const std = @import("std");const activity_status = @import("../output/activity_status.zig");const app_session_runtime = @import("app_session_runtime.zig");const debug_trace = @import("../shared/debug_trace.zig");const diff_mod = @import("../output/diff.zig");const file_mutation_contract = @import("../tooling/file_mutation_contract.zig");const command_output_content = @import("../tooling/command_output_content.zig");const io_mod = @import("../shared/io.zig");const permission_request = @import("../permissions/permission_request.zig");const task_helpers = @import("../tasks/task_helpers.zig");const text_utils = @import("../shared/text_utils.zig");const types = @import("../shared/types.zig");const worker_runtime = @import("../agent/worker_runtime.zig");const assistant_presentation = @import("../agent/assistant_presentation.zig");const assistant_pacer = @import("../../ui/assistant/pacer.zig");const ui_render = @import("../../ui/render.zig");const activity_runtime = @import("../output/activity_runtime.zig");const worker_status = @import("../output/worker_status.zig");const core_input_runtime = @import("../input/runtime.zig");const render_input = @import("../../ui/footer/render_input.zig");const interaction_state = @import("../../ui/footer/interaction_state.zig");const render_request = @import("../../ui/render_request.zig");const transcript_runtime = @import("../../ui/transcript/runtime.zig"); const QueuePreview = worker_runtime.QueuePreview;const WorkerEvent = worker_runtime.WorkerEvent;const InputRuntime = core_input_runtime.Runtime; const CancelledEventAdmission = enum {    admit,    drop,    defer_until_closure,}; const reset_style = ui_render.reset_style; test {    _ = activity_runtime;} fn allocDimmedTranscriptLine(alloc: std.mem.Allocator, text: []const u8) ![]u8 {    if (std.mem.endsWith(u8, text, "\n")) {        return std.fmt.allocPrint(alloc, "{s}{s}{s}\n", .{ ui_render.dim_style, text[0 .. text.len - 1], reset_style });    }    return std.fmt.allocPrint(alloc, "{s}{s}{s}", .{ ui_render.dim_style, text, reset_style });} fn discardTable(_: *anyopaque, table: assistant_presentation.TablePayload) !void {    var owned = table;    owned.deinit(std.heap.c_allocator);} fn discardCodeBlock(_: *anyopaque, block: assistant_presentation.CodeBlockPayload) !void {    var owned = block;    owned.deinit(std.heap.c_allocator);} fn discardThematicRule(_: *anyopaque) !void {} pub const WorkerEventHandlers = struct {    ctx: *anyopaque,    tool_lifecycle: activity_runtime.LifecyclePresenter,    write_user_prompt: *const fn (*anyopaque, types.UserTurn) anyerror!void,    write_user_prompt_with_skill_bindings: ?*const fn (*anyopaque, types.UserTurn, []const worker_runtime.SkillBinding, []const worker_runtime.SkillDisplaySpan) anyerror!void = null,    append_text: *const fn (*anyopaque, []const u8) anyerror!void,    append_table: *const fn (*anyopaque, assistant_presentation.TablePayload) anyerror!void = discardTable,    append_code_block: *const fn (*anyopaque, assistant_presentation.CodeBlockPayload) anyerror!void = discardCodeBlock,    append_thematic_rule: *const fn (*anyopaque) anyerror!void = discardThematicRule,    drain_assistant_text: *const fn (*anyopaque) anyerror!AssistantTextDrainResult,    open_model_picker: *const fn (*anyopaque) anyerror!void,    semantic_notice: *const fn (*anyopaque, types.SemanticNotice) anyerror!void,    command_output: *const fn (*anyopaque, ?types.ToolLifecycleId, command_output_content.Stream, []const u8) anyerror!void,    command_output_complete: *const fn (*anyopaque, ?types.ToolLifecycleId) anyerror!void,    diff_block: *const fn (*anyopaque, diff_mod.DiffEntryPayload) anyerror!void,    append_history_turn: *const fn (*anyopaque, types.FinishedPrompt) anyerror!void,    session_grant: *const fn (*anyopaque, types.PermissionGrant) anyerror!void,    error_text: *const fn (*anyopaque, types.SemanticNotice) anyerror!void,}; pub const AssistantTextDrainResult = enum {    drained,    blocked,}; const DetachedWorkerEventBatch = struct {    alloc: std.mem.Allocator,    events: std.ArrayList(WorkerEvent),    next_unvisited: usize = 0,     fn init(        alloc: std.mem.Allocator,        events: std.ArrayList(WorkerEvent),    ) DetachedWorkerEventBatch {        return .{            .alloc = alloc,            .events = events,        };    }     fn claim(self: *DetachedWorkerEventBatch) ?WorkerEvent {        if (self.next_unvisited >= self.events.items.len) return null;        const event = self.events.items[self.next_unvisited];        self.next_unvisited += 1;        return event;    }     fn claimedAndRemaining(self: *const DetachedWorkerEventBatch) []const WorkerEvent {        std.debug.assert(self.next_unvisited > 0);        return self.events.items[self.next_unvisited - 1 ..];    }     fn markClaimedAndRemainingTransferred(self: *DetachedWorkerEventBatch) void {        self.next_unvisited = self.events.items.len;    }     fn deinit(self: *DetachedWorkerEventBatch) void {        while (self.next_unvisited < self.events.items.len) : (self.next_unvisited += 1) {            worker_runtime.freeWorkerEvent(self.alloc, self.events.items[self.next_unvisited]);        }        self.events.deinit(self.alloc);        self.* = undefined;    }}; fn batchContainsInterruptedClosure(    events: []const WorkerEvent,    id: types.ToolLifecycleId,) bool {    for (events) |event| {        const lifecycle = switch (event) {            .tool_lifecycle => |value| value,            else => continue,        };        switch (lifecycle) {            .terminal => |terminal| {                if (terminal.outcome.kind == .cancelled and                    terminal.id.turn_id == id.turn_id and                    std.mem.eql(u8, terminal.id.call_id, id.call_id)) return true;            },            .turn_finished => |finished| {                if (finished.outcome == .interrupted and finished.turn_id == id.turn_id) return true;            },            .provisional, .authoritative_started, .progress => {},        }    }    return false;} fn batchSegmentEndsInterrupted(events: []const WorkerEvent) bool {    for (events, 0..) |event, index| {        if (index > 0) switch (event) {            .begin_prompt, .begin_prompt_with_skill_bindings => return false,            else => {},        };        const lifecycle = switch (event) {            .tool_lifecycle => |value| value,            else => continue,        };        switch (lifecycle) {            .turn_finished => |finished| if (finished.outcome == .interrupted) return true,            .provisional, .authoritative_started, .progress, .terminal => {},        }    }    return false;} pub fn Runtime(comptime App: type) type {    return struct {        fn cancelledToolStartAdmission(            presenter: activity_runtime.LifecyclePresenter,            id: types.ToolLifecycleId,            batch_events: []const WorkerEvent,        ) CancelledEventAdmission {            if (batchContainsInterruptedClosure(batch_events, id)) return .admit;            if (id.turn_id <= presenter.snapshot().finalized_turn_watermark) return .drop;            return .defer_until_closure;        }         fn cancelledEventAdmission(            presenter: activity_runtime.LifecyclePresenter,            event: WorkerEvent,            batch_events: []const WorkerEvent,        ) CancelledEventAdmission {            return switch (event) {                .assistant_presentation,                .open_model_picker,                .semantic_notice,                .command_output,                .turn_token_update,                .tool_payload_started,                .diff_block,                => .drop,                .route_recovery_status => |status| if (status.action == .paused)                    .admit                else                    .drop,                .command_output_complete => |lifecycle_id| if (cancelledCommandCompletionMatches(presenter, lifecycle_id))                    .admit                else                    .drop,                .tool_lifecycle => |lifecycle| switch (lifecycle) {                    .provisional => |started| cancelledToolStartAdmission(presenter, started.id, batch_events),                    .authoritative_started => |started| cancelledToolStartAdmission(presenter, started.id, batch_events),                    .progress => .drop,                    .terminal => |terminal| if (presenter.record(terminal.id) == null)                        .drop                    else                        .admit,                    .turn_finished => .admit,                },                .begin_prompt,                .begin_prompt_with_skill_bindings,                .append_user_feedback,                .notification,                .question_requested,                .clear_route_recovery_status,                .api_status_text,                .finish_prompt,                .session_grant,                .error_text,                => .admit,            };        }         fn cancelledCommandCompletionMatches(            presenter: activity_runtime.LifecyclePresenter,            lifecycle_id: ?types.ToolLifecycleId,        ) bool {            const id = lifecycle_id orelse return false;            const record = presenter.record(id) orelse return false;            return record.phase == .terminal and record.activity_kind == .command;        }         pub fn authorizeInteractiveAdmission(app: *App) !bool {            if (comptime @hasDecl(@TypeOf(app.worker), "interactiveAdmissionSnapshot")) {                switch (app.worker.interactiveAdmissionSnapshot()) {                    .open => return true,                    .stopped => return false,                    .finalization_failed => {                        if (comptime @hasField(App, "should_exit")) {                            app.should_exit = true;                        }                        return error.TurnFinalizationDeliveryFailed;                    },                }            }            return true;        }         pub fn queuePreview(app: *App) QueuePreview {            return app.worker.queuePreview();        }         pub fn syncQueuedPromptModel(app: *App, model: []const u8) !void {            try app.worker.syncQueuedPromptModel(std.heap.c_allocator, model);        }         pub fn syncQueuedPromptPermissionSnapshot(app: *App, snapshot: worker_runtime.PermissionSnapshot) void {            app.worker.syncQueuedPromptPermissionSnapshot(snapshot);        }         pub fn syncQueuedPromptPermissionState(            app: *App,            snapshot: worker_runtime.PermissionSnapshot,        ) !void {            try app.worker.syncQueuedPromptPermissionState(                std.heap.c_allocator,                app.permission_engine.grants.items,                snapshot,            );        }         pub fn propagateHistoryTurn(app: *App, turn: types.HistoryTurn, max_history_turns: usize) !void {            if (comptime @hasDecl(App, "propagateHistoryTurn")) {                try app.propagateHistoryTurn(turn);                return;            }            try app.worker.propagateHistoryTurn(std.heap.c_allocator, turn, max_history_turns);        }         pub fn propagateGrant(app: *App, tool_name: []const u8, target_path: []const u8) !void {            if (comptime @hasDecl(App, "propagateGrant")) {                try app.propagateGrant(tool_name, target_path);                return;            }            try app.worker.propagateGrant(std.heap.c_allocator, tool_name, target_path);        }         pub fn pushEvent(app: *App, event: WorkerEvent) !void {            if (comptime @hasDecl(App, "pushWorkerEvent")) {                const owned = try worker_runtime.dupeWorkerEvent(std.heap.c_allocator, event);                errdefer worker_runtime.freeWorkerEvent(std.heap.c_allocator, owned);                try app.pushWorkerEvent(owned);                return;            }            try app.worker.pushEvent(std.heap.c_allocator, event);        }         pub fn pushOwnedEvent(app: *App, event: WorkerEvent) !void {            if (comptime @hasDecl(App, "pushWorkerEvent")) {                try app.pushWorkerEvent(event);                return;            }            try app.worker.pushOwnedEvent(std.heap.c_allocator, event);        }         pub fn pushText(app: *App, text: []const u8) !void {            if (comptime @hasDecl(App, "pushText")) {                try app.pushText(text);            } else {                try pushEvent(app, .{ .assistant_presentation = .{                    .text = @constCast(text),                } });            }            debug_trace.eventf(                "worker",                "assistant_chunk_received",                .{},                "chunk_bytes={d}",                .{text.len},            );        }         pub fn pushTable(app: *App, table: assistant_presentation.TablePayload) !void {            try pushEvent(app, .{ .assistant_presentation = .{ .table = table } });        }         pub fn pushCodeBlock(app: *App, block: assistant_presentation.CodeBlockPayload) !void {            try pushEvent(app, .{ .assistant_presentation = .{ .code_block = block } });        }         pub fn pushThematicRule(app: *App) !void {            try pushEvent(app, .{ .assistant_presentation = .thematic_rule });        }         pub fn pushCommandOutput(            app: *App,            lifecycle_id: ?types.ToolLifecycleId,            stream: command_output_content.Stream,            chunk: []const u8,        ) !void {            if (chunk.len == 0) return;            if (comptime @hasDecl(App, "pushCommandOutputWithLifecycle")) {                try app.pushCommandOutputWithLifecycle(lifecycle_id, stream, chunk);                return;            }            if (comptime @hasDecl(App, "pushCommandOutput")) {                try app.pushCommandOutput(stream, chunk);                return;            }            try pushEvent(app, .{ .command_output = .{                .lifecycle_id = lifecycle_id,                .stream = stream,                .text = @constCast(chunk),            } });        }         pub fn pushCommandOutputComplete(app: *App, lifecycle_id: ?types.ToolLifecycleId) !void {            try pushEvent(app, .{ .command_output_complete = lifecycle_id });        }         pub fn pushToolLifecycle(            app: *App,            event: types.ToolLifecycleEvent,        ) !void {            try pushEvent(app, .{ .tool_lifecycle = event });        }         pub fn pushWebSearchProgress(app: *App, call_id: []const u8, progress: types.WebSearchProgress) !void {            const turn_id = app.worker.activeTurnId();            if (turn_id == 0 or call_id.len == 0) {                debug_trace.logf(                    "ui_activity",                    "web search progress missing active lifecycle identity",                    .{},                );                return;            }            const label = try formatWebSearchProgress(std.heap.c_allocator, progress);            defer std.heap.c_allocator.free(label);            try pushToolLifecycle(app, .{ .progress = .{                .id = .{ .turn_id = turn_id, .call_id = call_id },                .text = label,            } });        }         pub fn pushWebFetchProgress(app: *App, call_id: []const u8, progress: types.WebFetchProgress) !void {            const turn_id = app.worker.activeTurnId();            if (turn_id == 0 or call_id.len == 0) {                debug_trace.logf(                    "ui_activity",                    "web fetch progress missing active lifecycle identity",                    .{},                );                return;            }            const label = try formatWebFetchProgress(std.heap.c_allocator, progress);            defer std.heap.c_allocator.free(label);            try pushToolLifecycle(app, .{ .progress = .{                .id = .{ .turn_id = turn_id, .call_id = call_id },                .text = label,            } });        }         pub fn pushSemanticNotice(app: *App, notice: types.SemanticNotice) !void {            try pushEvent(app, .{ .semantic_notice = notice });        }         pub fn pushDiffBlock(app: *App, payload: diff_mod.DiffEntryPayload) !void {            try pushOwnedEvent(app, .{ .diff_block = payload });        }         pub fn syncState(            app: *App,            presenter: activity_runtime.LifecyclePresenter,        ) void {            const snapshot = app.worker.snapshotState(app.alloc) catch return;            defer snapshot.deinit(app.alloc);             const was_approval_active = app.approval_prompt.isActive();            const worker_pending_request = if (snapshot.pending_permission_request) |*request|                request.view()            else                null;            const child_pending_request: ?permission_request.PermissionRequest = if (worker_pending_request == null) blk: {                if (comptime @hasField(App, "subagents")) {                    if (comptime @hasDecl(@TypeOf(app.subagents), "mainApprovalRequest")) {                        break :blk app.subagents.mainApprovalRequest();                    }                }                break :blk null;            } else null;            if (comptime @hasField(App, "subagents")) {                if (comptime @hasDecl(@TypeOf(app.subagents), "markMainApprovalPresented")) {                    app.subagents.markMainApprovalPresented(child_pending_request != null);                }            }            const pending_request = worker_pending_request orelse child_pending_request;            const management_active = if (comptime @hasField(                @TypeOf(app.approval_prompt),                "rule_management",            ))                app.approval_prompt.rule_management != null            else                false;            const approval_changed = if (management_active and pending_request == null)                false            else                app.approval_prompt.syncRequest(app.alloc, pending_request) catch false;            const review_changed = if (comptime @hasDecl(@TypeOf(app.approval_prompt), "syncReview")) blk: {                const review = if (comptime @hasField(@TypeOf(snapshot), "pending_permission_review"))                    if (snapshot.pending_permission_review) |pending|                        if (pending_request) |request|                            if (pending.request_id == request.id) pending.review else null                        else                            null                    else                        null                else                    null;                break :blk app.approval_prompt.syncReview(review);            } else false;            if (comptime @hasField(App, "approval_screen")) {                const file_review_changed = review_changed and                    (pending_request == null or pending_request.?.file != null);                if (approval_changed or file_review_changed) {                    app.approval_screen.clear();                }            }            if (approval_changed or review_changed) {                app.shell.render_requests.request(.modal);            }            if (!was_approval_active and app.approval_prompt.isActive()) {                if (comptime @hasDecl(App, "dispatchAttentionRequired")) {                    app.dispatchAttentionRequired(snapshot.active_turn_id, .permission);                }            }             if (app.question_prompt.isActive()) {                if (app.worker.snapshotPendingQuestionBatch(app.alloc) catch return) |question_snapshot| {                    question_snapshot.deinit(app.alloc);                } else {                    app.question_prompt.discard(app.alloc, "worker_cleared");                    app.shell.render_requests.request(.modal);                }            }             const modal_active = app.approval_prompt.isActive() or app.question_prompt.isActive();            const queue_review_active = if (comptime @hasField(@TypeOf(snapshot), "queue_review_reason"))                snapshot.queue_review_reason != null            else                false;            const worker_events_pending = if (comptime @hasField(@TypeOf(snapshot), "pending_event_count"))                snapshot.pending_event_count > 0            else                false;            const visible_worker_active = app.stream.active and                !snapshot.cancel_requested and                (snapshot.processing or                    worker_events_pending or

Lines 1–500 of 4391.