Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530const std = @import("std");const paste_blocks = @import("../input/pasted_blocks.zig");const registered_entities = @import("../input/registered_entities.zig");const image_attachments = @import("../images/image_attachments.zig");const command_specs = @import("../slash_commands/command_specs.zig");const io_mod = @import("../shared/io.zig");const debug_trace = @import("../shared/debug_trace.zig");const entity_spans = @import("../shared/entity_spans.zig");const types = @import("../shared/types.zig");const worker_runtime = @import("../agent/worker_runtime.zig");const compaction_activity = @import("../output/compaction_activity.zig");const input_completion_runtime = @import("input_completion_runtime.zig");const input_limit_feedback = @import("input_limit_feedback.zig");
pub const PendingPhase = enum { awaiting_frame, awaiting_adoption, adopted, awaiting_auth, queued,};
const PendingPhaseError = error{InvalidPendingPhase};
pub const PendingPromptDraft = struct { turn_id: u64, prompt: []u8, images: []types.ImageAttachment, skill_display_spans: []worker_runtime.SkillDisplaySpan,
fn deinit(self: PendingPromptDraft, alloc: std.mem.Allocator) void { alloc.free(self.prompt); types.freeImageAttachmentSlice(alloc, self.images); worker_runtime.freeSkillDisplaySpans(alloc, self.skill_display_spans); }};
pub const PendingSubmission = struct { draft: PendingPromptDraft, phase: PendingPhase = .awaiting_frame, credential_admitted: bool = false, skill_refresh_generation: ?u64 = null,
fn init(draft: PendingPromptDraft) PendingSubmission { std.debug.assert(draft.turn_id != 0); return .{ .draft = draft }; }
pub fn deinit(self: *PendingSubmission, alloc: std.mem.Allocator) void { self.draft.deinit(alloc); self.* = undefined; }
fn ownsTurnStartHold(self: PendingSubmission) bool { return self.phase != .queued; }
fn markFrameCommitted(self: *PendingSubmission) bool { if (self.phase != .awaiting_frame) return false; self.phase = .awaiting_adoption; return true; }
fn markAdopted(self: *PendingSubmission) PendingPhaseError!void { if (self.phase != .awaiting_adoption) return error.InvalidPendingPhase; self.phase = .adopted; }
fn markQueued(self: *PendingSubmission) bool { if (self.phase != .adopted) return false; self.phase = .queued; return true; }};
pub const PendingSkillRefresh = enum { pending, current,};
pub const State = struct { pending: ?PendingSubmission = null, retry_after_auth: bool = false, compaction_pending: bool = false, /// Identity only; auth retains its existing pending execution flag. compaction_operation: ?compaction_activity.OperationId = null,};
fn buildPendingPromptDraft( alloc: std.mem.Allocator, turn_id: u64, prompt_source: []const u8, image_source: []const types.ImageAttachment, skill_tokens: []const registered_entities.SkillTokenSpan,) !PendingPromptDraft { if (turn_id == 0) return error.InvalidTurnId;
const prompt = try alloc.dupe(u8, prompt_source); errdefer alloc.free(prompt); const images = try types.dupeImageAttachmentSlice(alloc, image_source); errdefer types.freeImageAttachmentSlice(alloc, images);
const spans: []worker_runtime.SkillDisplaySpan = if (skill_tokens.len == 0) @constCast(&.{}) else try alloc.alloc(worker_runtime.SkillDisplaySpan, skill_tokens.len); var span_count: usize = 0; errdefer { for (spans[0..span_count]) |span| { alloc.free(span.name); alloc.free(span.path); } if (spans.len > 0) alloc.free(spans); } while (span_count < skill_tokens.len) : (span_count += 1) { const token = skill_tokens[span_count]; const name = try alloc.dupe(u8, token.name); errdefer alloc.free(name); spans[span_count] = .{ .raw_start = token.raw_start, .raw_end = token.raw_end, .name = name, .path = try alloc.dupe(u8, token.path), .display_source = token.display_source, .owns_trailing_separator = token.owns_trailing_separator, }; }
return .{ .turn_id = turn_id, .prompt = prompt, .images = images, .skill_display_spans = spans, };}
pub fn SubmitRuntime(comptime App: type) type { return struct { pub fn request_context_compaction(app: *App) !void { if (app.submission.compaction_pending) return; const observed = app.worker.compactionActivitySnapshot(); if (observed.operation) |op| if (op.active()) return; defer app.shell.render_requests.request(.footer); if (!app.hasContextToCompact()) { app.worker.rejectCompactionActivity(.no_op); return; } if (app.submission.pending != null or app.worker.isProcessing() or app.worker.queuedPromptCount() > 0 or app.worker.contextCompactionStatus() != .idle) { app.worker.rejectCompactionActivity(.busy); return; } app.submission.compaction_operation = app.worker.beginCompactionActivity(.manual, null); app.submission.compaction_pending = true; collect_context_compaction(app); if (app.submission.compaction_pending) { debug_trace.logf("input", "manual_compaction_auth_pending", .{}); } }
fn collect_context_compaction(app: *App) void { if (comptime !@hasDecl(App, "enqueueContextCompaction")) return; if (!app.submission.compaction_pending) return; admit_context_compaction(app) catch |err| { settle_pending_compaction(app, if (err == error.WorkerBusy) .{ .outcome = .busy } else compaction_activity.failure(err, .preparation, false)); clear_context_compaction(app, "admission_failed"); app.submission.retry_after_auth = false; debug_trace.logf("input", "manual compaction admission failed err={s}", .{@errorName(err)}); app.shell.render_requests.request(.footer); }; }
fn admit_context_compaction(app: *App) !void { switch (try App.collectPendingPromptCredential(app)) { .pending => return, .rejected => { settle_pending_compaction(app, compaction_activity.failure(error.CompactionAuthenticationRejected, .preparation, false)); clear_context_compaction(app, "auth_rejected"); app.submission.retry_after_auth = false; return; }, .current => {}, } const queued = try app.enqueueContextCompaction(app.submission.compaction_operation.?); if (!queued) settle_pending_compaction(app, .{ .outcome = .busy }); app.submission.compaction_pending = false; app.submission.compaction_operation = null; app.shell.render_requests.request(.footer); }
fn settle_pending_compaction(app: *App, feedback: compaction_activity.Feedback) void { if (comptime !@hasDecl(@TypeOf(app.worker), "settleCompactionActivity")) return; const id = app.submission.compaction_operation orelse return; app.worker.settleCompactionActivity(id, feedback); }
fn clear_context_compaction(app: *App, reason: []const u8) void { if (!app.submission.compaction_pending) return; settle_pending_compaction(app, .{ .outcome = .cancelled }); app.submission.compaction_pending = false; app.submission.compaction_operation = null; debug_trace.logf("input", "manual compaction intent cleared reason={s}", .{reason}); if (comptime @hasField(App, "auth")) { if (comptime @hasDecl(@TypeOf(app.auth), "cancelPromptCredentialRefresh")) app.auth.cancelPromptCredentialRefresh(); } }
pub fn requestPromptRetryAfterAuth(app: *App) void { app.submission.retry_after_auth = true; }
pub fn cancelPromptRetryAfterAuth(app: *App) void { app.submission.retry_after_auth = false; const pending = app.submission.pending orelse return; if (pending.phase != .awaiting_auth) return; clearPendingSubmission(app, "auth_retry_cancelled"); app.shell.render_requests.request(.transcript); app.shell.render_requests.request(.footer); }
fn takePromptRetryAfterAuth(app: *App) bool { const pending = app.submission.retry_after_auth; app.submission.retry_after_auth = false; return pending; }
fn resumePendingPromptAfterAuth(app: *App, trigger: []const u8) bool { const pending = if (app.submission.pending) |*value| value else return false; if (pending.phase != .awaiting_auth) return false; pending.phase = .adopted; app.submission.retry_after_auth = false; app.shell.render_requests.request(.footer); debug_trace.eventf( "input", "pending_prompt_auth_resumed", .{ .turn_id = pending.draft.turn_id }, "trigger={s}", .{trigger}, ); return true; }
pub fn resumePromptAfterAuth(app: *App, max_prompt_history: usize) !void { if (!takePromptRetryAfterAuth(app)) return; if (resumePendingPromptAfterAuth(app, "auth_completion")) return; try submit(app, max_prompt_history); } const completion_rt = input_completion_runtime.CompletionRuntime(App);
const PromptAdmission = enum { rejected, pending, enqueued, };
const PendingInstall = enum { unavailable, installed, };
const PendingSnapshotCleanup = enum { discard, preserve, };
const ComposerHistoryProjection = struct { input: []const u8, pasted_blocks: std.ArrayList(paste_blocks.PastedBlock) = .empty, image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty, skill_tokens: std.ArrayList(registered_entities.SkillTokenSpan) = .empty,
fn deinit(self: *ComposerHistoryProjection, alloc: std.mem.Allocator) void { self.pasted_blocks.deinit(alloc); self.image_tokens.deinit(alloc); self.skill_tokens.deinit(alloc); self.* = undefined; } };
fn composerHistoryEnabled(app: *const App) bool { if (comptime @hasField(App, "prompt_history")) { return app.prompt_history.enabled; } return true; }
pub fn noteCommittedFrame(app: *App) void { if (comptime !@hasField(App, "submission")) return; const pending = if (app.submission.pending) |*value| value else return; if (!pending.markFrameCommitted()) return; debug_trace.eventf( "input", "pending_prompt_frame_committed", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); adoptPendingSubmission(app) catch |err| { debug_trace.eventf( "input", "pending_prompt_adoption_retry", .{ .turn_id = pending.draft.turn_id }, "err={s}", .{@errorName(err)}, ); }; }
pub fn collectPendingSubmissionFacts(app: *App) void { if (comptime !@hasField(App, "submission")) return; collect_context_compaction(app); var pending = if (app.submission.pending) |*value| value else return; if (pending.phase == .awaiting_adoption) { adoptPendingSubmission(app) catch |err| { debug_trace.eventf( "input", "pending_prompt_adoption_retry", .{ .turn_id = pending.draft.turn_id }, "err={s}", .{@errorName(err)}, ); return; }; pending = &app.submission.pending.?; } if (pending.phase != .adopted) return;
if (!pending.credential_admitted) { if (comptime @hasDecl(App, "collectPendingPromptCredential")) { const readiness = App.collectPendingPromptCredential(app) catch |err| { finishPendingSubmissionFailure(app, err); return; }; switch (readiness) { .pending => return, .current => {}, .rejected => { pending = &app.submission.pending.?; pending.phase = .awaiting_auth; requestPromptRetryAfterAuth(app); debug_trace.eventf( "input", "pending_prompt_awaiting_auth", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); return; }, } } else if (comptime @hasDecl(App, "ensurePromptCredential")) { const admitted = preflightPrompt(app) catch |err| { finishPendingSubmissionFailure(app, err); return; }; pending = &app.submission.pending.?; if (!admitted) { pending.phase = .awaiting_auth; requestPromptRetryAfterAuth(app); debug_trace.eventf( "input", "pending_prompt_awaiting_auth", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); return; } } app.submission.pending.?.credential_admitted = true; pending = &app.submission.pending.?; }
if (comptime @hasDecl(App, "collectPendingSkillRefresh")) { const readiness = App.collectPendingSkillRefresh(app, pending) catch |err| { finishPendingSubmissionFailure(app, err); return; }; if (readiness == .pending) return; }
if (pending.draft.prompt.len > 0 and pending.draft.images.len == 0) { recordAcceptedInput(app, pending.draft.prompt); } if (comptime !@hasDecl(App, "finalizePendingSubmission")) { finishPendingSubmissionFailure(app, error.PendingFinalizationUnsupported); return; } App.finalizePendingSubmission(app, &pending.draft) catch |err| { finishPendingSubmissionFailure(app, err); return; }; if (!pending.markQueued()) { debug_trace.eventf( "input", "pending_prompt_queue_phase_invalid", .{ .turn_id = pending.draft.turn_id }, "phase={s}", .{@tagName(pending.phase)}, ); return; } app.worker.releaseTurnStartHold(); debug_trace.eventf( "input", "pending_prompt_queued", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); }
pub fn acceptPresentedPrompt(app: *App, turn_id: u64) !void { if (comptime !@hasField(App, "submission")) { return error.PendingSubmissionUnsupported; } const pending = app.submission.pending orelse return error.MissingPendingSubmission; if (pending.phase != .queued) return error.InvalidPendingPhase; if (pending.draft.turn_id != turn_id) return error.PendingTurnIdMismatch; clearPendingSubmissionWithSnapshots( app, "worker_begin_presented", .preserve, ); }
pub fn cancelPendingSubmission(app: *App) bool { if (comptime !@hasField(App, "submission")) return false; if (app.submission.compaction_pending) { clear_context_compaction(app, "cancelled"); app.shell.render_requests.request(.footer); return true; } const pending = app.submission.pending orelse return false; if (pending.phase == .queued) { if (comptime !@hasDecl(@TypeOf(app.worker), "removeQueuedPrompt")) { return false; } if (!app.worker.removeQueuedPrompt( std.heap.c_allocator, pending.draft.turn_id, pending.draft.images, )) { if (comptime @hasDecl(@TypeOf(app.worker), "activeTurnId") and @hasDecl(@TypeOf(app.worker), "requestCancel")) { if (app.worker.activeTurnId() == pending.draft.turn_id) { app.worker.requestCancel(); debug_trace.eventf( "input", "pending_prompt_active_cancel_requested", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); app.shell.render_requests.request(.footer); return true; } } debug_trace.eventf( "input", "pending_prompt_cancel_missed", .{ .turn_id = pending.draft.turn_id }, "phase={s}", .{@tagName(pending.phase)}, ); return false; } } clearPendingSubmission(app, "ctrl_c_pending_submission"); app.shell.render_requests.request(.transcript); app.shell.render_requests.request(.footer); return true; }
pub fn clearPendingSubmission(app: *App, reason: []const u8) void { if (comptime @hasField(App, "submission")) clear_context_compaction(app, reason); const snapshot_cleanup: PendingSnapshotCleanup = if (transferPendingImageSnapshotsToComposerHistory(app)) .preserve else .discard; clearPendingSubmissionWithSnapshots(app, reason, snapshot_cleanup); }
pub fn clearPendingSubmissionForSessionTransition(app: *App) void { if (comptime @hasField(App, "submission")) clear_context_compaction(app, "session_transition"); if (comptime @hasField(App, "auth")) { if (comptime @hasDecl(@TypeOf(app.auth), "cancelProviderPreparation")) _ = app.auth.cancelProviderPreparation(); } if (comptime !@hasField(App, "submission")) { app.worker.clearQueuedPrompts(std.heap.c_allocator, &.{}); return; } const pending = app.submission.pending orelse { app.worker.clearQueuedPrompts(std.heap.c_allocator, &.{}); return; }; const history_owns_snapshots = transferPendingImageSnapshotsToComposerHistory(app); if (pending.phase == .queued) { const retained_images = if (history_owns_snapshots) pending.draft.images else &.{}; if (comptime @hasDecl( @TypeOf(app.worker), "clearQueuedPromptsForSessionTransition", )) { app.worker.clearQueuedPromptsForSessionTransition( std.heap.c_allocator, pending.draft.turn_id, retained_images, ); } else { app.worker.clearQueuedPrompts( std.heap.c_allocator, retained_images, ); } clearPendingSubmissionWithSnapshots( app, "session_transition", .preserve, ); return; }
app.worker.clearQueuedPrompts(std.heap.c_allocator, &.{}); clearPendingSubmissionWithSnapshots( app, "session_transition", if (history_owns_snapshots) .preserve else .discard, ); }
fn clearPendingSubmissionWithSnapshots( app: *App, reason: []const u8, snapshot_cleanup: PendingSnapshotCleanup, ) void { if (comptime !@hasField(App, "submission")) return; var pending = app.submission.pending orelse return; app.submission.pending = null; if (pending.ownsTurnStartHold()) app.worker.releaseTurnStartHold(); if (snapshot_cleanup == .discard) { image_attachments.discardImageSnapshots(app.alloc, pending.draft.images); } debug_trace.eventf( "input", "pending_prompt_cleared", .{ .turn_id = pending.draft.turn_id }, "reason={s} phase={s}", .{ reason, @tagName(pending.phase) }, ); pending.deinit(app.alloc); }
fn adoptPendingSubmission(app: *App) !void { const pending = &app.submission.pending.?; if (pending.phase != .awaiting_adoption) return error.InvalidPendingPhase; if (comptime !@hasDecl(App, "adoptPendingUserPrompt")) { return error.PendingAdoptionUnsupported; } try App.adoptPendingUserPrompt(app, &pending.draft); try pending.markAdopted(); debug_trace.eventf( "input", "pending_prompt_adopted", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); }
fn finishPendingSubmissionFailure(app: *App, err: anyerror) void { const turn_id = app.submission.pending.?.draft.turn_id; const body = std.fmt.allocPrint( app.alloc, "failed to submit prompt after presentation ({s})", .{@errorName(err)}, ) catch |notice_err| { debug_trace.logf( "input", "pending prompt failure notice allocation failed err={s}", .{@errorName(notice_err)}, ); clearPendingSubmission(app, "finalization_failure"); return; }; defer app.alloc.free(body); app.writeDomainNotice(.{ .topic = "prompt", .tone = .@"error", .body = body, }, true) catch |notice_err| { debug_trace.logf( "input", "pending prompt failure notice output failed err={s}", .{@errorName(notice_err)}, ); }; debug_trace.eventf( "input", "pending_prompt_finalization_failed", .{ .turn_id = turn_id }, "err={s}", .{@errorName(err)}, ); clearPendingSubmission(app, "finalization_failure"); }
fn transferPendingImageSnapshotsToComposerHistory(app: *App) bool { if (comptime !@hasField(App, "input_runtime")) return false; if (comptime !@hasField(@TypeOf(app.input_runtime), "composer_history")) { return false; } const pending = app.submission.pending orelse return false; return app.input_runtime.composer_history.claimLatestImageSnapshots( pending.draft.images, ); }
pub fn submitInput(app: *App, max_prompt_history: usize) !void { try submit(app, max_prompt_history); }
pub fn submit(app: *App, max_prompt_history: usize) !void { if (comptime @hasField(App, "worker")) { if (comptime @hasDecl(@TypeOf(app.worker), "compactionActivitySnapshot")) { const observed = app.worker.compactionActivitySnapshot(); if (observed.operation) |op| { if (app.worker.dismissCompactionActivity(op.id, observed.revision)) app.shell.render_requests.request(.footer); } } } if (comptime @hasField(App, "submission")) { if (app.submission.compaction_pending and !pendingAuthRoutesLocalCommand(app)) return; if (app.submission.pending) |pending| { const route_local_command = pending.phase == .awaiting_auth and pendingAuthRoutesLocalCommand(app); if (!route_local_command and pending.phase == .awaiting_auth and app.input_runtime.edit_state.input.items.len == 0) { if (comptime @hasDecl(App, "retryPendingPromptCredential")) { switch (try App.retryPendingPromptCredential(app)) { .pending => { app.submission.retry_after_auth = false; app.submission.pending.?.phase = .adopted; app.shell.render_requests.request(.footer); }, .current => { const resumed = resumePendingPromptAfterAuth(app, "submit"); std.debug.assert(resumed); }, .rejected => {}, } } else if (try preflightPrompt(app)) { const resumed = resumePendingPromptAfterAuth(app, "submit"); std.debug.assert(resumed); } return; } if (route_local_command) { debug_trace.eventf( "input", "pending_prompt_auth_command_routed", .{ .turn_id = pending.draft.turn_id }, "", .{}, ); } else { debug_trace.eventf( "input", "prompt_submit_deferred_for_pending", .{ .turn_id = pending.draft.turn_id }, "phase={s}", .{@tagName(pending.phase)}, ); return; } } } const expanded_len = paste_blocks.expandedLen( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, ) orelse std.math.maxInt(usize); const max_input_len = if (comptime @hasDecl(App, "input_limits")) App.input_limits.composer_bytes else if (comptime @hasDecl(App, "input_byte_limit")) App.input_byte_limit else std.math.maxInt(usize); if (expanded_len > max_input_len) { try input_limit_feedback.report( App, app, .composer, expanded_len, ); return; }
const expanded = try paste_blocks.expand(app.alloc, app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items); defer if (expanded.owned) app.alloc.free(expanded.text);
const left_trimmed = std.mem.trimStart(u8, expanded.text, " \t\r\n"); const resolved_slash_submission = resolvedSlashSubmission(app, left_trimmed); if (knownSlashCommand(app, resolved_slash_submission)) |command| { if (requiresPromptCredential(command, resolved_slash_submission) and !try preflightPrompt(app)) return; try submitSlashCommand(app, left_trimmed, null, max_prompt_history); return; } if (shouldRouteUnknownSlashCommand(app, resolved_slash_submission)) { try submitSlashCommand(app, left_trimmed, null, max_prompt_history); return; } if (pendingImagesPrefixEnd( app.input_runtime.edit_state.input.items, expanded.text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.image_tokens.items, app.pending_images.items, )) |command_start| { const command_text = expanded.text[command_start..]; if (knownSlashCommand(app, command_text)) |command| { if (requiresPromptCredential(command, command_text) and !try preflightPrompt(app)) return; try submitSlashCommand( app, command_text, expanded.text[0..command_start], max_prompt_history, ); return; } }
const trimmed = std.mem.trim(u8, expanded.text, " \t\r\n");
if (trimmed.len == 0) { if (app.pending_images.items.len > 0) { const admission = try enqueuePromptForSubmit(app, "", &.{}); if (admission == .rejected) return; releasePendingImages(app); app.input_runtime.inputResetState().clearCurrent(app.alloc); if (acceptedPromptNeedsImmediateFooter(app)) { app.shell.render_requests.request(.footer); } return; } app.input_runtime.inputResetState().clearCurrent(app.alloc); app.shell.render_requests.request(.footer); return; }
const first_image_id = if (comptime @hasDecl(App, "peekNextImageId")) app.peekNextImageId() else try firstAvailableImageId(app.pending_images.items); var extracted = image_attachments.extractInlineImageAttachments( app.alloc, app.workspace_root, expanded.text, first_image_id, ) catch |err| { const message = switch (err) { error.InvalidImageId, error.ImageIdOverflow => return err, error.UnsupportedImageType => try app.alloc.dupe(u8, "unsupported image type"), error.FileNotFound => try app.alloc.dupe(u8, "image file not found"), error.ImageTooLarge => try app.alloc.dupe(u8, image_attachments.image_too_large_notice), else => try std.fmt.allocPrint(app.alloc, "failed to attach image: {s}", .{@errorName(err)}), }; defer app.alloc.free(message); try app.writeDomainNotice(.{ .topic = "images", .tone = .@"error", .body = message, }, true); app.shell.render_requests.request(.footer); return; }; var discard_extracted = true; defer if (discard_extracted) extracted.discard(app.alloc) else extracted.deinit(app.alloc); for (extracted.images) |image| { if (image_attachments.findImageIndexById(app.pending_images.items, image.id) != null) return error.DuplicateImageId; } if (try captureExtractedImages(app, extracted.images) == .rejected) { app.shell.render_requests.request(.footer); return; }
const has_inline_images = extracted.images.len > 0; const effective_text = extracted.text;
var staged_images = if (app.pending_images.items.len > 0 or extracted.images.len > 0) try stagePendingImages(app.alloc, app.pending_images.items, extracted.images) else null; defer if (staged_images) |*images| deinitOwnedImageList(app.alloc, images);
var image_occurrences = try projectImageOccurrencesForSubmit( app.alloc, app.input_runtime.edit_state.input.items, expanded.text, effective_text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.image_tokens.items, app.pending_images.items, extracted.edits, extracted.images, ); defer image_occurrences.deinit(app.alloc);
// One mapping owns both placeholder IDs and attachment order. const visual_text = if (staged_images) |*images| try mapSubmittedImages( app.alloc, effective_text, images, image_occurrences.items, firstRemappedImageId(app), ) else VisualText{ .text = effective_text, .owned = false }; defer if (visual_text.owned) app.alloc.free(visual_text.text); const display_skill_tokens = try projectSkillTokensForSubmit( app.alloc, app.input_runtime.edit_state.input.items, expanded.text, effective_text, visual_text.text, app.input_runtime.entities.pasted_blocks.items, app.input_runtime.entities.skill_tokens.items, extracted.edits, image_occurrences.items, ); defer if (display_skill_tokens.len > 0) app.alloc.free(display_skill_tokens); var history_projection: ?ComposerHistoryProjection = if (composerHistoryEnabled(app)) try prepareComposerHistoryProjection( app, expanded.text, visual_text.text, display_skill_tokens, image_occurrences.items, has_inline_images, ) else null; defer if (history_projection) |*projection| projection.deinit(app.alloc); const has_images_for_submit = if (staged_images) |*images| images.items.len > 0 else app.pending_images.items.len > 0;
const admission = if (staged_images) |*images| try enqueuePromptWithStagedImages( app, visual_text.text, display_skill_tokens, images, ) else try enqueuePromptForSubmit( app, visual_text.text, display_skill_tokens, ); if (admission == .rejected) return; commitStableExtractedImageIds(app, extracted.images); commitRemappedImageIds(app, visual_text.next_image_id); if (visual_text.text.len > 0) { if (history_projection) |*projection| { recordAcceptedPromptComposerHistory( app, max_prompt_history, projection, ); } } if (admission == .enqueued and !has_images_for_submit and visual_text.text.len > 0) { recordAcceptedInput(app, visual_text.text); } discard_extracted = false; releasePendingImages(app); app.input_runtime.inputResetState().clearCurrent(app.alloc); paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); if (acceptedPromptNeedsImmediateFooter(app)) { app.shell.render_requests.request(.footer); } }
fn acceptedPromptNeedsImmediateFooter(app: *const App) bool { if (comptime @hasField(App, "stream")) return app.stream.active; return true; }
fn preflightPrompt(app: *App) !bool { if (comptime @hasDecl(App, "ensurePromptCredential")) { return App.ensurePromptCredential(app); } return true; }
fn pendingAuthRoutesLocalCommand(app: *App) bool { const input = std.mem.trimStart( u8, app.input_runtime.edit_state.input.items, " \t\r\n", ); const submission = resolvedSlashSubmission(app, input); const command = knownSlashCommand(app, submission) orelse return false; return !requiresPromptCredential(command, submission); }
fn knownSlashCommand(app: *const App, text: []const u8) ?*const command_specs.SlashSpec { if (text.len == 0 or text[0] != '/') return null; var command_end: usize = 1; while (command_end < text.len and !std.ascii.isWhitespace(text[command_end])) : (command_end += 1) {} return app.slashRegistry().lookup(text[0..command_end]); }
fn shouldRouteUnknownSlashCommand(app: *App, text: []const u8) bool { if (text.len == 0 or text[0] != '/') return false; const command_end = std.mem.indexOfAny(u8, text, " \t\r\n") orelse text.len; if (command_end != text.len) return false; if (std.mem.indexOfScalar(u8, text[1..command_end], '/') != null) return false; if (app.input_runtime.picker.isInlinePickerDismissed(.slash)) return false; if (app.input_runtime.picker.inlinePickerTriggerKind(&app.input_runtime.edit_state) != .slash) return false; return completion_rt.slashCompletionCandidateCount(app) == 0; }
fn requiresPromptCredential(command: *const command_specs.SlashSpec, submission: []const u8) bool { if (!command.requires_prompt_credential) return false; if (command.kind != .model) return true;
var command_end: usize = 1; while (command_end < submission.len and !std.ascii.isWhitespace(submission[command_end])) : (command_end += 1) {} return std.mem.trim(u8, submission[command_end..], " \t\r\n").len > 0; }
fn pendingImagesPrefixEnd( raw_input: []const u8, expanded_text: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, images: []const types.ImageAttachment, ) ?usize { var index: usize = 0; while (index < expanded_text.len and std.ascii.isWhitespace(expanded_text[index])) : (index += 1) {}
var saw_placeholder = false; while (projectedImageTokenAt( raw_input, pasted_blocks, image_tokens, images, index, )) |projected| { saw_placeholder = true; index = projected.end; while (index < expanded_text.len and std.ascii.isWhitespace(expanded_text[index])) : (index += 1) {} } if (!saw_placeholder) return null; return index; }
fn projectedImageTokenAt( raw_input: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, images: []const types.ImageAttachment, expanded_start: usize, ) ?Span { for (image_tokens) |token| { if (!validRawImageToken(raw_input, token)) continue; if (image_attachments.findImageIndexById(images, token.id) == null) continue; const start = paste_blocks.projectOffsetThroughExpansion( raw_input, pasted_blocks, token.span.raw_start, ) orelse continue; if (start < expanded_start) continue; if (start > expanded_start) return null; const end = paste_blocks.projectOffsetThroughExpansion( raw_input, pasted_blocks, token.span.raw_end, ) orelse return null; return .{ .start = start, .end = end }; } return null; }
fn submitSlashCommand( app: *App, text: []const u8, retained_prefix: ?[]const u8, max_prompt_history: usize, ) !void { const command_text = resolvedSlashSubmission(app, text); const command_copy = try app.alloc.dupe(u8, command_text); defer app.alloc.free(command_copy); const prefix_copy: ?[]u8 = if (retained_prefix) |prefix| try app.alloc.dupe(u8, prefix) else null; defer if (prefix_copy) |prefix| app.alloc.free(prefix); var retained_image_tokens = if (prefix_copy) |prefix| try projectRetainedImageTokens(app, prefix.len) else std.ArrayList(entity_spans.ImageTokenSpan).empty; defer retained_image_tokens.deinit(app.alloc); if (prefix_copy) |prefix| { try app.input_runtime.edit_state.input.ensureTotalCapacity(app.alloc, prefix.len); try app.input_runtime.entities.image_tokens.ensureTotalCapacity( app.alloc, retained_image_tokens.items.len, ); }
app.input_runtime.inputResetState().clearCurrent(app.alloc); if (prefix_copy) |prefix| { app.input_runtime.edit_state.insertSliceAssumeCapacity(prefix); app.input_runtime.entities.image_tokens.appendSliceAssumeCapacity( retained_image_tokens.items, ); } paste_blocks.clearBlocks(app.alloc, &app.input_runtime.entities.pasted_blocks); if (retained_prefix == null and app.pending_images.items.len > 0) { debug_trace.logf( "input", "draft images dropped count={d} reason=local_command", .{app.pending_images.items.len}, ); app.clearPendingImages(); } app.shell.render_requests.request(.footer); defer if (retained_prefix != null) { if (app.pending_images.items.len == 0) { app.input_runtime.inputResetState().clearCurrent(app.alloc); } app.shell.render_requests.request(.footer); }; recordAcceptedSlashCommandHistory( app, max_prompt_history, command_copy, ); try App.handleCommand(app, command_copy); }
fn projectRetainedImageTokens( app: *App, expanded_prefix_len: usize, ) !std.ArrayList(entity_spans.ImageTokenSpan) { var projected: std.ArrayList(entity_spans.ImageTokenSpan) = .empty; errdefer projected.deinit(app.alloc); for (app.input_runtime.entities.image_tokens.items) |token| { if (!validRawImageToken(app.input_runtime.edit_state.input.items, token)) continue; if (image_attachments.findImageIndexById( app.pending_images.items, token.id, ) == null) continue; const start = paste_blocks.projectOffsetThroughExpansion( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, token.span.raw_start, ) orelse continue; const end = paste_blocks.projectOffsetThroughExpansion( app.input_runtime.edit_state.input.items, app.input_runtime.entities.pasted_blocks.items, token.span.raw_end, ) orelse continue; if (end > expanded_prefix_len) continue; try projected.append(app.alloc, .{ .id = token.id, .span = .{ .raw_start = start, .raw_end = end }, }); } return projected; }
fn resolvedSlashSubmission(app: *App, text: []const u8) []const u8 { if (completion_rt.visibleSlashCompletionCount(app) == 0) return text; return resolveSlashSubmission(app.slashRegistry(), text, app.input_runtime.picker.slash_completion_index); }
fn enqueuePromptForSubmit( app: *App, prompt: []const u8, skill_tokens: []const registered_entities.SkillTokenSpan, ) !PromptAdmission { switch (try installPendingSubmission(app, prompt, skill_tokens)) { .installed => return .pending, .unavailable => {}, } if (!try preflightPrompt(app)) return .rejected; const accepted = if (comptime @hasDecl(App, "enqueuePromptWithSkillBindings")) try App.enqueuePromptWithSkillBindings(app, prompt, skill_tokens) else try App.enqueuePrompt(app, prompt); if (!accepted) return .rejected; return .enqueued; }
fn installPendingSubmission( app: *App, prompt: []const u8, skill_tokens: []const registered_entities.SkillTokenSpan, ) !PendingInstall { if (comptime !@hasField(App, "submission") or !@hasField(App, "worker") or !@hasDecl(@TypeOf(app.worker), "tryHoldTurnStart") or !@hasDecl(@TypeOf(app.worker), "releaseTurnStartHold")) { return .unavailable; } if (comptime @hasField(App, "stream")) { if (app.stream.active) return .unavailable; } if (comptime @hasField(App, "pacer") and @hasDecl(@TypeOf(app.pacer), "hasPending")) { if (app.pacer.hasPending()) return .unavailable; } if (comptime @hasField(App, "shell") and @hasDecl(@TypeOf(app.shell), "fullTranscriptActive")) { if (app.shell.fullTranscriptActive()) return .unavailable; } std.debug.assert(app.submission.pending == null); if (!app.worker.tryHoldTurnStart()) return .unavailable; errdefer app.worker.releaseTurnStartHold();
const draft = try buildPendingPromptDraft( app.alloc, debug_trace.nextTurnId(), prompt, app.pending_images.items, skill_tokens, ); app.submission.pending = PendingSubmission.init(draft); app.shell.render_requests.request(.transcript); app.shell.render_requests.request(.footer); debug_trace.eventf( "input", "pending_prompt_installed", .{ .turn_id = draft.turn_id }, "prompt_bytes={d} images={d} skill_spans={d}", .{ draft.prompt.len, draft.images.len, draft.skill_display_spans.len }, ); return .installed; }
fn enqueuePromptWithStagedImages( app: *App, prompt: []const u8, skill_tokens: []const registered_entities.SkillTokenSpan, staged_images: *std.ArrayList(types.ImageAttachment), ) !PromptAdmission { const original_images = app.pending_images; app.pending_images = staged_images.*; staged_images.* = .empty; errdefer { deinitOwnedImageList(app.alloc, &app.pending_images); app.pending_images = original_images; }
const admission = try enqueuePromptForSubmit( app, prompt, skill_tokens, ); if (admission == .rejected) { staged_images.* = app.pending_images; app.pending_images = original_images; return admission; }
var committed_images = original_images; deinitOwnedImageList(app.alloc, &committed_images); return admission; }
fn firstAvailableImageId(images: []const types.ImageAttachment) !usize { var highest_id: usize = 0; for (images) |image| highest_id = @max(highest_id, image.id); return std.math.add(usize, highest_id, 1); }
fn commitStableExtractedImageIds( app: *App, images: []const types.ImageAttachment, ) void { if (comptime !@hasDecl(App, "nextImageId")) return; for (images) |image| { const committed_id = app.nextImageId(); std.debug.assert(committed_id == image.id); } }
fn captureExtractedImages( app: *App, images: []types.ImageAttachment, ) !image_attachments.CaptureAdmissionResult { return image_attachments.captureImageAttachmentsForAdmission(App, app, images); }
fn stagePendingImages( alloc: std.mem.Allocator, pending: []const types.ImageAttachment, extracted: []const types.ImageAttachment, ) !std.ArrayList(types.ImageAttachment) { const pending_copy = try types.dupeImageAttachmentSlice(alloc, pending); var staged = std.ArrayList(types.ImageAttachment).fromOwnedSlice(pending_copy); errdefer deinitOwnedImageList(alloc, &staged);
const extracted_copy = try types.dupeImageAttachmentSlice(alloc, extracted); errdefer types.freeImageAttachmentSlice(alloc, extracted_copy); if (extracted_copy.len > 0) { try staged.ensureUnusedCapacity(alloc, extracted_copy.len); staged.appendSliceAssumeCapacity(extracted_copy); alloc.free(extracted_copy); } return staged; }
fn deinitOwnedImageList(alloc: std.mem.Allocator, images: *std.ArrayList(types.ImageAttachment)) void { for (images.items) |image| types.freeImageAttachment(alloc, image); images.deinit(alloc); images.* = .empty; }
fn releasePendingImages(app: *App) void { if (comptime @hasDecl(App, "releasePendingImages")) { App.releasePendingImages(app); } else { app.clearPendingImages(); } }
fn recordAcceptedInput(app: *App, text: []const u8) void { if (comptime !@hasField(App, "prompt_history")) return; const outcome = app.prompt_history.recordAccepted( app.alloc, io_mod.milliTimestamp(), app.workspace_root, text, ); if (outcome != .warning) return; const err = app.prompt_history.lastAppendError() orelse error.PromptHistoryWriteFailed; const notice = std.fmt.allocPrint( app.alloc, "failed to save input history ({s}); submission continued", .{@errorName(err)}, ) catch |notice_err| { debug_trace.logf( "prompt_history", "warning allocation failed err={s}", .{@errorName(notice_err)}, ); return; }; defer app.alloc.free(notice); app.writeDomainNotice(.{ .topic = "history", .tone = .warning, .body = notice, }, true) catch |notice_err| { debug_trace.logf( "prompt_history", "warning output failed err={s}", .{@errorName(notice_err)}, ); }; }
fn recordAcceptedPromptComposerHistory( app: *App, max_prompt_history: usize, projection: *const ComposerHistoryProjection, ) void { recordComposerHistory( app, max_prompt_history, projection.input, projection.pasted_blocks.items, app.pending_images.items, projection.image_tokens.items, projection.skill_tokens.items, ); }
fn recordAcceptedSlashCommandHistory( app: *App, max_prompt_history: usize, command: []const u8, ) void { recordComposerHistory( app, max_prompt_history, command, &.{}, &.{}, &.{}, &.{}, ); recordAcceptedInput(app, command); }
fn recordComposerHistory( app: *App, max_prompt_history: usize, text: []const u8, pasted: []const paste_blocks.PastedBlock, images: []const types.ImageAttachment, image_tokens: []const entity_spans.ImageTokenSpan, skill_tokens: []const registered_entities.SkillTokenSpan, ) void { if (comptime @hasField(App, "prompt_history")) { if (!app.prompt_history.enabled) return; } app.input_runtime.composer_history.record( app.alloc, max_prompt_history, text, pasted, images, image_tokens, skill_tokens, ) catch |err| { debug_trace.logf( "input", "composer history record skipped err={s}", .{@errorName(err)}, ); }; }
fn prepareComposerHistoryProjection( app: *App, expanded_text: []const u8, visual_text: []const u8, display_skill_tokens: []const registered_entities.SkillTokenSpan, image_occurrences: []const ImageOccurrence, has_extracted_images: bool, ) !ComposerHistoryProjection { var submitted_image_tokens: std.ArrayList(entity_spans.ImageTokenSpan) = .empty; defer submitted_image_tokens.deinit(app.alloc); try submitted_image_tokens.ensureTotalCapacity( app.alloc, image_occurrences.len, ); for (image_occurrences) |occurrence| { if (occurrence.submitted_id == 0) continue; const span = occurrence.submitted_span orelse return error.InvalidImageOccurrence; submitted_image_tokens.appendAssumeCapacity(.{ .id = occurrence.submitted_id, .span = .{ .raw_start = span.start, .raw_end = span.end, }, }); }
if (app.input_runtime.entities.pasted_blocks.items.len > 0 and !has_extracted_images) { if (try prepareCompactComposerHistoryProjection( app, expanded_text, visual_text, display_skill_tokens, submitted_image_tokens.items, )) |compact| return compact; debug_trace.logf( "input", "accepted draft paste provenance unavailable after projection blocks={d}", .{app.input_runtime.entities.pasted_blocks.items.len}, ); } if (app.input_runtime.entities.pasted_blocks.items.len > 0 and has_extracted_images) { debug_trace.logf( "input", "accepted draft paste provenance unavailable after inline image extraction blocks={d}", .{app.input_runtime.entities.pasted_blocks.items.len}, ); }
var fallback = ComposerHistoryProjection{ .input = visual_text }; errdefer fallback.deinit(app.alloc); try fallback.image_tokens.appendSlice( app.alloc, submitted_image_tokens.items, ); try fallback.skill_tokens.appendSlice( app.alloc, display_skill_tokens, ); return fallback; }
fn prepareCompactComposerHistoryProjection( app: *App, expanded_text: []const u8, visual_text: []const u8, display_skill_tokens: []const registered_entities.SkillTokenSpan, submitted_image_tokens: []const entity_spans.ImageTokenSpan, ) !?ComposerHistoryProjection { const raw_input = app.input_runtime.edit_state.input.items; if (!std.mem.eql(u8, expanded_text, visual_text)) return null; const raw_start: usize = 0; const raw_end = raw_input.len;
var compact = ComposerHistoryProjection{ .input = raw_input[raw_start..raw_end], }; var transferred = false; defer if (!transferred) compact.deinit(app.alloc); for (app.input_runtime.entities.pasted_blocks.items) |block| { if (block.span.raw_end <= raw_start or block.span.raw_start >= raw_end) { continue; } if (block.span.raw_start < raw_start or block.span.raw_end > raw_end) { return null; } try compact.pasted_blocks.append(app.alloc, .{ .id = block.id, .text = block.text, .line_count = block.line_count, .span = .{ .raw_start = block.span.raw_start - raw_start, .raw_end = block.span.raw_end - raw_start, }, }); }
const expanded_compact = try paste_blocks.expand( app.alloc, compact.input, compact.pasted_blocks.items, ); defer if (expanded_compact.owned) app.alloc.free(expanded_compact.text); if (!std.mem.eql(u8, expanded_compact.text, visual_text)) return null;
for (app.input_runtime.entities.image_tokens.items) |token| { if (token.span.raw_end <= raw_start or token.span.raw_start >= raw_end) { continue; } if (token.span.raw_start < raw_start or token.span.raw_end > raw_end) { return null; } try compact.image_tokens.append(app.alloc, .{ .id = token.id, .span = .{ .raw_start = token.span.raw_start - raw_start, .raw_end = token.span.raw_end - raw_start, }, }); } if (!sameImageTokenIds( compact.image_tokens.items, submitted_image_tokens, )) { return null; }
for (app.input_runtime.entities.skill_tokens.items) |token| { if (token.raw_end <= raw_start or token.raw_start >= raw_end) continue; if (token.raw_start < raw_start or token.raw_end > raw_end) return null; try compact.skill_tokens.append(app.alloc, .{ .raw_start = token.raw_start - raw_start, .raw_end = token.raw_end - raw_start, .name = token.name, .path = token.path, .display_source = token.display_source, .owns_trailing_separator = token.owns_trailing_separator, }); } if (!sameSkillSources( compact.skill_tokens.items, display_skill_tokens, )) { return null; }
transferred = true; return compact; }
fn sameImageTokenIds( raw_tokens: []const entity_spans.ImageTokenSpan, submitted_tokens: []const entity_spans.ImageTokenSpan, ) bool { if (raw_tokens.len != submitted_tokens.len) return false; for (raw_tokens, submitted_tokens) |raw, submitted| { if (raw.id != submitted.id) return false; } return true; }
fn sameSkillSources( raw_tokens: []const registered_entities.SkillTokenSpan, submitted_tokens: []const registered_entities.SkillTokenSpan, ) bool { if (raw_tokens.len != submitted_tokens.len) return false; for (raw_tokens, submitted_tokens) |raw, submitted| { if (!std.mem.eql(u8, raw.name, submitted.name) or !std.mem.eql(u8, raw.path, submitted.path) or raw.display_source != submitted.display_source or raw.owns_trailing_separator != submitted.owns_trailing_separator) { return false; } } return true; }
const VisualText = struct { text: []const u8, owned: bool, next_image_id: ?usize = null, };
const ImageOccurrence = struct { /// Coordinates before collision-driven ID rewriting. span: Span, attachment_index: usize, source_id: usize, submitted_id: usize = 0, submitted_span: ?Span = null, };
fn projectImageOccurrencesForSubmit( alloc: std.mem.Allocator, raw_input: []const u8, expanded_text: []const u8, effective_text: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, image_tokens: []const entity_spans.ImageTokenSpan, pending_images: []const types.ImageAttachment, edits: []const image_attachments.InlineImageEdit, extracted_images: []const types.ImageAttachment, ) !std.ArrayList(ImageOccurrence) { var occurrences: std.ArrayList(ImageOccurrence) = .empty; errdefer occurrences.deinit(alloc); for (image_tokens) |token| { if (!validRawImageToken(raw_input, token)) continue; const attachment_index = image_attachments.findImageIndexById(pending_images, token.id) orelse continue; const expanded = projectSpanThroughPasteExpansion(raw_input, pasted_blocks, .{ .start = token.span.raw_start, .end = token.span.raw_end, }) orelse continue; const span = project_inline_span(expanded_text.len, expanded, edits) orelse continue; try appendImageOccurrenceSorted(alloc, &occurrences, .{ .span = span, .attachment_index = attachment_index, .source_id = token.id, }); }
if (edits.len != extracted_images.len) return error.InvalidImageOccurrence; for (edits, extracted_images, 0..) |edit, image, index| { if (edit.id != image.id or !edit.output.isValid(effective_text.len)) return error.InvalidImageOccurrence; const match = image_attachments.matchImagePlaceholder(effective_text, edit.output.raw_start) orelse return error.InvalidImageOccurrence; if (match.id != edit.id or edit.output.raw_start + match.length != edit.output.raw_end) return error.InvalidImageOccurrence; try appendImageOccurrenceSorted(alloc, &occurrences, .{ .span = .{ .start = edit.output.raw_start, .end = edit.output.raw_end }, .attachment_index = pending_images.len + index, .source_id = edit.id, }); } return occurrences; }
fn appendImageOccurrenceSorted( alloc: std.mem.Allocator, occurrences: *std.ArrayList(ImageOccurrence), occurrence: ImageOccurrence, ) !void { try occurrences.append(alloc, occurrence); var index = occurrences.items.len - 1; while (index > 0 and occurrences.items[index].span.start < occurrences.items[index - 1].span.start) { std.mem.swap( ImageOccurrence, &occurrences.items[index], &occurrences.items[index - 1], ); index -= 1; } }
fn validRawImageToken( raw_input: []const u8, token: entity_spans.ImageTokenSpan, ) bool { if (!token.span.isValid(raw_input.len)) return false; const match = image_attachments.matchImagePlaceholder( raw_input, token.span.raw_start, ) orelse return false; return match.id == token.id and token.span.raw_start + match.length == token.span.raw_end; }
fn mapSubmittedImages( alloc: std.mem.Allocator, text: []const u8, images: *std.ArrayList(types.ImageAttachment), occurrences: []ImageOccurrence, first_remapped_id: usize, ) !VisualText { if (images.items.len == 0) return .{ .text = text, .owned = false };
const used = try alloc.alloc(bool, images.items.len); defer alloc.free(used); @memset(used, false);
var ordered: std.ArrayList(types.ImageAttachment) = .empty; defer ordered.deinit(alloc); try ordered.ensureTotalCapacity(alloc, images.items.len);
var remapped_id = first_remapped_id; var ids_changed = false; for (occurrences) |*occurrence| { if (occurrence.attachment_index >= images.items.len or used[occurrence.attachment_index]) { return error.InvalidImageOccurrence; } var image = images.items[occurrence.attachment_index]; if (image.id != occurrence.source_id) return error.InvalidImageOccurrence; if (literalImageIdUsed(text, occurrences, image.id)) { remapped_id = try nextRemappedImageId( text, occurrences, images.items, remapped_id, ); image.id = remapped_id; remapped_id = try std.math.add(usize, remapped_id, 1); ids_changed = true; } occurrence.submitted_id = image.id; occurrence.submitted_span = occurrence.span; ordered.appendAssumeCapacity(image); used[occurrence.attachment_index] = true; }
for (images.items, used) |image, retained| { if (!retained) types.freeImageAttachment(alloc, image); } @memcpy(images.items[0..ordered.items.len], ordered.items); images.items.len = ordered.items.len;
if (!ids_changed) { return .{ .text = text, .owned = false }; } var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); var cursor: usize = 0; for (occurrences) |*occurrence| { if (occurrence.span.start < cursor or occurrence.span.end > text.len) { return error.InvalidImageOccurrence; } try out.appendSlice(alloc, text[cursor..occurrence.span.start]); const start = out.items.len; var placeholder_buf: [64]u8 = undefined; const placeholder = try image_attachments.formatImagePlaceholder( &placeholder_buf, occurrence.submitted_id, ); try out.appendSlice(alloc, placeholder); occurrence.submitted_span = .{ .start = start, .end = out.items.len }; cursor = occurrence.span.end; } try out.appendSlice(alloc, text[cursor..]); return .{ .text = try out.toOwnedSlice(alloc), .owned = true, .next_image_id = if (ids_changed) remapped_id else null, }; }
fn firstRemappedImageId(app: *const App) usize { if (comptime @hasDecl(App, "peekNextImageId")) { return app.peekNextImageId(); } return 1; }
fn commitRemappedImageIds(app: *App, next_image_id: ?usize) void { const next = next_image_id orelse return; if (comptime @hasDecl(App, "peekNextImageId") and @hasDecl(App, "nextImageId")) { while (app.peekNextImageId() < next) { const committed_id = app.nextImageId(); std.debug.assert(committed_id < next); } } }
fn nextRemappedImageId( text: []const u8, occurrences: []const ImageOccurrence, images: []const types.ImageAttachment, first_candidate: usize, ) !usize { var candidate = first_candidate; while (literalImageIdUsed(text, occurrences, candidate) or image_attachments.findImageIndexById(images, candidate) != null) { candidate = try std.math.add(usize, candidate, 1); } return candidate; }
fn literalImageIdUsed( text: []const u8, occurrences: []const ImageOccurrence, id: usize, ) bool { var index: usize = 0; while (index < text.len) { const match = image_attachments.matchImagePlaceholder(text, index) orelse { index += 1; continue; }; const end = index + match.length; var registered = false; for (occurrences) |occurrence| { if (occurrence.span.start == index and occurrence.span.end == end and occurrence.source_id == match.id) { registered = true; break; } } if (!registered and match.id == id) return true; index = end; } return false; }
fn projectSpanThroughSubmittedImages( text: []const u8, occurrences: []const ImageOccurrence, span: Span, ) ?Span { if (span.start > span.end or span.end > text.len) return null; const start = projectOffsetThroughSubmittedImages( text, occurrences, span.start, ) orelse return null; const end = projectOffsetThroughSubmittedImages( text, occurrences, span.end, ) orelse return null; if (end < start) return null; return .{ .start = start, .end = end }; }
fn projectOffsetThroughSubmittedImages( text: []const u8, occurrences: []const ImageOccurrence, source_offset: usize, ) ?usize { if (source_offset > text.len) return null;
var source_cursor: usize = 0; var output_cursor: usize = 0; for (occurrences) |occurrence| { const output = occurrence.submitted_span orelse return null; if (occurrence.span.start < source_cursor or occurrence.span.end <= occurrence.span.start or occurrence.span.end > text.len or occurrence.submitted_id == 0 or output.end <= output.start) { return null; } const start = std.math.add(usize, output_cursor, occurrence.span.start - source_cursor) catch return null; if (output.start != start) return null; if (source_offset < occurrence.span.start) { return std.math.add(usize, output_cursor, source_offset - source_cursor) catch null; } if (source_offset < occurrence.span.end) { return if (source_offset == occurrence.span.start) output.start else null; } output_cursor = output.end; source_cursor = occurrence.span.end; } return std.math.add(usize, output_cursor, source_offset - source_cursor) catch null; }
fn resolveSlashSubmission(registry: command_specs.SlashRegistry, text: []const u8, selected_index: usize) []const u8 { const count = command_specs.slashCompletionCount(registry, text); if (count == 0) return text; const idx = selected_index % count; return command_specs.nthSlashCompletion(registry, text, idx) orelse text; }
const Span = struct { start: usize, end: usize, };
fn projectSkillTokensForSubmit( alloc: std.mem.Allocator, raw_input: []const u8, expanded_text: []const u8, effective_text: []const u8, final_text: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, skill_tokens: []const registered_entities.SkillTokenSpan, edits: []const image_attachments.InlineImageEdit, image_occurrences: []const ImageOccurrence, ) ![]registered_entities.SkillTokenSpan { if (skill_tokens.len == 0) return &.{};
var projected: std.ArrayList(registered_entities.SkillTokenSpan) = .empty; errdefer projected.deinit(alloc);
for (skill_tokens) |token| { if (token.name.len == 0 or token.path.len == 0) continue; const expanded = projectSpanThroughPasteExpansion(raw_input, pasted_blocks, .{ .start = token.raw_start, .end = token.raw_end, }) orelse continue; const span = project_inline_span(expanded_text.len, expanded, edits) orelse continue;
const final_span = projectSpanThroughSubmittedImages( effective_text, image_occurrences, span, ) orelse continue; const marker = skillMarkerAt( final_text, final_span.start, token.name, ) orelse continue; if (marker.end != final_span.end) continue; try projected.append(alloc, .{ .raw_start = final_span.start, .raw_end = final_span.end, .name = token.name, .path = token.path, .display_source = token.display_source, .owns_trailing_separator = token.owns_trailing_separator, }); }
if (projected.items.len == 0) { projected.deinit(alloc); return &.{}; } return try projected.toOwnedSlice(alloc); }
fn projectSpanThroughPasteExpansion( raw_input: []const u8, pasted_blocks: []const paste_blocks.PastedBlock, span: Span, ) ?Span { if (span.start > span.end or span.end > raw_input.len) return null; const start = paste_blocks.projectOffsetThroughExpansion(raw_input, pasted_blocks, span.start) orelse return null; const end = paste_blocks.projectOffsetThroughExpansion(raw_input, pasted_blocks, span.end) orelse return null; if (end < start) return null; return .{ .start = start, .end = end }; }
fn project_inline_span(source_len: usize, span: Span, edits: []const image_attachments.InlineImageEdit) ?Span { const projected = image_attachments.project_inline_image_span(source_len, .{ .raw_start = span.start, .raw_end = span.end, }, edits) orelse return null; return .{ .start = projected.raw_start, .end = projected.raw_end }; }
fn skillMarkerAt(text: []const u8, start: usize, name: []const u8) ?Span { if (start >= text.len or text[start] != '$') return null; const end = start + 1 + name.len; if (end > text.len) return null; if (!std.mem.eql(u8, text[start + 1 .. end], name)) return null; if (end < text.len and isSkillNameByte(text[end])) return null; return .{ .start = start, .end = end }; }
fn isSkillNameByte(byte: u8) bool { return (byte >= 'A' and byte <= 'Z') or (byte >= 'a' and byte <= 'z') or (byte >= '0' and byte <= '9') or byte == '_' or byte == '-'; } };}
test "inline image collision map keeps reserved stable IDs and final coordinates distinct" { const alloc = std.testing.allocator; const Rt = SubmitRuntime(struct {}); const text = "literal [Image #9]\n[Image #9] $review [Image #10]"; const first = "literal [Image #9]\n".len; const second = text.len - "[Image #10]".len; var occurrences = [_]Rt.ImageOccurrence{ .{ .span = .{ .start = first, .end = first + "[Image #9]".len }, .attachment_index = 0, .source_id = 9 }, .{ .span = .{ .start = second, .end = text.len }, .attachment_index = 1, .source_id = 10 }, }; const original_first = occurrences[0].span; const original_second = occurrences[1].span; var images = std.ArrayList(types.ImageAttachment).fromOwnedSlice(try types.dupeImageAttachmentSlice(alloc, &.{ .{ .id = 9, .path = @constCast("first.png"), .media_type = @constCast("image/png") }, .{ .id = 10, .path = @constCast("second.png"), .media_type = @constCast("image/png") }, })); defer Rt.deinitOwnedImageList(alloc, &images); const result = try Rt.mapSubmittedImages(alloc, text, &images, &occurrences, 9); defer if (result.owned) alloc.free(result.text); try std.testing.expectEqualStrings("literal [Image #9]\n[Image #11] $review [Image #10]", result.text); try std.testing.expectEqual(@as(usize, 11), images.items[0].id); try std.testing.expectEqual(@as(usize, 10), images.items[1].id); try std.testing.expectEqualDeep(original_first, occurrences[0].span); try std.testing.expectEqualDeep(original_second, occurrences[1].span); for (occurrences, [_][]const u8{ "[Image #11]", "[Image #10]" }) |occurrence, expected| { const span = occurrence.submitted_span.?; try std.testing.expectEqualStrings(expected, result.text[span.start..span.end]); } const skill_start = std.mem.find(u8, text, "$review").?; const skill = Rt.projectSpanThroughSubmittedImages(text, &occurrences, .{ .start = skill_start, .end = skill_start + "$review".len }).?; try std.testing.expectEqualStrings("$review", result.text[skill.start..skill.end]);}
test "pending submission phase methods keep hold ownership explicit" { const alloc = std.testing.allocator; var pending = PendingSubmission.init(try buildPendingPromptDraft( alloc, 41, "use $review", &.{}, &.{.{ .raw_start = 4, .raw_end = 11, .name = "review", .path = "/tmp/review/SKILL.md", }}, )); defer pending.deinit(alloc);
try std.testing.expectEqual(PendingPhase.awaiting_frame, pending.phase); try std.testing.expect(pending.ownsTurnStartHold()); try std.testing.expectError(error.InvalidPendingPhase, pending.markAdopted());
try std.testing.expect(pending.markFrameCommitted()); try std.testing.expectEqual(PendingPhase.awaiting_adoption, pending.phase); try std.testing.expect(pending.ownsTurnStartHold()); try std.testing.expect(!pending.markFrameCommitted());
try pending.markAdopted(); try std.testing.expectEqual(PendingPhase.adopted, pending.phase); try std.testing.expect(pending.ownsTurnStartHold()); try std.testing.expectError(error.InvalidPendingPhase, pending.markAdopted());
try std.testing.expect(pending.markQueued()); try std.testing.expectEqual(PendingPhase.queued, pending.phase); try std.testing.expect(!pending.ownsTurnStartHold()); try std.testing.expect(!pending.markQueued()); try std.testing.expectEqual(@as(u64, 41), pending.draft.turn_id); try std.testing.expectEqualStrings("use $review", pending.draft.prompt); try std.testing.expectEqual(@as(usize, 1), pending.draft.skill_display_spans.len);}
fn checkPendingDraftConstructionAllocationFailure(alloc: std.mem.Allocator) !void { const draft = try buildPendingPromptDraft( alloc, 77, "hello $review", &.{.{ .id = 5, .path = @constCast("/tmp/image.png"), .media_type = @constCast("image/png"), }}, &.{.{ .raw_start = 6, .raw_end = 13, .name = "review", .path = "/tmp/review/SKILL.md", }}, ); defer draft.deinit(alloc); try std.testing.expectEqual(@as(u64, 77), draft.turn_id); try std.testing.expectEqualStrings("hello $review", draft.prompt); try std.testing.expectEqual(@as(usize, 1), draft.images.len); try std.testing.expectEqual(@as(usize, 1), draft.skill_display_spans.len);}
test "pending draft construction frees every partial allocation" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkPendingDraftConstructionAllocationFailure, .{}, );}
const CompactionAdmissionFake = struct { alloc: std.mem.Allocator = std.testing.allocator, submission: State = .{}, has_context: bool = true, readiness: enum { pending, current, rejected } = .pending, readiness_error: ?anyerror = null, enqueue_error: ?anyerror = null, credential_checks: usize = 0, enqueue_count: usize = 0, notice_error: bool = false, notice_count: usize = 0, notices: std.ArrayList(u8) = .empty, auth: struct { cancelled: usize = 0, pub fn cancelPromptCredentialRefresh(self: *@This()) void { self.cancelled += 1; } } = .{}, shell: struct { render_requests: @import("../../ui/render_request.zig").RenderRequestState = .{} } = .{}, worker: struct { busy: bool = false, queued: usize = 0, status: worker_runtime.ContextCompactionStatus = .idle, activity: compaction_activity.State = .{}, released_holds: usize = 0,
pub fn compactionActivitySnapshot(self: *@This()) compaction_activity.Snapshot { return self.activity.snapshot; } pub fn beginCompactionActivity(self: *@This(), origin: compaction_activity.Origin, turn_id: ?u64) compaction_activity.OperationId { return self.activity.begin(origin, turn_id, 0); } pub fn settleCompactionActivity(self: *@This(), id: compaction_activity.OperationId, feedback: compaction_activity.Feedback) void { self.activity.settle(id, feedback, 0); } pub fn rejectCompactionActivity(self: *@This(), outcome: compaction_activity.Outcome) void { const id = self.activity.begin(.manual, null, 0); self.activity.settle(id, .{ .outcome = outcome }, 0); } pub fn isProcessing(self: *@This()) bool { return self.busy; } pub fn queuedPromptCount(self: *@This()) usize { return self.queued; } pub fn contextCompactionStatus(self: *@This()) worker_runtime.ContextCompactionStatus { return self.status; } pub fn releaseTurnStartHold(self: *@This()) void { self.released_holds += 1; } pub fn clearQueuedPrompts(self: *@This(), _: std.mem.Allocator, _: []const types.ImageAttachment) void { self.queued = 0; } } = .{},
fn deinit(self: *CompactionAdmissionFake) void { SubmitRuntime(CompactionAdmissionFake).clear_context_compaction(self, "test_cleanup"); self.notices.deinit(self.alloc); } pub fn hasContextToCompact(self: *CompactionAdmissionFake) bool { return self.has_context; } pub fn collectPendingPromptCredential(self: *CompactionAdmissionFake) !@TypeOf(self.readiness) { self.credential_checks += 1; if (self.readiness_error) |err| return err; return self.readiness; } pub fn enqueueContextCompaction(self: *CompactionAdmissionFake, id: compaction_activity.OperationId) !bool { if (self.enqueue_error) |err| return err; std.debug.assert(id == self.submission.compaction_operation.?); self.worker.activity.queued(id, 1, 10); self.enqueue_count += 1; return true; } pub fn writeDomainNotice(self: *CompactionAdmissionFake, notice: types.SemanticNotice, _: bool) !void { self.notice_count += 1; if (self.notice_error) return error.OutOfMemory; try self.notices.appendSlice(self.alloc, notice.body); }};
test "manual compaction admission waits for authentication and enqueues once" { var app: CompactionAdmissionFake = .{}; defer app.deinit(); const Runtime = SubmitRuntime(CompactionAdmissionFake); try Runtime.request_context_compaction(&app); const accepted = app.worker.activity.snapshot; try Runtime.request_context_compaction(&app); try std.testing.expectEqualDeep(accepted, app.worker.activity.snapshot); try std.testing.expect(accepted.operation.?.phase == .preparing); try std.testing.expect(app.submission.compaction_pending); try std.testing.expectEqual(@as(usize, 1), app.credential_checks); try std.testing.expectEqual(@as(usize, 0), app.enqueue_count); app.readiness = .current; Runtime.collect_context_compaction(&app); Runtime.collect_context_compaction(&app); try std.testing.expect(!app.submission.compaction_pending); try std.testing.expectEqual(@as(usize, 1), app.enqueue_count); try std.testing.expectEqual(@as(usize, 0), app.auth.cancelled); try std.testing.expectEqual(accepted.operation.?.id, app.worker.activity.snapshot.operation.?.id); try std.testing.expectEqual(@as(i64, 10), app.worker.activity.snapshot.operation.?.started_at_ms); try std.testing.expectEqual(@as(?u64, 1), app.worker.activity.snapshot.operation.?.turn_id);}
test "manual compaction empty and busy admission does not prepare authentication" { for (0..4) |case| { var app: CompactionAdmissionFake = .{}; defer app.deinit(); switch (case) { 0 => app.has_context = false, 1 => app.worker.busy = true, 2 => app.worker.queued = 1, 3 => app.worker.status = .queued, else => unreachable, } try SubmitRuntime(CompactionAdmissionFake).request_context_compaction(&app); try std.testing.expect(!app.submission.compaction_pending); try std.testing.expectEqual(@as(usize, 0), app.credential_checks); try std.testing.expectEqual(@as(usize, 0), app.enqueue_count); const feedback = app.worker.activity.snapshot.operation.?.phase.terminal; try std.testing.expectEqual(if (case == 0) compaction_activity.Outcome.no_op else .busy, feedback.outcome); }}
test "manual compaction rejected authentication cannot submit a draft later" { var app: CompactionAdmissionFake = .{ .readiness = .rejected }; defer app.deinit(); app.submission.retry_after_auth = true; try SubmitRuntime(CompactionAdmissionFake).request_context_compaction(&app); try std.testing.expect(!app.submission.compaction_pending); try std.testing.expect(!app.submission.retry_after_auth); app.readiness = .current; SubmitRuntime(CompactionAdmissionFake).collect_context_compaction(&app); try std.testing.expectEqual(@as(usize, 0), app.enqueue_count);}
test "manual compaction admission errors stay local and release pending ownership" { for ([_]bool{ false, true }) |after_readiness| { var app: CompactionAdmissionFake = .{ .readiness = .current }; defer app.deinit(); if (after_readiness) app.enqueue_error = error.MissingApiKey else app.readiness_error = error.OutOfMemory; try SubmitRuntime(CompactionAdmissionFake).request_context_compaction(&app); try std.testing.expect(!app.submission.compaction_pending); try std.testing.expectEqual(@as(usize, 0), app.enqueue_count); try std.testing.expectEqual(@as(usize, 0), app.notices.items.len); const feedback = app.worker.activity.snapshot.operation.?.phase.terminal; try std.testing.expectEqual(compaction_activity.Outcome.failed, feedback.outcome); try std.testing.expectEqual(if (after_readiness) @as(anyerror, error.MissingApiKey) else error.OutOfMemory, feedback.err.?); }}
test "manual compaction queued work never produces a transcript notice" { var app: CompactionAdmissionFake = .{ .readiness = .current, .notice_error = true }; defer app.deinit(); try SubmitRuntime(CompactionAdmissionFake).request_context_compaction(&app); try std.testing.expectEqual(@as(usize, 1), app.enqueue_count); try std.testing.expectEqual(@as(usize, 0), app.notice_count); try std.testing.expect(!app.submission.compaction_pending); const accepted = app.worker.activity.snapshot; try SubmitRuntime(CompactionAdmissionFake).request_context_compaction(&app); try std.testing.expectEqualDeep(accepted, app.worker.activity.snapshot); try std.testing.expectEqual(@as(usize, 1), app.enqueue_count);}
test "manual compaction cancellation and transition clearing prevent late enqueue" { for (0..3) |boundary| { var app: CompactionAdmissionFake = .{}; defer app.deinit(); const Runtime = SubmitRuntime(CompactionAdmissionFake); try Runtime.request_context_compaction(&app); switch (boundary) { 0 => try std.testing.expect(Runtime.cancelPendingSubmission(&app)), 1 => Runtime.clearPendingSubmissionForSessionTransition(&app), 2 => Runtime.clearPendingSubmission(&app, "shutdown"), else => unreachable, } Runtime.clearPendingSubmission(&app, "repeated_cleanup"); app.readiness = .current; Runtime.collect_context_compaction(&app); try std.testing.expectEqual(@as(usize, 1), app.auth.cancelled); try std.testing.expectEqual(@as(usize, 0), app.enqueue_count); try std.testing.expectEqual(@as(usize, 0), app.worker.released_holds); try std.testing.expectEqual(compaction_activity.Outcome.cancelled, app.worker.activity.snapshot.operation.?.phase.terminal.outcome); }}
const PendingLifecycleFake = struct { const CredentialReadiness = enum { pending, current, rejected };
alloc: std.mem.Allocator, submission: State = .{}, worker: struct { held: bool = true, release_count: usize = 0, queued_turn_id: ?u64 = null, active_turn_id: u64 = 0, delete_count: usize = 0, cancel_count: usize = 0,
pub fn releaseTurnStartHold(self: *@This()) void { if (!self.held) return; self.held = false; self.release_count += 1; }
pub fn removeQueuedPrompt( self: *@This(), _: std.mem.Allocator, turn_id: u64, _: []const types.ImageAttachment, ) bool { if (self.queued_turn_id == null or self.queued_turn_id.? != turn_id) return false; self.queued_turn_id = null; self.delete_count += 1; return true; }
pub fn activeTurnId(self: *@This()) u64 { return self.active_turn_id; }
pub fn requestCancel(self: *@This()) void { self.cancel_count += 1; } } = .{}, shell: struct { render_requests: @import("../../ui/render_request.zig").RenderRequestState = .{}, } = .{}, adoption_failures_remaining: usize = 0, adoption_count: usize = 0, finalization_count: usize = 0, finalization_error: bool = false, notice_count: usize = 0, credential_checks: usize = 0, skill_refresh: enum { pending, current, failed } = .current, skill_refresh_checks: usize = 0,
fn deinit(self: *PendingLifecycleFake) void { SubmitRuntime(PendingLifecycleFake).clearPendingSubmission(self, "test_deinit"); }
pub fn adoptPendingUserPrompt( self: *PendingLifecycleFake, _: *const PendingPromptDraft, ) !void { if (self.adoption_failures_remaining > 0) { self.adoption_failures_remaining -= 1; return error.InjectedAdoptionFailure; } self.adoption_count += 1; }
pub fn finalizePendingSubmission( self: *PendingLifecycleFake, draft: *const PendingPromptDraft, ) !void { self.finalization_count += 1; if (self.finalization_error) return error.InjectedFinalizationFailure; self.worker.queued_turn_id = draft.turn_id; }
pub fn collectPendingPromptCredential( self: *PendingLifecycleFake, ) !CredentialReadiness { self.credential_checks += 1; return .current; }
pub fn collectPendingSkillRefresh( self: *PendingLifecycleFake, pending: *PendingSubmission, ) !PendingSkillRefresh { self.skill_refresh_checks += 1; if (pending.skill_refresh_generation == null) { pending.skill_refresh_generation = 1; } return switch (self.skill_refresh) { .pending => .pending, .current => .current, .failed => error.InjectedSkillRefreshFailure, }; }
pub fn writeDomainNotice( self: *PendingLifecycleFake, _: types.SemanticNotice, _: bool, ) !void { self.notice_count += 1; }};
fn pendingLifecycleFake(alloc: std.mem.Allocator, turn_id: u64) !PendingLifecycleFake { return .{ .alloc = alloc, .submission = .{ .pending = PendingSubmission.init(try buildPendingPromptDraft( alloc, turn_id, "visible prompt", &.{}, &.{}, )) }, };}
fn pendingLifecycleFakeWithSnapshot( alloc: std.mem.Allocator, turn_id: u64, snapshot_path: []const u8,) !PendingLifecycleFake { return .{ .alloc = alloc, .submission = .{ .pending = PendingSubmission.init(try buildPendingPromptDraft( alloc, turn_id, "visible image prompt", &.{.{ .id = 1, .path = @constCast("/tmp/original.png"), .media_type = @constCast("image/png"), .snapshot_path = @constCast(snapshot_path), }}, &.{}, )) }, };}
fn writePendingSnapshotFixture(tmp: *std.testing.TmpDir, name: []const u8) ![]u8 { var file = try tmp.dir.createFile(std.testing.io, name, .{}); defer file.close(std.testing.io); try file.writeStreamingAll(std.testing.io, "snapshot"); return io_mod.dirRealpathAlloc(std.testing.allocator, tmp.dir, name);}
fn expectPendingSnapshotMissing(path: []const u8) !void { try std.testing.expectError( error.FileNotFound, std.Io.Dir.openFileAbsolute(std.testing.io, path, .{}), );}
test "post-commit adoption failure keeps one retryable owner and hold" { const Runtime = SubmitRuntime(PendingLifecycleFake); var app = try pendingLifecycleFake(std.testing.allocator, 501); defer app.deinit(); app.adoption_failures_remaining = 1;
Runtime.noteCommittedFrame(&app); try std.testing.expectEqual(PendingPhase.awaiting_adoption, app.submission.pending.?.phase); try std.testing.expect(app.worker.held); try std.testing.expectEqual(@as(usize, 0), app.adoption_count); try std.testing.expectEqual(@as(usize, 0), app.finalization_count);
Runtime.collectPendingSubmissionFacts(&app); try std.testing.expectEqual(PendingPhase.queued, app.submission.pending.?.phase); try std.testing.expect(!app.worker.held); try std.testing.expectEqual(@as(usize, 1), app.worker.release_count); try std.testing.expectEqual(@as(usize, 1), app.adoption_count); try std.testing.expectEqual(@as(usize, 1), app.finalization_count);
try std.testing.expectError( error.PendingTurnIdMismatch, Runtime.acceptPresentedPrompt(&app, 999), ); try std.testing.expect(app.submission.pending != null); try Runtime.acceptPresentedPrompt(&app, 501); try std.testing.expect(app.submission.pending == null); try std.testing.expectEqual(@as(usize, 1), app.worker.release_count);}
test "pending submission waits for its skill catalog generation before queueing" { const Runtime = SubmitRuntime(PendingLifecycleFake); var app = try pendingLifecycleFake(std.testing.allocator, 502); defer app.deinit(); app.skill_refresh = .pending;
Runtime.noteCommittedFrame(&app); Runtime.collectPendingSubmissionFacts(&app); try std.testing.expectEqual(PendingPhase.adopted, app.submission.pending.?.phase); try std.testing.expect(app.worker.held); try std.testing.expectEqual(@as(usize, 1), app.credential_checks); try std.testing.expectEqual(@as(usize, 0), app.finalization_count); try std.testing.expectEqual(@as(?u64, 1), app.submission.pending.?.skill_refresh_generation);
Runtime.collectPendingSubmissionFacts(&app); try std.testing.expectEqual(@as(usize, 1), app.credential_checks);
app.skill_refresh = .current; Runtime.collectPendingSubmissionFacts(&app); try std.testing.expectEqual(PendingPhase.queued, app.submission.pending.?.phase); try std.testing.expect(!app.worker.held); try std.testing.expectEqual(@as(usize, 1), app.credential_checks); try std.testing.expectEqual(@as(usize, 1), app.finalization_count);}
test "post-ack finalization failure leaves notice and consumes pending owner" { const Runtime = SubmitRuntime(PendingLifecycleFake); var app = try pendingLifecycleFake(std.testing.allocator, 777); defer app.deinit(); app.finalization_error = true;
Runtime.noteCommittedFrame(&app); try std.testing.expectEqual(PendingPhase.adopted, app.submission.pending.?.phase); Runtime.collectPendingSubmissionFacts(&app);
try std.testing.expect(app.submission.pending == null); try std.testing.expect(!app.worker.held); try std.testing.expectEqual(@as(usize, 1), app.worker.release_count); try std.testing.expectEqual(@as(usize, 1), app.adoption_count); try std.testing.expectEqual(@as(usize, 1), app.finalization_count); try std.testing.expectEqual(@as(usize, 1), app.notice_count);}
test "Ctrl+C cancels pending ownership from every pre-worker phase" { const Runtime = SubmitRuntime(PendingLifecycleFake);
var awaiting_frame = try pendingLifecycleFake(std.testing.allocator, 801); defer awaiting_frame.deinit(); try std.testing.expect(Runtime.cancelPendingSubmission(&awaiting_frame)); try std.testing.expect(awaiting_frame.submission.pending == null); try std.testing.expectEqual(@as(usize, 1), awaiting_frame.worker.release_count);
var awaiting_adoption = try pendingLifecycleFake(std.testing.allocator, 802); defer awaiting_adoption.deinit(); awaiting_adoption.adoption_failures_remaining = 1; Runtime.noteCommittedFrame(&awaiting_adoption); try std.testing.expectEqual(PendingPhase.awaiting_adoption, awaiting_adoption.submission.pending.?.phase); try std.testing.expect(Runtime.cancelPendingSubmission(&awaiting_adoption)); try std.testing.expect(awaiting_adoption.submission.pending == null); try std.testing.expectEqual(@as(usize, 1), awaiting_adoption.worker.release_count); try std.testing.expect(awaiting_adoption.shell.render_requests.hasReason(.transcript)); try std.testing.expect(awaiting_adoption.shell.render_requests.hasReason(.footer));
var adopted = try pendingLifecycleFake(std.testing.allocator, 803); defer adopted.deinit(); Runtime.noteCommittedFrame(&adopted); try std.testing.expectEqual(PendingPhase.adopted, adopted.submission.pending.?.phase); try std.testing.expect(Runtime.cancelPendingSubmission(&adopted)); try std.testing.expect(adopted.submission.pending == null); try std.testing.expectEqual(@as(usize, 1), adopted.worker.release_count);
var queued = try pendingLifecycleFake(std.testing.allocator, 804); defer queued.deinit(); Runtime.noteCommittedFrame(&queued); Runtime.collectPendingSubmissionFacts(&queued); try std.testing.expectEqual(PendingPhase.queued, queued.submission.pending.?.phase); try std.testing.expectEqual(@as(?u64, 804), queued.worker.queued_turn_id); try std.testing.expect(Runtime.cancelPendingSubmission(&queued)); try std.testing.expect(queued.submission.pending == null); try std.testing.expectEqual(@as(?u64, null), queued.worker.queued_turn_id); try std.testing.expectEqual(@as(usize, 1), queued.worker.delete_count); try std.testing.expectEqual(@as(usize, 1), queued.worker.release_count);}
test "Ctrl+C requests cancellation after the pending turn leaves the queue" { const Runtime = SubmitRuntime(PendingLifecycleFake); var app = try pendingLifecycleFake(std.testing.allocator, 805); defer app.deinit(); Runtime.noteCommittedFrame(&app); Runtime.collectPendingSubmissionFacts(&app); app.worker.queued_turn_id = null; app.worker.active_turn_id = 805;
try std.testing.expect(Runtime.cancelPendingSubmission(&app)); try std.testing.expectEqual(@as(usize, 1), app.worker.cancel_count); try std.testing.expect(app.submission.pending != null); try Runtime.acceptPresentedPrompt(&app, 805); try std.testing.expect(app.submission.pending == null); try std.testing.expectEqual(@as(usize, 1), app.worker.release_count);}
test "pending terminal cleanup deletes snapshots until a worker claims the turn" { const alloc = std.testing.allocator; const Runtime = SubmitRuntime(PendingLifecycleFake); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup();
const awaiting_path = try writePendingSnapshotFixture(&tmp, "awaiting.bin"); defer alloc.free(awaiting_path); var awaiting = try pendingLifecycleFakeWithSnapshot(alloc, 901, awaiting_path); defer awaiting.deinit(); try std.testing.expect(Runtime.cancelPendingSubmission(&awaiting)); try expectPendingSnapshotMissing(awaiting_path);
const failed_path = try writePendingSnapshotFixture(&tmp, "failed.bin"); defer alloc.free(failed_path); var failed = try pendingLifecycleFakeWithSnapshot(alloc, 902, failed_path); defer failed.deinit(); failed.finalization_error = true; Runtime.noteCommittedFrame(&failed); Runtime.collectPendingSubmissionFacts(&failed); try std.testing.expect(failed.submission.pending == null); try expectPendingSnapshotMissing(failed_path);
const queued_path = try writePendingSnapshotFixture(&tmp, "queued.bin"); defer alloc.free(queued_path); var queued = try pendingLifecycleFakeWithSnapshot(alloc, 903, queued_path); defer queued.deinit(); Runtime.noteCommittedFrame(&queued); Runtime.collectPendingSubmissionFacts(&queued); try std.testing.expect(Runtime.cancelPendingSubmission(&queued)); try expectPendingSnapshotMissing(queued_path);
const claimed_path = try writePendingSnapshotFixture(&tmp, "claimed.bin"); defer alloc.free(claimed_path); var claimed = try pendingLifecycleFakeWithSnapshot(alloc, 904, claimed_path); defer claimed.deinit(); Runtime.noteCommittedFrame(&claimed); Runtime.collectPendingSubmissionFacts(&claimed); claimed.worker.queued_turn_id = null; claimed.worker.active_turn_id = 904; try std.testing.expect(Runtime.cancelPendingSubmission(&claimed)); try Runtime.acceptPresentedPrompt(&claimed, 904); var retained = try std.Io.Dir.openFileAbsolute(std.testing.io, claimed_path, .{}); retained.close(std.testing.io);}