const std = @import("std");
const credentials = @import("../auth/credentials.zig");
const secret = @import("../auth/secret.zig");
const io_mod = @import("../shared/io.zig");
const debug_trace = @import("../shared/debug_trace.zig");
const diagnostics = @import("../workspace/diagnostics.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 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 types = @import("../shared/types.zig");
const model_provider = @import("../config/model_provider.zig");
const assistant_presentation = @import("assistant_presentation.zig");
const compaction_activity = @import("../output/compaction_activity.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 PromptDelivery = union(enum) {
ordinary,
active_turn: u64,
continuation,
pub fn activeTurnId(self: PromptDelivery) ?u64 {
return switch (self) {
.active_turn => |turn_id| turn_id,
.ordinary, .continuation => null,
};
}
pub fn isSteering(self: PromptDelivery) bool {
return switch (self) {
.ordinary => false,
.active_turn, .continuation => true,
};
}
pub fn isContinuation(self: PromptDelivery) bool {
return self == .continuation;
}
};
pub const SteeringBoundaryKind = enum {
model,
cancelled,
finalizing,
};
const SteeringDelivery = types.SteeringDelivery;
/// Borrowed by the queue. Its owner outlives worker teardown.
pub const SteeringReceipt = struct {
operation_id: []const u8 = "",
state: std.atomic.Value(SteeringDelivery) = .init(.queued),
};
pub const SteeringBoundaryResult = union(enum) {
none,
continue_turn: [][]u8,
handoff,
interrupt,
};
pub const QueuedPrompt = struct {
turn_id: u64 = 0,
steering_receipt: ?*SteeringReceipt = null,
/// Runtime-derived delivery state. The worker retains ownership in every state.
delivery: PromptDelivery = .ordinary,
prompt: []u8,
images: []types.ImageAttachment,
authorized_image_catalog: []types.ImageAttachment = &.{},
model: []u8,
provider: model_provider.ProviderId = .gateway,
api_key: []u8,
gateway_team: ?[]u8 = null,
credential_source: ?types.CredentialSource = null,
account_id: ?[]u8 = null,
permission_mode: types.PermissionMode,
history: []types.HistoryTurn,
/// Absolute end of the leading canonical-history range loaded without
/// corrected result provenance. The conservative default protects callers
/// that do not own a SessionRuntime snapshot.
unversioned_history_count: usize = std.math.maxInt(usize),
root_user_intent_context: []u8 = &.{},
grants: []types.PermissionGrant,
skill_bindings: []SkillBinding = &.{},
skill_display_spans: []SkillDisplaySpan = &.{},
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,
/// The interactive transcript already contains this exact user turn.
/// Worker begin publishes only its identity so the UI can consume the
/// pending owner without painting a duplicate card.
user_prompt_already_presented: bool = false,
};
pub const ContextCompactionTask = struct {
turn_id: u64 = 0,
operation_id: ?compaction_activity.OperationId = null,
model: []u8,
provider: model_provider.ProviderId = .gateway,
api_key: []u8,
gateway_team: ?[]u8 = null,
credential_source: ?types.CredentialSource = null,
account_id: ?[]u8 = null,
history: []types.HistoryTurn,
unversioned_history_count: usize = std.math.maxInt(usize),
};
pub const ContextCompactionStatus = enum {
idle,
queued,
running,
};
pub const WorkItem = union(enum) {
prompt: QueuedPrompt,
compact_context: ContextCompactionTask,
};
pub const ActivePromptSnapshotOwnership = struct {
/// Owns snapshot deletion until the files are transferred to accepted
/// history or handed to a reference-counted finished-turn owner.
images: []const types.ImageAttachment,
state: enum {
active,
handed_off,
preserved,
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.state == .preserved or self.state == .discarded) 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 preserve(self: *ActivePromptSnapshotOwnership) bool {
if (self.images.len == 0) return false;
switch (self.state) {
.active => {
if (self.shared_ownership) |ownership| {
ownership.transfer();
ownership.release();
self.shared_ownership = null;
}
self.state = .preserved;
return true;
},
.handed_off => {
if (self.shared_ownership) |ownership| ownership.transfer();
return true;
},
.preserved => return true,
.discarded => return false,
}
}
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);
}
};
fn historyTurnImages(turn: types.HistoryTurn) []const types.ImageAttachment {
return switch (turn) {
.assistant => |value| value.user.images,
.interrupted => |value| value.user.images,
.compacted_summary => &.{},
};
}
fn sameImageSnapshots(
left: []const types.ImageAttachment,
right: []const types.ImageAttachment,
) bool {
if (left.len == 0 or left.len != right.len) return false;
for (left) |image| {
const other = image_attachments.findById(right, image.id) orelse return false;
if (!optionalBytesEqual(image.snapshot_path, other.snapshot_path) or
!optionalBytesEqual(image.snapshot_sha256, other.snapshot_sha256))
{
return false;
}
}
return true;
}
fn optionalBytesEqual(left: ?[]const u8, right: ?[]const u8) bool {
if (left == null or right == null) return left == null and right == null;
return std.mem.eql(u8, left.?, right.?);
}
pub const PermissionSnapshot = struct {
mode: types.PermissionMode,
};
pub const CommandOutputChunk = struct {
lifecycle_id: ?types.ToolLifecycleId = null,
stream: command_output_content.Stream,
text: []u8,
};
pub const SteeringPresentationSnapshot = struct {
messages: [][]u8 = &.{},
pending_feedback: [][]u8 = &.{},
waits_for_boundary: bool = false,
pub fn deinit(self: *SteeringPresentationSnapshot, alloc: std.mem.Allocator) void {
for (self.messages) |message| alloc.free(message);
if (self.messages.len > 0) alloc.free(self.messages);
for (self.pending_feedback) |message| alloc.free(message);
if (self.pending_feedback.len > 0) alloc.free(self.pending_feedback);
self.* = .{};
}
};
const PreparedHistoryPropagation = struct {
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 {
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 const StateSnapshot = struct {
processing: bool = false,
active_turn_id: u64 = 0,
queued_count: usize = 0,
pending_event_count: usize = 0,
cancel_requested: bool = false,
cancel_continues_turn: 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,
submission: @FieldType(types.QuestionBatchEntry, "submission") = .none,
};
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 ContextCompaction = struct {
turn_id: u64 = 0,
summary: types.CompactedSummaryHistoryTurn,
active_prefix: ?types.AssistantHistoryTurn = null,
retained_from: ?types.ContextHistoryCut = null,
max_history_turns: usize = 0,
};
pub const ContextCompactionHandler = *const fn (*anyopaque, ContextCompaction) anyerror!void;
pub const FreshPromptPreparation = struct {
turn_id: u64 = 0,
user: types.UserTurn,
prior_turn: ?types.HistoryTurn = null,
};
pub const FreshPromptHistory = struct {
history: []types.HistoryTurn,
authorized_image_catalog: []types.ImageAttachment,
root_user_intent_context: []u8,
unversioned_history_count: usize,
pub fn deinit(self: *FreshPromptHistory, alloc: std.mem.Allocator) void {
types.freeHistoryTurnSlice(alloc, self.history);
types.freeImageAttachmentSlice(alloc, self.authorized_image_catalog);
alloc.free(self.root_user_intent_context);
self.* = undefined;
}
};
pub const FreshPromptHandler = *const fn (*anyopaque, FreshPromptPreparation) anyerror!FreshPromptHistory;
const HistoryPublication = struct {
ctx: *anyopaque,
commit_fn: *const fn (*anyopaque) anyerror!void,
retained_from: ?types.ContextHistoryCut = null,
fn commit(self: HistoryPublication) !void {
try self.commit_fn(self.ctx);
}
};
const ContextCompactionPublication = struct {
ctx: *anyopaque,
handler: ContextCompactionHandler,
event: ContextCompaction,
fn commit(raw: *anyopaque) !void {
const self: *ContextCompactionPublication = @ptrCast(@alignCast(raw));
try self.handler(self.ctx, self.event);
}
};
const HistoryPublicationResponse = union(enum) {
idle,
waiting: u64,
committed: ?FreshPromptHistory,
failed: anyerror,
};
pub const WorkerEvent = union(enum) {
begin_prompt: types.UserTurn,
begin_prompt_with_skill_bindings: BeginPromptWithSkillBindings,
begin_presented_prompt: u64,
append_user_feedback: []u8,
assistant_presentation: assistant_presentation.Event,
notification: notification_contract.Notification,
question_requested,
open_model_picker,
semantic_notice: types.SemanticNotice,
/// A full-only observability record (network call outcome) for the ctrl+o
/// full transcript's detail section. Never enters the transcript store.
full_detail_record: types.SemanticNotice,
route_recovery_status: types.RouteRecoveryStatus,
clear_route_recovery_status,
api_status_text: []u8,
credential_refreshed: credentials.Credential,
command_output: CommandOutputChunk,
command_output_complete: ?types.ToolLifecycleId,
tool_lifecycle: types.ToolLifecycleEvent,
turn_token_update: types.TurnTokenProgress,
turn_phase_update: types.TurnPhaseUpdate,
diff_block: diff_mod.DiffEntryPayload,
context_compaction: ContextCompaction,
prepare_fresh_prompt: FreshPromptPreparation,
finish_prompt: types.FinishedPrompt,
session_grant: types.PermissionGrant,
error_text: types.SemanticNotice,
/// A turn that can never recover hands the prompt back to the composer.
/// Owned by the event; the consumer frees it.
restore_failed_prompt: []u8,
};
pub const WorkerEventBatch = struct {
events: std.ArrayList(WorkerEvent),
cancelled_turn_id: ?u64,
};
pub const WorkerRuntime = struct {
worker_mutex: std.Io.Mutex = .init,
worker_cond: std.Io.Condition = .init,
/// One admission-ordered queue for ordinary prompts and steering. Steering
/// remains in place until its target turn consumes or demotes it.
queued_prompts: std.ArrayList(QueuedPrompt) = .empty,
/// One immutable conversation snapshot shared by every queued prompt.
/// It is transferred or copied only when a prompt actually begins.
queued_history: []types.HistoryTurn = &.{},
queued_context_compaction: ?ContextCompactionTask = null,
active_context_compaction: bool = false,
history_publication_response: HistoryPublicationResponse = .idle,
worker_events: std.ArrayList(WorkerEvent) = .empty,
worker_processing: bool = false,
active_turn_id: u64 = 0,
active_tool_calls: std.StringHashMapUnmanaged(void) = .empty,
/// Allocator used for active call IDs. Allocator handles are capabilities,
/// not comparable identities; callers guarantee compatible ownership.
active_tool_allocator: ?std.mem.Allocator = null,
/// Retains the tool-producing step so later text from that same step cannot
/// reopen immediate steering before authoritative lifecycle IDs exist.
tool_phase_step_id: ?u64 = null,
worker_stop_requested: bool = false,
finalization_failure: ?FinalizationFailure = null,
worker_cancel_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
/// The active turn whose text steering admission owns the cancel flag.
/// Guarded by `worker_mutex`; explicit cancellation clears this ownership.
steering_cancel_turn_id: ?u64 = null,
direct_steering_closed: bool = 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,
/// When true, queued work will not start.
turn_start_held: bool = false,
next_permission_request_id: u64 = 1,
pending_permission_response: ?permission_request.OwnedPermissionResponse = null,
pending_permission_request_shared: ?permission_request.OwnedPermissionRequest = null,
pending_permission_review: ?*const diff_mod.FileReview = null,
pending_permission_waiting: bool = false,
pending_question_shared: ?OwnedQuestionBatch = null,
pending_question_response: QuestionResponse = .pending,
agent_turn_settings: AgentTurnSettings = .{},
active_agent_turn_settings: ?AgentTurnSettings = null,
active_context_snapshot: ?*const context_contract.GatheredContextSnapshot = null,
active_prompt_is_root_authority: bool = false,
active_prompt_snapshot_ownership: ?*ActivePromptSnapshotOwnership = null,
preserve_prompt_snapshot_turn_id: ?u64 = null,
/// Presentation metadata only; all access is protected by worker_mutex.
compaction_presentation: compaction_activity.State = .{},
pub fn beginCompactionActivity(self: *WorkerRuntime, origin: compaction_activity.Origin, turn_id: ?u64) compaction_activity.OperationId {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.compaction_presentation.begin(origin, turn_id, io_mod.milliTimestamp());
}
/// A rejected intent must not hide an already-running compaction.
pub fn rejectCompactionActivity(self: *WorkerRuntime, outcome: compaction_activity.Outcome) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.compaction_presentation.snapshot.operation) |op| if (op.active()) return;
const now = io_mod.milliTimestamp();
const id = self.compaction_presentation.begin(.manual, null, now);
self.compaction_presentation.settle(id, .{ .outcome = outcome }, now);
}
pub fn runCompactionActivity(self: *WorkerRuntime, id: compaction_activity.OperationId, stage: compaction_activity.Stage) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.compaction_presentation.running(id, stage);
}
pub fn settleCompactionActivity(self: *WorkerRuntime, id: compaction_activity.OperationId, feedback: compaction_activity.Feedback) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.compaction_presentation.settle(id, feedback, io_mod.milliTimestamp());
}
/// Cheap by-value read; does not clone permissions or allocate.
pub fn compactionActivitySnapshot(self: *WorkerRuntime) compaction_activity.Snapshot {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.compaction_presentation.snapshot;
}
pub fn dismissCompactionActivity(self: *WorkerRuntime, id: compaction_activity.OperationId, revision: u64) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.compaction_presentation.dismiss(id, revision);
}
pub fn expireCompactionActivity(self: *WorkerRuntime, id: compaction_activity.OperationId, revision: u64, now_ms: i64) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.compaction_presentation.expire(id, revision, now_ms);
}
fn stopCompactionActivityLocked(self: *WorkerRuntime) void {
const op = self.compaction_presentation.snapshot.operation orelse return;
if (op.turn_id != self.active_turn_id or !self.worker_processing) return;
self.compaction_presentation.stopping(op.id);
}
fn finishCompactionActivityLocked(self: *WorkerRuntime, turn_id: u64) void {
const op = self.compaction_presentation.snapshot.operation orelse return;
if (op.turn_id != turn_id or !op.active()) return;
diagnostics.traceCompactionLog(true, "unsettled operation at worker finish turn_id={d}", .{turn_id});
var feedback = compaction_activity.failure(
if (self.isCancelRequested()) error.Cancelled else error.CompactionInterrupted,
op.stage(),
self.isCancelRequested(),
);
// No transaction acknowledgement survived this fallback; do not promise rollback.
if (op.stage() == .publication) feedback.publication = .uncertain;
self.compaction_presentation.settle(op.id, feedback, io_mod.milliTimestamp());
}
fn queuedWorkCountLocked(self: *const WorkerRuntime) usize {
return self.queued_prompts.items.len +
@intFromBool(self.queued_context_compaction != null);
}
pub fn deinit(self: *WorkerRuntime, alloc: std.mem.Allocator) void {
if (self.pending_permission_response) |response| {
self.discardPermissionResponse(response, "deinit");
}
self.pending_permission_response = null;
if (self.pending_permission_request_shared) |*request| request.deinit(alloc);
self.pending_permission_request_shared = null;
self.pending_permission_review = null;
freeOwnedQuestionBatchOpt(alloc, self.pending_question_shared);
self.pending_question_shared = null;
freeQuestionResponse(alloc, self.pending_question_response);
self.pending_question_response = .pending;
for (self.queued_prompts.items) |prompt| discardQueuedPrompt(alloc, prompt, &.{});
self.queued_prompts.deinit(alloc);
types.freeHistoryTurnSlice(alloc, self.queued_history);
if (self.queued_context_compaction) |task| freeContextCompactionTask(alloc, task);
self.clearActiveToolCallsLocked();
for (self.worker_events.items) |event| freeWorkerEvent(alloc, event);
self.worker_events.deinit(alloc);
}
pub fn requestStop(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
self.worker_stop_requested = true;
if (self.queued_context_compaction) |task| {
if (task.operation_id) |id| self.compaction_presentation.settle(id, .{ .outcome = .cancelled }, io_mod.milliTimestamp());
}
self.stopCompactionActivityLocked();
self.worker_cond.broadcast(io_mod.getIo());
self.worker_mutex.unlock(io_mod.getIo());
}
pub fn requestShutdown(self: *WorkerRuntime) void {
self.markExplicitCancellation();
self.requestStop();
}
pub fn requestCancel(self: *WorkerRuntime) void {
debug_trace.logf("worker", "cancel requested processing={s} queued={d}", .{ if (self.worker_processing) "true" else "false", self.queuedPromptCount() });
debug_trace.eventf("interrupt", "cancel_requested", .{}, "processing={s} queued={d} active_tool_known=false", .{ if (self.worker_processing) "true" else "false", self.queuedPromptCount() });
self.markExplicitCancellation();
}
/// Interrupt the current gateway wait while preserving its recovery
/// checkpoint. The ordinary cancel flag wakes the existing socket watcher;
/// agent policy distinguishes the typed pause flag at the boundary.
pub fn requestRecoveryPause(self: *WorkerRuntime) void {
debug_trace.eventf("recovery", "pause_requested", .{}, "source=interactive_try_later", .{});
self.worker_recovery_pause_requested.store(true, .seq_cst);
self.markExplicitCancellation();
}
fn markExplicitCancellation(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.steering_cancel_turn_id = null;
self.worker_cancel_requested.store(true, .seq_cst);
self.stopCompactionActivityLocked();
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn requestInteractiveCancel(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const steering_pending = for (self.queued_prompts.items) |prompt| {
if (prompt.delivery.activeTurnId() == self.active_turn_id or
prompt.delivery.isContinuation()) break true;
} else false;
debug_trace.logf("worker", "cancel requested processing={s} queued={d} steering_pending={s}", .{
if (self.worker_processing) "true" else "false",
self.queuedWorkCountLocked(),
if (steering_pending) "true" else "false",
});
debug_trace.eventf("interrupt", "cancel_requested", .{}, "processing={s} queued={d} steering_pending={s} active_tool_known=false", .{
if (self.worker_processing) "true" else "false",
self.queuedWorkCountLocked(),
if (steering_pending) "true" else "false",
});
self.steering_cancel_turn_id = null;
self.worker_cancel_requested.store(true, .seq_cst);
self.stopCompactionActivityLocked();
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn cancelApprovalTurn(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.steering_cancel_turn_id = null;
self.worker_cancel_requested.store(true, .seq_cst);
self.stopCompactionActivityLocked();
_ = self.resolvePendingPermissionLocked(
permission_request.OwnedPermissionResponse.init(
std.heap.c_allocator,
.deny,
null,
),
);
}
pub fn isCancelRequested(self: *const WorkerRuntime) bool {
return self.worker_cancel_requested.load(.seq_cst);
}
pub fn cancellationStopsTurn(self: *WorkerRuntime) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.worker_cancel_requested.load(.seq_cst) and
self.steering_cancel_turn_id != self.active_turn_id;
}
pub fn isConnectivityWaitActive(self: *const WorkerRuntime) bool {
return self.worker_connectivity_wait_active.load(.seq_cst);
}
pub fn pushEvent(self: *WorkerRuntime, alloc: std.mem.Allocator, event: WorkerEvent) !void {
const owned = try dupeWorkerEvent(alloc, event);
errdefer freeWorkerEvent(alloc, owned);
try self.pushOwnedEvent(alloc, owned);
}
/// Transfers an already-owned event into the queue on success.
/// Every heap payload in `event` must have been allocated by `alloc`.
pub fn pushOwnedEvent(self: *WorkerRuntime, alloc: std.mem.Allocator, event: WorkerEvent) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
try self.worker_events.ensureUnusedCapacity(alloc, 1);
var tool_transition = try self.prepareActiveToolTransitionLocked(alloc, event);
errdefer tool_transition.deinit(alloc);
self.worker_events.appendAssumeCapacity(event);
self.applyActiveToolTransitionLocked(tool_transition);
self.applyToolBoundaryPhaseLocked(event);
self.applyRecoveryStateEvent(event);
self.worker_cond.broadcast(io_mod.getIo());
}
/// Transfers already-owned events to the front of the queue on success.
/// Every heap payload must have been allocated by `alloc`.
pub fn prependOwnedEvents(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
events: []const WorkerEvent,
) !void {
if (events.len == 0) return;
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
try self.worker_events.insertSlice(alloc, 0, events);
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn pushTurnFinished(self: *WorkerRuntime, alloc: std.mem.Allocator, event: types.TurnFinished) !void {
std.debug.assert(event.turn_id != 0);
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.worker_events.append(alloc, .{ .tool_lifecycle = .{
.turn_finished = event,
} }) catch {
self.latchFinalizationFailureLocked(.{
.turn_id = event.turn_id,
.outcome = event.outcome,
});
return error.TurnFinalizationDeliveryFailed;
};
self.applyRecoveryStateEvent(.{ .tool_lifecycle = .{
.turn_finished = event,
} });
self.worker_cond.broadcast(io_mod.getIo());
}
fn applyRecoveryStateEvent(self: *WorkerRuntime, event: WorkerEvent) void {
switch (event) {
.route_recovery_status => |status| {
self.worker_connectivity_wait_active.store(
status.action == .waiting_for_connectivity,
.seq_cst,
);
self.recovery_continuation_ready = status.action == .paused;
},
.clear_route_recovery_status,
.finish_prompt,
.error_text,
=> {
self.worker_connectivity_wait_active.store(false, .seq_cst);
self.recovery_continuation_ready = false;
},
.tool_lifecycle => |lifecycle| switch (lifecycle) {
.turn_finished => self.worker_connectivity_wait_active.store(false, .seq_cst),
.provisional, .authoritative_started, .progress, .terminal => return,
},
else => return,
}
}
pub fn latchFinalizationFailure(self: *WorkerRuntime, failure: FinalizationFailure) void {
std.debug.assert(failure.turn_id != 0);
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.latchFinalizationFailureLocked(failure);
}
fn latchFinalizationFailureLocked(self: *WorkerRuntime, failure: FinalizationFailure) void {
if (self.finalization_failure == null) {
self.finalization_failure = failure;
} else {
debug_trace.logf(
"worker",
"duplicate finalization failure ignored turn_id={d} first_turn_id={d}",
.{ failure.turn_id, self.finalization_failure.?.turn_id },
);
}
self.worker_stop_requested = true;
self.steering_cancel_turn_id = null;
self.worker_cancel_requested.store(true, .seq_cst);
self.stopCompactionActivityLocked();
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn interactiveAdmissionSnapshot(self: *WorkerRuntime) InteractiveAdmissionSnapshot {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.finalization_failure) |failure| {
return .{ .finalization_failed = failure };
}
if (self.worker_stop_requested) return .stopped;
return .open;
}
pub fn takeEventBatch(self: *WorkerRuntime) WorkerEventBatch {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const events = self.worker_events;
self.worker_events = .empty;
return .{
.events = events,
.cancelled_turn_id = if (self.worker_cancel_requested.load(.seq_cst) and
self.active_turn_id != 0 and
self.steering_cancel_turn_id != self.active_turn_id)
self.active_turn_id
else
null,
};
}
pub fn takeEvents(self: *WorkerRuntime) std.ArrayList(WorkerEvent) {
return self.takeEventBatch().events;
}
pub fn enqueuePrompt(self: *WorkerRuntime, alloc: std.mem.Allocator, prompt: QueuedPrompt) !void {
try self.admitPrompt(alloc, prompt, false);
}
pub fn admitInteractivePrompt(self: *WorkerRuntime, alloc: std.mem.Allocator, prompt: QueuedPrompt) !void {
try self.admitPrompt(alloc, prompt, true);
}
/// Takes prompt ownership only on true. Does not cancel an in-flight operation
/// or fall back to the ordinary FIFO of an ephemeral direct worker.
pub fn admitActiveSteering(self: *WorkerRuntime, alloc: std.mem.Allocator, prompt: QueuedPrompt) !bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (!self.worker_processing or self.active_turn_id == 0 or
self.direct_steering_closed or self.worker_stop_requested or
self.worker_cancel_requested.load(.seq_cst)) return false;
var queued = prompt;
queued.delivery = .{ .active_turn = self.active_turn_id };
try self.enqueuePromptLocked(alloc, queued);
return true;
}
pub fn enqueueContextCompaction(
self: *WorkerRuntime,
task: ContextCompactionTask,
) !void {
var queued = task;
if (queued.turn_id == 0) queued.turn_id = debug_trace.nextTurnId();
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.finalization_failure != null) return error.TurnFinalizationDeliveryFailed;
if (self.worker_stop_requested) return error.WorkerStopped;
if (self.worker_processing or self.queued_prompts.items.len > 0 or
self.queued_context_compaction != null)
{
return error.WorkerBusy;
}
if (queued.operation_id == null) queued.operation_id = self.compaction_presentation.begin(.manual, queued.turn_id, io_mod.milliTimestamp());
self.compaction_presentation.queued(queued.operation_id.?, queued.turn_id, io_mod.milliTimestamp());
self.queued_context_compaction = queued;
debug_trace.eventf(
"worker",
"context_compaction_enqueue",
.{ .turn_id = queued.turn_id },
"queue_depth=1",
.{},
);
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn contextCompactionStatus(self: *WorkerRuntime) ContextCompactionStatus {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.active_context_compaction) return .running;
if (self.queued_context_compaction != null) return .queued;
return .idle;
}
pub fn cancelContextCompaction(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
if (self.queued_context_compaction) |task| {
self.queued_context_compaction = null;
if (task.operation_id) |id| self.compaction_presentation.settle(id, .{ .outcome = .cancelled }, io_mod.milliTimestamp());
self.worker_cond.broadcast(io_mod.getIo());
self.worker_mutex.unlock(io_mod.getIo());
freeContextCompactionTask(alloc, task);
debug_trace.eventf(
"context_compaction",
"cancelled",
.{},
"phase=queued",
.{},
);
return true;
}
if (self.active_context_compaction) {
self.worker_cancel_requested.store(true, .seq_cst);
self.stopCompactionActivityLocked();
self.worker_cond.broadcast(io_mod.getIo());
self.worker_mutex.unlock(io_mod.getIo());
debug_trace.eventf(
"context_compaction",
"cancel_requested",
.{},
"phase=running",
.{},
);
return true;
}
self.worker_mutex.unlock(io_mod.getIo());
return false;
}
/// Transfers `prompt` to the active turn when steering is requested and the
/// turn still accepts guidance. Otherwise it enters the ordinary FIFO.
/// A steer interrupts the model stream immediately only when no wait
/// boundary is active; a running tool or an in-flight compaction defers
/// delivery to the next steering boundary instead of being cancelled.
fn admitPrompt(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
prompt: QueuedPrompt,
steer_if_active: bool,
) !void {
var queued = prompt;
if (queued.turn_id == 0) queued.turn_id = debug_trace.nextTurnId();
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.finalization_failure != null) return error.TurnFinalizationDeliveryFailed;
if (self.worker_stop_requested) return error.WorkerStopped;
if (queued.recovery_checkpoint != null and
!recoveryContinuationAdmission(
self.worker_processing,
self.queuedWorkCountLocked(),
self.recovery_continuation_ready,
))
{
return error.RecoveryBusy;
}
queued.agent_settings = self.agent_turn_settings;
var interrupt_after_admission = false;
const explicitly_cancelled = self.worker_cancel_requested.load(.seq_cst) and
self.steering_cancel_turn_id != self.active_turn_id;
if (steer_if_active and self.worker_processing and self.active_turn_id != 0 and
!explicitly_cancelled)
{
queued.delivery = .{ .active_turn = self.active_turn_id };
interrupt_after_admission = !self.hasSteeringWaitBoundaryLocked();
}
try self.enqueuePromptLocked(alloc, queued);
if (steer_if_active and self.worker_processing) {
debug_trace.eventf("worker", "steering_admission", .{ .turn_id = self.active_turn_id }, "queued_turn_id={d} targeted={} plain={} tool_boundary={} compaction_active={} interrupt_model={}", .{ queued.turn_id, queued.delivery.activeTurnId() == self.active_turn_id, sameTurnSteeringEligible(queued), self.hasActiveToolBoundaryLocked(), self.compactionInFlightLocked(), interrupt_after_admission });
}
if (interrupt_after_admission) {
self.steering_cancel_turn_id = if (sameTurnSteeringEligible(queued))
self.active_turn_id
else
null;
self.worker_cancel_requested.store(true, .seq_cst);
}
}
fn sameTurnSteeringEligible(prompt: QueuedPrompt) bool {
return prompt.images.len == 0 and
prompt.skill_bindings.len == 0 and
prompt.skill_display_spans.len == 0;
}
fn enqueuePromptLocked(self: *WorkerRuntime, alloc: std.mem.Allocator, queued_value: QueuedPrompt) !void {
var queued = queued_value;
try self.queued_prompts.ensureUnusedCapacity(alloc, 1);
const supplied_history = queued.history;
queued.history = &.{};
if (self.queued_prompts.items.len == 0) {
std.debug.assert(self.queued_history.len == 0);
self.queued_history = supplied_history;
} else {
types.freeHistoryTurnSlice(alloc, supplied_history);
}
self.queued_prompts.appendAssumeCapacity(queued);
debug_trace.logf(
"worker",
"queued prompt bytes={d} queue_depth={d} fast_mode={s} effort={s}",
.{ queued.prompt.len, self.queuedWorkCountLocked(), if (queued.agent_settings.fast_mode) "true" else "false", queued.agent_settings.effort.label() },
);
debug_trace.eventf(
"worker",
"prompt_enqueue",
.{ .turn_id = queued.turn_id },
"prompt_bytes={d} queue_depth={d} fast_mode={s} effort={s}",
.{ queued.prompt.len, self.queuedWorkCountLocked(), if (queued.agent_settings.fast_mode) "true" else "false", queued.agent_settings.effort.label() },
);
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn hasPendingPlainSteering(self: *WorkerRuntime) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (!self.worker_processing or self.active_turn_id == 0 or self.worker_stop_requested) return false;
var pending = false;
for (self.queued_prompts.items) |prompt| {
if (prompt.delivery.activeTurnId() != self.active_turn_id) continue;
if (!sameTurnSteeringEligible(prompt)) return false;
pending = true;
}
return pending;
}
/// Classifies and drains steering at one observed agent boundary. Returned
/// text is allocator-owned; handoff leaves the queued prompt worker-owned.
pub fn takeSteeringBoundary(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
turn_id: u64,
kind: SteeringBoundaryKind,
) !SteeringBoundaryResult {
return self.takeSteeringBoundaryInto(alloc, alloc, turn_id, kind);
}
/// Queue/event storage uses alloc; returned guidance uses result_alloc.
pub fn takeSteeringBoundaryInto(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
result_alloc: std.mem.Allocator,
turn_id: u64,
kind: SteeringBoundaryKind,
) !SteeringBoundaryResult {
const result = try self.takeSteeringBoundaryIntoInner(alloc, result_alloc, turn_id, kind);
debug_trace.eventf("worker", "steering_boundary_check", .{ .turn_id = turn_id }, "kind={s} outcome={s}", .{ @tagName(kind), @tagName(result) });
return result;
}
fn takeSteeringBoundaryIntoInner(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
result_alloc: std.mem.Allocator,
turn_id: u64,
kind: SteeringBoundaryKind,
) !SteeringBoundaryResult {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const cancel_requested = self.worker_cancel_requested.load(.seq_cst);
if (!self.worker_processing or self.active_turn_id != turn_id) {
return if (kind == .cancelled and cancel_requested) .interrupt else .none;
}
switch (kind) {
.cancelled => {
if (!cancel_requested) return .none;
if (self.steering_cancel_turn_id != turn_id) return .interrupt;
const messages = try self.takeSteeringLocked(alloc, result_alloc, turn_id);
if (messages.len == 0) return .interrupt;
self.steering_cancel_turn_id = null;
self.worker_cancel_requested.store(false, .seq_cst);
return .{ .continue_turn = messages };
},
.model, .finalizing => {
if (cancel_requested) return .none;
for (self.queued_prompts.items) |prompt| {
if (prompt.delivery.activeTurnId() == turn_id and
!sameTurnSteeringEligible(prompt)) return .handoff;
}
const messages = try self.takeSteeringLocked(alloc, result_alloc, turn_id);
if (kind == .finalizing and messages.len == 0) self.direct_steering_closed = true;
return if (messages.len == 0)
.none
else
.{ .continue_turn = messages };
},
}
}
fn takeSteeringLocked(self: *WorkerRuntime, alloc: std.mem.Allocator, result_alloc: std.mem.Allocator, turn_id: u64) ![][]u8 {
var steering_count: usize = 0;
for (self.queued_prompts.items) |prompt| {
if (prompt.delivery.activeTurnId() != turn_id) continue;
if (!sameTurnSteeringEligible(prompt)) return &.{};
steering_count += 1;
}
if (steering_count == 0) return &.{};
const messages = try result_alloc.alloc([]u8, steering_count);
var copied: usize = 0;
errdefer {
for (messages[0..copied]) |text| result_alloc.free(text);
result_alloc.free(messages);
}
const events = try alloc.alloc(WorkerEvent, steering_count);
var event_count: usize = 0;
errdefer {
for (events[0..event_count]) |event| freeWorkerEvent(alloc, event);
alloc.free(events);
}
for (self.queued_prompts.items) |prompt| {
if (prompt.delivery.activeTurnId() != turn_id) continue;
messages[copied] = try result_alloc.dupe(u8, prompt.prompt);
copied += 1;
if (prompt.steering_receipt == null) {
events[event_count] = .{
.append_user_feedback = try alloc.dupe(u8, prompt.prompt),
};
event_count += 1;
}
}
try self.worker_events.ensureUnusedCapacity(alloc, event_count);
for (events[0..event_count]) |event| self.worker_events.appendAssumeCapacity(event);
alloc.free(events);
var index: usize = 0;
while (index < self.queued_prompts.items.len) {
if (self.queued_prompts.items[index].delivery.activeTurnId() != turn_id) {
index += 1;
continue;
}
const prompt = self.queued_prompts.orderedRemove(index);
if (prompt.steering_receipt) |receipt| {
receipt.state.store(.applied, .seq_cst);
debug_trace.eventf("subagent", "feedback_applied", .{}, "operation={s}", .{receipt.operation_id});
}
freeQueuedPrompt(alloc, prompt);
}
if (self.queued_prompts.items.len == 0) {
types.freeHistoryTurnSlice(alloc, self.queued_history);
self.queued_history = &.{};
}
debug_trace.eventf("worker", "prompt_steering_consumed", .{ .turn_id = turn_id }, "count={d}", .{messages.len});
self.worker_cond.broadcast(io_mod.getIo());
return messages;
}
/// Hold turn admission only when no turn is active or queued.
pub fn tryHoldTurnStart(self: *WorkerRuntime) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.worker_processing or self.queuedWorkCountLocked() > 0 or self.turn_start_held) {
return false;
}
self.turn_start_held = true;
debug_trace.logf("worker", "turn start hold acquired", .{});
return true;
}
pub fn releaseTurnStartHold(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (!self.turn_start_held) return;
self.turn_start_held = false;
debug_trace.logf("worker", "turn start hold released", .{});
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn removeQueuedPrompt(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
turn_id: u64,
retained_images: []const types.ImageAttachment,
) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
var removed: ?QueuedPrompt = null;
for (self.queued_prompts.items, 0..) |queued, index| {
if (queued.turn_id != turn_id) continue;
removed = self.queued_prompts.orderedRemove(index);
break;
}
if (removed != null and self.queued_prompts.items.len == 0) {
types.freeHistoryTurnSlice(alloc, self.queued_history);
self.queued_history = &.{};
}
const remaining = self.queuedWorkCountLocked();
self.worker_mutex.unlock(io_mod.getIo());
const prompt = removed orelse return false;
discardQueuedPrompt(alloc, prompt, retained_images);
debug_trace.eventf(
"worker",
"queued_prompt_removed",
.{ .turn_id = turn_id },
"remaining={d}",
.{remaining},
);
return true;
}
/// Removes the newest queued text-only steering prompt that still waits for
/// a tool or compaction boundary and returns an allocator-owned copy of its
/// text so the composer can restore it for editing. Returns null when
/// nothing is safely retractable: no active turn, no wait boundary (an
/// immediate steer is already committed to its interrupt), a cancel
/// already in flight, or only handoff-class steers with images or skill
/// bindings.
pub fn popQueuedSteerForEdit(self: *WorkerRuntime, alloc: std.mem.Allocator) !?[]u8 {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (!self.worker_processing or self.active_turn_id == 0) return null;
if (!self.hasSteeringWaitBoundaryLocked()) return null;
if (self.worker_cancel_requested.load(.seq_cst)) return null;
var retractable: ?usize = null;
var index: usize = self.queued_prompts.items.len;
while (index > 0) {
index -= 1;
const prompt = self.queued_prompts.items[index];
if (prompt.delivery.activeTurnId() != self.active_turn_id) continue;
if (!sameTurnSteeringEligible(prompt)) continue;
retractable = index;
break;
}
const remove_index = retractable orelse return null;
const text = try alloc.dupe(u8, self.queued_prompts.items[remove_index].prompt);
const prompt = self.queued_prompts.orderedRemove(remove_index);
if (self.queued_prompts.items.len == 0) {
types.freeHistoryTurnSlice(alloc, self.queued_history);
self.queued_history = &.{};
}
const turn_id = prompt.turn_id;
const remaining = self.queuedWorkCountLocked();
freeQueuedPrompt(alloc, prompt);
debug_trace.eventf(
"worker",
"prompt_steering_retracted",
.{ .turn_id = turn_id },
"remaining={d}",
.{remaining},
);
self.worker_cond.broadcast(io_mod.getIo());
return text;
}
pub fn waitAndTakeNextPrompt(self: *WorkerRuntime, alloc: std.mem.Allocator) !?QueuedPrompt {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
while ((self.queued_prompts.items.len == 0 or self.turn_start_held) and
!self.worker_stop_requested)
{
self.worker_processing = false;
self.active_turn_id = 0;
self.tool_phase_step_id = null;
self.worker_cond.wait(io_mod.getIo(), &self.worker_mutex) catch break;
}
return self.takeNextPromptLocked(alloc);
}
pub fn waitAndTakeNextWork(self: *WorkerRuntime, alloc: std.mem.Allocator) !?WorkItem {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
while (((self.queued_prompts.items.len == 0 and
self.queued_context_compaction == null) or self.turn_start_held) and
!self.worker_stop_requested)
{
self.worker_processing = false;
self.active_turn_id = 0;
self.tool_phase_step_id = null;
self.worker_cond.wait(io_mod.getIo(), &self.worker_mutex) catch break;
}
return self.takeNextWorkLocked(alloc);
}
/// Nonblocking queue take for single-threaded hosts. Returns null while the
/// queue is empty, held, or stopped.
pub fn tryTakeNextPrompt(self: *WorkerRuntime, alloc: std.mem.Allocator) !?QueuedPrompt {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.queued_prompts.items.len == 0 or
self.turn_start_held or
self.worker_stop_requested)
{
return null;
}
return self.takeNextPromptLocked(alloc);
}
pub fn tryTakeNextWork(self: *WorkerRuntime, alloc: std.mem.Allocator) !?WorkItem {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if ((self.queued_prompts.items.len == 0 and
self.queued_context_compaction == null) or self.turn_start_held or
self.worker_stop_requested)
{
return null;
}
return self.takeNextWorkLocked(alloc);
}
fn takeNextWorkLocked(self: *WorkerRuntime, alloc: std.mem.Allocator) !?WorkItem {
if (self.worker_stop_requested) return null;
if (self.queued_context_compaction) |task| {
self.queued_context_compaction = null;
self.worker_cancel_requested.store(false, .seq_cst);
self.worker_recovery_pause_requested.store(false, .seq_cst);
self.worker_connectivity_wait_active.store(false, .seq_cst);
self.recovery_continuation_ready = false;
self.worker_processing = true;
self.active_turn_id = task.turn_id;
self.tool_phase_step_id = null;
self.active_context_compaction = true;
if (task.operation_id) |id| self.compaction_presentation.running(id, .preparation);
debug_trace.eventf(
"worker",
"context_compaction_begin",
.{ .turn_id = task.turn_id },
"remaining_queue={d} cancel_reset=true",
.{self.queuedWorkCountLocked()},
);
return .{ .compact_context = task };
}
const prompt = (try self.takeNextPromptLocked(alloc)) orelse return null;
return .{ .prompt = prompt };
}
fn takeNextPromptLocked(self: *WorkerRuntime, alloc: std.mem.Allocator) !?QueuedPrompt {
if (self.worker_stop_requested or self.queued_prompts.items.len == 0) return null;
const queued = self.queued_prompts.items[0];
const transfer_history = self.queued_prompts.items.len == 1;
const job_history = if (transfer_history)
self.queued_history
else
try session_runtime.snapshotOwnedContextHistory(
alloc,
self.queued_history,
0,
0,
);
errdefer if (!transfer_history) {
types.freeHistoryTurnSlice(alloc, job_history);
};
if (queued.recovery_checkpoint == null and queued.user_prompt_already_presented) {
try self.worker_events.append(alloc, .{
.begin_presented_prompt = queued.turn_id,
});
} else if (queued.recovery_checkpoint == null) {
const begin_prompt = try types.dupeUserTurn(alloc, .{ .text = queued.prompt, .images = queued.images });
errdefer types.freeUserTurn(alloc, begin_prompt);
if (queued.skill_bindings.len > 0 or queued.skill_display_spans.len > 0) {
const skill_bindings = try dupeSkillBindings(alloc, queued.skill_bindings);
errdefer freeSkillBindings(alloc, skill_bindings);
const skill_display_spans = try dupeSkillDisplaySpans(alloc, queued.skill_display_spans);
errdefer freeSkillDisplaySpans(alloc, skill_display_spans);
try self.worker_events.append(alloc, .{
.begin_prompt_with_skill_bindings = .{
.prompt = begin_prompt,
.skill_bindings = skill_bindings,
.skill_display_spans = skill_display_spans,
},
});
} else {
try self.worker_events.append(alloc, .{ .begin_prompt = begin_prompt });
}
}
var job = self.queued_prompts.orderedRemove(0);
if (transfer_history) self.queued_history = &.{};
job.history = job_history;
self.steering_cancel_turn_id = null;
self.worker_cancel_requested.store(false, .seq_cst);
self.worker_recovery_pause_requested.store(false, .seq_cst);
self.worker_connectivity_wait_active.store(false, .seq_cst);
self.recovery_continuation_ready = false;
self.worker_processing = true;
self.active_turn_id = job.turn_id;
self.tool_phase_step_id = null;
if (job.delivery.isContinuation()) {
for (self.queued_prompts.items) |*prompt| {
if (!prompt.delivery.isContinuation() or !sameTurnSteeringEligible(prompt.*)) continue;
prompt.delivery = .{ .active_turn = job.turn_id };
}
}
self.active_context_compaction = false;
debug_trace.logf(
"worker",
"begin prompt bytes={d} remaining_queue={d} fast_mode={s} effort={s}",
.{ job.prompt.len, self.queuedWorkCountLocked(), if (job.agent_settings.fast_mode) "true" else "false", job.agent_settings.effort.label() },
);
debug_trace.eventf(
"worker",
"worker_begin",
.{ .turn_id = job.turn_id },
"prompt_bytes={d} remaining_queue={d} cancel_reset=true fast_mode={s} effort={s}",
.{ job.prompt.len, self.queuedWorkCountLocked(), if (job.agent_settings.fast_mode) "true" else "false", job.agent_settings.effort.label() },
);
return job;
}
pub fn finishProcessing(self: *WorkerRuntime) void {
debug_trace.logf("worker", "finish processing queued={d}", .{self.queuedPromptCount()});
self.worker_mutex.lockUncancelable(io_mod.getIo());
// Close steering admission without moving entries, preserving the exact
// order in which steering and ordinary prompts were submitted.
const finished_turn_id = self.active_turn_id;
self.finishCompactionActivityLocked(finished_turn_id);
for (self.queued_prompts.items) |*prompt| {
if (prompt.delivery.activeTurnId() == finished_turn_id) {
prompt.delivery = .continuation;
debug_trace.eventf("worker", "steering_deferred_to_next_turn", .{ .turn_id = finished_turn_id }, "queued_turn_id={d} prompt_bytes={d}", .{ prompt.turn_id, prompt.prompt.len });
}
}
self.worker_processing = false;
self.active_turn_id = 0;
self.steering_cancel_turn_id = null;
self.clearActiveToolCallsLocked();
self.tool_phase_step_id = null;
self.active_context_compaction = false;
self.worker_connectivity_wait_active.store(false, .seq_cst);
self.worker_cond.broadcast(io_mod.getIo());
self.worker_mutex.unlock(io_mod.getIo());
}
/// Marks an agent turn that is executed directly rather than dequeued by
/// the interactive worker loop. The caller must pair a successful begin
/// with `finishProcessing`.
pub fn beginDirectProcessing(self: *WorkerRuntime, turn_id: u64) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.worker_processing or self.worker_stop_requested) return false;
self.direct_steering_closed = false;
self.worker_cancel_requested.store(false, .seq_cst);
self.worker_recovery_pause_requested.store(false, .seq_cst);
self.worker_connectivity_wait_active.store(false, .seq_cst);
self.worker_processing = true;
self.active_turn_id = turn_id;
self.tool_phase_step_id = null;
return true;
}
const ActiveToolTransition = union(enum) {
none,
started: []u8,
terminal: []const u8,
clear,
fn deinit(self: *ActiveToolTransition, alloc: std.mem.Allocator) void {
switch (self.*) {
.started => |call_id| alloc.free(call_id),
.none, .terminal, .clear => {},
}
self.* = .none;
}
};
fn prepareActiveToolTransitionLocked(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
event: WorkerEvent,
) !ActiveToolTransition {
return switch (event) {
.tool_lifecycle => |lifecycle| switch (lifecycle) {
.authoritative_started => |started| blk: {
if (started.id.turn_id != self.active_turn_id or
self.active_tool_calls.contains(started.id.call_id))
{
break :blk .none;
}
if (self.active_tool_allocator == null) {
self.active_tool_allocator = alloc;
}
try self.active_tool_calls.ensureUnusedCapacity(alloc, 1);
break :blk .{ .started = try alloc.dupe(u8, started.id.call_id) };
},
.terminal => |terminal| if (terminal.id.turn_id == self.active_turn_id and
self.active_tool_calls.contains(terminal.id.call_id))
.{ .terminal = terminal.id.call_id }
else
.none,
.turn_finished => |finished| if (finished.turn_id == self.active_turn_id) .clear else .none,
.provisional, .progress => .none,
},
else => .none,
};
}
fn applyActiveToolTransitionLocked(
self: *WorkerRuntime,
transition: ActiveToolTransition,
) void {
switch (transition) {
.none => {},
.started => |call_id| {
self.active_tool_calls.putAssumeCapacity(call_id, {});
},
.terminal => |call_id| {
const removed = self.active_tool_calls.fetchRemove(call_id) orelse return;
std.debug.assert(self.active_tool_allocator != null);
const alloc = self.active_tool_allocator.?;
alloc.free(removed.key);
},
.clear => self.clearActiveToolCallsLocked(),
}
}
fn applyToolBoundaryPhaseLocked(self: *WorkerRuntime, event: WorkerEvent) void {
switch (event) {
.turn_phase_update => |update| if (update.turn_id == self.active_turn_id) {
switch (update.phase) {
.running => self.tool_phase_step_id = update.step_id,
.waiting_for_subagent => {},
.thinking, .generating => if (self.tool_phase_step_id != update.step_id) {
self.tool_phase_step_id = null;
},
}
},
else => {},
}
}
fn hasActiveToolBoundaryLocked(self: *const WorkerRuntime) bool {
return self.tool_phase_step_id != null or self.active_tool_calls.count() > 0;
}
/// True while a context compaction owns the worker: either the compaction
/// work item is running, or the active turn is inside a compaction
/// transaction. Compaction reports through the activity channel rather
/// than the tool/phase event feed, so it is consulted separately.
fn compactionInFlightLocked(self: *const WorkerRuntime) bool {
if (self.active_context_compaction) return true;
if (self.compaction_presentation.snapshot.operation) |operation| return operation.active();
return false;
}
/// Steering must wait whenever the active work cannot absorb guidance right
/// now: a running tool or tool-producing step, or a compaction in flight.
/// Cancelling either would discard in-progress work the user expects to
/// finish; the message is delivered at the next boundary instead.
fn hasSteeringWaitBoundaryLocked(self: *const WorkerRuntime) bool {
return self.hasActiveToolBoundaryLocked() or self.compactionInFlightLocked();
}
fn clearActiveToolCallsLocked(self: *WorkerRuntime) void {
const alloc = self.active_tool_allocator orelse {
std.debug.assert(self.active_tool_calls.count() == 0);
return;
};
var keys = self.active_tool_calls.keyIterator();
while (keys.next()) |key| alloc.free(@constCast(key.*));
self.active_tool_calls.deinit(alloc);
self.active_tool_calls = .empty;
self.active_tool_allocator = null;
}
pub fn waitUntilIdle(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
while (self.worker_processing) {
self.worker_cond.wait(io_mod.getIo(), &self.worker_mutex) catch break;
}
}
pub fn isProcessing(self: *WorkerRuntime) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.worker_processing;
}
pub fn snapshotState(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
) permission_request.RequestCloneError!StateSnapshot {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const cancel_requested = self.worker_cancel_requested.load(.seq_cst);
return .{
.processing = self.worker_processing,
.active_turn_id = self.active_turn_id,
.queued_count = self.queuedWorkCountLocked(),
.pending_event_count = self.worker_events.items.len,
.cancel_requested = cancel_requested,
.cancel_continues_turn = cancel_requested and
self.steering_cancel_turn_id == self.active_turn_id,
.pending_permission_request = if (self.permissionRequestAwaitingDecisionLocked()) blk: {
const request = &self.pending_permission_request_shared.?;
break :blk try permission_request.OwnedPermissionRequest.dupe(alloc, request.view());
} else null,
.pending_permission_review = if (self.permissionRequestAwaitingDecisionLocked())
if (self.pending_permission_review) |review|
.{
.request_id = self.pending_permission_request_shared.?.id,
.review = review,
}
else
null
else
null,
};
}
pub fn activeTurnId(self: *WorkerRuntime) u64 {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.active_turn_id;
}
/// Returns one coherent, allocator-owned steering presentation snapshot.
pub fn snapshotSteeringPresentation(self: *WorkerRuntime, alloc: std.mem.Allocator) !SteeringPresentationSnapshot {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
var steering_count: usize = 0;
for (self.queued_prompts.items) |prompt| {
if (prompt.delivery.isSteering()) steering_count += 1;
}
var snapshot: SteeringPresentationSnapshot = .{
.waits_for_boundary = steering_count > 0 and self.hasSteeringWaitBoundaryLocked(),
};
errdefer snapshot.deinit(alloc);
snapshot.pending_feedback = pending: {
var count: usize = 0;
for (self.worker_events.items) |event| {
if (event == .append_user_feedback) count += 1;
}
if (count == 0) break :pending &.{};
const feedback = try alloc.alloc([]u8, count);
errdefer alloc.free(feedback);
var written: usize = 0;
errdefer for (feedback[0..written]) |message| alloc.free(message);
for (self.worker_events.items) |event| {
if (event == .append_user_feedback) {
feedback[written] = try alloc.dupe(u8, event.append_user_feedback);
written += 1;
}
}
break :pending feedback;
};
if (steering_count == 0) return snapshot;
const messages = try alloc.alloc([]u8, steering_count);
var copied: usize = 0;
errdefer {
for (messages[0..copied]) |message| alloc.free(message);
alloc.free(messages);
}
for (self.queued_prompts.items) |prompt| {
if (!prompt.delivery.isSteering()) continue;
messages[copied] = try alloc.dupe(u8, prompt.prompt);
copied += 1;
}
snapshot.messages = messages;
return snapshot;
}
pub fn queuedPromptCount(self: *WorkerRuntime) usize {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.queuedWorkCountLocked();
}
pub fn clearQueuedPrompts(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
retained_images: []const types.ImageAttachment,
) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.clearQueuedPromptsLocked(alloc, retained_images);
}
/// Clears the queue while atomically transferring deletion responsibility
/// for a matching active or finished turn to the supplied retained images.
pub fn clearQueuedPromptsForSessionTransition(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
turn_id: u64,
retained_images: []const types.ImageAttachment,
) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (retained_images.len > 0) {
_ = self.preservePromptSnapshotsLocked(turn_id, retained_images);
}
self.clearQueuedPromptsLocked(alloc, retained_images);
}
fn clearQueuedPromptsLocked(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
retained_images: []const types.ImageAttachment,
) void {
const dropped = self.queuedWorkCountLocked();
if (dropped > 0) {
debug_trace.logf("worker", "clear queued prompts dropped={d}", .{dropped});
debug_trace.eventf("worker", "queued_prompts_cleared", .{}, "dropped={d}", .{dropped});
}
for (self.queued_prompts.items) |prompt| {
discardQueuedPrompt(alloc, prompt, retained_images);
}
self.queued_prompts.clearRetainingCapacity();
types.freeHistoryTurnSlice(alloc, self.queued_history);
self.queued_history = &.{};
if (self.queued_context_compaction) |task| {
if (task.operation_id) |id| self.compaction_presentation.settle(id, .{ .outcome = .cancelled }, io_mod.milliTimestamp());
freeContextCompactionTask(alloc, task);
self.queued_context_compaction = null;
}
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn discardEvents(self: *WorkerRuntime, alloc: std.mem.Allocator) void {
var events = self.takeEvents();
defer events.deinit(alloc);
for (events.items) |event| freeWorkerEvent(alloc, event);
}
pub fn syncQueuedPromptModel(self: *WorkerRuntime, alloc: std.mem.Allocator, model: []const u8) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
for (self.queued_prompts.items) |*prompt| {
const next_model = try alloc.dupe(u8, model);
alloc.free(prompt.model);
prompt.model = next_model;
}
}
pub fn syncQueuedPromptPermissionSnapshot(self: *WorkerRuntime, snapshot: PermissionSnapshot) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
for (self.queued_prompts.items) |*prompt| {
prompt.permission_mode = snapshot.mode;
}
}
pub fn syncQueuedPromptFastMode(self: *WorkerRuntime, enabled: bool) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.agent_turn_settings.fast_mode = enabled;
for (self.queued_prompts.items) |*prompt| prompt.agent_settings.fast_mode = enabled;
}
pub fn syncQueuedPromptEffort(self: *WorkerRuntime, effort: types.ReasoningEffort) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.agent_turn_settings.effort = effort;
for (self.queued_prompts.items) |*prompt| prompt.agent_settings.effort = effort;
}
pub fn setActiveAgentTurnSettings(self: *WorkerRuntime, settings: AgentTurnSettings) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.active_agent_turn_settings = settings;
}
pub fn clearActiveAgentTurnSettings(self: *WorkerRuntime) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
self.active_agent_turn_settings = null;
}
pub fn effectiveAgentTurnSettings(self: *WorkerRuntime) AgentTurnSettings {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.active_agent_turn_settings orelse self.agent_turn_settings;
}
pub fn syncQueuedPromptPermissionState(self: *WorkerRuntime, alloc: std.mem.Allocator, grants: []const types.PermissionGrant, snapshot: PermissionSnapshot) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
for (self.queued_prompts.items) |*prompt| {
const next_grants = try types.dupePermissionGrantSlice(alloc, grants);
types.freePermissionGrantSlice(alloc, prompt.grants);
prompt.grants = next_grants;
prompt.permission_mode = snapshot.mode;
}
}
pub fn propagateHistoryTurn(self: *WorkerRuntime, alloc: std.mem.Allocator, turn: types.HistoryTurn, max_history_turns: usize) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
try self.propagateHistoryTurnLocked(alloc, turn, max_history_turns, null);
}
pub fn commitContextCompaction(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
summary: types.CompactedSummaryHistoryTurn,
active_prefix: ?types.AssistantHistoryTurn,
retained_from: ?types.ContextHistoryCut,
max_history_turns: usize,
) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const result = try self.awaitHistoryPublicationLocked(alloc, .{
.context_compaction = .{
.turn_id = self.active_turn_id,
.summary = summary,
.active_prefix = active_prefix,
.retained_from = retained_from,
.max_history_turns = max_history_turns,
},
});
std.debug.assert(result == null);
}
pub fn prepareFreshPrompt(self: *WorkerRuntime, alloc: std.mem.Allocator, preparation: FreshPromptPreparation) !FreshPromptHistory {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
var request = preparation;
request.turn_id = self.active_turn_id;
return (try self.awaitHistoryPublicationLocked(alloc, .{ .prepare_fresh_prompt = request })) orelse error.InvalidHistoryPublication;
}
fn awaitHistoryPublicationLocked(self: *WorkerRuntime, alloc: std.mem.Allocator, event: WorkerEvent) !?FreshPromptHistory {
if (self.worker_stop_requested or self.isCancelRequested()) return error.Aborted;
std.debug.assert(self.history_publication_response == .idle);
const owned_event = try dupeWorkerEvent(alloc, event);
var owns_event = true;
defer if (owns_event) freeWorkerEvent(alloc, owned_event);
try self.worker_events.ensureUnusedCapacity(alloc, 1);
self.history_publication_response = .{ .waiting = self.active_turn_id };
defer {
if (self.history_publication_response == .committed) {
if (self.history_publication_response.committed) |*snapshot| snapshot.deinit(alloc);
}
self.history_publication_response = .idle;
}
self.worker_events.appendAssumeCapacity(owned_event);
owns_event = false;
self.worker_cond.broadcast(io_mod.getIo());
while (self.history_publication_response == .waiting and
!self.worker_stop_requested and !self.isCancelRequested())
{
try self.worker_cond.wait(io_mod.getIo(), &self.worker_mutex);
}
switch (self.history_publication_response) {
.committed => |result| {
self.history_publication_response = .idle;
return result;
},
.failed => |err| return err,
.waiting, .idle => return error.Aborted,
}
}
pub fn failPendingHistoryPublication(self: *WorkerRuntime, err: anyerror) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.history_publication_response != .waiting) return;
self.history_publication_response = .{ .failed = err };
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn resolveContextCompaction(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
event: ContextCompaction,
ctx: *anyopaque,
handler: ContextCompactionHandler,
) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.history_publication_response != .waiting or
self.history_publication_response.waiting != event.turn_id)
{
debug_trace.logf("worker", "compaction delivery dropped reason=stale turn_id={d}", .{event.turn_id});
return;
}
defer self.worker_cond.broadcast(io_mod.getIo());
if (self.worker_stop_requested or self.isCancelRequested()) {
self.history_publication_response = .{ .failed = error.Aborted };
return;
}
self.publishContextCompactionLocked(alloc, event, ctx, handler) catch |err| {
self.history_publication_response = .{ .failed = err };
return;
};
self.history_publication_response = .{ .committed = null };
}
pub fn resolveFreshPrompt(self: *WorkerRuntime, alloc: std.mem.Allocator, event: FreshPromptPreparation, ctx: *anyopaque, handler: FreshPromptHandler) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.history_publication_response != .waiting or self.history_publication_response.waiting != event.turn_id) {
debug_trace.logf("worker", "fresh prompt preparation dropped reason=stale turn_id={d}", .{event.turn_id});
return;
}
defer self.worker_cond.broadcast(io_mod.getIo());
if (self.worker_stop_requested or self.isCancelRequested()) {
self.history_publication_response = .{ .failed = error.Aborted };
return;
}
const snapshot = self.publishFreshPromptLocked(alloc, event, ctx, handler) catch |err| {
self.history_publication_response = .{ .failed = err };
return;
};
self.history_publication_response = .{ .committed = snapshot };
}
pub fn publishFreshPrompt(self: *WorkerRuntime, alloc: std.mem.Allocator, event: FreshPromptPreparation, ctx: *anyopaque, handler: FreshPromptHandler) !FreshPromptHistory {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
return self.publishFreshPromptLocked(alloc, event, ctx, handler);
}
fn publishFreshPromptLocked(self: *WorkerRuntime, alloc: std.mem.Allocator, event: FreshPromptPreparation, ctx: *anyopaque, handler: FreshPromptHandler) !FreshPromptHistory {
const prior_turn = event.prior_turn orelse return handler(ctx, event);
const Publication = struct {
ctx: *anyopaque,
handler: FreshPromptHandler,
event: FreshPromptPreparation,
result: ?FreshPromptHistory = null,
fn commit(raw: *anyopaque) !void {
const self_: *@This() = @ptrCast(@alignCast(raw));
self_.result = try self_.handler(self_.ctx, self_.event);
}
};
var publication: Publication = .{ .ctx = ctx, .handler = handler, .event = event };
try self.propagateHistoryTurnLocked(alloc, prior_turn, 0, .{ .ctx = &publication, .commit_fn = Publication.commit });
return publication.result.?;
}
pub fn publishContextCompaction(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
event: ContextCompaction,
ctx: *anyopaque,
handler: ContextCompactionHandler,
) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
try self.publishContextCompactionLocked(alloc, event, ctx, handler);
}
fn publishContextCompactionLocked(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
event: ContextCompaction,
ctx: *anyopaque,
handler: ContextCompactionHandler,
) !void {
var publication: ContextCompactionPublication = .{ .ctx = ctx, .handler = handler, .event = event };
try self.propagateHistoryTurnLocked(
alloc,
.{ .compacted_summary = event.summary },
event.max_history_turns,
.{ .ctx = &publication, .commit_fn = ContextCompactionPublication.commit, .retained_from = event.retained_from },
);
if (event.active_prefix != null) {
if (self.active_prompt_snapshot_ownership) |ownership| {
_ = ownership.preserve();
}
}
}
fn propagateHistoryTurnLocked(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
turn: types.HistoryTurn,
max_history_turns: usize,
publication: ?HistoryPublication,
) !void {
errdefer |err| if (err == error.SessionPersistenceUncertain) {
if (self.active_prompt_snapshot_ownership) |ownership| {
_ = ownership.preserve();
}
};
if (self.queued_prompts.items.len == 0) {
if (publication) |value| try value.commit();
return;
}
const active_ownership = if (self.active_prompt_snapshot_ownership) |ownership|
try ownership.ensureSharedOwnership(alloc)
else
null;
const cut = if (publication) |value| value.retained_from else null;
const next_history = if (cut) |retained_from| try session_runtime.prepareCompactedHistory(alloc, self.queued_history, turn.compacted_summary, retained_from) else try appendHistoryTurnProjection(
alloc,
self.queued_history,
turn,
max_history_turns,
);
var next_history_owned = true;
defer if (next_history_owned) types.freeHistoryTurnSlice(alloc, next_history);
const prepared = try alloc.alloc(
PreparedHistoryPropagation,
self.queued_prompts.items.len,
);
var prepared_count: usize = 0;
var committed = false;
defer {
if (!committed) {
for (prepared[0..prepared_count]) |entry| entry.deinit(alloc);
}
alloc.free(prepared);
}
while (prepared_count < self.queued_prompts.items.len) : (prepared_count += 1) {
const prompt = self.queued_prompts.items[prepared_count];
const next_image_catalog = try session_runtime.merge_image_catalog_history_turn(
alloc,
prompt.authorized_image_catalog,
turn,
);
errdefer types.freeImageAttachmentSlice(alloc, next_image_catalog);
const next_root_user_intent_context = try auto_classifier_context.refreshQueuedRootUserContext(
alloc,
prompt.prompt,
prompt.root_user_intent_context,
turn,
);
errdefer alloc.free(next_root_user_intent_context);
const next_snapshot_file_ownerships = if (active_ownership) |ownership| blk: {
const ownership_count = std.math.add(
usize,
prompt.snapshot_file_ownerships.len,
1,
) catch return error.OutOfMemory;
const ownerships = try alloc.alloc(
types.SnapshotFileOwnership,
ownership_count,
);
std.mem.copyForwards(
types.SnapshotFileOwnership,
ownerships[0..prompt.snapshot_file_ownerships.len],
prompt.snapshot_file_ownerships,
);
ownership.retain();
ownerships[prompt.snapshot_file_ownerships.len] = ownership;
break :blk ownerships;
} else null;
errdefer {
if (next_snapshot_file_ownerships) |ownerships| {
ownerships[ownerships.len - 1].release();
alloc.free(ownerships);
}
}
prepared[prepared_count] = .{
.root_user_intent_context = next_root_user_intent_context,
.authorized_image_catalog = next_image_catalog,
.snapshot_file_ownerships = next_snapshot_file_ownerships,
.added_snapshot_ownership = if (next_snapshot_file_ownerships) |ownerships|
ownerships[ownerships.len - 1]
else
null,
};
}
if (publication) |value| try value.commit();
if (next_history.len <= self.queued_history.len) {
debug_trace.logf("worker", "queued history replaced by context checkpoint turns={d}", .{self.queued_history.len});
}
types.freeHistoryTurnSlice(alloc, self.queued_history);
self.queued_history = next_history;
next_history_owned = false;
for (self.queued_prompts.items, prepared) |*prompt, next| {
if (turn == .compacted_summary) prompt.unversioned_history_count = 0;
if (prompt.root_user_intent_context.len > 0) alloc.free(prompt.root_user_intent_context);
prompt.root_user_intent_context = next.root_user_intent_context;
types.freeImageAttachmentSlice(alloc, prompt.authorized_image_catalog);
prompt.authorized_image_catalog = next.authorized_image_catalog;
if (next.snapshot_file_ownerships) |ownerships| {
if (prompt.snapshot_file_ownerships.len > 0) {
alloc.free(prompt.snapshot_file_ownerships);
}
prompt.snapshot_file_ownerships = ownerships;
}
}
committed = true;
}
pub fn beginActivePromptSnapshots(
self: *WorkerRuntime,
ownership: *ActivePromptSnapshotOwnership,
) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
std.debug.assert(self.active_prompt_snapshot_ownership == null);
if (self.preserve_prompt_snapshot_turn_id == self.active_turn_id) {
_ = ownership.preserve();
self.preserve_prompt_snapshot_turn_id = null;
}
self.active_prompt_snapshot_ownership = ownership;
}
pub fn handoffActivePromptSnapshots(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
) !?types.SnapshotFileOwnership {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const ownership = self.active_prompt_snapshot_ownership orelse return null;
return ownership.handoff(alloc);
}
pub fn endActivePromptSnapshots(
self: *WorkerRuntime,
ownership: *ActivePromptSnapshotOwnership,
) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
std.debug.assert(self.active_prompt_snapshot_ownership == ownership);
self.active_prompt_snapshot_ownership = null;
ownership.deinit();
}
pub fn preservePromptSnapshots(self: *WorkerRuntime, turn_id: u64, images: []const types.ImageAttachment) void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
_ = self.preservePromptSnapshotsLocked(turn_id, images);
}
fn preservePromptSnapshotsLocked(
self: *WorkerRuntime,
turn_id: u64,
images: []const types.ImageAttachment,
) bool {
if (turn_id == 0 or images.len == 0) return false;
var preserved = false;
if (self.active_turn_id == turn_id) {
if (self.active_prompt_snapshot_ownership) |ownership| {
if (sameImageSnapshots(ownership.images, images)) {
preserved = ownership.preserve();
}
} else {
// The unique active turn bridges queue take to ownership
// registration while both operations remain mutex-serialized.
self.preserve_prompt_snapshot_turn_id = turn_id;
preserved = true;
}
}
for (self.worker_events.items) |*event| {
if (event.* != .finish_prompt) continue;
const finished = &event.finish_prompt;
const finished_images = historyTurnImages(finished.turn);
if (!sameImageSnapshots(finished_images, images)) continue;
if (finished.snapshot_file_ownership) |ownership| {
ownership.transfer();
preserved = true;
}
}
return preserved;
}
pub fn propagateGrant(self: *WorkerRuntime, alloc: std.mem.Allocator, tool_name: []const u8, target_path: []const u8) !void {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
for (self.queued_prompts.items) |*prompt| try appendGrantToQueuedPrompt(alloc, prompt, tool_name, target_path);
}
pub fn requestPermissionBlocking(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
request: permission_request.PermissionRequest,
) permission_request.RequestCloneError!permission_request.OwnedPermissionResponse {
return self.requestPermissionBlockingWithReview(alloc, request, null);
}
pub fn requestPermissionBlockingWithReview(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
request: permission_request.PermissionRequest,
review: ?*const diff_mod.FileReview,
) permission_request.RequestCloneError!permission_request.OwnedPermissionResponse {
return self.requestPermissionBlockingObserved(alloc, request, review, null) catch |err| switch (err) {
error.PermissionRegistrationFailed,
error.PermissionCapacityExceeded,
=> unreachable,
else => |request_err| return request_err,
};
}
pub const PermissionRequestObserver = struct {
context: *anyopaque,
observe_fn: *const fn (
*anyopaque,
*WorkerRuntime,
permission_request.PermissionRequest,
) error{
OutOfMemory,
PermissionRegistrationFailed,
PermissionCapacityExceeded,
}!void,
};
pub fn requestPermissionBlockingObserved(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
request: permission_request.PermissionRequest,
review: ?*const diff_mod.FileReview,
observer: ?PermissionRequestObserver,
) (permission_request.RequestCloneError || error{
PermissionRegistrationFailed,
PermissionCapacityExceeded,
})!permission_request.OwnedPermissionResponse {
var shared_request: ?permission_request.OwnedPermissionRequest =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
request,
);
errdefer if (shared_request) |*owned| owned.deinit(alloc);
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
shared_request.?.id = try self.allocatePermissionRequestIdLocked();
if (self.pending_permission_request_shared) |*old| old.deinit(alloc);
self.pending_permission_request_shared = shared_request.?;
shared_request = null;
self.pending_permission_review = review;
if (self.pending_permission_response) |response| {
self.discardPermissionResponse(response, "request_replaced");
}
self.pending_permission_response = null;
self.pending_permission_waiting = true;
if (observer) |value| {
value.observe_fn(
value.context,
self,
self.pending_permission_request_shared.?.view(),
) catch |err| {
self.pending_permission_waiting = false;
self.pending_permission_request_shared.?.deinit(alloc);
self.pending_permission_request_shared = null;
self.pending_permission_review = null;
return err;
};
}
while (self.pending_permission_response == null and !self.worker_stop_requested) {
self.worker_cond.wait(io_mod.getIo(), &self.worker_mutex) catch break;
}
const response = self.pending_permission_response orelse
permission_request.OwnedPermissionResponse.init(alloc, .deny, null);
self.pending_permission_waiting = false;
self.pending_permission_response = null;
if (self.pending_permission_request_shared) |*old| {
old.deinit(alloc);
self.pending_permission_request_shared = null;
}
self.pending_permission_review = null;
return response;
}
pub fn submitPermissionResponse(
self: *WorkerRuntime,
expected_request_id: u64,
response: permission_request.OwnedPermissionResponse,
) PermissionSubmissionResult {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.permissionSubmissionBlockedLocked(expected_request_id)) |blocked| {
self.discardPermissionResponse(response, @tagName(blocked));
return blocked;
}
std.debug.assert(self.resolvePendingPermissionLocked(response));
return .accepted;
}
pub const PermissionCommitError = error{
OutOfMemory,
PermissionCommitFailed,
PermissionCapacityExceeded,
};
pub const PermissionCommit = struct {
context: *anyopaque,
commit_fn: *const fn (*anyopaque) PermissionCommitError!void,
};
/// Keeps canonical pending-request identity stable across a durable host
/// commit. The response becomes visible and the waiter wakes only after the
/// callback succeeds. On callback failure the owned response is discarded.
pub fn submitPermissionResponseAfterCommit(
self: *WorkerRuntime,
expected_request_id: u64,
response: permission_request.OwnedPermissionResponse,
commit: ?PermissionCommit,
) PermissionCommitError!PermissionSubmissionResult {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.permissionSubmissionBlockedLocked(expected_request_id)) |blocked| {
self.discardPermissionResponse(response, @tagName(blocked));
return blocked;
}
if (commit) |effect| effect.commit_fn(effect.context) catch |err| {
self.discardPermissionResponse(response, "commit_failed");
return err;
};
std.debug.assert(self.resolvePendingPermissionLocked(response));
return .accepted;
}
fn permissionSubmissionBlockedLocked(
self: *const WorkerRuntime,
expected_request_id: u64,
) ?PermissionSubmissionResult {
if (!self.permissionRequestAwaitedLocked() or
self.pending_permission_response != null)
{
return .no_pending;
}
if (self.pending_permission_request_shared.?.id != expected_request_id) {
return .stale;
}
return null;
}
fn allocatePermissionRequestIdLocked(
self: *WorkerRuntime,
) error{RequestIdExhausted}!u64 {
const request_id = self.next_permission_request_id;
if (request_id == 0) return error.RequestIdExhausted;
self.next_permission_request_id = std.math.add(
u64,
request_id,
1,
) catch 0;
return request_id;
}
fn permissionRequestAwaitedLocked(self: *const WorkerRuntime) bool {
return self.worker_processing and
!self.worker_stop_requested and
self.pending_permission_waiting and
self.pending_permission_request_shared != null;
}
fn permissionRequestAwaitingDecisionLocked(
self: *const WorkerRuntime,
) bool {
return self.permissionRequestAwaitedLocked() and
self.pending_permission_response == null;
}
fn resolvePendingPermissionLocked(
self: *WorkerRuntime,
response: permission_request.OwnedPermissionResponse,
) bool {
if (!self.permissionRequestAwaitingDecisionLocked()) {
self.discardPermissionResponse(response, "unawaited");
return false;
}
self.pending_permission_response = response;
self.worker_cond.broadcast(io_mod.getIo());
return true;
}
fn discardPermissionResponse(
_: *WorkerRuntime,
response: permission_request.OwnedPermissionResponse,
reason: []const u8,
) void {
var owned = response;
if (owned.feedback) |feedback| {
debug_trace.logf(
"permission",
"approval feedback discarded reason={s} bytes={d}",
.{ reason, feedback.len },
);
}
owned.deinit();
}
pub fn requestQuestionBatchAnswerBlocking(self: *WorkerRuntime, alloc: std.mem.Allocator, entries: []const types.QuestionBatchEntry) !?[][]u8 {
return self.requestQuestionBatchAnswerBlockingWithSource(alloc, entries, .agent_question);
}
pub fn requestRouteRecoveryAnswerBlocking(self: *WorkerRuntime, alloc: std.mem.Allocator, entries: []const types.QuestionBatchEntry) !?[][]u8 {
return self.requestQuestionBatchAnswerBlockingWithSource(alloc, entries, .route_recovery);
}
pub fn requestMcpElicitationAnswerBlocking(self: *WorkerRuntime, alloc: std.mem.Allocator, entries: []const types.QuestionBatchEntry) !?[][]u8 {
return self.requestQuestionBatchAnswerBlockingWithSource(alloc, entries, .mcp_elicitation);
}
fn requestQuestionBatchAnswerBlockingWithSource(
self: *WorkerRuntime,
alloc: std.mem.Allocator,
entries: []const types.QuestionBatchEntry,
source: QuestionPromptSource,
) !?[][]u8 {
const owned = try dupeOwnedQuestionBatch(alloc, entries, source);
var owns_pending_question = true;
errdefer if (owns_pending_question) freeOwnedQuestionBatch(alloc, owned);
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
std.debug.assert(self.pending_question_shared == null);
self.pending_question_shared = owned;
freeQuestionResponse(alloc, self.pending_question_response);
self.pending_question_response = .pending;
self.worker_events.append(alloc, .question_requested) catch |err| {
self.pending_question_shared = null;
self.pending_question_response = .pending;
return err;
};
owns_pending_question = false;
self.worker_cond.broadcast(io_mod.getIo());
while (self.pending_question_response == .pending and !self.worker_stop_requested) {
self.worker_cond.wait(io_mod.getIo(), &self.worker_mutex) catch break;
}
if (self.pending_question_shared) |pending| {
freeOwnedQuestionBatch(alloc, pending);
self.pending_question_shared = null;
}
const response = self.pending_question_response;
self.pending_question_response = .pending;
return switch (response) {
.answered => |labels| labels,
.cancelled, .pending => null,
};
}
pub fn submitQuestionBatchAnswer(self: *WorkerRuntime, alloc: std.mem.Allocator, answers: ?[]const []const u8) !void {
var new_response: QuestionResponse = .cancelled;
if (answers) |labels| {
const dup = try alloc.alloc([]u8, labels.len);
errdefer alloc.free(dup);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) alloc.free(dup[i]);
}
while (filled < labels.len) : (filled += 1) dup[filled] = try alloc.dupe(u8, labels[filled]);
new_response = .{ .answered = dup };
}
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.pending_question_shared == null or self.pending_question_response != .pending) {
freeQuestionResponse(alloc, new_response);
debug_trace.logf("worker", "ignored late question response pending=false", .{});
return;
}
freeQuestionResponse(alloc, self.pending_question_response);
self.pending_question_response = new_response;
self.worker_cond.broadcast(io_mod.getIo());
}
pub fn cancelPendingQuestionBatch(self: *WorkerRuntime) bool {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
if (self.pending_question_shared == null or self.pending_question_response != .pending) return false;
self.pending_question_response = .cancelled;
self.worker_cond.broadcast(io_mod.getIo());
return true;
}
pub fn snapshotPendingQuestionBatch(self: *WorkerRuntime, alloc: std.mem.Allocator) !?PendingQuestionBatchSnapshot {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const pending = self.pending_question_shared orelse return null;
return try dupePendingBatchSnapshot(alloc, pending);
}
pub fn pendingQuestionBatchSource(self: *WorkerRuntime) QuestionPromptSource {
self.worker_mutex.lockUncancelable(io_mod.getIo());
defer self.worker_mutex.unlock(io_mod.getIo());
const pending = self.pending_question_shared orelse return .agent_question;
return pending.source;
}
};
fn recoveryContinuationAdmission(
processing: bool,
queued_count: usize,
terminal_pause_ready: bool,
) bool {
return queued_count == 0 and (!processing or terminal_pause_ready);
}
test "recovery continuation admission requires an empty idle or terminally paused worker" {
try std.testing.expect(recoveryContinuationAdmission(false, 0, false));
try std.testing.expect(!recoveryContinuationAdmission(true, 0, false));
try std.testing.expect(recoveryContinuationAdmission(true, 0, true));
try std.testing.expect(!recoveryContinuationAdmission(false, 1, true));
}
test "terminal recovery pause admits continuation before worker cleanup" {
const alloc = std.testing.allocator;
var runtime: WorkerRuntime = .{};
defer runtime.deinit(alloc);
const Fixture = struct {
fn make(allocator: std.mem.Allocator) !QueuedPrompt {
var prompt = try makePrompt(allocator, "continue preserved turn", "model");
errdefer freeQueuedPrompt(allocator, prompt);
prompt.recovery_checkpoint = try (session_codec.RecoveryCheckpoint{
.turn_id = 41,
.user = .{ .text = @constCast("unfinished prompt") },
.assistant_source = @constCast("partial response"),
.cause = .network_interrupted,
.action = .paused,
.authority = .{ .provider = .gateway, .model = @constCast("model") },
.requested_fast_mode = false,
.fast_mode = false,
.max_provider_attempts = 10,
.consumed_provider_attempts = 10,
}).dupe(allocator);
return prompt;
}
};
runtime.worker_processing = true;
try runtime.pushEvent(alloc, .{ .route_recovery_status = .{
.kind = .terminal_provider_error,
.failed_attempt = 10,
.attempt_limit = 10,
.cause = .network_interrupted,
.action = .paused,
.required_action = .continue_later,
} });
try runtime.pushTurnFinished(alloc, .{
.turn_id = 41,
.outcome = .paused,
});
const continuation = try Fixture.make(alloc);
runtime.enqueuePrompt(alloc, continuation) catch |err| {
freeQueuedPrompt(alloc, continuation);
return err;
};
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
}
test "connectivity wait state follows worker events before UI presentation" {
var runtime: WorkerRuntime = .{};
defer runtime.deinit(std.testing.allocator);
try runtime.pushEvent(std.testing.allocator, .{ .route_recovery_status = .{
.kind = .auto_retry,
.cause = .system_resumed,
.action = .waiting_for_connectivity,
} });
try std.testing.expect(runtime.isConnectivityWaitActive());
try runtime.pushEvent(std.testing.allocator, .{ .route_recovery_status = .{
.kind = .auto_retry,
.cause = .network_interrupted,
.action = .retrying_request,
} });
try std.testing.expect(!runtime.isConnectivityWaitActive());
try runtime.pushEvent(std.testing.allocator, .{ .route_recovery_status = .{
.kind = .auto_retry,
.cause = .system_resumed,
.action = .waiting_for_connectivity,
} });
try runtime.pushEvent(std.testing.allocator, .clear_route_recovery_status);
try std.testing.expect(!runtime.isConnectivityWaitActive());
}
fn dupeOwnedQuestionEntry(alloc: std.mem.Allocator, entry: types.QuestionBatchEntry) !OwnedQuestionEntry {
const question_dup = try alloc.dupe(u8, entry.question);
errdefer alloc.free(question_dup);
const options_dup = try alloc.alloc(OwnedQuestionOption, entry.options.len);
errdefer alloc.free(options_dup);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) {
alloc.free(options_dup[i].label);
if (options_dup[i].description) |d| alloc.free(d);
}
}
while (filled < entry.options.len) : (filled += 1) {
const src = entry.options[filled];
const label_dup = try alloc.dupe(u8, src.label);
errdefer alloc.free(label_dup);
const desc_dup: ?[]u8 = if (src.description) |d| try alloc.dupe(u8, d) else null;
options_dup[filled] = .{ .label = label_dup, .description = desc_dup };
}
return .{ .question = question_dup, .options = options_dup, .submission = entry.submission };
}
fn freeOwnedQuestionEntry(alloc: std.mem.Allocator, entry: OwnedQuestionEntry) void {
alloc.free(entry.question);
for (entry.options) |opt| {
alloc.free(opt.label);
if (opt.description) |d| alloc.free(d);
}
alloc.free(entry.options);
}
fn dupeOwnedQuestionBatch(
alloc: std.mem.Allocator,
entries: []const types.QuestionBatchEntry,
source: QuestionPromptSource,
) !OwnedQuestionBatch {
const owned_entries = try alloc.alloc(OwnedQuestionEntry, entries.len);
errdefer alloc.free(owned_entries);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) freeOwnedQuestionEntry(alloc, owned_entries[i]);
}
while (filled < entries.len) : (filled += 1) owned_entries[filled] = try dupeOwnedQuestionEntry(alloc, entries[filled]);
return .{ .entries = owned_entries, .source = source };
}
fn freeOwnedQuestionBatch(alloc: std.mem.Allocator, owned: OwnedQuestionBatch) void {
for (owned.entries) |entry| freeOwnedQuestionEntry(alloc, entry);
alloc.free(owned.entries);
}
fn freeOwnedQuestionBatchOpt(alloc: std.mem.Allocator, owned: ?OwnedQuestionBatch) void {
if (owned) |o| freeOwnedQuestionBatch(alloc, o);
}
fn freeQuestionResponse(alloc: std.mem.Allocator, response: QuestionResponse) void {
switch (response) {
.answered => |labels| {
for (labels) |label| alloc.free(label);
alloc.free(labels);
},
.cancelled, .pending => {},
}
}
fn dupePendingEntrySnapshot(alloc: std.mem.Allocator, pending: OwnedQuestionEntry) !types.QuestionBatchEntry {
const question_dup = try alloc.dupe(u8, pending.question);
errdefer alloc.free(question_dup);
const options_dup = try alloc.alloc(types.QuestionOption, pending.options.len);
errdefer alloc.free(options_dup);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) {
alloc.free(@constCast(options_dup[i].label));
if (options_dup[i].description) |d| alloc.free(@constCast(d));
}
}
while (filled < pending.options.len) : (filled += 1) {
const src = pending.options[filled];
const label_dup = try alloc.dupe(u8, src.label);
errdefer alloc.free(label_dup);
const desc_dup: ?[]const u8 = if (src.description) |d| try alloc.dupe(u8, d) else null;
options_dup[filled] = .{ .label = label_dup, .description = desc_dup };
}
return .{ .question = question_dup, .options = options_dup, .submission = pending.submission };
}
fn dupePendingBatchSnapshot(alloc: std.mem.Allocator, pending: OwnedQuestionBatch) !PendingQuestionBatchSnapshot {
const entries_dup = try alloc.alloc(types.QuestionBatchEntry, pending.entries.len);
errdefer alloc.free(entries_dup);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) {
alloc.free(@constCast(entries_dup[i].question));
for (entries_dup[i].options) |opt| {
alloc.free(@constCast(opt.label));
if (opt.description) |d| alloc.free(@constCast(d));
}
alloc.free(@constCast(entries_dup[i].options));
}
}
while (filled < pending.entries.len) : (filled += 1) entries_dup[filled] = try dupePendingEntrySnapshot(alloc, pending.entries[filled]);
return .{ .entries = entries_dup, .source = pending.source };
}
fn appendGrantToQueuedPrompt(alloc: std.mem.Allocator, prompt: *QueuedPrompt, tool_name: []const u8, target_path: []const u8) !void {
for (prompt.grants) |grant| {
if (std.mem.eql(u8, grant.tool_name, tool_name) and std.mem.eql(u8, grant.target_path, target_path)) return;
}
const tool_name_dup = try alloc.dupe(u8, tool_name);
errdefer alloc.free(tool_name_dup);
const target_path_dup = try alloc.dupe(u8, target_path);
errdefer alloc.free(target_path_dup);
const current = prompt.grants;
const next = try alloc.alloc(types.PermissionGrant, current.len + 1);
errdefer alloc.free(next);
if (current.len > 0) std.mem.copyForwards(types.PermissionGrant, next[0..current.len], current);
next[current.len] = .{ .tool_name = tool_name_dup, .target_path = target_path_dup };
alloc.free(current);
prompt.grants = next;
}
fn appendHistoryTurnProjection(
alloc: std.mem.Allocator,
current: []const types.HistoryTurn,
turn: types.HistoryTurn,
max_history_turns: usize,
) ![]types.HistoryTurn {
_ = max_history_turns;
const retained = if (turn == .compacted_summary and
std.mem.startsWith(u8, turn.compacted_summary.summary, types.context_handoff_open))
&.{}
else
current;
const next = try alloc.alloc(types.HistoryTurn, retained.len + 1);
errdefer alloc.free(next);
var copied: usize = 0;
errdefer for (next[0..copied]) |owned| types.freeHistoryTurn(alloc, owned);
for (retained, 0..) |entry, index| {
next[index] = try types.dupeHistoryTurn(alloc, entry);
copied += 1;
}
next[retained.len] = try types.dupeHistoryTurn(alloc, turn);
return next;
}
pub fn freeQueuedPrompt(alloc: std.mem.Allocator, prompt: QueuedPrompt) void {
if (prompt.steering_receipt) |receipt| {
if (receipt.state.cmpxchgStrong(.queued, .not_applied, .seq_cst, .seq_cst) == null) {
debug_trace.eventf("subagent", "feedback_not_applied", .{}, "operation={s} reason=queue_discarded", .{receipt.operation_id});
}
}
alloc.free(prompt.prompt);
types.freeImageAttachmentSlice(alloc, prompt.images);
types.freeImageAttachmentSlice(alloc, prompt.authorized_image_catalog);
alloc.free(prompt.model);
secret.zeroAndFree(alloc, prompt.api_key);
if (prompt.gateway_team) |team| alloc.free(team);
if (prompt.account_id) |account_id| alloc.free(account_id);
types.freeHistoryTurnSlice(alloc, prompt.history);
if (prompt.root_user_intent_context.len > 0) alloc.free(prompt.root_user_intent_context);
types.freePermissionGrantSlice(alloc, prompt.grants);
freeSkillBindings(alloc, prompt.skill_bindings);
freeSkillDisplaySpans(alloc, prompt.skill_display_spans);
var context_snapshot = prompt.context_snapshot;
context_snapshot.deinit(alloc);
for (prompt.snapshot_file_ownerships) |ownership| ownership.release();
if (prompt.snapshot_file_ownerships.len > 0) {
alloc.free(prompt.snapshot_file_ownerships);
}
if (prompt.recovery_checkpoint) |checkpoint| {
var owned = checkpoint;
owned.deinit(alloc);
}
}
pub fn freeContextCompactionTask(
alloc: std.mem.Allocator,
task: ContextCompactionTask,
) void {
alloc.free(task.model);
secret.zeroAndFree(alloc, task.api_key);
if (task.gateway_team) |team| alloc.free(team);
if (task.account_id) |account_id| alloc.free(account_id);
types.freeHistoryTurnSlice(alloc, task.history);
}
pub fn freeWorkItem(alloc: std.mem.Allocator, work: WorkItem) void {
switch (work) {
.prompt => |prompt| freeQueuedPrompt(alloc, prompt),
.compact_context => |task| freeContextCompactionTask(alloc, task),
}
}
fn discardQueuedPrompt(
alloc: std.mem.Allocator,
prompt: QueuedPrompt,
retained_images: []const types.ImageAttachment,
) void {
if (prompt.recovery_checkpoint == null) {
image_attachments.deleteUnreferencedImageSnapshots(prompt.images, retained_images);
}
freeQueuedPrompt(alloc, prompt);
}
test "active prompt snapshot ownership discards every pre-transfer boundary" {
const boundaries = [_][]const u8{
"preflight_notice",
"skill_prompt",
"tool_projection",
"permission_preparation",
"process_queued_prompt",
"postflight_finish",
};
for (boundaries, 0..) |_, index| {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var name_buffer: [32]u8 = undefined;
const name = try std.fmt.bufPrint(&name_buffer, "snapshot-{d}.bin", .{index});
{
var file = try tmp.dir.createFile(std.testing.io, name, .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, name);
defer std.testing.allocator.free(path);
const images = [_]types.ImageAttachment{.{
.id = index + 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
var ownership = ActivePromptSnapshotOwnership.init(&images);
ownership.deinit();
ownership.deinit();
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, path, .{}),
);
}
}
test "session and checkpoint transfer preserve active and finished prompt snapshots" {
const alloc = std.testing.allocator;
const phases = [_]enum { take_before_begin, active, finished }{
.take_before_begin,
.active,
.finished,
};
for ([_]bool{ false, true }) |checkpoint_accepted| {
for (phases) |phase| {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const name = switch (phase) {
.take_before_begin => "take-before-begin.bin",
.active => "active.bin",
.finished => "finished.bin",
};
{
var file = try tmp.dir.createFile(std.testing.io, name, .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, name);
defer alloc.free(path);
const images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.active_turn_id = 41;
var active = ActivePromptSnapshotOwnership.init(&images);
if (phase != .take_before_begin) runtime.beginActivePromptSnapshots(&active);
if (phase == .finished) {
const ownership = (try runtime.handoffActivePromptSnapshots(alloc)) orelse
return error.TestExpectedSnapshotOwnership;
try runtime.pushEvent(alloc, .{ .finish_prompt = .{
.turn = .{ .assistant = .{
.user = .{ .text = @constCast("prompt"), .images = @constCast(&images) },
.assistant = @constCast("done"),
} },
.snapshot_file_ownership = ownership,
} });
ownership.release();
runtime.endActivePromptSnapshots(&active);
runtime.active_turn_id = 0;
}
if (checkpoint_accepted) {
runtime.preservePromptSnapshots(41, &images);
} else {
runtime.clearQueuedPromptsForSessionTransition(alloc, 41, &images);
}
if (phase == .take_before_begin) runtime.beginActivePromptSnapshots(&active);
if (phase != .finished) runtime.endActivePromptSnapshots(&active);
runtime.discardEvents(alloc);
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
try std.Io.Dir.deleteFileAbsolute(std.testing.io, path);
}
}
}
test "compaction activity survives worker finish without allocating a permission snapshot" {
var runtime: WorkerRuntime = .{};
defer runtime.deinit(std.testing.allocator);
try std.testing.expect(runtime.beginDirectProcessing(17));
const first = runtime.beginCompactionActivity(.automatic, 17);
runtime.runCompactionActivity(first, .publication);
runtime.requestInteractiveCancel();
try std.testing.expect(runtime.compactionActivitySnapshot().operation.?.phase == .stopping);
runtime.settleCompactionActivity(first, .{ .outcome = .succeeded, .stage = .publication, .publication = .committed });
runtime.finishProcessing();
try std.testing.expectEqual(@as(u64, 0), runtime.activeTurnId());
const terminal = runtime.compactionActivitySnapshot();
try std.testing.expectEqual(first, terminal.operation.?.id);
try std.testing.expectEqual(compaction_activity.Outcome.succeeded, terminal.operation.?.phase.terminal.outcome);
runtime.settleCompactionActivity(first, .{ .outcome = .cancelled });
try std.testing.expectEqualDeep(terminal, runtime.compactionActivitySnapshot());
const next = runtime.beginCompactionActivity(.manual, null);
const started = runtime.compactionActivitySnapshot();
runtime.rejectCompactionActivity(.busy);
try std.testing.expectEqualDeep(started, runtime.compactionActivitySnapshot());
try std.testing.expect(!runtime.dismissCompactionActivity(first, terminal.revision));
runtime.settleCompactionActivity(first, compaction_activity.failure(error.Aborted, .publication, true));
try std.testing.expectEqual(next, runtime.compactionActivitySnapshot().operation.?.id);
runtime.settleCompactionActivity(next, compaction_activity.failure(error.OutOfMemory, .preparation, false));
try std.testing.expectEqual(error.OutOfMemory, runtime.compactionActivitySnapshot().operation.?.phase.terminal.err.?);
}
test "manual compaction is a typed worker item and not a prompt" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const task = ContextCompactionTask{
.model = try alloc.dupe(u8, "provider/model"),
.api_key = try alloc.dupe(u8, "key"),
.history = try alloc.alloc(types.HistoryTurn, 0),
};
try runtime.enqueueContextCompaction(task);
const taken = (try runtime.tryTakeNextWork(alloc)) orelse
return error.TestExpectedQueuedWork;
defer freeWorkItem(alloc, taken);
try std.testing.expect(taken == .compact_context);
const snapshot = runtime.compactionActivitySnapshot();
try std.testing.expectEqual(taken.compact_context.operation_id.?, snapshot.operation.?.id);
try std.testing.expectEqual(taken.compact_context.turn_id, snapshot.operation.?.turn_id.?);
try std.testing.expect(snapshot.operation.?.phase == .running);
runtime.finishProcessing();
try std.testing.expectEqual(error.CompactionInterrupted, runtime.compactionActivitySnapshot().operation.?.phase.terminal.err.?);
}
test "session transition queue clearing settles only the dropped compaction identity" {
const alloc = std.testing.allocator;
for ([_]bool{ false, true }) |retain_snapshots| {
for ([_]bool{ false, true }) |newer_activity| {
var runtime: WorkerRuntime = .{};
defer runtime.deinit(alloc);
try runtime.enqueueContextCompaction(.{
.turn_id = 41,
.model = try alloc.dupe(u8, "provider/model"),
.api_key = try alloc.dupe(u8, "key"),
.history = try alloc.alloc(types.HistoryTurn, 0),
});
const queued = runtime.compactionActivitySnapshot();
if (newer_activity) _ = runtime.beginCompactionActivity(.manual, null);
const observed = runtime.compactionActivitySnapshot();
if (retain_snapshots) {
runtime.clearQueuedPromptsForSessionTransition(alloc, 41, &.{});
} else {
runtime.clearQueuedPrompts(alloc, &.{});
}
try std.testing.expectEqual(ContextCompactionStatus.idle, runtime.contextCompactionStatus());
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
try std.testing.expect((try runtime.tryTakeNextWork(alloc)) == null);
try std.testing.expect(!runtime.isCancelRequested());
const settled = runtime.compactionActivitySnapshot();
if (newer_activity) {
try std.testing.expectEqualDeep(observed, settled);
} else {
try std.testing.expectEqual(queued.operation.?.id, settled.operation.?.id);
try std.testing.expectEqual(compaction_activity.Outcome.cancelled, settled.operation.?.phase.terminal.outcome);
try std.testing.expect(!settled.operation.?.active());
try std.testing.expect(settled.revision > queued.revision);
}
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expectEqualDeep(settled, runtime.compactionActivitySnapshot());
}
}
}
test "queued manual compaction can be cancelled before worker execution" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const task = ContextCompactionTask{
.model = try alloc.dupe(u8, "provider/model"),
.api_key = try alloc.dupe(u8, "key"),
.history = try alloc.alloc(types.HistoryTurn, 0),
};
try runtime.enqueueContextCompaction(task);
try std.testing.expectEqual(ContextCompactionStatus.queued, runtime.contextCompactionStatus());
try std.testing.expect(runtime.cancelContextCompaction(alloc));
try std.testing.expectEqual(ContextCompactionStatus.idle, runtime.contextCompactionStatus());
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
try std.testing.expect((try runtime.tryTakeNextWork(alloc)) == null);
try std.testing.expectEqual(compaction_activity.Outcome.cancelled, runtime.compactionActivitySnapshot().operation.?.phase.terminal.outcome);
}
test "running manual compaction cancellation uses the worker cancel flag" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const task = ContextCompactionTask{
.model = try alloc.dupe(u8, "provider/model"),
.api_key = try alloc.dupe(u8, "key"),
.history = try alloc.alloc(types.HistoryTurn, 0),
};
try runtime.enqueueContextCompaction(task);
const taken = (try runtime.tryTakeNextWork(alloc)) orelse
return error.TestExpectedQueuedWork;
defer freeWorkItem(alloc, taken);
try std.testing.expectEqual(ContextCompactionStatus.running, runtime.contextCompactionStatus());
try std.testing.expect(runtime.cancelContextCompaction(alloc));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expect(runtime.compactionActivitySnapshot().operation.?.phase == .stopping);
runtime.finishProcessing();
try std.testing.expectEqual(@as(u64, 0), runtime.activeTurnId());
try std.testing.expectEqual(compaction_activity.Outcome.cancelled, runtime.compactionActivitySnapshot().operation.?.phase.terminal.outcome);
try std.testing.expectEqual(ContextCompactionStatus.idle, runtime.contextCompactionStatus());
}
test "session transfer discards mismatched active prompt snapshots" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "active.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "active.bin");
defer alloc.free(path);
const active_images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
const unrelated_images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/other.png"),
.media_type = @constCast("image/png"),
.snapshot_path = @constCast("/tmp/other.bin"),
.snapshot_sha256 = @constCast("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
}};
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.active_turn_id = 42;
var active = ActivePromptSnapshotOwnership.init(&active_images);
runtime.beginActivePromptSnapshots(&active);
runtime.clearQueuedPromptsForSessionTransition(alloc, 42, &unrelated_images);
runtime.endActivePromptSnapshots(&active);
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, path, .{}),
);
}
test "accepted active prompt ownership does not delete history files" {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var first = try tmp.dir.createFile(std.testing.io, "first.bin", .{});
defer first.close(std.testing.io);
try first.writeStreamingAll(std.testing.io, "first");
}
{
var second = try tmp.dir.createFile(std.testing.io, "second.bin", .{});
defer second.close(std.testing.io);
try second.writeStreamingAll(std.testing.io, "second");
}
const first_path = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, "first.bin");
defer std.testing.allocator.free(first_path);
const second_path = try io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, "second.bin");
defer std.testing.allocator.free(second_path);
const images = [_]types.ImageAttachment{
.{
.id = 1,
.path = @constCast("/tmp/first.png"),
.media_type = @constCast("image/png"),
.snapshot_path = first_path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
},
.{
.id = 2,
.path = @constCast("/tmp/second.png"),
.media_type = @constCast("image/png"),
.snapshot_path = second_path,
.snapshot_sha256 = @constCast("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
},
};
var ownership = ActivePromptSnapshotOwnership.init(&images);
const accepted = (try ownership.handoff(std.testing.allocator)) orelse
return error.TestExpectedEqual;
accepted.transfer();
accepted.release();
ownership.deinit();
try std.Io.Dir.accessAbsolute(std.testing.io, first_path, .{});
try std.Io.Dir.accessAbsolute(std.testing.io, second_path, .{});
}
test "propagated queued snapshots survive finish failure until the last borrower releases" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "snapshot.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "snapshot.bin");
defer alloc.free(path);
var images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = @constCast("active"), .images = &images },
.assistant = @constCast("done"),
} };
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "first queued", "model"));
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "second queued", "model"));
var active = ActivePromptSnapshotOwnership.init(&images);
runtime.beginActivePromptSnapshots(&active);
try runtime.propagateHistoryTurn(alloc, turn, 8);
const failed_finish_ownership =
(try runtime.handoffActivePromptSnapshots(alloc)) orelse
return error.TestExpectedEqual;
failed_finish_ownership.release();
runtime.endActivePromptSnapshots(&active);
for (runtime.queued_prompts.items) |prompt| {
try std.testing.expectEqual(@as(usize, 1), prompt.authorized_image_catalog.len);
try std.testing.expectEqual(@as(usize, 1), prompt.snapshot_file_ownerships.len);
try std.Io.Dir.accessAbsolute(
std.testing.io,
prompt.authorized_image_catalog[0].snapshot_path.?,
.{},
);
}
const first = (try runtime.tryTakeNextPrompt(alloc)).?;
freeQueuedPrompt(alloc, first);
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, path, .{}),
);
}
test "accepted history prevents propagated queue cleanup from deleting snapshots" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "snapshot.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "snapshot.bin");
defer alloc.free(path);
var images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = @constCast("active"), .images = &images },
.assistant = @constCast("done"),
} };
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued", "model"));
var active = ActivePromptSnapshotOwnership.init(&images);
runtime.beginActivePromptSnapshots(&active);
try runtime.propagateHistoryTurn(alloc, turn, 8);
const accepted =
(try runtime.handoffActivePromptSnapshots(alloc)) orelse
return error.TestExpectedEqual;
accepted.transfer();
accepted.release();
runtime.endActivePromptSnapshots(&active);
runtime.clearQueuedPrompts(alloc, &.{});
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
}
test "compaction preserves active snapshots only after persistence succeeds" {
const Persist = struct {
fail: bool,
fn commit(raw: *anyopaque, _: ContextCompaction) !void {
const self: *@This() = @ptrCast(@alignCast(raw));
if (self.fail) return error.TestPersistenceFailure;
}
};
const alloc = std.testing.allocator;
for ([_]bool{ false, true }) |queued| {
for ([_]bool{ false, true }) |fail| {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "snapshot.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "snapshot.bin");
defer alloc.free(path);
var images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
var runtime: WorkerRuntime = .{};
defer runtime.deinit(alloc);
if (queued) try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued", "model"));
var ownership = ActivePromptSnapshotOwnership.init(&images);
runtime.beginActivePromptSnapshots(&ownership);
var active = true;
defer if (active) runtime.endActivePromptSnapshots(&ownership);
var persist: Persist = .{ .fail = fail };
const event: ContextCompaction = .{
.summary = .{
.summary = @constCast("image checkpoint"),
.removed_turn_count = 1,
.compaction_count = 1,
},
.active_prefix = .{
.user = .{ .text = @constCast("inspect image"), .images = &images },
.assistant = @constCast(""),
},
};
if (fail) {
try std.testing.expectError(error.TestPersistenceFailure, runtime.publishContextCompaction(alloc, event, &persist, Persist.commit));
} else {
try runtime.publishContextCompaction(alloc, event, &persist, Persist.commit);
try runtime.publishContextCompaction(alloc, event, &persist, Persist.commit);
}
runtime.endActivePromptSnapshots(&ownership);
active = false;
runtime.clearQueuedPrompts(alloc, &.{});
if (fail) {
try std.testing.expectError(error.FileNotFound, std.Io.Dir.accessAbsolute(std.testing.io, path, .{}));
} else {
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
try std.testing.expect(ownership.shared_ownership == null);
}
}
}
}
test "failed queued prompt dequeue retains borrowed snapshot ownership" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "snapshot.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "snapshot.bin");
defer alloc.free(path);
var images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = @constCast("active"), .images = &images },
.assistant = @constCast("done"),
} };
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued", "model"));
var active = ActivePromptSnapshotOwnership.init(&images);
runtime.beginActivePromptSnapshots(&active);
try runtime.propagateHistoryTurn(alloc, turn, 8);
const failed_finish_ownership =
(try runtime.handoffActivePromptSnapshots(alloc)) orelse
return error.TestExpectedEqual;
failed_finish_ownership.release();
runtime.endActivePromptSnapshots(&active);
var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 });
try std.testing.expectError(
error.OutOfMemory,
runtime.waitAndTakeNextPrompt(failing.allocator()),
);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
try std.testing.expectEqual(
@as(usize, 1),
runtime.queued_prompts.items[0].snapshot_file_ownerships.len,
);
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, path, .{}),
);
}
fn checkHistoryPropagationAllocation(
alloc: std.mem.Allocator,
snapshot_path: []const u8,
fail_index: usize,
) !bool {
{
var file = try std.Io.Dir.createFileAbsolute(
std.testing.io,
snapshot_path,
.{ .truncate = true },
);
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
var images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = @constCast(snapshot_path),
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = @constCast("active"), .images = &images },
.assistant = @constCast("done"),
} };
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const first = try makePrompt(alloc, "first queued", "model");
var owns_first = true;
errdefer if (owns_first) freeQueuedPrompt(alloc, first);
try runtime.enqueuePrompt(alloc, first);
owns_first = false;
const second = try makePrompt(alloc, "second queued", "model");
var owns_second = true;
errdefer if (owns_second) freeQueuedPrompt(alloc, second);
try runtime.enqueuePrompt(alloc, second);
owns_second = false;
var active = ActivePromptSnapshotOwnership.init(&images);
runtime.beginActivePromptSnapshots(&active);
defer runtime.endActivePromptSnapshots(&active);
var failing = std.testing.FailingAllocator.init(
alloc,
.{ .fail_index = fail_index },
);
runtime.propagateHistoryTurn(failing.allocator(), turn, 8) catch |err| {
if (!failing.has_induced_failure) return err;
for (runtime.queued_prompts.items) |prompt| {
try std.testing.expectEqual(@as(usize, 0), prompt.history.len);
try std.testing.expectEqual(@as(usize, 0), prompt.authorized_image_catalog.len);
try std.testing.expectEqual(@as(usize, 0), prompt.snapshot_file_ownerships.len);
}
try std.testing.expectEqual(@as(usize, 0), runtime.queued_history.len);
return false;
};
for (runtime.queued_prompts.items) |prompt| {
try std.testing.expectEqual(@as(usize, 0), prompt.history.len);
try std.testing.expectEqual(@as(usize, 1), prompt.authorized_image_catalog.len);
try std.testing.expectEqual(@as(usize, 1), prompt.snapshot_file_ownerships.len);
}
try std.testing.expectEqual(@as(usize, 1), runtime.queued_history.len);
return true;
}
test "multi-queue history propagation is allocation-failure atomic" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const root = try io_mod.dirRealpathAlloc(alloc, tmp.dir, ".");
defer alloc.free(root);
const snapshot_path = try std.fs.path.join(alloc, &.{ root, "snapshot.bin" });
defer alloc.free(snapshot_path);
var fail_index: usize = 0;
while (fail_index < 256) : (fail_index += 1) {
if (try checkHistoryPropagationAllocation(alloc, snapshot_path, fail_index)) {
return;
}
}
return error.TestUnexpectedResult;
}
fn checkContextCompactionCommitAllocation(
alloc: std.mem.Allocator,
fail_index: usize,
) !bool {
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var prompt = try makePrompt(alloc, "queued", "model");
var owns_prompt = true;
errdefer if (owns_prompt) freeQueuedPrompt(alloc, prompt);
prompt.history = try session_runtime.snapshotOwnedContextHistory(alloc, &.{.{ .assistant = .{
.user = .{ .text = @constCast("prior user") },
.assistant = @constCast("prior answer"),
} }}, 0, 0);
try runtime.enqueuePrompt(alloc, prompt);
owns_prompt = false;
const turn: types.HistoryTurn = .{ .compacted_summary = .{
.summary = @constCast("checkpoint"),
.removed_turn_count = 1,
.compaction_count = 1,
} };
var failing = std.testing.FailingAllocator.init(
alloc,
.{ .fail_index = fail_index },
);
runtime.publishContextCompaction(failing.allocator(), .{ .summary = turn.compacted_summary, .max_history_turns = 8 }, &runtime, discardCompactionForTest) catch |err| {
if (!failing.has_induced_failure) return err;
try std.testing.expectEqual(
@as(usize, 1),
runtime.queued_history.len,
);
try std.testing.expectEqualStrings("prior answer", runtime.queued_history[0].assistant.assistant);
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
return false;
};
try std.testing.expectEqual(
@as(usize, 1),
runtime.queued_history.len,
);
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
try std.testing.expectEqualStrings(turn.compacted_summary.summary, runtime.queued_history[0].compacted_summary.summary);
return true;
}
fn discardCompactionForTest(_: *anyopaque, _: ContextCompaction) !void {}
test "context compaction callback waits for UI persistence" {
const Probe = struct {
returned: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
failure: ?anyerror = null,
fn run(self: *@This(), runtime: *WorkerRuntime) void {
runtime.commitContextCompaction(std.testing.allocator, .{
.summary = @constCast("checkpoint"),
.removed_turn_count = 1,
.compaction_count = 1,
}, null, null, 8) catch |err| {
self.failure = err;
};
self.returned.store(true, .release);
}
};
var runtime: WorkerRuntime = .{};
defer runtime.deinit(std.testing.allocator);
var probe: Probe = .{};
const thread = try std.Thread.spawn(.{}, Probe.run, .{ &probe, &runtime });
defer {
runtime.requestShutdown();
thread.join();
}
const deadline = io_mod.milliTimestamp() + 5_000;
while (true) {
var state = try runtime.snapshotState(std.testing.allocator);
defer state.deinit(std.testing.allocator);
if (state.pending_event_count > 0) break;
if (io_mod.milliTimestamp() >= deadline) return error.TestExpectedCompactionEvent;
io_mod.sleep(std.time.ns_per_ms);
}
io_mod.sleep(10 * std.time.ns_per_ms);
try std.testing.expect(!probe.returned.load(.acquire));
}
test "context compaction acknowledgment commits or fails before worker continuation" {
const Probe = struct {
const Outcome = enum { commit, fail, cancel_after_commit };
runtime: *WorkerRuntime,
outcome: Outcome,
returned: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
continued: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
failure: ?anyerror = null,
persisted: bool = false,
fn run(self: *@This()) void {
self.runtime.commitContextCompaction(std.testing.allocator, .{
.summary = @constCast("checkpoint"),
.removed_turn_count = 1,
.compaction_count = 1,
}, .{
.user = .{ .text = @constCast("active request") },
.assistant = @constCast(""),
}, null, 8) catch |err| {
self.failure = err;
self.returned.store(true, .release);
return;
};
self.continued.store(true, .release);
self.returned.store(true, .release);
}
fn commit(raw: *anyopaque, event: ContextCompaction) !void {
const self: *@This() = @ptrCast(@alignCast(raw));
try std.testing.expectEqualStrings("active request", event.active_prefix.?.user.text);
try std.testing.expectEqualStrings("old answer", self.runtime.queued_history[0].assistant.assistant);
if (self.outcome == .fail) return error.TestPersistenceFailure;
self.persisted = true;
if (self.outcome == .cancel_after_commit) {
self.runtime.worker_cancel_requested.store(true, .seq_cst);
}
}
};
const alloc = std.testing.allocator;
for ([_]Probe.Outcome{ .commit, .fail, .cancel_after_commit }) |outcome| {
var runtime: WorkerRuntime = .{};
defer runtime.deinit(alloc);
var prompt = try makePrompt(alloc, "queued", "model");
prompt.history = try session_runtime.snapshotOwnedContextHistory(alloc, &.{.{ .assistant = .{
.user = .{ .text = @constCast("old request") },
.assistant = @constCast("old answer"),
} }}, 0, 0);
try runtime.enqueuePrompt(alloc, prompt);
var probe: Probe = .{ .runtime = &runtime, .outcome = outcome };
const thread = try std.Thread.spawn(.{}, Probe.run, .{&probe});
var joined = false;
defer if (!joined) {
runtime.requestShutdown();
thread.join();
};
const deadline = io_mod.milliTimestamp() + 5_000;
while (true) {
var state = try runtime.snapshotState(alloc);
defer state.deinit(alloc);
if (state.pending_event_count > 0) break;
if (io_mod.milliTimestamp() >= deadline) return error.TestExpectedCompactionEvent;
io_mod.sleep(std.time.ns_per_ms);
}
var events = runtime.takeEvents();
defer events.deinit(alloc);
defer for (events.items) |event| freeWorkerEvent(alloc, event);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(!probe.returned.load(.acquire));
try std.testing.expect(!probe.continued.load(.acquire));
runtime.resolveContextCompaction(alloc, events.items[0].context_compaction, &probe, Probe.commit);
thread.join();
joined = true;
if (outcome == .fail) {
try std.testing.expectEqual(error.TestPersistenceFailure, probe.failure.?);
try std.testing.expect(!probe.persisted);
try std.testing.expect(!probe.continued.load(.acquire));
try std.testing.expectEqualStrings("old answer", runtime.queued_history[0].assistant.assistant);
} else {
try std.testing.expect(probe.failure == null);
try std.testing.expect(probe.persisted);
try std.testing.expect(probe.continued.load(.acquire));
try std.testing.expect(runtime.queued_history[0] == .compacted_summary);
}
}
}
test "context compaction waiter exits on cancellation and rejects stale delivery" {
const Probe = struct {
returned: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
failure: ?anyerror = null,
commits: usize = 0,
fn run(self: *@This(), runtime: *WorkerRuntime) void {
runtime.commitContextCompaction(std.testing.allocator, .{
.summary = @constCast("checkpoint"),
.removed_turn_count = 1,
.compaction_count = 1,
}, null, null, 8) catch |err| {
self.failure = err;
};
self.returned.store(true, .release);
}
fn commit(raw: *anyopaque, _: ContextCompaction) !void {
const self: *@This() = @ptrCast(@alignCast(raw));
self.commits += 1;
}
};
const alloc = std.testing.allocator;
for ([_]bool{ false, true }) |shutdown| {
var runtime: WorkerRuntime = .{};
defer runtime.deinit(alloc);
var probe: Probe = .{};
const thread = try std.Thread.spawn(.{}, Probe.run, .{ &probe, &runtime });
var joined = false;
defer if (!joined) {
runtime.requestShutdown();
thread.join();
};
const deadline = io_mod.milliTimestamp() + 5_000;
while (true) {
var state = try runtime.snapshotState(alloc);
defer state.deinit(alloc);
if (state.pending_event_count > 0) break;
if (io_mod.milliTimestamp() >= deadline) return error.TestExpectedCompactionEvent;
io_mod.sleep(std.time.ns_per_ms);
}
var events = runtime.takeEvents();
defer events.deinit(alloc);
defer for (events.items) |event| freeWorkerEvent(alloc, event);
if (shutdown) runtime.requestShutdown() else runtime.requestInteractiveCancel();
while (!probe.returned.load(.acquire)) {
if (io_mod.milliTimestamp() >= deadline) return error.TestExpectedCompactionCancellation;
io_mod.sleep(std.time.ns_per_ms);
}
thread.join();
joined = true;
try std.testing.expectEqual(error.Aborted, probe.failure.?);
runtime.resolveContextCompaction(alloc, events.items[0].context_compaction, &probe, Probe.commit);
try std.testing.expectEqual(@as(usize, 0), probe.commits);
}
}
test "context compaction commit is allocation-failure atomic" {
var fail_index: usize = 0;
while (fail_index < 128) : (fail_index += 1) {
if (try checkContextCompactionCommitAllocation(
std.testing.allocator,
fail_index,
)) return;
}
return error.TestUnexpectedResult;
}
test "queued prompts begin with the latest checkpoint and its suffix" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var first = try makePrompt(alloc, "first queued", "model");
first.history = try session_runtime.snapshotOwnedContextHistory(alloc, &.{.{
.assistant = .{ .user = .{ .text = @constCast("old request") }, .assistant = @constCast("old answer") },
}}, 0, 0);
try runtime.enqueuePrompt(alloc, first);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "second queued", "model"));
const checkpoint: types.HistoryTurn = .{ .compacted_summary = .{
.summary = @constCast("current summary"),
.removed_turn_count = 1,
.compaction_count = 1,
} };
try runtime.publishContextCompaction(alloc, .{ .summary = checkpoint.compacted_summary }, &runtime, discardCompactionForTest);
try std.testing.expectEqual(@as(usize, 1), runtime.queued_history.len);
try runtime.propagateHistoryTurn(alloc, .{ .assistant = .{
.user = .{ .text = @constCast("later request") },
.assistant = @constCast("later answer"),
} }, 0);
for (0..2) |_| {
const job = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
try std.testing.expectEqual(@as(usize, 2), job.history.len);
try std.testing.expectEqualStrings(checkpoint.compacted_summary.summary, job.history[0].compacted_summary.summary);
try std.testing.expectEqualStrings("later answer", job.history[1].assistant.assistant);
}
try std.testing.expectEqual(@as(usize, 0), runtime.queued_history.len);
}
test "finish ownership handoff deletes snapshots after its last unaccepted release" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "snapshot.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "snapshot.bin");
defer alloc.free(path);
const images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
var active = ActivePromptSnapshotOwnership.init(&images);
const ownership = (try active.handoff(alloc)) orelse return error.TestExpectedEqual;
active.deinit();
const queued = try types.dupeFinishedPrompt(alloc, .{
.turn = .{ .compacted_summary = .{
.summary = @constCast("queued"),
.removed_turn_count = 1,
.compaction_count = 1,
} },
.snapshot_file_ownership = ownership,
});
ownership.release();
const paced = try types.dupeFinishedPrompt(alloc, queued);
types.freeFinishedPrompt(alloc, queued);
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
types.freeFinishedPrompt(alloc, paced);
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, path, .{}),
);
}
test "accepted finish ownership survives queue and pacing release" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var file = try tmp.dir.createFile(std.testing.io, "snapshot.bin", .{});
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "snapshot.bin");
defer alloc.free(path);
const images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
var active = ActivePromptSnapshotOwnership.init(&images);
const ownership = (try active.handoff(alloc)) orelse return error.TestExpectedEqual;
active.deinit();
const finished = try types.dupeFinishedPrompt(alloc, .{
.turn = .{ .compacted_summary = .{
.summary = @constCast("accepted"),
.removed_turn_count = 1,
.compaction_count = 1,
} },
.snapshot_file_ownership = ownership,
});
ownership.release();
finished.snapshot_file_ownership.?.transfer();
types.freeFinishedPrompt(alloc, finished);
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
}
fn checkFinishOwnershipHandoffAllocation(
alloc: std.mem.Allocator,
snapshot_path: []const u8,
) !void {
{
var file = try std.Io.Dir.createFileAbsolute(
std.testing.io,
snapshot_path,
.{ .truncate = true },
);
defer file.close(std.testing.io);
try file.writeStreamingAll(std.testing.io, "snapshot");
}
const images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/tmp/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = @constCast(snapshot_path),
.snapshot_sha256 = @constCast("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
}};
var active = ActivePromptSnapshotOwnership.init(&images);
const ownership = active.handoff(alloc) catch |err| {
active.deinit();
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, snapshot_path, .{}),
);
return err;
};
active.deinit();
ownership.?.release();
try std.testing.expectError(
error.FileNotFound,
std.Io.Dir.accessAbsolute(std.testing.io, snapshot_path, .{}),
);
}
test "finish ownership handoff preserves allocator and filesystem ownership" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const root = try io_mod.dirRealpathAlloc(alloc, tmp.dir, ".");
defer alloc.free(root);
const snapshot_path = try std.fs.path.join(alloc, &.{ root, "snapshot.bin" });
defer alloc.free(snapshot_path);
try std.testing.checkAllAllocationFailures(
alloc,
checkFinishOwnershipHandoffAllocation,
.{snapshot_path},
);
}
pub fn dupeSkillBindings(alloc: std.mem.Allocator, bindings: []const SkillBinding) ![]SkillBinding {
if (bindings.len == 0) return &.{};
const copy = try alloc.alloc(SkillBinding, bindings.len);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) {
alloc.free(copy[i].name);
alloc.free(copy[i].path);
}
alloc.free(copy);
}
while (filled < bindings.len) : (filled += 1) {
copy[filled] = .{
.name = try alloc.dupe(u8, bindings[filled].name),
.path = try alloc.dupe(u8, bindings[filled].path),
};
}
return copy;
}
pub fn freeSkillBindings(alloc: std.mem.Allocator, bindings: []SkillBinding) void {
for (bindings) |binding| {
alloc.free(binding.name);
alloc.free(binding.path);
}
if (bindings.len > 0) alloc.free(bindings);
}
pub fn dupeSkillDisplaySpans(alloc: std.mem.Allocator, spans: []const SkillDisplaySpan) ![]SkillDisplaySpan {
if (spans.len == 0) return &.{};
const copy = try alloc.alloc(SkillDisplaySpan, spans.len);
var filled: usize = 0;
errdefer {
var i: usize = 0;
while (i < filled) : (i += 1) {
alloc.free(copy[i].name);
alloc.free(copy[i].path);
}
alloc.free(copy);
}
while (filled < spans.len) : (filled += 1) {
copy[filled] = .{
.raw_start = spans[filled].raw_start,
.raw_end = spans[filled].raw_end,
.name = try alloc.dupe(u8, spans[filled].name),
.path = try alloc.dupe(u8, spans[filled].path),
.display_source = spans[filled].display_source,
.owns_trailing_separator = spans[filled].owns_trailing_separator,
};
}
return copy;
}
pub fn freeSkillDisplaySpans(alloc: std.mem.Allocator, spans: []SkillDisplaySpan) void {
for (spans) |span| {
alloc.free(span.name);
alloc.free(span.path);
}
if (spans.len > 0) alloc.free(spans);
}
pub fn dupeWorkerEvent(alloc: std.mem.Allocator, event: WorkerEvent) !WorkerEvent {
return switch (event) {
.begin_prompt => |prompt| .{ .begin_prompt = try types.dupeUserTurn(alloc, prompt) },
.begin_prompt_with_skill_bindings => |begin| blk: {
const prompt = try types.dupeUserTurn(alloc, begin.prompt);
errdefer types.freeUserTurn(alloc, prompt);
const skill_bindings = try dupeSkillBindings(alloc, begin.skill_bindings);
errdefer freeSkillBindings(alloc, skill_bindings);
const skill_display_spans = try dupeSkillDisplaySpans(alloc, begin.skill_display_spans);
break :blk .{ .begin_prompt_with_skill_bindings = .{
.prompt = prompt,
.skill_bindings = skill_bindings,
.skill_display_spans = skill_display_spans,
} };
},
.begin_presented_prompt => |turn_id| .{ .begin_presented_prompt = turn_id },
.append_user_feedback => |text| .{ .append_user_feedback = try alloc.dupe(u8, text) },
.assistant_presentation => |presentation| .{
.assistant_presentation = try presentation.clone(alloc),
},
.notification => |notification| .{ .notification = notification },
.question_requested => .question_requested,
.open_model_picker => .open_model_picker,
.semantic_notice => |notice| .{ .semantic_notice = try types.dupeSemanticNotice(alloc, notice) },
.full_detail_record => |notice| .{ .full_detail_record = try types.dupeSemanticNotice(alloc, notice) },
.route_recovery_status => |status| .{ .route_recovery_status = status },
.clear_route_recovery_status => .clear_route_recovery_status,
.api_status_text => |text| .{ .api_status_text = try alloc.dupe(u8, text) },
.credential_refreshed => |credential| .{
.credential_refreshed = try credential.clone(alloc),
},
.command_output => |chunk| .{ .command_output = .{
.lifecycle_id = if (chunk.lifecycle_id) |id| .{
.turn_id = id.turn_id,
.call_id = try alloc.dupe(u8, id.call_id),
} else null,
.stream = chunk.stream,
.text = try alloc.dupe(u8, chunk.text),
} },
.command_output_complete => |lifecycle_id| .{ .command_output_complete = if (lifecycle_id) |id| .{
.turn_id = id.turn_id,
.call_id = try alloc.dupe(u8, id.call_id),
} else null },
.tool_lifecycle => |lifecycle| .{
.tool_lifecycle = try dupeToolLifecycleEvent(alloc, lifecycle),
},
.turn_token_update => |update| .{ .turn_token_update = update },
.turn_phase_update => |update| .{ .turn_phase_update = update },
.diff_block => |payload| blk: {
const preview = try alloc.dupe(u8, payload.preview);
errdefer alloc.free(preview);
const full = if (payload.full) |source| full_value: {
const content = try alloc.dupe(u8, source.content);
errdefer alloc.free(content);
const call_id = try alloc.dupe(u8, source.lifecycle_id.call_id);
break :full_value diff_mod.FullDiff{
.content = content,
.lifecycle_id = .{
.turn_id = source.lifecycle_id.turn_id,
.call_id = call_id,
},
};
} else null;
errdefer if (full) |owned| owned.deinit(alloc);
break :blk .{ .diff_block = .{
.preview = preview,
.additions = payload.additions,
.deletions = payload.deletions,
.full = full,
} };
},
.prepare_fresh_prompt => |value| blk: {
const user = try types.dupeUserTurn(alloc, value.user);
errdefer types.freeUserTurn(alloc, user);
break :blk .{ .prepare_fresh_prompt = .{
.turn_id = value.turn_id,
.user = user,
.prior_turn = if (value.prior_turn) |prior| try types.dupeHistoryTurn(alloc, prior) else null,
} };
},
.context_compaction => |value| blk: {
const summary = try types.dupeHistoryTurn(alloc, .{ .compacted_summary = value.summary });
errdefer types.freeHistoryTurn(alloc, summary);
const prefix = if (value.active_prefix) |prefix|
(try types.dupeHistoryTurn(alloc, .{ .assistant = prefix })).assistant
else
null;
break :blk .{ .context_compaction = .{
.turn_id = value.turn_id,
.summary = summary.compacted_summary,
.active_prefix = prefix,
.retained_from = value.retained_from,
.max_history_turns = value.max_history_turns,
} };
},
.finish_prompt => |finished| .{ .finish_prompt = try types.dupeFinishedPrompt(alloc, finished) },
.session_grant => |grant| blk: {
const tool_name = try alloc.dupe(u8, grant.tool_name);
errdefer alloc.free(tool_name);
const target_path = try alloc.dupe(u8, grant.target_path);
break :blk .{ .session_grant = .{
.tool_name = tool_name,
.target_path = target_path,
} };
},
.error_text => |notice| .{ .error_text = try types.dupeSemanticNotice(alloc, notice) },
.restore_failed_prompt => |text| .{ .restore_failed_prompt = try alloc.dupe(u8, text) },
};
}
pub fn freeWorkerEvent(alloc: std.mem.Allocator, event: WorkerEvent) void {
switch (event) {
.begin_prompt => |prompt| types.freeUserTurn(alloc, prompt),
.begin_prompt_with_skill_bindings => |begin| {
types.freeUserTurn(alloc, begin.prompt);
freeSkillBindings(alloc, begin.skill_bindings);
freeSkillDisplaySpans(alloc, begin.skill_display_spans);
},
.begin_presented_prompt => {},
.append_user_feedback => |text| alloc.free(text),
.assistant_presentation => |presentation| {
var owned = presentation;
owned.deinit(alloc);
},
.notification => {},
.open_model_picker => {},
.semantic_notice => |notice| types.freeSemanticNotice(alloc, notice),
.full_detail_record => |notice| types.freeSemanticNotice(alloc, notice),
.route_recovery_status => {},
.clear_route_recovery_status => {},
.api_status_text => |text| alloc.free(text),
.credential_refreshed => |credential| {
var owned = credential;
owned.deinit(alloc);
},
.command_output => |chunk| {
if (chunk.lifecycle_id) |id| alloc.free(@constCast(id.call_id));
alloc.free(chunk.text);
},
.command_output_complete => |lifecycle_id| {
if (lifecycle_id) |id| alloc.free(@constCast(id.call_id));
},
.tool_lifecycle => |lifecycle| freeToolLifecycleEvent(alloc, lifecycle),
.diff_block => |payload| diff_mod.freeDiffEntryPayload(alloc, payload),
.prepare_fresh_prompt => |value| {
types.freeUserTurn(alloc, value.user);
if (value.prior_turn) |prior| types.freeHistoryTurn(alloc, prior);
},
.context_compaction => |value| {
types.freeHistoryTurn(alloc, .{ .compacted_summary = value.summary });
if (value.active_prefix) |prefix| types.freeHistoryTurn(alloc, .{ .assistant = prefix });
},
.finish_prompt => |finished| types.freeFinishedPrompt(alloc, finished),
.session_grant => |grant| {
alloc.free(grant.tool_name);
alloc.free(grant.target_path);
},
.error_text => |notice| types.freeSemanticNotice(alloc, notice),
.restore_failed_prompt => |text| alloc.free(text),
else => {},
}
}
pub fn dupeToolLifecycleEvent(
alloc: std.mem.Allocator,
event: types.ToolLifecycleEvent,
) !types.ToolLifecycleEvent {
return switch (event) {
.provisional => |started| blk: {
const call_id = try alloc.dupe(u8, started.id.call_id);
errdefer alloc.free(call_id);
const tool_name = if (started.tool_name) |name| try alloc.dupe(u8, name) else null;
break :blk .{ .provisional = .{
.id = .{ .turn_id = started.id.turn_id, .call_id = call_id },
.presentation_group_id = started.presentation_group_id,
.tool_name = tool_name,
.activity_kind = started.activity_kind,
} };
},
.authoritative_started => |started| blk: {
const call_id = try alloc.dupe(u8, started.id.call_id);
errdefer alloc.free(call_id);
const alias = if (started.reconciles_provisional_call_id) |value|
try alloc.dupe(u8, value)
else
null;
errdefer if (alias) |value| alloc.free(value);
const tool_name = try alloc.dupe(u8, started.tool_name);
errdefer alloc.free(tool_name);
const arguments_json = if (started.arguments_json) |value|
try alloc.dupe(u8, value)
else
null;
errdefer if (arguments_json) |value| alloc.free(value);
break :blk .{ .authoritative_started = .{
.id = .{ .turn_id = started.id.turn_id, .call_id = call_id },
.presentation_group_id = started.presentation_group_id,
.reconciles_provisional_call_id = alias,
.tool_name = tool_name,
.activity_kind = started.activity_kind,
.arguments_json = arguments_json,
.place_after_current_transcript = started.place_after_current_transcript,
} };
},
.progress => |progress| blk: {
const call_id = try alloc.dupe(u8, progress.id.call_id);
errdefer alloc.free(call_id);
const text = try alloc.dupe(u8, progress.text);
break :blk .{ .progress = .{
.id = .{ .turn_id = progress.id.turn_id, .call_id = call_id },
.text = text,
} };
},
.terminal => |terminal| blk: {
const call_id = try alloc.dupe(u8, terminal.id.call_id);
errdefer alloc.free(call_id);
const summary = try alloc.dupe(u8, terminal.outcome.summary);
errdefer alloc.free(summary);
const result = if (terminal.result) |value|
try alloc.dupe(u8, value)
else
null;
errdefer if (result) |value| alloc.free(value);
const result_memory = try dupeToolResultMemory(alloc, terminal.result_memory);
errdefer freeToolResultMemory(alloc, result_memory);
const command_artifact_handle = if (terminal.command_artifact_handle) |handle|
try alloc.dupe(u8, handle)
else
null;
errdefer if (command_artifact_handle) |handle| alloc.free(handle);
break :blk .{ .terminal = .{
.id = .{ .turn_id = terminal.id.turn_id, .call_id = call_id },
.outcome = .{
.kind = terminal.outcome.kind,
.summary = summary,
},
.result = result,
.result_memory = result_memory,
.command_artifact_handle = command_artifact_handle,
} };
},
.turn_finished => |finished| .{ .turn_finished = finished },
};
}
pub fn freeToolLifecycleEvent(
alloc: std.mem.Allocator,
event: types.ToolLifecycleEvent,
) void {
switch (event) {
.provisional => |started| {
alloc.free(@constCast(started.id.call_id));
if (started.tool_name) |name| alloc.free(@constCast(name));
},
.authoritative_started => |started| {
alloc.free(@constCast(started.id.call_id));
if (started.reconciles_provisional_call_id) |alias| {
alloc.free(@constCast(alias));
}
alloc.free(@constCast(started.tool_name));
if (started.arguments_json) |arguments_json| {
alloc.free(@constCast(arguments_json));
}
},
.progress => |progress| {
alloc.free(@constCast(progress.id.call_id));
alloc.free(@constCast(progress.text));
},
.terminal => |terminal| {
alloc.free(@constCast(terminal.id.call_id));
alloc.free(@constCast(terminal.outcome.summary));
if (terminal.result) |result| alloc.free(@constCast(result));
freeToolResultMemory(alloc, terminal.result_memory);
if (terminal.command_artifact_handle) |handle| alloc.free(@constCast(handle));
},
.turn_finished => {},
}
}
fn dupeToolResultMemory(
alloc: std.mem.Allocator,
source: ?types.ToolResultMemory,
) !?types.ToolResultMemory {
const memory = source orelse return null;
const output_handle = if (memory.output_handle) |handle|
try alloc.dupe(u8, handle)
else
null;
errdefer if (output_handle) |handle| alloc.free(handle);
const preview = if (memory.preview) |value|
try alloc.dupe(u8, value)
else
null;
errdefer if (preview) |value| alloc.free(value);
const command_output_replay = if (memory.command_output_replay) |replay|
try types.dupeCommandOutputReplay(alloc, replay)
else
null;
errdefer if (command_output_replay) |replay| types.freeCommandOutputReplay(alloc, replay);
return .{
.output_handle = output_handle,
.preview = preview,
.output_bytes = memory.output_bytes,
.stored_output_bytes = memory.stored_output_bytes,
.truncated = memory.truncated,
.model_view_covers_full_file = memory.model_view_covers_full_file,
.command_output_replay = command_output_replay,
.command_process_presentation = memory.command_process_presentation,
.terminal_action_presentation = memory.terminal_action_presentation,
};
}
fn freeToolResultMemory(
alloc: std.mem.Allocator,
memory: ?types.ToolResultMemory,
) void {
const value = memory orelse return;
if (value.output_handle) |handle| alloc.free(@constCast(handle));
if (value.preview) |preview| alloc.free(@constCast(preview));
if (value.command_output_replay) |replay| {
types.freeCommandOutputReplay(alloc, replay);
}
}
fn makePrompt(alloc: std.mem.Allocator, text: []const u8, model: []const u8) !QueuedPrompt {
return .{
.prompt = try alloc.dupe(u8, text),
.images = &.{},
.model = try alloc.dupe(u8, model),
.api_key = try alloc.dupe(u8, "key"),
.permission_mode = .auto,
.history = try alloc.alloc(types.HistoryTurn, 0),
.grants = try alloc.alloc(types.PermissionGrant, 0),
};
}
fn makeImagePrompt(alloc: std.mem.Allocator, text: []const u8, model: []const u8) !QueuedPrompt {
var prompt = try makePrompt(alloc, text, model);
errdefer freeQueuedPrompt(alloc, prompt);
const images = try alloc.alloc(types.ImageAttachment, 1);
errdefer alloc.free(images);
const path = try alloc.dupe(u8, "/tmp/steering.png");
errdefer alloc.free(path);
const media_type = try alloc.dupe(u8, "image/png");
errdefer alloc.free(media_type);
images[0] = .{ .path = path, .media_type = media_type };
prompt.images = images;
return prompt;
}
fn makeGrant(alloc: std.mem.Allocator, tool_name: []const u8, target_path: []const u8) !types.PermissionGrant {
return .{ .tool_name = try alloc.dupe(u8, tool_name), .target_path = try alloc.dupe(u8, target_path) };
}
test "direct steering applies at the boundary without cancellation and seals before teardown" {
const alloc = std.testing.allocator;
var receipt = SteeringReceipt{};
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try std.testing.expect(runtime.beginDirectProcessing(41));
var prompt = try makePrompt(alloc, "review feedback", "model");
prompt.steering_receipt = &receipt;
try std.testing.expect(try runtime.admitActiveSteering(alloc, prompt));
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(SteeringDelivery.queued, receipt.state.load(.seq_cst));
var output = std.heap.ArenaAllocator.init(alloc);
defer output.deinit();
const boundary = try runtime.takeSteeringBoundaryInto(alloc, output.allocator(), 41, .finalizing);
try std.testing.expectEqualStrings("review feedback", boundary.continue_turn[0]);
try std.testing.expectEqual(SteeringDelivery.applied, receipt.state.load(.seq_cst));
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
try std.testing.expect((try runtime.takeSteeringBoundary(alloc, 41, .finalizing)) == .none);
const late = try makePrompt(alloc, "too late", "model");
defer freeQueuedPrompt(alloc, late);
try std.testing.expect(!try runtime.admitActiveSteering(alloc, late));
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
}
test "direct steering output allocation failure preserves feedback and cancellation discards it" {
const alloc = std.testing.allocator;
var receipt = SteeringReceipt{};
var runtime = WorkerRuntime{};
try std.testing.expect(runtime.beginDirectProcessing(41));
var prompt = try makePrompt(alloc, "must not disappear", "model");
prompt.steering_receipt = &receipt;
try std.testing.expect(try runtime.admitActiveSteering(alloc, prompt));
var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 });
try std.testing.expectError(error.OutOfMemory, runtime.takeSteeringBoundaryInto(alloc, failing.allocator(), 41, .model));
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
try std.testing.expectEqual(SteeringDelivery.queued, receipt.state.load(.seq_cst));
runtime.requestCancel();
try std.testing.expect((try runtime.takeSteeringBoundary(alloc, 41, .cancelled)) == .interrupt);
runtime.deinit(alloc);
try std.testing.expectEqual(SteeringDelivery.not_applied, receipt.state.load(.seq_cst));
}
test "direct steering rejects inactive cancelled and full-allocation admission without taking ownership" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const prompt = try makePrompt(alloc, "feedback", "model");
defer freeQueuedPrompt(alloc, prompt);
try std.testing.expect(!try runtime.admitActiveSteering(alloc, prompt));
try std.testing.expect(runtime.beginDirectProcessing(41));
var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 });
try std.testing.expectError(error.OutOfMemory, runtime.admitActiveSteering(failing.allocator(), prompt));
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
runtime.requestCancel();
try std.testing.expect(!try runtime.admitActiveSteering(alloc, prompt));
}
fn makePromptWithGrant(alloc: std.mem.Allocator, text: []const u8, model: []const u8, tool_name: []const u8, target_path: []const u8) !QueuedPrompt {
var prompt = try makePrompt(alloc, text, model);
errdefer freeQueuedPrompt(alloc, prompt);
const grants = try alloc.alloc(types.PermissionGrant, 1);
grants[0] = try makeGrant(alloc, tool_name, target_path);
alloc.free(prompt.grants);
prompt.grants = grants;
return prompt;
}
fn freeEventList(alloc: std.mem.Allocator, events: *std.ArrayList(WorkerEvent)) void {
for (events.items) |event| freeWorkerEvent(alloc, event);
events.deinit(alloc);
}
fn expectContinuedSteering(result: SteeringBoundaryResult) ![]const []const u8 {
return switch (result) {
.continue_turn => |messages| messages,
.none, .handoff, .interrupt => error.TestExpectedContinuedSteering,
};
}
test "subagent steering peek preserves the existing queue consumer" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try std.testing.expect(!runtime.hasPendingPlainSteering());
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second", "model"));
try std.testing.expect(runtime.hasPendingPlainSteering());
try std.testing.expect(runtime.hasPendingPlainSteering());
{
var images: [1]types.ImageAttachment = undefined;
runtime.queued_prompts.items[1].images = &images;
defer runtime.queued_prompts.items[1].images = &.{};
try std.testing.expect(!runtime.hasPendingPlainSteering());
}
try std.testing.expectEqual(@as(usize, 2), runtime.queued_prompts.items.len);
const guidance = try expectContinuedSteering(try runtime.takeSteeringBoundary(alloc, 41, .cancelled));
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqualStrings("first", guidance[0]);
try std.testing.expectEqualStrings("second", guidance[1]);
try std.testing.expect(!runtime.hasPendingPlainSteering());
}
test "interactive prompt admission derives steering from active worker state" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, 41, .cancelled),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("steer", guidance[0]);
}
test "interactive prompt during running manual compaction waits without cancelling" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueueContextCompaction(.{
.turn_id = 41,
.model = try alloc.dupe(u8, "provider/model"),
.api_key = try alloc.dupe(u8, "key"),
.history = try alloc.alloc(types.HistoryTurn, 0),
});
const taken = (try runtime.tryTakeNextWork(alloc)) orelse
return error.TestExpectedQueuedWork;
defer freeWorkItem(alloc, taken);
try std.testing.expectEqual(ContextCompactionStatus.running, runtime.contextCompactionStatus());
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
// The compaction is a wait boundary: no cancel, no immediate interrupt.
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(?u64, null), runtime.steering_cancel_turn_id);
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expect(snapshot.waits_for_boundary);
// A compaction work item never takes a steering boundary; finishing it
// promotes the waiting prompt to a continuation that runs next.
runtime.settleCompactionActivity(taken.compact_context.operation_id.?, .{
.outcome = .succeeded,
.stage = .publication,
.publication = .committed,
});
runtime.finishProcessing();
const next = (try runtime.tryTakeNextWork(alloc)) orelse
return error.TestExpectedNextPrompt;
defer freeWorkItem(alloc, next);
try std.testing.expect(next == .prompt);
try std.testing.expect(next.prompt.delivery.isContinuation());
try std.testing.expectEqualStrings("steer", next.prompt.prompt);
}
test "interactive prompt during in-turn compaction activity waits without cancelling" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
// In-turn compaction reports through the activity channel, not the tool feed.
const operation = runtime.beginCompactionActivity(.automatic, 41);
runtime.runCompactionActivity(operation, .summary);
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(?u64, null), runtime.steering_cancel_turn_id);
// The next model boundary drains the waiting steer as same-turn guidance.
const guidance = try expectContinuedSteering(try runtime.takeSteeringBoundary(alloc, 41, .model));
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("steer", guidance[0]);
runtime.settleCompactionActivity(operation, .{
.outcome = .succeeded,
.stage = .summary,
.publication = .committed,
});
runtime.finishProcessing();
}
test "interactive prompt after in-turn compaction settled steers immediately" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
const operation = runtime.beginCompactionActivity(.automatic, 41);
runtime.runCompactionActivity(operation, .summary);
runtime.settleCompactionActivity(operation, .{
.outcome = .succeeded,
.stage = .summary,
.publication = .committed,
});
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
// No active boundary remains, so immediate-steer semantics are unchanged.
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expectEqual(@as(?u64, 41), runtime.steering_cancel_turn_id);
const guidance = try expectContinuedSteering(try runtime.takeSteeringBoundary(alloc, 41, .cancelled));
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("steer", guidance[0]);
}
test "active prompt admission drains steering in FIFO order" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second", "model"));
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, 41, .cancelled),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 2), guidance.len);
try std.testing.expectEqualStrings("first", guidance[0]);
try std.testing.expectEqualStrings("second", guidance[1]);
try std.testing.expectEqual(@as(usize, 0), runtime.queued_prompts.items.len);
try std.testing.expectEqual(@as(usize, 2), runtime.worker_events.items.len);
try std.testing.expectEqualStrings("first", runtime.worker_events.items[0].append_user_feedback);
try std.testing.expectEqualStrings("second", runtime.worker_events.items[1].append_user_feedback);
}
test "immediate steering owns and clears only its cancellation" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second", "model"));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expect(!runtime.cancellationStopsTurn());
var steering_snapshot = try runtime.snapshotState(alloc);
defer steering_snapshot.deinit(alloc);
try std.testing.expect(steering_snapshot.cancel_requested);
try std.testing.expect(steering_snapshot.cancel_continues_turn);
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, 41, .cancelled),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 2), guidance.len);
try std.testing.expectEqualStrings("first", guidance[0]);
try std.testing.expectEqualStrings("second", guidance[1]);
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
}
test "explicit interrupt overrides immediate steering cancellation" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
runtime.requestInteractiveCancel();
try std.testing.expect(runtime.cancellationStopsTurn());
var batch = runtime.takeEventBatch();
defer freeEventList(alloc, &batch.events);
try std.testing.expectEqual(@as(?u64, 41), batch.cancelled_turn_id);
var interrupt_snapshot = try runtime.snapshotState(alloc);
defer interrupt_snapshot.deinit(alloc);
try std.testing.expect(interrupt_snapshot.cancel_requested);
try std.testing.expect(!interrupt_snapshot.cancel_continues_turn);
const boundary = try runtime.takeSteeringBoundary(alloc, 41, .cancelled);
try std.testing.expect(boundary == .interrupt);
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
}
test "interactive prompt after explicit cancellation starts a new visible turn" {
const alloc = std.testing.allocator;
for ([_]bool{ false, true }) |already_presented| {
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try std.testing.expect(runtime.beginDirectProcessing(41));
runtime.requestInteractiveCancel();
var prompt = try makePrompt(alloc, "ok", "model");
prompt.turn_id = 42;
prompt.user_prompt_already_presented = already_presented;
try runtime.admitInteractivePrompt(alloc, prompt);
try std.testing.expect(runtime.cancellationStopsTurn());
try std.testing.expect(runtime.queued_prompts.items[0].delivery == .ordinary);
try std.testing.expect(try runtime.takeSteeringBoundary(alloc, 41, .cancelled) == .interrupt);
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
runtime.finishProcessing();
const next = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, next);
try std.testing.expectEqual(@as(u64, 42), next.turn_id);
try std.testing.expectEqualStrings("ok", next.prompt);
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 1), runtime.worker_events.items.len);
if (already_presented) {
try std.testing.expectEqual(@as(u64, 42), runtime.worker_events.items[0].begin_presented_prompt);
} else {
try std.testing.expectEqualStrings("ok", runtime.worker_events.items[0].begin_prompt.text);
}
runtime.finishProcessing();
}
}
test "sequential tool lifecycle does not inspect compatible allocator context identities" {
var first_context: u8 = 0;
var second_context: u8 = 0;
var first_view = std.heap.c_allocator;
var second_view = std.heap.c_allocator;
first_view.ptr = @ptrCast(&first_context);
second_view.ptr = @ptrCast(&second_context);
var runtime = WorkerRuntime{};
defer runtime.deinit(std.heap.c_allocator);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(first_view, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_first" },
.reconciles_provisional_call_id = null,
.tool_name = "shell",
.activity_kind = .command,
} } });
try runtime.pushEvent(first_view, .{ .tool_lifecycle = .{ .terminal = .{
.id = .{ .turn_id = 41, .call_id = "call_first" },
.outcome = .{ .kind = .completed, .summary = "completed" },
} } });
try runtime.pushEvent(second_view, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_second" },
.reconciles_provisional_call_id = null,
.tool_name = "shell",
.activity_kind = .command,
} } });
try std.testing.expect(runtime.active_tool_calls.contains("call_second"));
}
test "tool lifecycle decides whether interactive steering interrupts immediately" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.reconciles_provisional_call_id = null,
.tool_name = "terminal",
.activity_kind = .command,
} } });
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_parallel" },
.reconciles_provisional_call_id = null,
.tool_name = "read_file",
.activity_kind = .read,
} } });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "wait for tool", "model"));
try std.testing.expect(!runtime.isCancelRequested());
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .terminal = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.outcome = .{ .kind = .completed, .summary = "completed" },
} } });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "still wait", "model"));
try std.testing.expect(!runtime.isCancelRequested());
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .terminal = .{
.id = .{ .turn_id = 41, .call_id = "call_parallel" },
.outcome = .{ .kind = .completed, .summary = "completed" },
} } });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "interrupt generation", "model"));
try std.testing.expect(runtime.isCancelRequested());
}
test "subagent wait phase preserves steering control and existing tool boundaries" {
const alloc = std.testing.allocator;
for ([_]?u64{ null, 7 }) |tool_step| {
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
runtime.tool_phase_step_id = tool_step;
for ([_][]const u8{ "first steering", "second steering" }) |text| {
try runtime.pushEvent(alloc, .{ .turn_phase_update = .{
.turn_id = 41,
.step_id = 8,
.phase = .waiting_for_subagent,
} });
try std.testing.expectEqual(tool_step, runtime.tool_phase_step_id);
try std.testing.expectEqual(@as(usize, 0), runtime.active_tool_calls.count());
try std.testing.expect(!runtime.isCancelRequested());
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, text, "model"));
try std.testing.expectEqual(tool_step == null, runtime.isCancelRequested());
const guidance = try expectContinuedSteering(try runtime.takeSteeringBoundary(
alloc,
41,
if (tool_step == null) .cancelled else .model,
));
defer {
for (guidance) |item| alloc.free(item);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings(text, guidance[0]);
try std.testing.expect(!runtime.isCancelRequested());
}
runtime.requestCancel();
try std.testing.expect((try runtime.takeSteeringBoundary(alloc, 41, .cancelled)) == .interrupt);
}
}
test "tool handoff phase holds steering only through its model step" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(alloc, .{ .turn_phase_update = .{
.turn_id = 41,
.step_id = 7,
.phase = .running,
} });
try runtime.pushEvent(alloc, .{ .turn_phase_update = .{
.turn_id = 41,
.step_id = 7,
.phase = .generating,
} });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "wait for pending tools", "model"));
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expect(snapshot.waits_for_boundary);
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, 41, .model),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqualStrings("wait for pending tools", guidance[0]);
try runtime.pushEvent(alloc, .{ .turn_phase_update = .{
.turn_id = 41,
.step_id = 8,
.phase = .thinking,
} });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "interrupt next model step", "model"));
try std.testing.expect(runtime.isCancelRequested());
}
test "failed steering admission does not cancel the active turn" {
var runtime = WorkerRuntime{};
defer runtime.deinit(std.testing.allocator);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
var failing = std.testing.FailingAllocator.init(
std.testing.allocator,
.{ .fail_index = 0 },
);
const prompt = QueuedPrompt{
.prompt = @constCast("retain me"),
.images = &.{},
.model = @constCast("model"),
.api_key = @constCast("key"),
.permission_mode = .auto,
.history = &.{},
.grants = &.{},
};
try std.testing.expectError(
error.OutOfMemory,
runtime.admitInteractivePrompt(failing.allocator(), prompt),
);
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
}
test "rich interactive input remains steering and hands off at the tool boundary" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.reconciles_provisional_call_id = null,
.tool_name = "terminal",
.activity_kind = .command,
} } });
var prompt = try makePrompt(alloc, "inspect this image", "model");
errdefer freeQueuedPrompt(alloc, prompt);
prompt.images = try alloc.alloc(types.ImageAttachment, 1);
prompt.images[0] = .{
.path = try alloc.dupe(u8, "/tmp/steering.png"),
.media_type = try alloc.dupe(u8, "image/png"),
};
try runtime.admitInteractivePrompt(alloc, prompt);
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 1), snapshot.messages.len);
try std.testing.expect(!runtime.isCancelRequested());
const boundary = try runtime.takeSteeringBoundary(alloc, 41, .model);
try std.testing.expect(boundary == .handoff);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
}
test "queued steer waiting at a tool boundary pops back for editing" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.reconciles_provisional_call_id = null,
.tool_name = "terminal",
.activity_kind = .command,
} } });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first steer", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second steer", "model"));
try std.testing.expect(!runtime.isCancelRequested());
const second = (try runtime.popQueuedSteerForEdit(alloc)).?;
defer alloc.free(second);
try std.testing.expectEqualStrings("second steer", second);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 1), snapshot.messages.len);
try std.testing.expectEqualStrings("first steer", snapshot.messages[0]);
try std.testing.expect(snapshot.waits_for_boundary);
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, 41, .model),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("first steer", guidance[0]);
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
try std.testing.expect(try runtime.popQueuedSteerForEdit(alloc) == null);
}
test "immediate steer already committed to an interrupt is not retractable" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expect(try runtime.popQueuedSteerForEdit(alloc) == null);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, 41, .cancelled),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("steer", guidance[0]);
}
test "steer retraction skips handoff prompts and takes the newest text steer" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.reconciles_provisional_call_id = null,
.tool_name = "terminal",
.activity_kind = .command,
} } });
var rich = try makePrompt(alloc, "inspect this image", "model");
rich.images = try alloc.alloc(types.ImageAttachment, 1);
rich.images[0] = .{
.path = try alloc.dupe(u8, "/tmp/steering.png"),
.media_type = try alloc.dupe(u8, "image/png"),
};
try runtime.admitInteractivePrompt(alloc, rich);
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "plain text", "model"));
const text = (try runtime.popQueuedSteerForEdit(alloc)).?;
defer alloc.free(text);
try std.testing.expectEqualStrings("plain text", text);
try std.testing.expect(try runtime.popQueuedSteerForEdit(alloc) == null);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
const boundary = try runtime.takeSteeringBoundary(alloc, 41, .model);
try std.testing.expect(boundary == .handoff);
}
test "steer retraction requires an active processing turn" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued", "model"));
try std.testing.expect(try runtime.popQueuedSteerForEdit(alloc) == null);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
}
test "failed steer retraction preserves the queue" {
const backing = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(backing);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(backing, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.reconciles_provisional_call_id = null,
.tool_name = "terminal",
.activity_kind = .command,
} } });
try runtime.admitInteractivePrompt(backing, try makePrompt(backing, "steer", "model"));
var failing = std.testing.FailingAllocator.init(
backing,
.{ .fail_index = 0 },
);
try std.testing.expectError(
error.OutOfMemory,
runtime.popQueuedSteerForEdit(failing.allocator()),
);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
}
test "immediate steering is visible while the provider cutoff settles" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second", "model"));
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 2), snapshot.messages.len);
try std.testing.expectEqualStrings("first", snapshot.messages[0]);
try std.testing.expectEqualStrings("second", snapshot.messages[1]);
try std.testing.expect(!snapshot.waits_for_boundary);
}
test "steering presentation survives queue to feedback transfer without duplicate input" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try std.testing.expect(runtime.beginDirectProcessing(41));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "same text", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "same text", "model"));
var queued = try runtime.snapshotSteeringPresentation(alloc);
defer queued.deinit(alloc);
try std.testing.expectEqual(@as(usize, 2), queued.messages.len);
try std.testing.expectEqual(@as(usize, 0), queued.pending_feedback.len);
const guidance = try expectContinuedSteering(try runtime.takeSteeringBoundary(alloc, 41, .cancelled));
defer {
for (guidance) |message| alloc.free(message);
alloc.free(guidance);
}
var consumed = try runtime.snapshotSteeringPresentation(alloc);
defer consumed.deinit(alloc);
try std.testing.expectEqual(@as(usize, 0), consumed.messages.len);
try std.testing.expectEqual(@as(usize, 2), consumed.pending_feedback.len);
for (consumed.pending_feedback) |message| try std.testing.expectEqualStrings("same text", message);
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
var drained = try runtime.snapshotSteeringPresentation(alloc);
defer drained.deinit(alloc);
try std.testing.expectEqual(@as(usize, 0), drained.pending_feedback.len);
try std.testing.expectEqual(@as(usize, 2), events.items.len);
runtime.finishProcessing();
}
test "steering snapshot allocation failure preserves queue and feedback ownership" {
try std.testing.checkAllAllocationFailures(std.testing.allocator, struct {
fn run(alloc: std.mem.Allocator) !void {
const backing = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(backing);
try std.testing.expect(runtime.beginDirectProcessing(41));
try runtime.pushEvent(backing, .{ .append_user_feedback = @constCast("earlier") });
try runtime.admitInteractivePrompt(backing, try makePrompt(backing, "later", "model"));
var snapshot = runtime.snapshotSteeringPresentation(alloc) catch |err| {
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
try std.testing.expectEqual(@as(usize, 1), runtime.worker_events.items.len);
return err;
};
defer snapshot.deinit(alloc);
try std.testing.expectEqualStrings("earlier", snapshot.pending_feedback[0]);
try std.testing.expectEqualStrings("later", snapshot.messages[0]);
runtime.finishProcessing();
}
}.run, .{});
}
test "tool-blocked steering snapshot owns visible messages in admission order" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.pushEvent(alloc, .{ .tool_lifecycle = .{ .authoritative_started = .{
.id = .{ .turn_id = 41, .call_id = "call_running" },
.reconciles_provisional_call_id = null,
.tool_name = "terminal",
.activity_kind = .command,
} } });
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second", "model"));
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 2), snapshot.messages.len);
try std.testing.expectEqualStrings("first", snapshot.messages[0]);
try std.testing.expectEqualStrings("second", snapshot.messages[1]);
try std.testing.expect(snapshot.waits_for_boundary);
}
test "late steering keeps admission order when demoted on finish" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 9;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer first", "model"));
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queue second", "model"));
runtime.finishProcessing();
try std.testing.expect(!runtime.worker_processing);
try std.testing.expectEqual(@as(u64, 0), runtime.active_turn_id);
try std.testing.expectEqual(@as(usize, 2), runtime.queuedPromptCount());
try std.testing.expectEqualStrings("steer first", runtime.queued_prompts.items[0].prompt);
try std.testing.expect(runtime.queued_prompts.items[0].delivery.isContinuation());
try std.testing.expectEqualStrings("queue second", runtime.queued_prompts.items[1].prompt);
try std.testing.expect(!runtime.queued_prompts.items[1].delivery.isContinuation());
}
test "promoted steering retargets remaining guidance without exposing a queue" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 9;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "second", "model"));
runtime.finishProcessing();
const promoted = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, promoted);
try std.testing.expect(promoted.delivery.isContinuation());
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 1), snapshot.messages.len);
const guidance = try expectContinuedSteering(
try runtime.takeSteeringBoundary(alloc, promoted.turn_id, .model),
);
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("second", guidance[0]);
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
}
test "promoted steering keeps rich trailing guidance for its own turn" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 9;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "first", "model"));
var rich = try makePrompt(alloc, "second with image", "model");
var owns_rich = true;
errdefer if (owns_rich) freeQueuedPrompt(alloc, rich);
rich.images = try alloc.alloc(types.ImageAttachment, 1);
rich.images[0] = .{
.path = try alloc.dupe(u8, "/tmp/trailing.png"),
.media_type = try alloc.dupe(u8, "image/png"),
};
try runtime.admitInteractivePrompt(alloc, rich);
owns_rich = false;
runtime.finishProcessing();
const promoted = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, promoted);
try std.testing.expect(promoted.delivery.isContinuation());
var snapshot = try runtime.snapshotSteeringPresentation(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 1), snapshot.messages.len);
const boundary = try runtime.takeSteeringBoundary(alloc, promoted.turn_id, .model);
try std.testing.expect(boundary == .none);
runtime.requestInteractiveCancel();
runtime.finishProcessing();
const trailing = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, trailing);
try std.testing.expect(trailing.delivery.isContinuation());
try std.testing.expectEqualStrings("second with image", trailing.prompt);
}
test "clear queued prompts also clears steering" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 9;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued", "model"));
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
const boundary = try runtime.takeSteeringBoundary(alloc, 9, .model);
try std.testing.expect(boundary == .none);
}
test "idle interactive admission uses ordinary queue" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "next", "model"));
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
try std.testing.expect(runtime.queued_prompts.items[0].delivery == .ordinary);
}
test "request shutdown sets stop and cancel flags" {
var runtime = WorkerRuntime{};
defer runtime.deinit(std.testing.allocator);
runtime.requestShutdown();
try std.testing.expect(runtime.worker_stop_requested);
try std.testing.expect(runtime.worker_cancel_requested.load(.seq_cst));
}
test "takeEventBatch snapshots cancellation with detached events" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.active_turn_id = 7;
runtime.worker_cancel_requested.store(true, .seq_cst);
runtime.worker_recovery_pause_requested.store(true, .seq_cst);
try runtime.pushEvent(alloc, .{ .assistant_presentation = .{
.text = @constCast("late"),
} });
var batch = runtime.takeEventBatch();
defer freeEventList(alloc, &batch.events);
runtime.active_turn_id = 8;
runtime.worker_cancel_requested.store(false, .seq_cst);
try std.testing.expectEqual(@as(?u64, 7), batch.cancelled_turn_id);
try std.testing.expectEqual(@as(usize, 1), batch.events.items.len);
try std.testing.expect(batch.events.items[0] == .assistant_presentation);
try std.testing.expect(batch.events.items[0].assistant_presentation == .text);
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
}
test "rich immediate steering revokes an earlier continuable cancellation" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "plain steer", "model"));
try runtime.admitInteractivePrompt(alloc, try makeImagePrompt(alloc, "rich steer", "model"));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expect(runtime.cancellationStopsTurn());
try runtime.pushEvent(alloc, .{ .assistant_presentation = .{ .text = @constCast("must stay hidden") } });
var batch = runtime.takeEventBatch();
defer freeEventList(alloc, &batch.events);
try std.testing.expectEqual(@as(?u64, 41), batch.cancelled_turn_id);
try std.testing.expectEqual(@as(usize, 1), batch.events.items.len);
try std.testing.expect(
try runtime.takeSteeringBoundary(alloc, 41, .cancelled) == .interrupt,
);
}
test "plain input cannot make an earlier rich cancellation continuable" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makeImagePrompt(alloc, "rich steer", "model"));
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "plain next turn", "model"));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expect(runtime.cancellationStopsTurn());
try std.testing.expectEqual(@as(?u64, null), runtime.steering_cancel_turn_id);
try std.testing.expectEqual(PromptDelivery.ordinary, runtime.queued_prompts.items[1].delivery);
var batch = runtime.takeEventBatch();
defer freeEventList(alloc, &batch.events);
try std.testing.expectEqual(@as(?u64, 41), batch.cancelled_turn_id);
try std.testing.expect(
try runtime.takeSteeringBoundary(alloc, 41, .cancelled) == .interrupt,
);
}
test "takeEventBatch keeps the steering tail live before boundary adoption" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer", "model"));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expect(!runtime.cancellationStopsTurn());
try runtime.pushEvent(alloc, .{ .assistant_presentation = .{ .text = @constCast("buffered tail") } });
var batch = runtime.takeEventBatch();
defer freeEventList(alloc, &batch.events);
try std.testing.expectEqual(@as(?u64, null), batch.cancelled_turn_id);
try std.testing.expectEqual(@as(usize, 1), batch.events.items.len);
try std.testing.expectEqualStrings("buffered tail", batch.events.items[0].assistant_presentation.text);
const guidance = try expectContinuedSteering(try runtime.takeSteeringBoundary(alloc, 41, .cancelled));
defer {
for (guidance) |text| alloc.free(text);
alloc.free(guidance);
}
try std.testing.expectEqual(@as(usize, 1), guidance.len);
try std.testing.expectEqualStrings("steer", guidance[0]);
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expectEqual(@as(?u64, null), batch.cancelled_turn_id);
var feedback = runtime.takeEventBatch();
defer freeEventList(alloc, &feedback.events);
try std.testing.expectEqual(@as(usize, 1), feedback.events.items.len);
try std.testing.expectEqualStrings("steer", feedback.events.items[0].append_user_feedback);
}
test "takeEventBatch requires a matching active steering owner to continue" {
const cases = [_]struct { active: u64, owner: ?u64, requested: bool, stopped: ?u64 }{
.{ .active = 7, .owner = null, .requested = true, .stopped = 7 },
.{ .active = 7, .owner = 6, .requested = true, .stopped = 7 },
.{ .active = 7, .owner = 7, .requested = true, .stopped = null },
.{ .active = 7, .owner = null, .requested = false, .stopped = null },
.{ .active = 0, .owner = null, .requested = true, .stopped = null },
};
for (cases) |case| {
var runtime = WorkerRuntime{};
defer runtime.deinit(std.testing.allocator);
runtime.active_turn_id = case.active;
runtime.steering_cancel_turn_id = case.owner;
runtime.worker_cancel_requested.store(case.requested, .seq_cst);
var batch = runtime.takeEventBatch();
defer freeEventList(std.testing.allocator, &batch.events);
try std.testing.expectEqual(case.stopped, batch.cancelled_turn_id);
}
}
test "state snapshot reports completion queued after event batch detach" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.pushEvent(alloc, .{ .command_output = .{
.stream = .stdout,
.text = @constCast("record\n"),
} });
var batch = runtime.takeEventBatch();
defer freeEventList(alloc, &batch.events);
try runtime.pushEvent(alloc, .{ .command_output_complete = null });
var snapshot = try runtime.snapshotState(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 1), snapshot.pending_event_count);
try std.testing.expectEqual(@as(usize, 1), batch.events.items.len);
try std.testing.expect(batch.events.items[0] == .command_output);
try std.testing.expect(runtime.worker_events.items[0] == .command_output_complete);
}
fn checkStateSnapshotFailurePreservesPendingEvents(alloc: std.mem.Allocator) !void {
const owner_alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(owner_alloc);
try runtime.pushEvent(owner_alloc, .{ .command_output_complete = null });
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
owner_alloc,
.{ .id = 17, .label = "pending command completion" },
);
var snapshot = runtime.snapshotState(alloc) catch |err| {
try std.testing.expectEqual(@as(usize, 1), runtime.worker_events.items.len);
try std.testing.expect(runtime.worker_events.items[0] == .command_output_complete);
try std.testing.expectEqualStrings(
"pending command completion",
runtime.pending_permission_request_shared.?.label,
);
return err;
};
defer snapshot.deinit(alloc);
try std.testing.expectEqual(@as(usize, 1), snapshot.pending_event_count);
try std.testing.expectEqual(@as(usize, 1), runtime.worker_events.items.len);
try std.testing.expect(runtime.worker_events.items[0] == .command_output_complete);
}
test "state snapshot allocation failures preserve pending event ownership" {
try std.testing.checkAllAllocationFailures(
std.testing.allocator,
checkStateSnapshotFailurePreservesPendingEvents,
.{},
);
}
test "finalization failure latches fixed metadata and closes interactive admission" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try std.testing.expect(runtime.interactiveAdmissionSnapshot() == .open);
runtime.latchFinalizationFailure(.{
.turn_id = 41,
.outcome = .failed,
});
runtime.latchFinalizationFailure(.{
.turn_id = 42,
.outcome = .interrupted,
});
switch (runtime.interactiveAdmissionSnapshot()) {
.finalization_failed => |failure| {
try std.testing.expectEqual(@as(u64, 41), failure.turn_id);
try std.testing.expectEqual(types.TurnPresentationOutcome.failed, failure.outcome);
},
else => return error.TestExpectedEqual,
}
try std.testing.expect(runtime.worker_stop_requested);
try std.testing.expect(runtime.worker_cancel_requested.load(.seq_cst));
const prompt = try makePrompt(alloc, "rejected", "model");
try std.testing.expectError(error.TurnFinalizationDeliveryFailed, runtime.enqueuePrompt(alloc, prompt));
freeQueuedPrompt(alloc, prompt);
try std.testing.expectEqual(@as(usize, 0), runtime.queued_prompts.items.len);
}
test "dedicated turn finalizer queues before optional finish payload" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.pushTurnFinished(alloc, .{
.turn_id = 99,
.outcome = .completed,
});
try runtime.pushEvent(alloc, .{ .finish_prompt = .{
.turn = .{ .assistant = .{
.user = .{ .text = @constCast("prompt") },
.assistant = @constCast("answer"),
} },
} });
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 2), events.items.len);
try std.testing.expect(events.items[0] == .tool_lifecycle);
try std.testing.expectEqual(
@as(u64, 99),
events.items[0].tool_lifecycle.turn_finished.turn_id,
);
try std.testing.expect(events.items[1] == .finish_prompt);
}
test "queue, event, snapshot, sync, history, and grant behavior" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const first_prompt = try makePrompt(alloc, "first", "old");
try runtime.enqueuePrompt(alloc, first_prompt);
const second_prompt = try makePrompt(alloc, "second", "old");
try runtime.enqueuePrompt(alloc, second_prompt);
try std.testing.expectEqual(@as(usize, 2), runtime.queuedPromptCount());
runtime.worker_cancel_requested.store(true, .seq_cst);
const job = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
try std.testing.expect(job.turn_id != 0);
try std.testing.expectEqualStrings("first", job.prompt);
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expect(runtime.worker_processing);
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .begin_prompt);
try std.testing.expectEqualStrings("first", events.items[0].begin_prompt.text);
try std.testing.expect(events.items[0].begin_prompt.text.ptr != job.prompt.ptr);
try runtime.syncQueuedPromptModel(alloc, "next");
try std.testing.expectEqualStrings("next", runtime.queued_prompts.items[0].model);
runtime.syncQueuedPromptPermissionSnapshot(.{
.mode = .ask,
});
try std.testing.expectEqual(types.PermissionMode.ask, runtime.queued_prompts.items[0].permission_mode);
try std.testing.expectEqual(types.PermissionMode.auto, job.permission_mode);
const grant_source = try alloc.alloc(types.PermissionGrant, 1);
defer types.freePermissionGrantSlice(alloc, grant_source);
grant_source[0] = try makeGrant(alloc, "read_file", "/tmp/a");
try runtime.syncQueuedPromptPermissionState(alloc, grant_source, .{
.mode = .auto,
});
grant_source[0].tool_name[0] = 'R';
grant_source[0].target_path[1] = 'T';
try std.testing.expectEqualStrings("read_file", runtime.queued_prompts.items[0].grants[0].tool_name);
try std.testing.expectEqualStrings("/tmp/a", runtime.queued_prompts.items[0].grants[0].target_path);
const old_turn: types.HistoryTurn = .{ .compacted_summary = .{
.summary = try alloc.dupe(u8, "old"),
.removed_turn_count = 0,
.compaction_count = 0,
} };
defer types.freeHistoryTurn(alloc, old_turn);
const seeded_history = try appendHistoryTurnProjection(
alloc,
runtime.queued_history,
old_turn,
2,
);
types.freeHistoryTurnSlice(alloc, runtime.queued_history);
runtime.queued_history = seeded_history;
const turn: types.HistoryTurn = .{ .compacted_summary = .{
.summary = try alloc.dupe(u8, "summary"),
.removed_turn_count = 0,
.compaction_count = 0,
} };
defer types.freeHistoryTurn(alloc, turn);
try runtime.propagateHistoryTurn(alloc, turn, 1);
try std.testing.expectEqual(@as(usize, 2), runtime.queued_history.len);
try std.testing.expect(runtime.queued_history[1] == .compacted_summary);
try std.testing.expectEqualStrings("summary", runtime.queued_history[1].compacted_summary.summary);
try std.testing.expectEqual(@as(usize, 0), runtime.queued_prompts.items[0].unversioned_history_count);
try runtime.propagateGrant(alloc, "read_file", "/tmp/a");
try std.testing.expectEqual(@as(usize, 1), runtime.queued_prompts.items[0].grants.len);
try runtime.propagateGrant(alloc, "read_file", "/tmp/b");
try std.testing.expectEqual(@as(usize, 2), runtime.queued_prompts.items[0].grants.len);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 1, .label = "pending" },
);
var snapshot = try runtime.snapshotState(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqualStrings("pending", snapshot.pending_permission_request.?.label);
try std.testing.expect(
snapshot.pending_permission_request.?.label.ptr !=
runtime.pending_permission_request_shared.?.label.ptr,
);
}
test "permission response durable barrier commits before waiter wake" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 9, .label = "prepared action" },
);
const Capture = struct {
runtime: *WorkerRuntime,
committed: bool = false,
fn commit(raw: *anyopaque) WorkerRuntime.PermissionCommitError!void {
const self: *@This() = @ptrCast(@alignCast(raw));
if (self.runtime.pending_permission_response != null) {
return error.PermissionCommitFailed;
}
self.committed = true;
}
};
var capture = Capture{ .runtime = &runtime };
const result = try runtime.submitPermissionResponseAfterCommit(
9,
permission_request.OwnedPermissionResponse.init(alloc, .always, null),
.{ .context = &capture, .commit_fn = Capture.commit },
);
try std.testing.expectEqual(PermissionSubmissionResult.accepted, result);
try std.testing.expect(capture.committed);
try std.testing.expect(runtime.pending_permission_response != null);
}
test "permission response durable barrier failure does not wake waiter" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 10, .label = "prepared action" },
);
const Fail = struct {
fn commit(_: *anyopaque) WorkerRuntime.PermissionCommitError!void {
return error.PermissionCommitFailed;
}
};
try std.testing.expectError(
error.PermissionCommitFailed,
runtime.submitPermissionResponseAfterCommit(
10,
permission_request.OwnedPermissionResponse.init(alloc, .once, null),
.{ .context = &runtime, .commit_fn = Fail.commit },
),
);
try std.testing.expect(runtime.pending_permission_response == null);
try std.testing.expect(runtime.pending_permission_waiting);
}
test "permission capacity rejection removes the unpublished waiter" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const Reject = struct {
fn observe(
_: *anyopaque,
_: *WorkerRuntime,
_: permission_request.PermissionRequest,
) error{
OutOfMemory,
PermissionRegistrationFailed,
PermissionCapacityExceeded,
}!void {
return error.PermissionCapacityExceeded;
}
};
try std.testing.expectError(
error.PermissionCapacityExceeded,
runtime.requestPermissionBlockingObserved(
alloc,
.{ .id = 11, .label = "capacity-bound action" },
null,
.{ .context = &runtime, .observe_fn = Reject.observe },
),
);
try std.testing.expect(!runtime.pending_permission_waiting);
try std.testing.expect(runtime.pending_permission_request_shared == null);
try std.testing.expect(runtime.pending_permission_review == null);
try std.testing.expect(runtime.pending_permission_response == null);
}
test "waitAndTakeNextPrompt emits bound skill prompt event when queued prompt has bindings" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var prompt = try makePrompt(alloc, "raw $review then $review and $review", "model");
errdefer freeQueuedPrompt(alloc, prompt);
prompt.skill_bindings = try dupeSkillBindings(alloc, &[_]SkillBinding{.{
.name = @constCast("review"),
.path = @constCast("/tmp/.codex/skills/review"),
}});
prompt.skill_display_spans = try dupeSkillDisplaySpans(alloc, &[_]SkillDisplaySpan{
.{
.raw_start = "raw $review then ".len,
.raw_end = "raw $review then $review".len,
.name = @constCast("review"),
.path = @constCast("/tmp/.codex/skills/review"),
.display_source = .workspace_codex,
.owns_trailing_separator = true,
},
.{
.raw_start = "raw $review then $review and ".len,
.raw_end = "raw $review then $review and $review".len,
.name = @constCast("review"),
.path = @constCast("/tmp/.codex/skills/review"),
},
});
try runtime.enqueuePrompt(alloc, prompt);
const job = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .begin_prompt_with_skill_bindings);
try std.testing.expectEqualStrings("raw $review then $review and $review", events.items[0].begin_prompt_with_skill_bindings.prompt.text);
try std.testing.expectEqual(@as(usize, 1), events.items[0].begin_prompt_with_skill_bindings.skill_bindings.len);
try std.testing.expectEqualStrings("review", events.items[0].begin_prompt_with_skill_bindings.skill_bindings[0].name);
try std.testing.expectEqualStrings("/tmp/.codex/skills/review", events.items[0].begin_prompt_with_skill_bindings.skill_bindings[0].path);
try std.testing.expectEqual(@as(usize, 2), events.items[0].begin_prompt_with_skill_bindings.skill_display_spans.len);
try std.testing.expectEqual(@as(usize, "raw $review then ".len), events.items[0].begin_prompt_with_skill_bindings.skill_display_spans[0].raw_start);
try std.testing.expectEqual(@as(usize, "raw $review then $review".len), events.items[0].begin_prompt_with_skill_bindings.skill_display_spans[0].raw_end);
try std.testing.expectEqualStrings("review", events.items[0].begin_prompt_with_skill_bindings.skill_display_spans[0].name);
try std.testing.expectEqual(
skill_contract.SkillSource.workspace_codex,
events.items[0].begin_prompt_with_skill_bindings.skill_display_spans[0].display_source.?,
);
try std.testing.expect(events.items[0].begin_prompt_with_skill_bindings.skill_display_spans[0].owns_trailing_separator);
}
test "permission snapshot keeps the full file review borrowed and request-bound" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var review = try diff_mod.FileReview.init(
alloc,
"",
"line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\n",
);
defer review.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{
.id = 44,
.label = "file_mutation",
.file = .{
.kind = .write,
.intent = .mutation,
.preview = .{
.path = "note.txt",
.lines = &.{
.{ .op = .addition, .new_line = 1, .text = "line 1" },
.{ .op = .addition, .new_line = 7, .text = "line 7" },
},
.additions = 7,
.deletions = 0,
.truncated = true,
},
.scope = .workspace_files,
},
},
);
runtime.pending_permission_review = &review;
var snapshot = try runtime.snapshotState(alloc);
defer snapshot.deinit(alloc);
try std.testing.expectEqual(
@as(usize, 2),
snapshot.pending_permission_request.?.file.?.preview.lines.len,
);
try std.testing.expectEqual(@as(u64, 44), snapshot.pending_permission_review.?.request_id);
try std.testing.expect(snapshot.pending_permission_review.?.review == &review);
try std.testing.expectEqual(@as(usize, 7), snapshot.pending_permission_review.?.review.rowCount());
}
test "held turn propagation refreshes queued prompt trusted reviewer context" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var queued = try makePrompt(alloc, "queued current prompt", "model");
var canonical_history = [_]types.HistoryTurn{.{ .assistant = .{
.user = .{ .text = @constCast("original first request") },
.assistant = @constCast("original answer"),
} }};
queued.root_user_intent_context = try auto_classifier_context.buildCanonicalRootUserContext(
alloc,
queued.prompt,
&canonical_history,
);
try runtime.enqueuePrompt(alloc, queued);
const original_prompt_ptr = runtime.queued_prompts.items[0].prompt.ptr;
const original_prompt_len = runtime.queued_prompts.items[0].prompt.len;
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = try alloc.dupe(u8, "Revoke permission: do not modify any more files.") },
.assistant = try alloc.dupe(u8, "completed prior answer"),
} };
defer types.freeHistoryTurn(alloc, turn);
try runtime.propagateHistoryTurn(alloc, turn, 8);
try std.testing.expectEqual(@as(usize, 1), runtime.queued_history.len);
try std.testing.expect(runtime.queued_history[0] == .assistant);
try std.testing.expectEqualStrings("Revoke permission: do not modify any more files.", runtime.queued_history[0].assistant.user.text);
try std.testing.expectEqualStrings("completed prior answer", runtime.queued_history[0].assistant.assistant);
try std.testing.expectEqualStrings("queued current prompt", runtime.queued_prompts.items[0].prompt);
try std.testing.expectEqual(original_prompt_len, runtime.queued_prompts.items[0].prompt.len);
try std.testing.expect(original_prompt_ptr == runtime.queued_prompts.items[0].prompt.ptr);
const review_context = runtime.queued_prompts.items[0].root_user_intent_context;
try std.testing.expect(std.mem.find(
u8,
review_context,
"first_root_user_request: original first request",
) != null);
try std.testing.expect(std.mem.find(
u8,
review_context,
"recent_root_user_request: Revoke permission: do not modify any more files.",
) != null);
try std.testing.expect(std.mem.find(
u8,
review_context,
"current_request: queued current prompt",
) != null);
const newer_turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = try alloc.dupe(u8, "newer completed request") },
.assistant = try alloc.dupe(u8, "newer completed answer"),
} };
defer types.freeHistoryTurn(alloc, newer_turn);
try runtime.propagateHistoryTurn(alloc, newer_turn, 8);
const refreshed = runtime.queued_prompts.items[0].root_user_intent_context;
try std.testing.expect(std.mem.find(
u8,
refreshed,
"recent_root_user_request: Revoke permission: do not modify any more files.",
) != null);
try std.testing.expect(std.mem.find(
u8,
refreshed,
"recent_root_user_request: newer completed request",
) != null);
}
test "completed image turns propagate authority to queued follow-ups" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var prompt = try makePrompt(alloc, "queued follow-up", "model");
prompt.authorized_image_catalog = try types.dupeImageAttachmentSlice(alloc, &.{.{
.id = 2,
.path = @constCast("/tmp/older.png"),
.media_type = @constCast("image/png"),
}});
try runtime.enqueuePrompt(alloc, prompt);
var completed_images = [_]types.ImageAttachment{.{
.id = 8,
.path = @constCast("/tmp/completed.png"),
.media_type = @constCast("image/png"),
}};
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = @constCast("active image turn"), .images = &completed_images },
.assistant = @constCast("done"),
} };
try runtime.propagateHistoryTurn(alloc, turn, 1);
const queued = runtime.queued_prompts.items[0];
try std.testing.expectEqual(@as(usize, 2), queued.authorized_image_catalog.len);
try std.testing.expectEqual(@as(usize, 2), queued.authorized_image_catalog[0].id);
try std.testing.expectEqual(@as(usize, 8), queued.authorized_image_catalog[1].id);
try std.testing.expectEqualStrings("/tmp/completed.png", queued.authorized_image_catalog[1].path);
}
test "explicit cancellation keeps targeted steering runnable" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 17;
try runtime.admitInteractivePrompt(alloc, try makePrompt(alloc, "steer now", "model"));
runtime.requestInteractiveCancel();
try std.testing.expect(runtime.isCancelRequested());
runtime.finishProcessing();
const next = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, next);
try std.testing.expectEqualStrings("steer now", next.prompt);
}
const PromptTakeThreadState = struct {
started: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
finished: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
err: ?anyerror = null,
job: ?QueuedPrompt = null,
};
fn runPromptTake(state: *PromptTakeThreadState, runtime: *WorkerRuntime) void {
state.started.store(true, .seq_cst);
state.job = runtime.waitAndTakeNextPrompt(std.testing.allocator) catch |err| {
state.err = err;
state.finished.store(true, .seq_cst);
return;
};
state.finished.store(true, .seq_cst);
}
test "turn start hold rejects busy worker and blocks take while held" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
try std.testing.expect(!runtime.tryHoldTurnStart());
runtime.worker_processing = false;
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued blocks park", "model"));
try std.testing.expect(!runtime.tryHoldTurnStart());
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expect(runtime.tryHoldTurnStart());
try std.testing.expect(!runtime.tryHoldTurnStart());
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "held blocked prompt", "model"));
var state = PromptTakeThreadState{};
const thread = try std.Thread.spawn(.{}, runPromptTake, .{ &state, &runtime });
var joined = false;
defer if (!joined) {
runtime.releaseTurnStartHold();
runtime.requestStop();
thread.join();
};
var attempts: usize = 0;
while (!state.started.load(.seq_cst) and attempts < 100) : (attempts += 1) {
io_mod.sleep(std.time.ns_per_ms);
}
try std.testing.expect(state.started.load(.seq_cst));
io_mod.sleep(10 * std.time.ns_per_ms);
try std.testing.expect(!state.finished.load(.seq_cst));
runtime.releaseTurnStartHold();
thread.join();
joined = true;
try std.testing.expect(state.err == null);
const job = state.job.?;
defer freeQueuedPrompt(alloc, job);
try std.testing.expectEqualStrings("held blocked prompt", job.prompt);
}
test "waitAndTakeNextPrompt assigns turn id and resets cancellation for next prompt" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_cancel_requested.store(true, .seq_cst);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "next", "model"));
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
const job = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
try std.testing.expect(job.turn_id != 0);
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expect(!runtime.worker_recovery_pause_requested.load(.seq_cst));
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .begin_prompt);
}
test "already-presented queued prompt emits identity without duplicating user payload" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var queued = try makePrompt(alloc, "already visible", "model");
queued.turn_id = 9001;
queued.user_prompt_already_presented = true;
try runtime.enqueuePrompt(alloc, queued);
const job = (try runtime.tryTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .begin_presented_prompt);
try std.testing.expectEqual(@as(u64, 9001), events.items[0].begin_presented_prompt);
}
test "requestRecoveryPause wakes the active operation without losing pause intent" {
var worker = WorkerRuntime{};
worker.requestRecoveryPause();
try std.testing.expect(worker.worker_recovery_pause_requested.load(.seq_cst));
try std.testing.expect(worker.worker_cancel_requested.load(.seq_cst));
}
test "enqueuePrompt stamps agent settings from worker runtime" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.agent_turn_settings = .{
.max_tool_result_bytes = 4096,
.first_call_tool_choice = .none,
};
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "next", "model"));
const job = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
try std.testing.expectEqual(@as(usize, 4096), job.agent_settings.max_tool_result_bytes);
try std.testing.expectEqual(types.ToolChoice.none, job.agent_settings.first_call_tool_choice);
}
test "enqueuePrompt stamps fast mode and effort from worker runtime" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.agent_turn_settings = .{
.fast_mode = true,
.effort = types.ReasoningEffort.literal("high"),
};
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "next", "model"));
const job = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
try std.testing.expect(job.agent_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), job.agent_settings.effort);
}
test "sync queued prompt fast mode and effort updates queued prompts and future defaults" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.agent_turn_settings = .{
.fast_mode = false,
.effort = types.ReasoningEffort.literal("low"),
};
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "queued", "model"));
runtime.syncQueuedPromptFastMode(true);
runtime.syncQueuedPromptEffort(types.ReasoningEffort.literal("high"));
try std.testing.expect(runtime.queued_prompts.items[0].agent_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), runtime.queued_prompts.items[0].agent_settings.effort);
try std.testing.expect(runtime.agent_turn_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), runtime.agent_turn_settings.effort);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "future", "model"));
try std.testing.expect(runtime.queued_prompts.items[1].agent_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), runtime.queued_prompts.items[1].agent_settings.effort);
}
const EnqueueThreadState = struct {
started: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
err: ?anyerror = null,
};
fn runEnqueuePrompt(state: *EnqueueThreadState, runtime: *WorkerRuntime, text: []const u8, model: []const u8) void {
const prompt = makePrompt(std.testing.allocator, text, model) catch |err| {
state.err = err;
return;
};
state.started.store(true, .seq_cst);
runtime.enqueuePrompt(std.testing.allocator, prompt) catch |err| {
freeQueuedPrompt(std.testing.allocator, prompt);
state.err = err;
return;
};
}
fn waitForEnqueueThreadStart(state: *EnqueueThreadState) !void {
var attempts: usize = 0;
while (attempts < 100) : (attempts += 1) {
if (state.started.load(.seq_cst)) return;
io_mod.sleep(1 * std.time.ns_per_ms);
}
return error.TestExpectedEqual;
}
test "enqueuePrompt snapshots settings under the same mutex as sync" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.agent_turn_settings = .{
.fast_mode = false,
.effort = types.ReasoningEffort.literal("low"),
};
runtime.worker_mutex.lockUncancelable(io_mod.getIo());
var state = EnqueueThreadState{};
const thread = try std.Thread.spawn(.{}, runEnqueuePrompt, .{ &state, &runtime, "blocked", "model" });
try waitForEnqueueThreadStart(&state);
io_mod.sleep(10 * std.time.ns_per_ms);
runtime.agent_turn_settings.fast_mode = true;
runtime.agent_turn_settings.effort = types.ReasoningEffort.literal("high");
runtime.worker_mutex.unlock(io_mod.getIo());
thread.join();
try std.testing.expect(state.err == null);
try std.testing.expectEqual(@as(usize, 1), runtime.queued_prompts.items.len);
try std.testing.expect(runtime.queued_prompts.items[0].agent_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), runtime.queued_prompts.items[0].agent_settings.effort);
}
test "taken prompt keeps original fast mode and effort after later sync" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.agent_turn_settings = .{
.fast_mode = true,
.effort = types.ReasoningEffort.literal("high"),
};
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "active", "model"));
const job = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, job);
runtime.syncQueuedPromptFastMode(false);
runtime.syncQueuedPromptEffort(types.ReasoningEffort.literal("low"));
try std.testing.expect(job.agent_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), job.agent_settings.effort);
try std.testing.expect(!runtime.agent_turn_settings.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("low"), runtime.agent_turn_settings.effort);
}
test "effectiveAgentTurnSettings prefers active turn settings until cleared" {
var runtime = WorkerRuntime{};
defer runtime.deinit(std.testing.allocator);
runtime.agent_turn_settings = .{
.max_tool_result_bytes = 2048,
.first_call_tool_choice = .auto,
.fast_mode = false,
.effort = types.ReasoningEffort.literal("low"),
};
try std.testing.expectEqual(@as(usize, 2048), runtime.effectiveAgentTurnSettings().max_tool_result_bytes);
try std.testing.expect(!runtime.effectiveAgentTurnSettings().fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("low"), runtime.effectiveAgentTurnSettings().effort);
runtime.setActiveAgentTurnSettings(.{
.max_tool_result_bytes = 4096,
.first_call_tool_choice = .none,
.fast_mode = true,
.effort = types.ReasoningEffort.literal("high"),
});
const active = runtime.effectiveAgentTurnSettings();
try std.testing.expectEqual(@as(usize, 4096), active.max_tool_result_bytes);
try std.testing.expectEqual(types.ToolChoice.none, active.first_call_tool_choice);
try std.testing.expect(active.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("high"), active.effort);
runtime.clearActiveAgentTurnSettings();
const fallback = runtime.effectiveAgentTurnSettings();
try std.testing.expectEqual(@as(usize, 2048), fallback.max_tool_result_bytes);
try std.testing.expect(!fallback.fast_mode);
try std.testing.expectEqual(types.ReasoningEffort.literal("low"), fallback.effort);
}
test "queued permission mode updates without mutating dequeued fallback" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var held_prompt = try makePrompt(alloc, "held", "model");
held_prompt.permission_mode = .ask;
try runtime.enqueuePrompt(alloc, held_prompt);
var queued_prompt = try makePrompt(alloc, "queued", "model");
queued_prompt.permission_mode = .ask;
try runtime.enqueuePrompt(alloc, queued_prompt);
const active = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, active);
runtime.syncQueuedPromptPermissionSnapshot(.{
.mode = .auto,
});
try std.testing.expectEqual(types.PermissionMode.ask, active.permission_mode);
try std.testing.expectEqual(types.PermissionMode.auto, runtime.queued_prompts.items[0].permission_mode);
}
test "submitted text only queues while a prompt is active" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "use all your tools", "model"));
const active = (try runtime.waitAndTakeNextPrompt(alloc)).?;
defer freeQueuedPrompt(alloc, active);
try std.testing.expect(runtime.worker_processing);
try std.testing.expect(!runtime.isCancelRequested());
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 1, .label = "shell.run launch chrome" },
);
const question_options = [_]types.QuestionOption{.{ .label = "Wait", .description = null }};
const question_entries = [_]types.QuestionBatchEntry{.{
.question = "Keep waiting?",
.options = &question_options,
}};
runtime.pending_question_shared = try dupeOwnedQuestionBatch(
alloc,
&question_entries,
.agent_question,
);
const submitted = [_][]const u8{
"Stop",
"continue",
"What are you doing right now?",
"What files are in this repository?",
"checkpoint?",
};
for (submitted) |text| {
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, text, "model"));
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expect(runtime.pending_permission_response == null);
try std.testing.expect(runtime.pending_permission_request_shared != null);
try std.testing.expect(runtime.pending_question_shared != null);
try std.testing.expect(runtime.pending_question_response == .pending);
}
try std.testing.expectEqual(submitted.len, runtime.queuedPromptCount());
for (submitted, runtime.queued_prompts.items) |expected, queued| {
try std.testing.expectEqualStrings(expected, queued.prompt);
}
}
test "prompt take and grant append allocation failures preserve state" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePromptWithGrant(alloc, "keep", "model", "read_file", "/tmp/a"));
runtime.worker_cancel_requested.store(true, .seq_cst);
var failing_take = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 });
try std.testing.expectError(error.OutOfMemory, runtime.waitAndTakeNextPrompt(failing_take.allocator()));
try std.testing.expectEqual(@as(usize, 1), runtime.queuedPromptCount());
try std.testing.expectEqualStrings("keep", runtime.queued_prompts.items[0].prompt);
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
const original = runtime.queued_prompts.items[0].grants.ptr;
var failing_grant = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 1 });
try std.testing.expectError(error.OutOfMemory, runtime.propagateGrant(failing_grant.allocator(), "write_file", "/tmp/b"));
try std.testing.expectEqual(original, runtime.queued_prompts.items[0].grants.ptr);
try std.testing.expectEqual(@as(usize, 1), runtime.queued_prompts.items[0].grants.len);
var failing_slice = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 2 });
try std.testing.expectError(error.OutOfMemory, runtime.propagateGrant(failing_slice.allocator(), "write_file", "/tmp/c"));
try std.testing.expectEqual(original, runtime.queued_prompts.items[0].grants.ptr);
try std.testing.expectEqual(@as(usize, 1), runtime.queued_prompts.items[0].grants.len);
}
test "clear queued prompts preserves events and discard frees event payloads" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.enqueuePrompt(alloc, try makePrompt(alloc, "drop", "model"));
try runtime.pushEvent(alloc, .{ .assistant_presentation = .{
.text = @constCast("kept"),
} });
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
try std.testing.expectEqual(@as(usize, 0), runtime.queued_prompts.items.len);
try std.testing.expectEqual(@as(usize, 1), runtime.worker_events.items.len);
runtime.discardEvents(alloc);
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
freeQueuedPrompt(alloc, try makePrompt(alloc, "free", "model"));
freeWorkerEvent(alloc, .{ .begin_prompt = try types.dupeUserTurn(alloc, .{ .text = @constCast("user"), .images = &.{} }) });
freeWorkerEvent(alloc, .{ .assistant_presentation = .{
.text = try alloc.dupe(u8, "append"),
} });
const table = try assistant_presentation.parseTablePayload(alloc, "| H |\n|---|\n| v |\n");
freeWorkerEvent(alloc, .{ .assistant_presentation = .{ .table = table } });
freeWorkerEvent(alloc, try dupeWorkerEvent(alloc, .{ .semantic_notice = .{
.topic = "system",
.tone = .information,
.body = "notice",
} }));
freeWorkerEvent(alloc, .{ .route_recovery_status = .{ .kind = .auto_retry, .failed_attempt = 1, .attempt_limit = 3 } });
freeWorkerEvent(alloc, .clear_route_recovery_status);
freeWorkerEvent(alloc, .{ .command_output = .{ .stream = .stdout, .text = try alloc.dupe(u8, "out") } });
freeWorkerEvent(alloc, .{ .command_output_complete = null });
freeWorkerEvent(alloc, .{ .command_output_complete = .{ .turn_id = 1, .call_id = try alloc.dupe(u8, "1") } });
freeWorkerEvent(alloc, .{ .tool_lifecycle = .{
.provisional = .{
.id = .{ .turn_id = 1, .call_id = try alloc.dupe(u8, "1") },
.tool_name = try alloc.dupe(u8, "read_file"),
.activity_kind = .read,
},
} });
freeWorkerEvent(alloc, .{ .tool_lifecycle = .{
.progress = .{
.id = .{ .turn_id = 1, .call_id = try alloc.dupe(u8, "1") },
.text = try alloc.dupe(u8, "reading"),
},
} });
freeWorkerEvent(alloc, .{ .tool_lifecycle = .{
.terminal = .{
.id = .{ .turn_id = 1, .call_id = try alloc.dupe(u8, "1") },
.outcome = .{
.kind = .completed,
.summary = try alloc.dupe(u8, "read"),
},
},
} });
freeWorkerEvent(alloc, .{ .turn_token_update = .{ .input_tokens = 10, .output_tokens = 5 } });
freeWorkerEvent(alloc, .{ .diff_block = .{ .preview = try alloc.dupe(u8, "preview") } });
freeWorkerEvent(alloc, .{ .tool_lifecycle = .{
.turn_finished = .{ .turn_id = 1, .outcome = .completed },
} });
freeWorkerEvent(alloc, .{ .finish_prompt = .{ .turn = .{ .compacted_summary = .{
.summary = try alloc.dupe(u8, "summary"),
.removed_turn_count = 1,
.compaction_count = 1,
} } } });
freeWorkerEvent(alloc, .{ .session_grant = try makeGrant(alloc, "read_file", "/tmp/a") });
freeWorkerEvent(alloc, try dupeWorkerEvent(alloc, .{ .error_text = .{
.topic = "system",
.tone = .@"error",
.body = "err",
} }));
}
test "typed lifecycle worker events duplicate and free every payload variant" {
const alloc = std.testing.allocator;
const events = [_]types.ToolLifecycleEvent{
.{ .provisional = .{
.id = .{ .turn_id = 1, .call_id = "provisional" },
.presentation_group_id = .{ .turn_id = 1, .anchor_step_id = 3 },
.tool_name = "read_file",
.activity_kind = .read,
} },
.{ .provisional = .{
.id = .{ .turn_id = 1, .call_id = "provisional" },
.tool_name = "glob_files",
.activity_kind = .list,
} },
.{ .authoritative_started = .{
.id = .{ .turn_id = 1, .call_id = "final" },
.presentation_group_id = .{ .turn_id = 1, .anchor_step_id = 3 },
.reconciles_provisional_call_id = "provisional",
.tool_name = "glob_files",
.activity_kind = .list,
} },
.{ .progress = .{
.id = .{ .turn_id = 1, .call_id = "final" },
.text = "Listing files",
} },
.{ .terminal = .{
.id = .{ .turn_id = 1, .call_id = "final" },
.outcome = .{ .kind = .completed, .summary = "Listed files" },
.command_artifact_handle = "fx-command-final.log",
} },
.{ .turn_finished = .{ .turn_id = 1, .outcome = .completed } },
};
for (events) |event| {
const owned = try dupeToolLifecycleEvent(alloc, event);
freeToolLifecycleEvent(alloc, owned);
}
}
fn checkToolLifecycleDupAllocationFailure(alloc: std.mem.Allocator) !void {
const owned = try dupeToolLifecycleEvent(alloc, .{ .authoritative_started = .{
.id = .{ .turn_id = 7, .call_id = "final" },
.reconciles_provisional_call_id = "provisional",
.tool_name = "read_file",
.activity_kind = .read,
} });
defer freeToolLifecycleEvent(alloc, owned);
try std.testing.expectEqualStrings(
"final",
owned.authoritative_started.id.call_id,
);
try std.testing.expectEqualStrings(
"provisional",
owned.authoritative_started.reconciles_provisional_call_id.?,
);
try std.testing.expectEqualStrings(
"read_file",
owned.authoritative_started.tool_name,
);
}
test "typed lifecycle duplication frees every partial allocation" {
try std.testing.checkAllAllocationFailures(
std.testing.allocator,
checkToolLifecycleDupAllocationFailure,
.{},
);
}
test "pushEvent copies session grants into queue allocator" {
const source_alloc = std.testing.allocator;
const queue_alloc = std.heap.c_allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(queue_alloc);
const grant = try makeGrant(source_alloc, "read_file", "/tmp/source.zig");
const tool_ptr = grant.tool_name.ptr;
const target_ptr = grant.target_path.ptr;
try runtime.pushEvent(queue_alloc, .{ .session_grant = grant });
source_alloc.free(grant.tool_name);
source_alloc.free(grant.target_path);
var events = runtime.takeEvents();
defer freeEventList(queue_alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .session_grant);
try std.testing.expectEqualStrings("read_file", events.items[0].session_grant.tool_name);
try std.testing.expectEqualStrings("/tmp/source.zig", events.items[0].session_grant.target_path);
try std.testing.expect(events.items[0].session_grant.tool_name.ptr != tool_ptr);
try std.testing.expect(events.items[0].session_grant.target_path.ptr != target_ptr);
}
test "pushEvent copies string events into queue allocator" {
const source_alloc = std.testing.allocator;
const queue_alloc = std.heap.c_allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(queue_alloc);
const text = try source_alloc.dupe(u8, "borrowed assistant text");
const text_ptr = text.ptr;
try runtime.pushEvent(queue_alloc, .{ .assistant_presentation = .{ .text = text } });
source_alloc.free(text);
var events = runtime.takeEvents();
defer freeEventList(queue_alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .assistant_presentation);
try std.testing.expect(events.items[0].assistant_presentation == .text);
try std.testing.expectEqualStrings("borrowed assistant text", events.items[0].assistant_presentation.text);
try std.testing.expect(events.items[0].assistant_presentation.text.ptr != text_ptr);
}
test "pushEvent copies semantic table payloads into the queue allocator" {
const source_alloc = std.testing.allocator;
const queue_alloc = std.heap.c_allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(queue_alloc);
var table = try assistant_presentation.parseTablePayload(
source_alloc,
"| Name | Count |\n" ++
"|------|------:|\n" ++
"| api | 7 |\n",
);
const source_cell_ptr = table.rows[1].cells[0].ptr;
try runtime.pushEvent(queue_alloc, .{ .assistant_presentation = .{ .table = table } });
table.deinit(source_alloc);
var events = runtime.takeEvents();
defer freeEventList(queue_alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .assistant_presentation);
try std.testing.expect(events.items[0].assistant_presentation == .table);
try std.testing.expectEqualStrings("api", events.items[0].assistant_presentation.table.rows[1].cells[0]);
try std.testing.expect(events.items[0].assistant_presentation.table.rows[1].cells[0].ptr != source_cell_ptr);
}
test "pushEvent copies code block payload into queue allocator" {
const source_alloc = std.testing.allocator;
const queue_alloc = std.heap.c_allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(queue_alloc);
var block = assistant_presentation.CodeBlockPayload{
.language = try source_alloc.dupe(u8, "zig"),
.code = try source_alloc.dupe(u8, " const value = 1;\n"),
};
const source_code_ptr = block.code.ptr;
try runtime.pushEvent(queue_alloc, .{ .assistant_presentation = .{ .code_block = block } });
block.deinit(source_alloc);
var events = runtime.takeEvents();
defer freeEventList(queue_alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .assistant_presentation);
try std.testing.expect(events.items[0].assistant_presentation == .code_block);
try std.testing.expectEqualStrings("zig", events.items[0].assistant_presentation.code_block.language);
try std.testing.expectEqualStrings(" const value = 1;\n", events.items[0].assistant_presentation.code_block.code);
try std.testing.expect(events.items[0].assistant_presentation.code_block.code.ptr != source_code_ptr);
}
test "pushEvent copies absolute turn token progress into queue allocator" {
const queue_alloc = std.heap.c_allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(queue_alloc);
const progress: types.TurnTokenProgress = .{
.input_tokens = 10,
.output_tokens = 5,
.input_exact = false,
.output_exact = false,
};
try runtime.pushEvent(queue_alloc, .{ .turn_token_update = progress });
var events = runtime.takeEvents();
defer freeEventList(queue_alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .turn_token_update);
try std.testing.expectEqual(progress, events.items[0].turn_token_update);
}
test "pushEvent copies finish prompt assistant turns into queue allocator" {
const source_alloc = std.testing.allocator;
const queue_alloc = std.heap.c_allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(queue_alloc);
const turn: types.HistoryTurn = .{ .assistant = .{
.user = .{ .text = try source_alloc.dupe(u8, "source prompt"), .images = &.{} },
.assistant = try source_alloc.dupe(u8, "source answer"),
} };
const prompt_ptr = turn.assistant.user.text.ptr;
const answer_ptr = turn.assistant.assistant.ptr;
try runtime.pushEvent(queue_alloc, .{ .finish_prompt = .{
.turn = turn,
.summary = .{
.thinking_duration_ms = 1,
.turn_duration_ms = 2,
.token_progress = .{ .input_tokens = 7 },
},
} });
types.freeHistoryTurn(source_alloc, turn);
var events = runtime.takeEvents();
defer freeEventList(queue_alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .finish_prompt);
try std.testing.expect(events.items[0].finish_prompt.turn == .assistant);
try std.testing.expectEqualStrings("source prompt", events.items[0].finish_prompt.turn.assistant.user.text);
try std.testing.expectEqualStrings("source answer", events.items[0].finish_prompt.turn.assistant.assistant);
try std.testing.expect(events.items[0].finish_prompt.turn.assistant.user.text.ptr != prompt_ptr);
try std.testing.expect(events.items[0].finish_prompt.turn.assistant.assistant.ptr != answer_ptr);
try std.testing.expectEqual(@as(u64, 7), events.items[0].finish_prompt.summary.?.token_progress.input_tokens);
}
test "pushOwnedEvent transfers diff block payloads without copying" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
const preview = try alloc.dupe(u8, "preview");
try runtime.pushOwnedEvent(alloc, .{ .diff_block = .{
.preview = preview,
} });
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .diff_block);
try std.testing.expectEqual(preview.ptr, events.items[0].diff_block.preview.ptr);
}
test "dupeWorkerEvent clones canonical diff payload and full detail" {
const alloc = std.testing.allocator;
const source: WorkerEvent = .{ .diff_block = .{
.preview = @constCast("preview"),
.additions = 2,
.deletions = 1,
.full = .{
.content = @constCast("full detail"),
.lifecycle_id = .{ .turn_id = 9, .call_id = "call-9" },
},
} };
const owned = try dupeWorkerEvent(alloc, source);
defer freeWorkerEvent(alloc, owned);
try std.testing.expectEqualStrings("preview", owned.diff_block.preview);
try std.testing.expect(source.diff_block.preview.ptr != owned.diff_block.preview.ptr);
try std.testing.expectEqual(@as(u32, 2), owned.diff_block.additions);
try std.testing.expectEqual(@as(u32, 1), owned.diff_block.deletions);
const full = owned.diff_block.full orelse return error.TestExpectedFullDiff;
try std.testing.expectEqualStrings("full detail", full.content);
try std.testing.expectEqual(@as(u64, 9), full.lifecycle_id.turn_id);
try std.testing.expectEqualStrings("call-9", full.lifecycle_id.call_id);
try std.testing.expect(source.diff_block.full.?.content.ptr != full.content.ptr);
try std.testing.expect(source.diff_block.full.?.lifecycle_id.call_id.ptr != full.lifecycle_id.call_id.ptr);
}
test "dupeWorkerEvent owns a complete refreshed credential publication" {
const alloc = std.testing.allocator;
const source: WorkerEvent = .{ .credential_refreshed = .{
.token = @constCast("fresh-token"),
.source = .fx_login,
.team_id = @constCast("team_123"),
.refresh_after_ms = 100,
} };
var owned = try dupeWorkerEvent(alloc, source);
defer freeWorkerEvent(alloc, owned);
const credential = &owned.credential_refreshed;
try std.testing.expectEqualStrings("fresh-token", credential.token);
try std.testing.expect(credential.token.ptr != source.credential_refreshed.token.ptr);
try std.testing.expectEqualStrings("team_123", credential.team_id.?);
try std.testing.expectEqual(@as(?i64, 100), credential.refresh_after_ms);
}
test "pushEvent clones every semantic notice field into the queue allocator" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var topic = [_]u8{ 'c', 'o', 'n', 't', 'e', 'x', 't' };
var body = [_]u8{ 'w', 'a', 'r', 'n', 'i', 'n', 'g' };
const topic_ptr = topic[0..].ptr;
const body_ptr = body[0..].ptr;
try runtime.pushEvent(alloc, .{ .semantic_notice = .{
.topic = &topic,
.tone = .warning,
.body = &body,
.visibility = .full_only,
} });
topic[0] = 'X';
body[0] = 'X';
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .semantic_notice);
const notice = events.items[0].semantic_notice;
try std.testing.expectEqualStrings("context", notice.topic);
try std.testing.expectEqual(types.NoticeTone.warning, notice.tone);
try std.testing.expectEqualStrings("warning", notice.body);
try std.testing.expectEqual(types.NoticeVisibility.full_only, notice.visibility);
try std.testing.expect(notice.topic.ptr != topic_ptr);
try std.testing.expect(notice.body.ptr != body_ptr);
}
fn checkSemanticWorkerEventDuplicationAllocationFailure(alloc: std.mem.Allocator) !void {
const ordinary = try dupeWorkerEvent(alloc, .{ .semantic_notice = .{
.topic = "context",
.tone = .warning,
.body = "source limit reached",
.visibility = .full_only,
} });
defer freeWorkerEvent(alloc, ordinary);
const error_event = try dupeWorkerEvent(alloc, .{ .error_text = .{
.topic = "github",
.tone = .@"error",
.body = "publish failed",
} });
defer freeWorkerEvent(alloc, error_event);
try std.testing.expectEqualStrings("context", ordinary.semantic_notice.topic);
try std.testing.expectEqualStrings("source limit reached", ordinary.semantic_notice.body);
try std.testing.expectEqualStrings("github", error_event.error_text.topic);
try std.testing.expectEqualStrings("publish failed", error_event.error_text.body);
}
test "ordinary and error semantic worker events free partial duplication allocations" {
try std.testing.checkAllAllocationFailures(
std.testing.allocator,
checkSemanticWorkerEventDuplicationAllocationFailure,
.{},
);
}
test "prependOwnedEvents preserves retained order ahead of newer events" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.pushEvent(alloc, .{ .semantic_notice = .{
.topic = "system",
.tone = .information,
.body = "newer",
} });
const retained = [_]WorkerEvent{
.{ .assistant_presentation = .{
.text = try alloc.dupe(u8, "first"),
} },
try dupeWorkerEvent(alloc, .{ .semantic_notice = .{
.topic = "system",
.tone = .information,
.body = "second",
} }),
};
errdefer {
for (retained) |event| freeWorkerEvent(alloc, event);
}
try runtime.prependOwnedEvents(alloc, &retained);
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 3), events.items.len);
try std.testing.expectEqualStrings("first", events.items[0].assistant_presentation.text);
try std.testing.expectEqualStrings("second", events.items[1].semantic_notice.body);
try std.testing.expectEqualStrings("newer", events.items[2].semantic_notice.body);
}
fn checkSemanticNoticeEnqueueAllocationFailure(alloc: std.mem.Allocator) !void {
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.pushEvent(alloc, .{ .semantic_notice = .{
.topic = "permissions",
.tone = .success,
.body = "approval",
.visibility = .full_only,
} });
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 1), events.items.len);
try std.testing.expect(events.items[0] == .semantic_notice);
try std.testing.expectEqualStrings("permissions", events.items[0].semantic_notice.topic);
try std.testing.expectEqualStrings("approval", events.items[0].semantic_notice.body);
}
test "semantic notice enqueue frees partial topic body and queue allocations" {
try std.testing.checkAllAllocationFailures(
std.testing.allocator,
checkSemanticNoticeEnqueueAllocationFailure,
.{},
);
}
const PermissionThreadState = struct {
decision: ?types.ToolPermissionDecision = null,
err: ?anyerror = null,
};
fn runPermissionRequest(state: *PermissionThreadState, runtime: *WorkerRuntime, label: []const u8) void {
var response = runtime.requestPermissionBlocking(
std.testing.allocator,
.{ .label = label },
) catch |err| {
state.err = err;
return;
};
defer response.deinit();
state.decision = response.decision;
}
fn submitPermissionDecisionForTest(
runtime: *WorkerRuntime,
request_id: u64,
decision: types.ToolPermissionDecision,
) PermissionSubmissionResult {
return runtime.submitPermissionResponse(
request_id,
permission_request.OwnedPermissionResponse.init(
std.testing.allocator,
decision,
null,
),
);
}
fn waitForPermissionLabel(worker: *WorkerRuntime, expected: []const u8) !u64 {
var attempts: usize = 0;
while (attempts < 100) : (attempts += 1) {
var snapshot = try worker.snapshotState(std.testing.allocator);
defer snapshot.deinit(std.testing.allocator);
if (snapshot.pending_permission_request) |request| {
try std.testing.expectEqualStrings(expected, request.label);
try std.testing.expect(request.id != 0);
return request.id;
}
io_mod.sleep(1 * std.time.ns_per_ms);
}
return error.TestExpectedEqual;
}
test "permission blocking handles submit and stop" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
var state = PermissionThreadState{};
const thread = try std.Thread.spawn(.{}, runPermissionRequest, .{ &state, &runtime, "Allow?" });
const request_id = try waitForPermissionLabel(&runtime, "Allow?");
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
submitPermissionDecisionForTest(&runtime, request_id, .once),
);
thread.join();
try std.testing.expect(state.err == null);
try std.testing.expectEqual(types.ToolPermissionDecision.once, state.decision.?);
var snapshot = try runtime.snapshotState(alloc);
defer snapshot.deinit(alloc);
try std.testing.expect(snapshot.pending_permission_request == null);
var deny_state = PermissionThreadState{};
const deny_thread = try std.Thread.spawn(.{}, runPermissionRequest, .{ &deny_state, &runtime, "Deny?" });
const deny_request_id = try waitForPermissionLabel(&runtime, "Deny?");
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
submitPermissionDecisionForTest(&runtime, deny_request_id, .deny),
);
deny_thread.join();
try std.testing.expect(deny_state.err == null);
try std.testing.expectEqual(types.ToolPermissionDecision.deny, deny_state.decision.?);
var deny_snapshot = try runtime.snapshotState(alloc);
defer deny_snapshot.deinit(alloc);
try std.testing.expect(deny_snapshot.pending_permission_request == null);
var stop_runtime = WorkerRuntime{};
defer stop_runtime.deinit(alloc);
stop_runtime.worker_processing = true;
var stop_state = PermissionThreadState{};
const stop_thread = try std.Thread.spawn(.{}, runPermissionRequest, .{ &stop_state, &stop_runtime, "Stop?" });
_ = try waitForPermissionLabel(&stop_runtime, "Stop?");
stop_runtime.requestStop();
stop_thread.join();
try std.testing.expect(stop_state.err == null);
try std.testing.expectEqual(types.ToolPermissionDecision.deny, stop_state.decision.?);
var stop_snapshot = try stop_runtime.snapshotState(alloc);
defer stop_snapshot.deinit(alloc);
try std.testing.expect(stop_snapshot.pending_permission_request == null);
var shutdown_runtime = WorkerRuntime{};
defer shutdown_runtime.deinit(alloc);
shutdown_runtime.worker_processing = true;
var shutdown_state = PermissionThreadState{};
const shutdown_thread = try std.Thread.spawn(.{}, runPermissionRequest, .{ &shutdown_state, &shutdown_runtime, "Shutdown?" });
_ = try waitForPermissionLabel(&shutdown_runtime, "Shutdown?");
shutdown_runtime.requestShutdown();
shutdown_thread.join();
try std.testing.expect(shutdown_state.err == null);
try std.testing.expectEqual(types.ToolPermissionDecision.deny, shutdown_state.decision.?);
var shutdown_snapshot = try shutdown_runtime.snapshotState(alloc);
defer shutdown_snapshot.deinit(alloc);
try std.testing.expect(shutdown_snapshot.pending_permission_request == null);
}
test "permission response accepts feedback ownership" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared = try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 44, .label = "feedback" },
);
const feedback = try alloc.dupe(u8, "summarize the result");
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
runtime.submitPermissionResponse(
44,
permission_request.OwnedPermissionResponse.init(alloc, .once, feedback),
),
);
try std.testing.expectEqualStrings(
"summarize the result",
runtime.pending_permission_response.?.feedback.?,
);
}
test "permission submission rejects stale ids and preserves first resolution" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 22, .label = "B" },
);
try std.testing.expectEqual(
PermissionSubmissionResult.stale,
submitPermissionDecisionForTest(&runtime, 21, .always),
);
try std.testing.expect(runtime.pending_permission_response == null);
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
submitPermissionDecisionForTest(&runtime, 22, .deny),
);
try std.testing.expectEqual(
types.ToolPermissionDecision.deny,
runtime.pending_permission_response.?.decision,
);
try std.testing.expectEqual(
PermissionSubmissionResult.no_pending,
submitPermissionDecisionForTest(&runtime, 22, .once),
);
try std.testing.expectEqual(
PermissionSubmissionResult.no_pending,
submitPermissionDecisionForTest(&runtime, 22, .always),
);
try std.testing.expectEqual(
PermissionSubmissionResult.no_pending,
submitPermissionDecisionForTest(&runtime, 22, .deny),
);
try std.testing.expectEqual(
types.ToolPermissionDecision.deny,
runtime.pending_permission_response.?.decision,
);
var snapshot = try runtime.snapshotState(alloc);
defer snapshot.deinit(alloc);
try std.testing.expect(snapshot.pending_permission_request == null);
}
test "stale A decisions cannot resolve a newer B request" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
var a_state = PermissionThreadState{};
const a_thread = try std.Thread.spawn(
.{},
runPermissionRequest,
.{ &a_state, &runtime, "A" },
);
const a_id = try waitForPermissionLabel(&runtime, "A");
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
submitPermissionDecisionForTest(&runtime, a_id, .once),
);
a_thread.join();
try std.testing.expectEqual(types.ToolPermissionDecision.once, a_state.decision.?);
var b_state = PermissionThreadState{};
const b_thread = try std.Thread.spawn(
.{},
runPermissionRequest,
.{ &b_state, &runtime, "B" },
);
const b_id = try waitForPermissionLabel(&runtime, "B");
try std.testing.expect(b_id != a_id);
const stale_decisions = [_]types.ToolPermissionDecision{
.once,
.always,
.deny,
.deny,
};
for (stale_decisions) |decision| {
try std.testing.expectEqual(
PermissionSubmissionResult.stale,
submitPermissionDecisionForTest(&runtime, a_id, decision),
);
}
try std.testing.expect(runtime.pending_permission_response == null);
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
submitPermissionDecisionForTest(&runtime, b_id, .always),
);
b_thread.join();
try std.testing.expectEqual(
types.ToolPermissionDecision.always,
b_state.decision.?,
);
}
test "permission request ids never wrap or reuse zero" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.next_permission_request_id = std.math.maxInt(u64);
var last_state = PermissionThreadState{};
const last_thread = try std.Thread.spawn(
.{},
runPermissionRequest,
.{ &last_state, &runtime, "last id" },
);
const last_id = try waitForPermissionLabel(&runtime, "last id");
try std.testing.expectEqual(std.math.maxInt(u64), last_id);
try std.testing.expectEqual(
PermissionSubmissionResult.accepted,
submitPermissionDecisionForTest(&runtime, last_id, .deny),
);
last_thread.join();
try std.testing.expect(last_state.err == null);
var exhausted_state = PermissionThreadState{};
const exhausted_thread = try std.Thread.spawn(
.{},
runPermissionRequest,
.{ &exhausted_state, &runtime, "exhausted" },
);
exhausted_thread.join();
try std.testing.expectEqual(
error.RequestIdExhausted,
exhausted_state.err.?,
);
}
test "approval turn cancellation resolves the current worker request once" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 33, .label = "current" },
);
runtime.cancelApprovalTurn();
try std.testing.expect(runtime.isCancelRequested());
try std.testing.expectEqual(
types.ToolPermissionDecision.deny,
runtime.pending_permission_response.?.decision,
);
try std.testing.expectEqual(
PermissionSubmissionResult.no_pending,
submitPermissionDecisionForTest(&runtime, 33, .always),
);
}
test "approval cancellation leaves admitted steering available for the next turn" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.active_turn_id = 41;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 33, .label = "current" },
);
try runtime.admitInteractivePrompt(
alloc,
try makePrompt(alloc, "continue with the correction", "model"),
);
runtime.cancelApprovalTurn();
runtime.finishProcessing();
const next = (try runtime.tryTakeNextPrompt(alloc)) orelse
return error.TestExpectedSteeringContinuation;
defer freeQueuedPrompt(alloc, next);
try std.testing.expect(next.delivery.isContinuation());
try std.testing.expectEqualStrings("continue with the correction", next.prompt);
}
test "text enqueue allocation failure preserves active interactive state" {
const alloc = std.testing.allocator;
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
runtime.worker_processing = true;
runtime.pending_permission_waiting = true;
runtime.pending_permission_request_shared =
try permission_request.OwnedPermissionRequest.dupe(
alloc,
.{ .id = 3, .label = "active permission" },
);
const question_options = [_]types.QuestionOption{.{ .label = "Wait", .description = null }};
const question_entries = [_]types.QuestionBatchEntry{.{
.question = "Keep waiting?",
.options = &question_options,
}};
runtime.pending_question_shared = try dupeOwnedQuestionBatch(
alloc,
&question_entries,
.agent_question,
);
const prompt = try makePrompt(
alloc,
"Stop",
"model",
);
var failing = std.testing.FailingAllocator.init(
alloc,
.{ .fail_index = 0 },
);
try std.testing.expectError(
error.OutOfMemory,
runtime.enqueuePrompt(failing.allocator(), prompt),
);
freeQueuedPrompt(alloc, prompt);
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
try std.testing.expect(!runtime.isCancelRequested());
try std.testing.expect(runtime.pending_permission_response == null);
try std.testing.expect(runtime.pending_permission_request_shared != null);
try std.testing.expectEqualStrings(
"active permission",
runtime.pending_permission_request_shared.?.label,
);
try std.testing.expect(runtime.pending_question_shared != null);
try std.testing.expect(runtime.pending_question_response == .pending);
}
const QuestionThreadState = struct {
answers: ?[][]u8 = null,
err: ?anyerror = null,
};
fn runQuestionRequest(state: *QuestionThreadState, runtime: *WorkerRuntime, entries: []const types.QuestionBatchEntry) void {
state.answers = runtime.requestQuestionBatchAnswerBlocking(std.testing.allocator, entries) catch |err| {
state.err = err;
return;
};
}
fn runRouteRecoveryRequest(state: *QuestionThreadState, runtime: *WorkerRuntime, entries: []const types.QuestionBatchEntry) void {
state.answers = runtime.requestRouteRecoveryAnswerBlocking(std.testing.allocator, entries) catch |err| {
state.err = err;
return;
};
}
fn waitForQuestionSnapshot(worker: *WorkerRuntime) !PendingQuestionBatchSnapshot {
var attempts: usize = 0;
while (attempts < 100) : (attempts += 1) {
if (try worker.snapshotPendingQuestionBatch(std.testing.allocator)) |snapshot| return snapshot;
io_mod.sleep(1 * std.time.ns_per_ms);
}
return error.TestExpectedEqual;
}
fn freeAnswers(alloc: std.mem.Allocator, answers: ?[][]u8) void {
if (answers) |labels| {
for (labels) |label| alloc.free(label);
alloc.free(labels);
}
}
test "question batch snapshot answer and cancellation" {
const alloc = std.testing.allocator;
const options = [_]types.QuestionOption{
.{ .label = "Yes", .description = "Do it" },
.{ .label = "No", .description = null },
};
const entries = [_]types.QuestionBatchEntry{.{ .question = "Continue?", .options = &options }};
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var state = QuestionThreadState{};
const thread = try std.Thread.spawn(.{}, runQuestionRequest, .{ &state, &runtime, &entries });
var snapshot = try waitForQuestionSnapshot(&runtime);
defer snapshot.deinit(alloc);
try std.testing.expectEqualStrings("Continue?", snapshot.entries[0].question);
try std.testing.expectEqualStrings("Yes", snapshot.entries[0].options[0].label);
try std.testing.expect(snapshot.entries[0].question.ptr != entries[0].question.ptr);
try runtime.submitQuestionBatchAnswer(alloc, &.{"Yes"});
thread.join();
defer freeAnswers(alloc, state.answers);
try std.testing.expect(state.err == null);
try std.testing.expectEqualStrings("Yes", state.answers.?[0]);
try std.testing.expect(try runtime.snapshotPendingQuestionBatch(alloc) == null);
var cancel_runtime = WorkerRuntime{};
defer cancel_runtime.deinit(alloc);
var cancel_state = QuestionThreadState{};
const cancel_thread = try std.Thread.spawn(.{}, runQuestionRequest, .{ &cancel_state, &cancel_runtime, &entries });
var cancel_snapshot = try waitForQuestionSnapshot(&cancel_runtime);
cancel_snapshot.deinit(alloc);
try cancel_runtime.submitQuestionBatchAnswer(alloc, null);
cancel_thread.join();
try std.testing.expect(cancel_state.err == null);
try std.testing.expect(cancel_state.answers == null);
try std.testing.expect(try cancel_runtime.snapshotPendingQuestionBatch(alloc) == null);
}
test "question batch source distinguishes route recovery from agent questions" {
const alloc = std.testing.allocator;
const options = [_]types.QuestionOption{.{ .label = "Try again later", .description = null }};
const entries = [_]types.QuestionBatchEntry{.{ .question = "Route failed. What should fx do?", .options = &options }};
var route_runtime = WorkerRuntime{};
defer route_runtime.deinit(alloc);
var route_state = QuestionThreadState{};
const route_thread = try std.Thread.spawn(.{}, runRouteRecoveryRequest, .{ &route_state, &route_runtime, &entries });
var route_snapshot = try waitForQuestionSnapshot(&route_runtime);
defer route_snapshot.deinit(alloc);
try std.testing.expectEqual(QuestionPromptSource.route_recovery, route_snapshot.source);
try std.testing.expectEqual(QuestionPromptSource.route_recovery, route_runtime.pendingQuestionBatchSource());
try route_runtime.submitQuestionBatchAnswer(alloc, &.{"Try again later"});
route_thread.join();
defer freeAnswers(alloc, route_state.answers);
try std.testing.expect(route_state.err == null);
try std.testing.expectEqualStrings("Try again later", route_state.answers.?[0]);
var agent_runtime = WorkerRuntime{};
defer agent_runtime.deinit(alloc);
var agent_state = QuestionThreadState{};
const agent_thread = try std.Thread.spawn(.{}, runQuestionRequest, .{ &agent_state, &agent_runtime, &entries });
var agent_snapshot = try waitForQuestionSnapshot(&agent_runtime);
defer agent_snapshot.deinit(alloc);
try std.testing.expectEqual(QuestionPromptSource.agent_question, agent_snapshot.source);
try std.testing.expectEqual(QuestionPromptSource.agent_question, agent_runtime.pendingQuestionBatchSource());
try agent_runtime.submitQuestionBatchAnswer(alloc, &.{"Try again later"});
agent_thread.join();
defer freeAnswers(alloc, agent_state.answers);
try std.testing.expect(agent_state.err == null);
}
test "question request queues an ordered boundary after prior assistant text" {
const alloc = std.testing.allocator;
const options = [_]types.QuestionOption{.{ .label = "Yes", .description = null }};
const entries = [_]types.QuestionBatchEntry{.{ .question = "Continue?", .options = &options }};
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
try runtime.pushEvent(alloc, .{ .assistant_presentation = .{
.text = @constCast("before question"),
} });
var state = QuestionThreadState{};
const thread = try std.Thread.spawn(.{}, runQuestionRequest, .{ &state, &runtime, &entries });
var snapshot = try waitForQuestionSnapshot(&runtime);
defer snapshot.deinit(alloc);
var events = runtime.takeEvents();
defer freeEventList(alloc, &events);
try std.testing.expectEqual(@as(usize, 2), events.items.len);
try std.testing.expect(events.items[0] == .assistant_presentation);
try std.testing.expect(events.items[0].assistant_presentation == .text);
try std.testing.expectEqualStrings("before question", events.items[0].assistant_presentation.text);
try std.testing.expect(events.items[1] == .question_requested);
try runtime.submitQuestionBatchAnswer(alloc, &.{"Yes"});
thread.join();
defer freeAnswers(alloc, state.answers);
try std.testing.expect(state.err == null);
try std.testing.expectEqualStrings("Yes", state.answers.?[0]);
}
test "question request rolls back pending state when its boundary event cannot be queued" {
const options = [_]types.QuestionOption{.{ .label = "Yes", .description = null }};
const entries = [_]types.QuestionBatchEntry{.{ .question = "Continue?", .options = &options }};
var probe = std.testing.FailingAllocator.init(std.testing.allocator, .{});
var probe_runtime = WorkerRuntime{};
probe_runtime.worker_stop_requested = true;
const probe_result = try probe_runtime.requestQuestionBatchAnswerBlocking(probe.allocator(), &entries);
try std.testing.expect(probe_result == null);
const marker_append_alloc_index = probe.alloc_index - 1;
probe_runtime.deinit(probe.allocator());
try std.testing.expectEqual(probe.allocated_bytes, probe.freed_bytes);
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
.fail_index = marker_append_alloc_index,
});
var runtime = WorkerRuntime{};
defer runtime.deinit(failing.allocator());
runtime.worker_stop_requested = true;
try std.testing.expectError(
error.OutOfMemory,
runtime.requestQuestionBatchAnswerBlocking(failing.allocator(), &entries),
);
try std.testing.expect(runtime.pending_question_shared == null);
try std.testing.expect(runtime.pending_question_response == .pending);
try std.testing.expectEqual(@as(usize, 0), runtime.worker_events.items.len);
}
test "discarding queued recovery releases metadata without deleting saved images" {
const alloc = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "saved.bin", .data = "snapshot" });
const path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "saved.bin");
defer alloc.free(path);
const images = [_]types.ImageAttachment{.{
.id = 1,
.path = @constCast("/missing/source.png"),
.media_type = @constCast("image/png"),
.snapshot_path = path,
.snapshot_sha256 = @constCast("a" ** 64),
}};
var runtime = WorkerRuntime{};
defer runtime.deinit(alloc);
var prompt = try makePrompt(alloc, "recover", "test/model");
var owned = true;
defer if (owned) freeQueuedPrompt(alloc, prompt);
prompt.images = try types.dupeImageAttachmentSlice(alloc, &images);
prompt.recovery_checkpoint = try (session_codec.RecoveryCheckpoint{
.turn_id = 1,
.user = .{ .text = @constCast("recover"), .images = @constCast(&images) },
.assistant_source = @constCast(""),
.cause = .network_interrupted,
.action = .retrying_request,
.authority = .{ .provider = .gateway, .model = @constCast("test/model") },
.requested_fast_mode = false,
.fast_mode = false,
.max_provider_attempts = 10,
.consumed_provider_attempts = 1,
}).dupe(alloc);
try runtime.enqueuePrompt(alloc, prompt);
owned = false;
runtime.clearQueuedPrompts(alloc, &.{});
try std.testing.expectEqual(@as(usize, 0), runtime.queuedPromptCount());
try std.Io.Dir.accessAbsolute(std.testing.io, path, .{});
}