const std = @import("std");const secret = @import("../auth/secret.zig");const io_mod = @import("../shared/io.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 image_attachments = @import("../images/image_attachments.zig");const context_contract = @import("../workspace/context_contract.zig");const sandbox = @import("../permissions/sandbox.zig");const auto_classifier_context = @import("../permissions/auto_classifier_context.zig");const permission_request = @import("../permissions/permission_request.zig");const notification_contract = @import("../notifications/notification_contract.zig");const session_runtime = @import("../session/session.zig");const session_codec = @import("../session/session_codec.zig");const skill_contract = @import("../skills/skill_contract.zig");const tool_result_limits = @import("../tooling/tool_result_limits.zig");const command_output_content = @import("../tooling/command_output_content.zig");const paste_blocks = @import("../input/pasted_blocks.zig");const entity_spans = @import("../shared/entity_spans.zig");const types = @import("../shared/types.zig");const assistant_presentation = @import("assistant_presentation.zig"); pub const AgentTurnSettings = struct { max_tool_result_bytes: usize = tool_result_limits.default_max_tool_result_bytes, first_call_tool_choice: types.ToolChoice = .auto, fast_mode: bool = false, effort: types.ReasoningEffort = .auto,}; pub const SkillBinding = struct { name: []u8, path: []u8,}; pub const SkillDisplaySpan = struct { raw_start: usize, raw_end: usize, name: []u8, path: []u8, display_source: ?skill_contract.SkillSource = null, owns_trailing_separator: bool = false,}; pub const BeginPromptWithSkillBindings = struct { prompt: types.UserTurn, skill_bindings: []SkillBinding = &.{}, skill_display_spans: []SkillDisplaySpan = &.{},}; pub const QueueReviewDraft = struct { input: []u8, pasted_blocks: []paste_blocks.PastedBlock = &.{}, image_tokens: []entity_spans.ImageTokenSpan = &.{}, skill_display_spans: []SkillDisplaySpan = &.{},}; pub const QueuedPrompt = struct { turn_id: u64 = 0, prompt: []u8, images: []types.ImageAttachment, authorized_image_catalog: []types.ImageAttachment = &.{}, model: []u8, api_key: []u8, gateway_team: ?[]u8 = null, credential_source: ?types.CredentialSource = null, permission_mode: types.PermissionMode, sandbox_backend: sandbox.BackendKind = .none, history: []types.HistoryTurn, root_user_intent_context: []u8 = &.{}, grants: []types.PermissionGrant, skill_bindings: []SkillBinding = &.{}, skill_display_spans: []SkillDisplaySpan = &.{}, review_draft: ?QueueReviewDraft = null, context_snapshot: context_contract.GatheredContextSnapshot = .{}, agent_settings: AgentTurnSettings = .{}, snapshot_file_ownerships: []types.SnapshotFileOwnership = &.{}, /// Present only when an explicit host action resumes a durable paused /// model turn. The same ownership rules as the other queued fields apply. recovery_checkpoint: ?session_codec.RecoveryCheckpoint = null, /// True when this output surface already contains the checkpoint's /// assistant source. Recovery still restores the source for overlap and /// persistence, but publishes only novel continuation text. recovery_source_already_presented: bool = false,}; pub const ActivePromptSnapshotOwnership = struct { images: []const types.ImageAttachment, state: enum { active, handed_off, discarded, } = .active, shared_ownership: ?types.SnapshotFileOwnership = null, pub fn init(images: []const types.ImageAttachment) ActivePromptSnapshotOwnership { return .{ .images = images }; } fn ensureSharedOwnership( self: *ActivePromptSnapshotOwnership, alloc: std.mem.Allocator, ) !?types.SnapshotFileOwnership { if (self.images.len == 0) return null; if (self.shared_ownership) |ownership| return ownership; const ownership = (try SnapshotFileOwnershipState.create(alloc, self.images)).handle(); self.shared_ownership = ownership; return ownership; } fn handoff( self: *ActivePromptSnapshotOwnership, alloc: std.mem.Allocator, ) !?types.SnapshotFileOwnership { if (self.state != .active or self.images.len == 0) return null; const ownership = (try self.ensureSharedOwnership(alloc)).?; self.state = .handed_off; return ownership; } fn deinit(self: *ActivePromptSnapshotOwnership) void { if (self.state == .active) { if (self.shared_ownership) |ownership| { ownership.release(); } else { image_attachments.deleteUnreferencedImageSnapshots(self.images, &.{}); } self.state = .discarded; } }}; const SnapshotFileOwnershipState = struct { alloc: std.mem.Allocator, images: []types.ImageAttachment, references: std.atomic.Value(usize) = std.atomic.Value(usize).init(1), transferred: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), fn create( alloc: std.mem.Allocator, images: []const types.ImageAttachment, ) !*SnapshotFileOwnershipState { const owned_images = try types.dupeImageAttachmentSlice(alloc, images); errdefer types.freeImageAttachmentSlice(alloc, owned_images); const state = try alloc.create(SnapshotFileOwnershipState); state.* = .{ .alloc = alloc, .images = owned_images, }; return state; } fn handle(self: *SnapshotFileOwnershipState) types.SnapshotFileOwnership { return .{ .ctx = self, .retain_fn = retain, .release_fn = release, .transfer_fn = transfer, }; } fn retain(raw: *anyopaque) void { const self: *SnapshotFileOwnershipState = @ptrCast(@alignCast(raw)); _ = self.references.fetchAdd(1, .seq_cst); } fn release(raw: *anyopaque) void { const self: *SnapshotFileOwnershipState = @ptrCast(@alignCast(raw)); if (self.references.fetchSub(1, .seq_cst) != 1) return; if (!self.transferred.load(.seq_cst)) { image_attachments.deleteUnreferencedImageSnapshots(self.images, &.{}); } const alloc = self.alloc; types.freeImageAttachmentSlice(alloc, self.images); alloc.destroy(self); } fn transfer(raw: *anyopaque) void { const self: *SnapshotFileOwnershipState = @ptrCast(@alignCast(raw)); self.transferred.store(true, .seq_cst); }}; pub const PermissionSnapshot = struct { mode: types.PermissionMode, sandbox_backend: sandbox.BackendKind,}; pub const CommandOutputChunk = struct { lifecycle_id: ?types.ToolLifecycleId = null, stream: command_output_content.Stream, text: []u8,}; pub const QueuePreview = struct { count: usize = 0, paused: bool = false,}; pub const QueueReviewReason = enum { manual, post_cancel,}; pub const QueuedPromptDraft = struct { turn_id: u64, prompt: []u8, images: []types.ImageAttachment, skill_display_spans: []SkillDisplaySpan, review_draft: ?QueueReviewDraft = null, pub fn reviewInput(self: QueuedPromptDraft) []const u8 { return if (self.review_draft) |review| review.input else self.prompt; } pub fn reviewSkillDisplaySpans( self: QueuedPromptDraft, ) []const SkillDisplaySpan { return if (self.review_draft) |review| review.skill_display_spans else self.skill_display_spans; }}; const PreparedQueuedPromptDraft = struct { turn_id: u64, prompt: []u8, images: []types.ImageAttachment, authorized_image_catalog: []types.ImageAttachment, skill_bindings: []SkillBinding, skill_display_spans: []SkillDisplaySpan, review_draft: ?QueueReviewDraft,}; const PreparedHistoryPropagation = struct { history: []types.HistoryTurn, root_user_intent_context: []u8, authorized_image_catalog: []types.ImageAttachment, snapshot_file_ownerships: ?[]types.SnapshotFileOwnership, added_snapshot_ownership: ?types.SnapshotFileOwnership, fn deinit(self: PreparedHistoryPropagation, alloc: std.mem.Allocator) void { types.freeHistoryTurnSlice(alloc, self.history); alloc.free(self.root_user_intent_context); types.freeImageAttachmentSlice(alloc, self.authorized_image_catalog); if (self.added_snapshot_ownership) |ownership| ownership.release(); if (self.snapshot_file_ownerships) |ownerships| alloc.free(ownerships); }}; pub fn freeQueuedPromptDraft(alloc: std.mem.Allocator, draft: QueuedPromptDraft) void { alloc.free(draft.prompt); types.freeImageAttachmentSlice(alloc, draft.images); freeSkillDisplaySpans(alloc, draft.skill_display_spans); freeQueueReviewDraftOpt(alloc, draft.review_draft);} pub fn freeQueuedPromptDrafts(alloc: std.mem.Allocator, drafts: []QueuedPromptDraft) void { for (drafts) |draft| freeQueuedPromptDraft(alloc, draft); if (drafts.len > 0) alloc.free(drafts);} pub fn dupeQueueReviewDraft( alloc: std.mem.Allocator, draft: QueueReviewDraft,) !QueueReviewDraft { const input = try alloc.dupe(u8, draft.input); errdefer alloc.free(input); const pasted_blocks: []paste_blocks.PastedBlock = if (draft.pasted_blocks.len > 0) try alloc.alloc(paste_blocks.PastedBlock, draft.pasted_blocks.len) else @constCast(&.{}); var pasted_count: usize = 0; errdefer { for (pasted_blocks[0..pasted_count]) |block| alloc.free(block.text); if (pasted_blocks.len > 0) alloc.free(pasted_blocks); } while (pasted_count < draft.pasted_blocks.len) : (pasted_count += 1) { pasted_blocks[pasted_count] = .{ .id = draft.pasted_blocks[pasted_count].id, .text = try alloc.dupe(u8, draft.pasted_blocks[pasted_count].text), .line_count = draft.pasted_blocks[pasted_count].line_count, .span = draft.pasted_blocks[pasted_count].span, }; } const image_tokens: []entity_spans.ImageTokenSpan = if (draft.image_tokens.len > 0) try alloc.dupe(entity_spans.ImageTokenSpan, draft.image_tokens) else @constCast(&.{}); errdefer if (image_tokens.len > 0) alloc.free(image_tokens); const skill_display_spans = try dupeSkillDisplaySpans( alloc, draft.skill_display_spans, ); return .{ .input = input, .pasted_blocks = pasted_blocks, .image_tokens = image_tokens, .skill_display_spans = skill_display_spans, };} pub fn freeQueueReviewDraft( alloc: std.mem.Allocator, draft: QueueReviewDraft,) void { alloc.free(draft.input); for (draft.pasted_blocks) |block| alloc.free(block.text); if (draft.pasted_blocks.len > 0) alloc.free(draft.pasted_blocks); if (draft.image_tokens.len > 0) alloc.free(draft.image_tokens); freeSkillDisplaySpans(alloc, draft.skill_display_spans);} fn freeQueueReviewDraftOpt( alloc: std.mem.Allocator, draft: ?QueueReviewDraft,) void { if (draft) |owned| freeQueueReviewDraft(alloc, owned);} fn freePreparedQueuedPromptDraft(alloc: std.mem.Allocator, draft: PreparedQueuedPromptDraft) void { alloc.free(draft.prompt); types.freeImageAttachmentSlice(alloc, draft.images); types.freeImageAttachmentSlice(alloc, draft.authorized_image_catalog); freeSkillBindings(alloc, draft.skill_bindings); freeSkillDisplaySpans(alloc, draft.skill_display_spans); freeQueueReviewDraftOpt(alloc, draft.review_draft);} fn skillBindingsFromDisplaySpans( alloc: std.mem.Allocator, spans: []const SkillDisplaySpan,) ![]SkillBinding { var bindings: std.ArrayList(SkillBinding) = .empty; errdefer { for (bindings.items) |binding| { alloc.free(binding.name); alloc.free(binding.path); } bindings.deinit(alloc); } for (spans) |span| { var already_present = false; for (bindings.items) |binding| { if (std.mem.eql(u8, binding.path, span.path)) { already_present = true; break; } } if (already_present) continue; const name = try alloc.dupe(u8, span.name); errdefer alloc.free(name); const path = try alloc.dupe(u8, span.path); errdefer alloc.free(path); try bindings.append(alloc, .{ .name = name, .path = path }); } if (bindings.items.len == 0) { bindings.deinit(alloc); return &.{}; } return bindings.toOwnedSlice(alloc);} pub const StateSnapshot = struct { processing: bool = false, active_turn_id: u64 = 0, queued_count: usize = 0, pending_event_count: usize = 0, queue_review_reason: ?QueueReviewReason = null, cancel_requested: bool = false, pending_permission_request: ?permission_request.OwnedPermissionRequest = null, pending_permission_review: ?PendingPermissionReview = null, pub fn deinit(self: StateSnapshot, alloc: std.mem.Allocator) void { if (self.pending_permission_request) |request| { var owned = request; owned.deinit(alloc); } }}; pub const PendingPermissionReview = struct { request_id: u64, review: *const diff_mod.FileReview,}; pub const FinalizationFailure = struct { turn_id: u64, outcome: types.TurnPresentationOutcome,}; pub const InteractiveAdmissionSnapshot = union(enum) { open, stopped, finalization_failed: FinalizationFailure,}; pub const PermissionSubmissionResult = enum { accepted, stale, no_pending,}; const OwnedQuestionOption = struct { label: []u8, description: ?[]u8 = null,}; const OwnedQuestionEntry = struct { question: []u8, options: []OwnedQuestionOption,}; pub const QuestionPromptSource = enum { agent_question, route_recovery, mcp_elicitation,}; const OwnedQuestionBatch = struct { entries: []OwnedQuestionEntry, source: QuestionPromptSource = .agent_question,}; const QuestionResponse = union(enum) { pending, cancelled, answered: [][]u8,}; pub const PendingQuestionBatchSnapshot = struct { entries: []types.QuestionBatchEntry, source: QuestionPromptSource = .agent_question, pub fn deinit(self: PendingQuestionBatchSnapshot, alloc: std.mem.Allocator) void { for (self.entries) |entry| { alloc.free(@constCast(entry.question)); for (entry.options) |opt| { alloc.free(@constCast(opt.label)); if (opt.description) |desc| alloc.free(@constCast(desc)); } alloc.free(@constCast(entry.options)); } alloc.free(self.entries); }}; pub const WorkerEvent = union(enum) { begin_prompt: types.UserTurn, begin_prompt_with_skill_bindings: BeginPromptWithSkillBindings, append_user_feedback: []u8, assistant_presentation: assistant_presentation.Event, notification: notification_contract.Notification, question_requested, open_model_picker, semantic_notice: types.SemanticNotice, route_recovery_status: types.RouteRecoveryStatus, clear_route_recovery_status, api_status_text: []u8, command_output: CommandOutputChunk, command_output_complete: ?types.ToolLifecycleId, tool_lifecycle: types.ToolLifecycleEvent, turn_token_update: types.TurnTokenProgress, /// A tool call that publishes no status row of its own began streaming its /// arguments. The turn is working, with nothing to print until it lands. tool_payload_started, diff_block: diff_mod.DiffEntryPayload, finish_prompt: types.FinishedPrompt, session_grant: types.PermissionGrant, error_text: types.SemanticNotice,}; pub const WorkerEventBatch = struct { events: std.ArrayList(WorkerEvent), cancel_requested: bool,}; pub const WorkerRuntime = struct { worker_mutex: std.Io.Mutex = .init, worker_cond: std.Io.Condition = .init, queued_prompts: std.ArrayList(QueuedPrompt) = .empty, worker_events: std.ArrayList(WorkerEvent) = .empty, worker_processing: bool = false, active_turn_id: u64 = 0, worker_stop_requested: bool = false, finalization_failure: ?FinalizationFailure = null, worker_cancel_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), worker_recovery_pause_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), worker_connectivity_wait_active: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), /// Set under `worker_mutex` once the active turn publishes its terminal /// recovery pause. The turn may still be finalizing, but cannot perform /// more model work, so one continuation may queue before processing clears. recovery_continuation_ready: bool = false, queued_prompt_count: usize = 0, /// `null` means admission is open; otherwise queue take is paused for review. queue_admission: ?QueueReviewReason = null,Lines 1–500 of 5224. Load more
const std = @import("std"); const secret = @import("../auth/secret.zig"); const io_mod = @import("../shared/io.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 image_attachments = @import("../images/image_attachments.zig"); const context_contract = @import("../workspace/context_contract.zig"); const sandbox = @import("../permissions/sandbox.zig"); const auto_classifier_context = @import("../permissions/auto_classifier_context.zig"); const permission_request = @import("../permissions/permission_request.zig"); const notification_contract = @import("../notifications/notification_contract.zig"); const session_runtime = @import("../session/session.zig"); const session_codec = @import("../session/session_codec.zig"); const skill_contract = @import("../skills/skill_contract.zig"); const tool_result_limits = @import("../tooling/tool_result_limits.zig"); const command_output_content = @import("../tooling/command_output_content.zig"); const paste_blocks = @import("../input/pasted_blocks.zig"); const entity_spans = @import("../shared/entity_spans.zig"); const types = @import("../shared/types.zig"); const assistant_presentation = @import("assistant_presentation.zig"); pub const AgentTurnSettings = struct { max_tool_result_bytes: usize = tool_result_limits.default_max_tool_result_bytes, first_call_tool_choice: types.ToolChoice = .auto, fast_mode: bool = false, effort: types.ReasoningEffort = .auto, }; pub const SkillBinding = struct { name: []u8, path: []u8, }; pub const SkillDisplaySpan = struct { raw_start: usize, raw_end: usize, name: []u8, path: []u8, display_source: ?skill_contract.SkillSource = null, owns_trailing_separator: bool = false, }; pub const BeginPromptWithSkillBindings = struct { prompt: types.UserTurn, skill_bindings: []SkillBinding = &.{}, skill_display_spans: []SkillDisplaySpan = &.{}, }; pub const QueueReviewDraft = struct { input: []u8, pasted_blocks: []paste_blocks.PastedBlock = &.{}, image_tokens: []entity_spans.ImageTokenSpan = &.{}, skill_display_spans: []SkillDisplaySpan = &.{}, }; pub const QueuedPrompt = struct { turn_id: u64 = 0, prompt: []u8, images: []types.ImageAttachment, authorized_image_catalog: []types.ImageAttachment = &.{}, model: []u8, api_key: []u8, gateway_team: ?[]u8 = null, credential_source: ?types.CredentialSource = null, permission_mode: types.PermissionMode, sandbox_backend: sandbox.BackendKind = .none, history: []types.HistoryTurn, root_user_intent_context: []u8 = &.{}, grants: []types.PermissionGrant, skill_bindings: []SkillBinding = &.{}, skill_display_spans: []SkillDisplaySpan = &.{}, review_draft: ?QueueReviewDraft = null, context_snapshot: context_contract.GatheredContextSnapshot = .{}, agent_settings: AgentTurnSettings = .{}, snapshot_file_ownerships: []types.SnapshotFileOwnership = &.{}, /// Present only when an explicit host action resumes a durable paused /// model turn. The same ownership rules as the other queued fields apply. recovery_checkpoint: ?session_codec.RecoveryCheckpoint = null, /// True when this output surface already contains the checkpoint's /// assistant source. Recovery still restores the source for overlap and /// persistence, but publishes only novel continuation text. recovery_source_already_presented: bool = false, }; pub const ActivePromptSnapshotOwnership = struct { images: []const types.ImageAttachment, state: enum { active, handed_off, discarded, } = .active, shared_ownership: ?types.SnapshotFileOwnership = null, pub fn init(images: []const types.ImageAttachment) ActivePromptSnapshotOwnership { return .{ .images = images }; } fn ensureSharedOwnership( self: *ActivePromptSnapshotOwnership, alloc: std.mem.Allocator, ) !?types.SnapshotFileOwnership { if (self.images.len == 0) return null; if (self.shared_ownership) |ownership| return ownership; const ownership = (try SnapshotFileOwnershipState.create(alloc, self.images)).handle(); self.shared_ownership = ownership; return ownership; } fn handoff( self: *ActivePromptSnapshotOwnership, alloc: std.mem.Allocator, ) !?types.SnapshotFileOwnership { if (self.state != .active or self.images.len == 0) return null; const ownership = (try self.ensureSharedOwnership(alloc)).?; self.state = .handed_off; return ownership; } fn deinit(self: *ActivePromptSnapshotOwnership) void { if (self.state == .active) { if (self.shared_ownership) |ownership| { ownership.release(); } else { image_attachments.deleteUnreferencedImageSnapshots(self.images, &.{}); } self.state = .discarded; } } }; const SnapshotFileOwnershipState = struct { alloc: std.mem.Allocator, images: []types.ImageAttachment, references: std.atomic.Value(usize) = std.atomic.Value(usize).init(1), transferred: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), fn create( alloc: std.mem.Allocator, images: []const types.ImageAttachment, ) !*SnapshotFileOwnershipState { const owned_images = try types.dupeImageAttachmentSlice(alloc, images); errdefer types.freeImageAttachmentSlice(alloc, owned_images); const state = try alloc.create(SnapshotFileOwnershipState); state.* = .{ .alloc = alloc, .images = owned_images, }; return state; } fn handle(self: *SnapshotFileOwnershipState) types.SnapshotFileOwnership { return .{ .ctx = self, .retain_fn = retain, .release_fn = release, .transfer_fn = transfer, }; } fn retain(raw: *anyopaque) void { const self: *SnapshotFileOwnershipState = @ptrCast(@alignCast(raw)); _ = self.references.fetchAdd(1, .seq_cst); } fn release(raw: *anyopaque) void { const self: *SnapshotFileOwnershipState = @ptrCast(@alignCast(raw)); if (self.references.fetchSub(1, .seq_cst) != 1) return; if (!self.transferred.load(.seq_cst)) { image_attachments.deleteUnreferencedImageSnapshots(self.images, &.{}); } const alloc = self.alloc; types.freeImageAttachmentSlice(alloc, self.images); alloc.destroy(self); } fn transfer(raw: *anyopaque) void { const self: *SnapshotFileOwnershipState = @ptrCast(@alignCast(raw)); self.transferred.store(true, .seq_cst); } }; pub const PermissionSnapshot = struct { mode: types.PermissionMode, sandbox_backend: sandbox.BackendKind, }; pub const CommandOutputChunk = struct { lifecycle_id: ?types.ToolLifecycleId = null, stream: command_output_content.Stream, text: []u8, }; pub const QueuePreview = struct { count: usize = 0, paused: bool = false, }; pub const QueueReviewReason = enum { manual, post_cancel, }; pub const QueuedPromptDraft = struct { turn_id: u64, prompt: []u8, images: []types.ImageAttachment, skill_display_spans: []SkillDisplaySpan, review_draft: ?QueueReviewDraft = null, pub fn reviewInput(self: QueuedPromptDraft) []const u8 { return if (self.review_draft) |review| review.input else self.prompt; } pub fn reviewSkillDisplaySpans( self: QueuedPromptDraft, ) []const SkillDisplaySpan { return if (self.review_draft) |review| review.skill_display_spans else self.skill_display_spans; } }; const PreparedQueuedPromptDraft = struct { turn_id: u64, prompt: []u8, images: []types.ImageAttachment, authorized_image_catalog: []types.ImageAttachment, skill_bindings: []SkillBinding, skill_display_spans: []SkillDisplaySpan, review_draft: ?QueueReviewDraft, }; const PreparedHistoryPropagation = struct { history: []types.HistoryTurn, root_user_intent_context: []u8, authorized_image_catalog: []types.ImageAttachment, snapshot_file_ownerships: ?[]types.SnapshotFileOwnership, added_snapshot_ownership: ?types.SnapshotFileOwnership, fn deinit(self: PreparedHistoryPropagation, alloc: std.mem.Allocator) void { types.freeHistoryTurnSlice(alloc, self.history); alloc.free(self.root_user_intent_context); types.freeImageAttachmentSlice(alloc, self.authorized_image_catalog); if (self.added_snapshot_ownership) |ownership| ownership.release(); if (self.snapshot_file_ownerships) |ownerships| alloc.free(ownerships); } }; pub fn freeQueuedPromptDraft(alloc: std.mem.Allocator, draft: QueuedPromptDraft) void { alloc.free(draft.prompt); types.freeImageAttachmentSlice(alloc, draft.images); freeSkillDisplaySpans(alloc, draft.skill_display_spans); freeQueueReviewDraftOpt(alloc, draft.review_draft); } pub fn freeQueuedPromptDrafts(alloc: std.mem.Allocator, drafts: []QueuedPromptDraft) void { for (drafts) |draft| freeQueuedPromptDraft(alloc, draft); if (drafts.len > 0) alloc.free(drafts); } pub fn dupeQueueReviewDraft( alloc: std.mem.Allocator, draft: QueueReviewDraft, ) !QueueReviewDraft { const input = try alloc.dupe(u8, draft.input); errdefer alloc.free(input); const pasted_blocks: []paste_blocks.PastedBlock = if (draft.pasted_blocks.len > 0) try alloc.alloc(paste_blocks.PastedBlock, draft.pasted_blocks.len) else @constCast(&.{}); var pasted_count: usize = 0; errdefer { for (pasted_blocks[0..pasted_count]) |block| alloc.free(block.text); if (pasted_blocks.len > 0) alloc.free(pasted_blocks); } while (pasted_count < draft.pasted_blocks.len) : (pasted_count += 1) { pasted_blocks[pasted_count] = .{ .id = draft.pasted_blocks[pasted_count].id, .text = try alloc.dupe(u8, draft.pasted_blocks[pasted_count].text), .line_count = draft.pasted_blocks[pasted_count].line_count, .span = draft.pasted_blocks[pasted_count].span, }; } const image_tokens: []entity_spans.ImageTokenSpan = if (draft.image_tokens.len > 0) try alloc.dupe(entity_spans.ImageTokenSpan, draft.image_tokens) else @constCast(&.{}); errdefer if (image_tokens.len > 0) alloc.free(image_tokens); const skill_display_spans = try dupeSkillDisplaySpans( alloc, draft.skill_display_spans, ); return .{ .input = input, .pasted_blocks = pasted_blocks, .image_tokens = image_tokens, .skill_display_spans = skill_display_spans, }; } pub fn freeQueueReviewDraft( alloc: std.mem.Allocator, draft: QueueReviewDraft, ) void { alloc.free(draft.input); for (draft.pasted_blocks) |block| alloc.free(block.text); if (draft.pasted_blocks.len > 0) alloc.free(draft.pasted_blocks); if (draft.image_tokens.len > 0) alloc.free(draft.image_tokens); freeSkillDisplaySpans(alloc, draft.skill_display_spans); } fn freeQueueReviewDraftOpt( alloc: std.mem.Allocator, draft: ?QueueReviewDraft, ) void { if (draft) |owned| freeQueueReviewDraft(alloc, owned); } fn freePreparedQueuedPromptDraft(alloc: std.mem.Allocator, draft: PreparedQueuedPromptDraft) void { alloc.free(draft.prompt); types.freeImageAttachmentSlice(alloc, draft.images); types.freeImageAttachmentSlice(alloc, draft.authorized_image_catalog); freeSkillBindings(alloc, draft.skill_bindings); freeSkillDisplaySpans(alloc, draft.skill_display_spans); freeQueueReviewDraftOpt(alloc, draft.review_draft); } fn skillBindingsFromDisplaySpans( alloc: std.mem.Allocator, spans: []const SkillDisplaySpan, ) ![]SkillBinding { var bindings: std.ArrayList(SkillBinding) = .empty; errdefer { for (bindings.items) |binding| { alloc.free(binding.name); alloc.free(binding.path); } bindings.deinit(alloc); } for (spans) |span| { var already_present = false; for (bindings.items) |binding| { if (std.mem.eql(u8, binding.path, span.path)) { already_present = true; break; } } if (already_present) continue; const name = try alloc.dupe(u8, span.name); errdefer alloc.free(name); const path = try alloc.dupe(u8, span.path); errdefer alloc.free(path); try bindings.append(alloc, .{ .name = name, .path = path }); } if (bindings.items.len == 0) { bindings.deinit(alloc); return &.{}; } return bindings.toOwnedSlice(alloc); } pub const StateSnapshot = struct { processing: bool = false, active_turn_id: u64 = 0, queued_count: usize = 0, pending_event_count: usize = 0, queue_review_reason: ?QueueReviewReason = null, cancel_requested: bool = false, pending_permission_request: ?permission_request.OwnedPermissionRequest = null, pending_permission_review: ?PendingPermissionReview = null, pub fn deinit(self: StateSnapshot, alloc: std.mem.Allocator) void { if (self.pending_permission_request) |request| { var owned = request; owned.deinit(alloc); } } }; pub const PendingPermissionReview = struct { request_id: u64, review: *const diff_mod.FileReview, }; pub const FinalizationFailure = struct { turn_id: u64, outcome: types.TurnPresentationOutcome, }; pub const InteractiveAdmissionSnapshot = union(enum) { open, stopped, finalization_failed: FinalizationFailure, }; pub const PermissionSubmissionResult = enum { accepted, stale, no_pending, }; const OwnedQuestionOption = struct { label: []u8, description: ?[]u8 = null, }; const OwnedQuestionEntry = struct { question: []u8, options: []OwnedQuestionOption, }; pub const QuestionPromptSource = enum { agent_question, route_recovery, mcp_elicitation, }; const OwnedQuestionBatch = struct { entries: []OwnedQuestionEntry, source: QuestionPromptSource = .agent_question, }; const QuestionResponse = union(enum) { pending, cancelled, answered: [][]u8, }; pub const PendingQuestionBatchSnapshot = struct { entries: []types.QuestionBatchEntry, source: QuestionPromptSource = .agent_question, pub fn deinit(self: PendingQuestionBatchSnapshot, alloc: std.mem.Allocator) void { for (self.entries) |entry| { alloc.free(@constCast(entry.question)); for (entry.options) |opt| { alloc.free(@constCast(opt.label)); if (opt.description) |desc| alloc.free(@constCast(desc)); } alloc.free(@constCast(entry.options)); } alloc.free(self.entries); } }; pub const WorkerEvent = union(enum) { begin_prompt: types.UserTurn, begin_prompt_with_skill_bindings: BeginPromptWithSkillBindings, append_user_feedback: []u8, assistant_presentation: assistant_presentation.Event, notification: notification_contract.Notification, question_requested, open_model_picker, semantic_notice: types.SemanticNotice, route_recovery_status: types.RouteRecoveryStatus, clear_route_recovery_status, api_status_text: []u8, command_output: CommandOutputChunk, command_output_complete: ?types.ToolLifecycleId, tool_lifecycle: types.ToolLifecycleEvent, turn_token_update: types.TurnTokenProgress, /// A tool call that publishes no status row of its own began streaming its /// arguments. The turn is working, with nothing to print until it lands. tool_payload_started, diff_block: diff_mod.DiffEntryPayload, finish_prompt: types.FinishedPrompt, session_grant: types.PermissionGrant, error_text: types.SemanticNotice, }; pub const WorkerEventBatch = struct { events: std.ArrayList(WorkerEvent), cancel_requested: bool, }; pub const WorkerRuntime = struct { worker_mutex: std.Io.Mutex = .init, worker_cond: std.Io.Condition = .init, queued_prompts: std.ArrayList(QueuedPrompt) = .empty, worker_events: std.ArrayList(WorkerEvent) = .empty, worker_processing: bool = false, active_turn_id: u64 = 0, worker_stop_requested: bool = false, finalization_failure: ?FinalizationFailure = null, worker_cancel_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), worker_recovery_pause_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), worker_connectivity_wait_active: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), /// Set under `worker_mutex` once the active turn publishes its terminal /// recovery pause. The turn may still be finalizing, but cannot perform /// more model work, so one continuation may queue before processing clears. recovery_continuation_ready: bool = false, queued_prompt_count: usize = 0, /// `null` means admission is open; otherwise queue take is paused for review. queue_admission: ?QueueReviewReason = null,