const std = @import("std");const background_runtime = @import("../background/background_runtime.zig");const change_tracker = @import("change_tracker.zig");const sandbox = @import("../permissions/sandbox.zig");const session_runtime = @import("../session/session.zig");const types = @import("../shared/types.zig");const context_limits = @import("../config/context_limits.zig");const workspace_access = @import("workspace_access.zig"); const Allocator = std.mem.Allocator; pub const Limits = struct { pub const scoped_rules: usize = 32; pub const project_omission_records: usize = 32; pub const git_read_budget_ns: i128 = 50 * std.time.ns_per_ms; pub const git_metadata_file_bytes: usize = 4096; pub const git_config_file_bytes: usize = 16 * 1024; pub const git_index_file_bytes: u64 = 16 * 1024; pub const git_index_entries: u32 = 32;}; pub const ProviderError = Allocator.Error || error{ NoSpaceLeft, WriteFailed,}; pub const GatheredContribution = struct { provider_id: []u8, content: []u8,}; pub const TargetKind = enum { file, directory,}; pub const ApplicableTarget = struct { path: []const u8, kind: TargetKind,}; /// Returns owned target storage whose paths remain owned by `images`.pub fn applicableTargetsForImages( alloc: Allocator, images: []const types.ImageAttachment,) Allocator.Error![]ApplicableTarget { if (images.len == 0) return &.{}; const targets = try alloc.alloc(ApplicableTarget, images.len); for (images, targets) |image, *target| { target.* = .{ .path = image.path, .kind = .file }; } return targets;} pub const OmissionReason = enum { home_unavailable, home_outside_workspace, unsafe_target, unreadable, oversized, non_regular, symlink, selection_cap, pub fn label(self: OmissionReason) []const u8 { return switch (self) { .home_unavailable => "home unavailable", .home_outside_workspace => "workspace is not below home", .unsafe_target => "unsafe target", .unreadable => "unreadable rule file", .oversized => "oversized rule file", .non_regular => "non-regular rule file", .symlink => "symlinked rule file", .selection_cap => "selection cap", }; }}; pub const ContextOmissionInput = struct { source: []const u8, reason: OmissionReason,}; pub const ContextOmissionSummary = struct { omitted_count: usize, reason_counts: [std.meta.fields(OmissionReason).len]usize, digest: [std.crypto.hash.sha2.Sha256.digest_length]u8,}; pub const ContextOmissionSummaryBuilder = struct { omitted_count: usize = 0, reason_counts: [std.meta.fields(OmissionReason).len]usize = @splat(0), hasher: std.crypto.hash.sha2.Sha256 = std.crypto.hash.sha2.Sha256.init(.{}), pub fn add(self: *ContextOmissionSummaryBuilder, source: []const u8, reason: OmissionReason) void { self.omitted_count += 1; self.reason_counts[@intFromEnum(reason)] += 1; self.hasher.update("fx.context.omission-record\x00"); self.hasher.update(&.{@intFromEnum(reason)}); hashUsize(&self.hasher, source.len); self.hasher.update(source); } pub fn merge(self: *ContextOmissionSummaryBuilder, summary: ContextOmissionSummary) void { if (summary.omitted_count == 0) return; self.omitted_count += summary.omitted_count; for (&self.reason_counts, summary.reason_counts) |*count, additional| count.* += additional; self.hasher.update("fx.context.omission-summary\x00"); hashUsize(&self.hasher, summary.omitted_count); for (summary.reason_counts) |count| hashUsize(&self.hasher, count); self.hasher.update(&summary.digest); } pub fn finish(self: *const ContextOmissionSummaryBuilder) ?ContextOmissionSummary { if (self.omitted_count == 0) return null; var hasher = self.hasher; return .{ .omitted_count = self.omitted_count, .reason_counts = self.reason_counts, .digest = hasher.finalResult(), }; }}; fn hashUsize(hasher: *std.crypto.hash.sha2.Sha256, value: usize) void { var bytes: [8]u8 = undefined; std.mem.writeInt(u64, &bytes, @intCast(value), .big); hasher.update(&bytes);} pub const InitialContextInput = struct { workspace_root: []const u8, access_scope: ?workspace_access.AccessScope = null, targets: []const ApplicableTarget = &.{}, omissions: []const ContextOmissionInput = &.{}, omission_summary: ?ContextOmissionSummary = null, context_limits: context_limits.Values = .{},}; pub const LaterContextInput = struct { workspace_root: []const u8, access_scope: ?workspace_access.AccessScope = null, targets: []const ApplicableTarget, delivered_sources: []const []const u8, evaluated_endpoints: []const []const u8, context_limits: context_limits.Values = .{},}; /// Owns provider-produced context bytes and delivery-state additions. The/// caller must call deinit with the allocator passed to the provider.pub const ProviderContext = struct { content: ?[]u8 = null, delivered_sources: [][]u8 = &.{}, evaluated_endpoints: [][]u8 = &.{}, notices: [][]u8 = &.{}, pub fn deinit(self: *ProviderContext, alloc: Allocator) void { if (self.content) |content| { if (content.len > 0) alloc.free(content); } freeOwnedStrings(alloc, self.delivered_sources); freeOwnedStrings(alloc, self.evaluated_endpoints); freeOwnedStrings(alloc, self.notices); self.* = .{}; } pub fn modelVisibleBytes(self: ProviderContext) []const u8 { return self.content orelse ""; }}; /// Owns gathered contribution bytes and provenance. The caller must call/// deinit with the allocator passed to gatherDefaultSnapshot or dupe.pub const GatheredContextSnapshot = struct { contribution: ?GatheredContribution = null, delivered_sources: [][]u8 = &.{}, evaluated_endpoints: [][]u8 = &.{}, notices: [][]u8 = &.{}, pub fn deinit(self: *GatheredContextSnapshot, alloc: Allocator) void { if (self.contribution) |contribution| { if (contribution.provider_id.len > 0) alloc.free(contribution.provider_id); if (contribution.content.len > 0) alloc.free(contribution.content); } freeOwnedStrings(alloc, self.delivered_sources); freeOwnedStrings(alloc, self.evaluated_endpoints); freeOwnedStrings(alloc, self.notices); self.* = .{}; } pub fn dupe(self: GatheredContextSnapshot, alloc: Allocator) Allocator.Error!GatheredContextSnapshot { var result: GatheredContextSnapshot = .{}; errdefer result.deinit(alloc); if (self.contribution) |contribution| { result.contribution = try dupeContribution(alloc, contribution); } result.delivered_sources = try dupeOwnedStrings(alloc, self.delivered_sources); result.evaluated_endpoints = try dupeOwnedStrings(alloc, self.evaluated_endpoints); result.notices = try dupeOwnedStrings(alloc, self.notices); return result; } pub fn modelVisibleBytes(self: GatheredContextSnapshot) []const u8 { return if (self.contribution) |contribution| contribution.content else ""; }}; pub const DeliveryState = struct { delivered_sources: std.ArrayList([]u8) = .empty, evaluated_endpoints: std.ArrayList([]u8) = .empty, pub fn initFromSnapshot(alloc: Allocator, snapshot: GatheredContextSnapshot) Allocator.Error!DeliveryState { var state: DeliveryState = .{}; errdefer state.deinit(alloc); try appendDupedStrings(alloc, &state.delivered_sources, snapshot.delivered_sources); try appendDupedStrings(alloc, &state.evaluated_endpoints, snapshot.evaluated_endpoints); return state; } pub fn deinit(self: *DeliveryState, alloc: Allocator) void { for (self.delivered_sources.items) |source| alloc.free(source); self.delivered_sources.deinit(alloc); for (self.evaluated_endpoints.items) |endpoint| alloc.free(endpoint); self.evaluated_endpoints.deinit(alloc); self.* = .{}; } pub fn commit(self: *DeliveryState, alloc: Allocator, selected: *ProviderContext) Allocator.Error!void { try self.delivered_sources.ensureUnusedCapacity(alloc, selected.delivered_sources.len); try self.evaluated_endpoints.ensureUnusedCapacity(alloc, selected.evaluated_endpoints.len); for (selected.delivered_sources) |source| self.delivered_sources.appendAssumeCapacity(source); for (selected.evaluated_endpoints) |endpoint| self.evaluated_endpoints.appendAssumeCapacity(endpoint); if (selected.delivered_sources.len > 0) alloc.free(selected.delivered_sources); if (selected.evaluated_endpoints.len > 0) alloc.free(selected.evaluated_endpoints); selected.delivered_sources = &.{}; selected.evaluated_endpoints = &.{}; }}; pub const StaticContextInput = struct { project_context: []const u8,}; pub const HostWorkspaceContext = struct { root: []const u8, cwd: []const u8, home: []const u8,}; pub const TransientContextInput = struct { workspace_root: []const u8, host_workspace: ?HostWorkspaceContext = null, access_scope: ?workspace_access.AccessScope = null, interactive: bool, permission_mode: types.PermissionMode, sandbox_backend: sandbox.BackendKind, tracker: ?*change_tracker.ChangeTracker, background: *background_runtime.BackgroundRuntime, session: *session_runtime.SessionRuntime,}; pub const Provider = struct { id: []const u8, gather_project_context_fn: *const fn (Allocator, InitialContextInput) ProviderError!ProviderContext, select_applicable_project_context_fn: *const fn (Allocator, LaterContextInput) ProviderError!ProviderContext, append_static_fn: *const fn (StaticContextInput, Allocator, *std.ArrayList(types.ChatMessage)) ProviderError!void, append_transient_fn: *const fn (TransientContextInput, Allocator, *std.ArrayList(types.ChatMessage)) ProviderError!void, pub fn gatherProjectContext(self: Provider, alloc: Allocator, input: InitialContextInput) ProviderError!ProviderContext { return self.gather_project_context_fn(alloc, input); } pub fn selectApplicableProjectContext(self: Provider, alloc: Allocator, input: LaterContextInput) ProviderError!ProviderContext { return self.select_applicable_project_context_fn(alloc, input); } pub fn appendStatic(self: Provider, input: StaticContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.append_static_fn(input, alloc, messages); } pub fn appendTransient(self: Provider, input: TransientContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.append_transient_fn(input, alloc, messages); }}; pub fn selectNoApplicableProjectContext(_: Allocator, _: LaterContextInput) ProviderError!ProviderContext { return .{};} pub const Registry = struct { default_provider: Provider, pub fn defaultProvider(self: Registry) Provider { return self.default_provider; } pub fn gatherDefaultSnapshot(self: Registry, alloc: Allocator, input: InitialContextInput) ProviderError!GatheredContextSnapshot { const provider = self.defaultProvider(); var gathered = try provider.gatherProjectContext(alloc, input); errdefer gathered.deinit(alloc); var snapshot: GatheredContextSnapshot = .{ .delivered_sources = gathered.delivered_sources, .evaluated_endpoints = gathered.evaluated_endpoints, .notices = gathered.notices, }; gathered.delivered_sources = &.{}; gathered.evaluated_endpoints = &.{}; gathered.notices = &.{}; errdefer snapshot.deinit(alloc); if (gathered.content) |content| { if (content.len > 0) { const provider_id = try alloc.dupe(u8, provider.id); snapshot.contribution = .{ .provider_id = provider_id, .content = content, }; gathered.content = null; } else { alloc.free(content); gathered.content = null; } } return snapshot; } pub fn selectDefaultApplicableContext(self: Registry, alloc: Allocator, input: LaterContextInput) ProviderError!ProviderContext { return self.defaultProvider().selectApplicableProjectContext(alloc, input); } pub fn appendDefaultStatic(self: Registry, input: StaticContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.defaultProvider().appendStatic(input, alloc, messages); } pub fn appendDefaultTransient(self: Registry, input: TransientContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.defaultProvider().appendTransient(input, alloc, messages); }}; fn freeOwnedStrings(alloc: Allocator, strings: [][]u8) void { for (strings) |string| alloc.free(string); if (strings.len > 0) alloc.free(strings);} fn dupeContribution(alloc: Allocator, contribution: GatheredContribution) Allocator.Error!GatheredContribution { const provider_id = try alloc.dupe(u8, contribution.provider_id); errdefer if (provider_id.len > 0) alloc.free(provider_id); const content = try alloc.dupe(u8, contribution.content); return .{ .provider_id = provider_id, .content = content };} fn dupeOwnedStrings(alloc: Allocator, strings: []const []const u8) Allocator.Error![][]u8 { if (strings.len == 0) return &.{}; const result = try alloc.alloc([]u8, strings.len); var initialized: usize = 0; errdefer { for (result[0..initialized]) |string| alloc.free(string); alloc.free(result); } for (strings, 0..) |string, index| { result[index] = try alloc.dupe(u8, string); initialized += 1; } return result;} fn appendDupedStrings(alloc: Allocator, destination: *std.ArrayList([]u8), strings: []const []const u8) Allocator.Error!void { try destination.ensureUnusedCapacity(alloc, strings.len); for (strings) |string| { destination.appendAssumeCapacity(try alloc.dupe(u8, string)); }} pub const contract_name = "fx.shared_model_context.v1"; pub const Fragment = enum { workspace_identity, repo_identity, scoped_instructions, available_tools, permission_mode, environment_metadata, session_metadata, fn label(self: Fragment) []const u8 { return switch (self) { .workspace_identity => "workspace_identity", .repo_identity => "repo_identity", .scoped_instructions => "scoped_instructions", .available_tools => "available_tools", .permission_mode => "permission_mode", .environment_metadata => "environment_metadata", .session_metadata => "session_metadata", }; } fn contractText(self: Fragment) []const u8 { return switch (self) { .workspace_identity => "workspace root and current directory", .repo_identity => "git branch, worktree state, and sanitized GitHub origin when known", .scoped_instructions => "bounded global, workspace, and nested scoped rules plus the bounded visible skill catalog", .available_tools => "gateway-advertised tool schemas after permission, deferred MCP discovery, and entrypoint filtering", .permission_mode => "captured ask, auto, or yolo baseline for the active turn without rule or grant contents", .environment_metadata => "OS, shell, date, home directory, sandbox, and live/non-live background runtime hints", .session_metadata => "model, step budget, max tool-result bytes, conversation history, grants, loaded skills, and persisted session id when present", }; }}; pub const EntryPoint = enum { interactive, ask, acp, subagent, fn label(self: EntryPoint) []const u8 { return switch (self) { .interactive => "interactive", .ask => "fx ask", .acp => "ACP", .subagent => "subagent", }; }}; pub const DriftStatus = enum { intentional, temporary, phase12_follow_up, fn label(self: DriftStatus) []const u8 { return switch (self) { .intentional => "intentional", .temporary => "temporary", .phase12_follow_up => "phase12_follow_up", }; }}; pub const EntrypointInventory = struct { entrypoint: EntryPoint, assembly_path: []const u8, static_context: []const u8, transient_context: []const u8, tools: []const u8, permission: []const u8, session: []const u8, drift_status: DriftStatus, drift: []const u8,}; const required_fragments = [_]Fragment{ .workspace_identity, .repo_identity, .scoped_instructions, .available_tools, .permission_mode, .environment_metadata, .session_metadata,}; const current_inventory = [_]EntrypointInventory{ .{ .entrypoint = .interactive, .assembly_path = "main.App.enqueuePrompt -> app_agent_runtime.processQueuedPrompt -> agent_runtime dependencies", .static_context = "builtins/context captures one global/root/ancestor/applicable AGENTS.md snapshot before enqueue, then adds scoped deltas from effective structured tool targets", .transient_context = "tool_runtime transient context each model step: env_context, captured permission mode, sandbox, background runtime, non-live background history", .tools = "App.snapshotGatewayToolProjection pairs full tool advertisement with included custom-provider guidance after permission and deferred MCP discovery", .permission = "PermissionEngine mode, persistent rules, and session grants are enforced by interactive permission prompts", .session = "live SessionRuntime history plus persisted session/log/artifact stores when enabled", .drift_status = .intentional, .drift = "live terminal approvals, clarification UI, and full interactive tool surface differ from headless entrypoints by design", }, .{ .entrypoint = .ask, .assembly_path = "cli_ask.runPromptInternal -> agent_runtime dependencies", .static_context = "builtins/context captures one global/root/ancestor/applicable AGENTS.md snapshot before the prompt, then adds scoped deltas from effective structured tool targets", .transient_context = "tool_runtime transient context each model step with captured permission mode, noninteractive output callbacks, and no live user question path", .tools = "mode-filtered paired tool advertisement and included custom-provider guidance with permission rules and deferred MCP discovery", .permission = "ask, --auto, or --yolo mode; approval-required actions fail with a noninteractive blocker instead of prompting unless yolo bypasses Fx policy", .session = "fresh headless SessionRuntime, optional persisted session id, empty prior history, no local approval grants", .drift_status = .intentional, .drift = "noninteractive permission blockers and absent live clarification UI differ from interactive by design", }, .{ .entrypoint = .acp, .assembly_path = "acp.prompt.handlePrompt -> agent_runtime dependencies", .static_context = "builtins/context captures one global/root/ancestor/applicable AGENTS.md snapshot from accepted local resources before each ACP prompt, then adds scoped tool-target deltas", .transient_context = "tool_runtime transient context each model step using the prompt-captured permission mode, ACP session state, and noninteractive tool/update callbacks", .tools = "mode-filtered paired tool advertisement and included custom-provider guidance with ACP session permission rules, deferred MCP discovery", .permission = "ACP session mode ask/auto; approval-required actions map to refusal or policy decisions instead of terminal prompts", .session = "active ACP session id, selected model, mode, persisted history on load, and session runtime history", .drift_status = .intentional, .drift = "ACP JSON-RPC session updates and refusal mapping differ from terminal output by protocol contract", }, .{ .entrypoint = .subagent, .assembly_path = "subagent.agent_adapter.run -> execution.runNormalAgentTurn -> agent_runtime dependencies", .static_context = "reuses the launching surface's project-context snapshot bytes and its system and skill prompt sections; child delivery state starts empty so scoped tool-target deltas are re-evaluated for the child",Lines 1–500 of 942. Load more
const std = @import("std"); const background_runtime = @import("../background/background_runtime.zig"); const change_tracker = @import("change_tracker.zig"); const sandbox = @import("../permissions/sandbox.zig"); const session_runtime = @import("../session/session.zig"); const types = @import("../shared/types.zig"); const context_limits = @import("../config/context_limits.zig"); const workspace_access = @import("workspace_access.zig"); const Allocator = std.mem.Allocator; pub const Limits = struct { pub const scoped_rules: usize = 32; pub const project_omission_records: usize = 32; pub const git_read_budget_ns: i128 = 50 * std.time.ns_per_ms; pub const git_metadata_file_bytes: usize = 4096; pub const git_config_file_bytes: usize = 16 * 1024; pub const git_index_file_bytes: u64 = 16 * 1024; pub const git_index_entries: u32 = 32; }; pub const ProviderError = Allocator.Error || error{ NoSpaceLeft, WriteFailed, }; pub const GatheredContribution = struct { provider_id: []u8, content: []u8, }; pub const TargetKind = enum { file, directory, }; pub const ApplicableTarget = struct { path: []const u8, kind: TargetKind, }; /// Returns owned target storage whose paths remain owned by `images`. pub fn applicableTargetsForImages( alloc: Allocator, images: []const types.ImageAttachment, ) Allocator.Error![]ApplicableTarget { if (images.len == 0) return &.{}; const targets = try alloc.alloc(ApplicableTarget, images.len); for (images, targets) |image, *target| { target.* = .{ .path = image.path, .kind = .file }; } return targets; } pub const OmissionReason = enum { home_unavailable, home_outside_workspace, unsafe_target, unreadable, oversized, non_regular, symlink, selection_cap, pub fn label(self: OmissionReason) []const u8 { return switch (self) { .home_unavailable => "home unavailable", .home_outside_workspace => "workspace is not below home", .unsafe_target => "unsafe target", .unreadable => "unreadable rule file", .oversized => "oversized rule file", .non_regular => "non-regular rule file", .symlink => "symlinked rule file", .selection_cap => "selection cap", }; } }; pub const ContextOmissionInput = struct { source: []const u8, reason: OmissionReason, }; pub const ContextOmissionSummary = struct { omitted_count: usize, reason_counts: [std.meta.fields(OmissionReason).len]usize, digest: [std.crypto.hash.sha2.Sha256.digest_length]u8, }; pub const ContextOmissionSummaryBuilder = struct { omitted_count: usize = 0, reason_counts: [std.meta.fields(OmissionReason).len]usize = @splat(0), hasher: std.crypto.hash.sha2.Sha256 = std.crypto.hash.sha2.Sha256.init(.{}), pub fn add(self: *ContextOmissionSummaryBuilder, source: []const u8, reason: OmissionReason) void { self.omitted_count += 1; self.reason_counts[@intFromEnum(reason)] += 1; self.hasher.update("fx.context.omission-record\x00"); self.hasher.update(&.{@intFromEnum(reason)}); hashUsize(&self.hasher, source.len); self.hasher.update(source); } pub fn merge(self: *ContextOmissionSummaryBuilder, summary: ContextOmissionSummary) void { if (summary.omitted_count == 0) return; self.omitted_count += summary.omitted_count; for (&self.reason_counts, summary.reason_counts) |*count, additional| count.* += additional; self.hasher.update("fx.context.omission-summary\x00"); hashUsize(&self.hasher, summary.omitted_count); for (summary.reason_counts) |count| hashUsize(&self.hasher, count); self.hasher.update(&summary.digest); } pub fn finish(self: *const ContextOmissionSummaryBuilder) ?ContextOmissionSummary { if (self.omitted_count == 0) return null; var hasher = self.hasher; return .{ .omitted_count = self.omitted_count, .reason_counts = self.reason_counts, .digest = hasher.finalResult(), }; } }; fn hashUsize(hasher: *std.crypto.hash.sha2.Sha256, value: usize) void { var bytes: [8]u8 = undefined; std.mem.writeInt(u64, &bytes, @intCast(value), .big); hasher.update(&bytes); } pub const InitialContextInput = struct { workspace_root: []const u8, access_scope: ?workspace_access.AccessScope = null, targets: []const ApplicableTarget = &.{}, omissions: []const ContextOmissionInput = &.{}, omission_summary: ?ContextOmissionSummary = null, context_limits: context_limits.Values = .{}, }; pub const LaterContextInput = struct { workspace_root: []const u8, access_scope: ?workspace_access.AccessScope = null, targets: []const ApplicableTarget, delivered_sources: []const []const u8, evaluated_endpoints: []const []const u8, context_limits: context_limits.Values = .{}, }; /// Owns provider-produced context bytes and delivery-state additions. The /// caller must call deinit with the allocator passed to the provider. pub const ProviderContext = struct { content: ?[]u8 = null, delivered_sources: [][]u8 = &.{}, evaluated_endpoints: [][]u8 = &.{}, notices: [][]u8 = &.{}, pub fn deinit(self: *ProviderContext, alloc: Allocator) void { if (self.content) |content| { if (content.len > 0) alloc.free(content); } freeOwnedStrings(alloc, self.delivered_sources); freeOwnedStrings(alloc, self.evaluated_endpoints); freeOwnedStrings(alloc, self.notices); self.* = .{}; } pub fn modelVisibleBytes(self: ProviderContext) []const u8 { return self.content orelse ""; } }; /// Owns gathered contribution bytes and provenance. The caller must call /// deinit with the allocator passed to gatherDefaultSnapshot or dupe. pub const GatheredContextSnapshot = struct { contribution: ?GatheredContribution = null, delivered_sources: [][]u8 = &.{}, evaluated_endpoints: [][]u8 = &.{}, notices: [][]u8 = &.{}, pub fn deinit(self: *GatheredContextSnapshot, alloc: Allocator) void { if (self.contribution) |contribution| { if (contribution.provider_id.len > 0) alloc.free(contribution.provider_id); if (contribution.content.len > 0) alloc.free(contribution.content); } freeOwnedStrings(alloc, self.delivered_sources); freeOwnedStrings(alloc, self.evaluated_endpoints); freeOwnedStrings(alloc, self.notices); self.* = .{}; } pub fn dupe(self: GatheredContextSnapshot, alloc: Allocator) Allocator.Error!GatheredContextSnapshot { var result: GatheredContextSnapshot = .{}; errdefer result.deinit(alloc); if (self.contribution) |contribution| { result.contribution = try dupeContribution(alloc, contribution); } result.delivered_sources = try dupeOwnedStrings(alloc, self.delivered_sources); result.evaluated_endpoints = try dupeOwnedStrings(alloc, self.evaluated_endpoints); result.notices = try dupeOwnedStrings(alloc, self.notices); return result; } pub fn modelVisibleBytes(self: GatheredContextSnapshot) []const u8 { return if (self.contribution) |contribution| contribution.content else ""; } }; pub const DeliveryState = struct { delivered_sources: std.ArrayList([]u8) = .empty, evaluated_endpoints: std.ArrayList([]u8) = .empty, pub fn initFromSnapshot(alloc: Allocator, snapshot: GatheredContextSnapshot) Allocator.Error!DeliveryState { var state: DeliveryState = .{}; errdefer state.deinit(alloc); try appendDupedStrings(alloc, &state.delivered_sources, snapshot.delivered_sources); try appendDupedStrings(alloc, &state.evaluated_endpoints, snapshot.evaluated_endpoints); return state; } pub fn deinit(self: *DeliveryState, alloc: Allocator) void { for (self.delivered_sources.items) |source| alloc.free(source); self.delivered_sources.deinit(alloc); for (self.evaluated_endpoints.items) |endpoint| alloc.free(endpoint); self.evaluated_endpoints.deinit(alloc); self.* = .{}; } pub fn commit(self: *DeliveryState, alloc: Allocator, selected: *ProviderContext) Allocator.Error!void { try self.delivered_sources.ensureUnusedCapacity(alloc, selected.delivered_sources.len); try self.evaluated_endpoints.ensureUnusedCapacity(alloc, selected.evaluated_endpoints.len); for (selected.delivered_sources) |source| self.delivered_sources.appendAssumeCapacity(source); for (selected.evaluated_endpoints) |endpoint| self.evaluated_endpoints.appendAssumeCapacity(endpoint); if (selected.delivered_sources.len > 0) alloc.free(selected.delivered_sources); if (selected.evaluated_endpoints.len > 0) alloc.free(selected.evaluated_endpoints); selected.delivered_sources = &.{}; selected.evaluated_endpoints = &.{}; } }; pub const StaticContextInput = struct { project_context: []const u8, }; pub const HostWorkspaceContext = struct { root: []const u8, cwd: []const u8, home: []const u8, }; pub const TransientContextInput = struct { workspace_root: []const u8, host_workspace: ?HostWorkspaceContext = null, access_scope: ?workspace_access.AccessScope = null, interactive: bool, permission_mode: types.PermissionMode, sandbox_backend: sandbox.BackendKind, tracker: ?*change_tracker.ChangeTracker, background: *background_runtime.BackgroundRuntime, session: *session_runtime.SessionRuntime, }; pub const Provider = struct { id: []const u8, gather_project_context_fn: *const fn (Allocator, InitialContextInput) ProviderError!ProviderContext, select_applicable_project_context_fn: *const fn (Allocator, LaterContextInput) ProviderError!ProviderContext, append_static_fn: *const fn (StaticContextInput, Allocator, *std.ArrayList(types.ChatMessage)) ProviderError!void, append_transient_fn: *const fn (TransientContextInput, Allocator, *std.ArrayList(types.ChatMessage)) ProviderError!void, pub fn gatherProjectContext(self: Provider, alloc: Allocator, input: InitialContextInput) ProviderError!ProviderContext { return self.gather_project_context_fn(alloc, input); } pub fn selectApplicableProjectContext(self: Provider, alloc: Allocator, input: LaterContextInput) ProviderError!ProviderContext { return self.select_applicable_project_context_fn(alloc, input); } pub fn appendStatic(self: Provider, input: StaticContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.append_static_fn(input, alloc, messages); } pub fn appendTransient(self: Provider, input: TransientContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.append_transient_fn(input, alloc, messages); } }; pub fn selectNoApplicableProjectContext(_: Allocator, _: LaterContextInput) ProviderError!ProviderContext { return .{}; } pub const Registry = struct { default_provider: Provider, pub fn defaultProvider(self: Registry) Provider { return self.default_provider; } pub fn gatherDefaultSnapshot(self: Registry, alloc: Allocator, input: InitialContextInput) ProviderError!GatheredContextSnapshot { const provider = self.defaultProvider(); var gathered = try provider.gatherProjectContext(alloc, input); errdefer gathered.deinit(alloc); var snapshot: GatheredContextSnapshot = .{ .delivered_sources = gathered.delivered_sources, .evaluated_endpoints = gathered.evaluated_endpoints, .notices = gathered.notices, }; gathered.delivered_sources = &.{}; gathered.evaluated_endpoints = &.{}; gathered.notices = &.{}; errdefer snapshot.deinit(alloc); if (gathered.content) |content| { if (content.len > 0) { const provider_id = try alloc.dupe(u8, provider.id); snapshot.contribution = .{ .provider_id = provider_id, .content = content, }; gathered.content = null; } else { alloc.free(content); gathered.content = null; } } return snapshot; } pub fn selectDefaultApplicableContext(self: Registry, alloc: Allocator, input: LaterContextInput) ProviderError!ProviderContext { return self.defaultProvider().selectApplicableProjectContext(alloc, input); } pub fn appendDefaultStatic(self: Registry, input: StaticContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.defaultProvider().appendStatic(input, alloc, messages); } pub fn appendDefaultTransient(self: Registry, input: TransientContextInput, alloc: Allocator, messages: *std.ArrayList(types.ChatMessage)) ProviderError!void { return self.defaultProvider().appendTransient(input, alloc, messages); } }; fn freeOwnedStrings(alloc: Allocator, strings: [][]u8) void { for (strings) |string| alloc.free(string); if (strings.len > 0) alloc.free(strings); } fn dupeContribution(alloc: Allocator, contribution: GatheredContribution) Allocator.Error!GatheredContribution { const provider_id = try alloc.dupe(u8, contribution.provider_id); errdefer if (provider_id.len > 0) alloc.free(provider_id); const content = try alloc.dupe(u8, contribution.content); return .{ .provider_id = provider_id, .content = content }; } fn dupeOwnedStrings(alloc: Allocator, strings: []const []const u8) Allocator.Error![][]u8 { if (strings.len == 0) return &.{}; const result = try alloc.alloc([]u8, strings.len); var initialized: usize = 0; errdefer { for (result[0..initialized]) |string| alloc.free(string); alloc.free(result); } for (strings, 0..) |string, index| { result[index] = try alloc.dupe(u8, string); initialized += 1; } return result; } fn appendDupedStrings(alloc: Allocator, destination: *std.ArrayList([]u8), strings: []const []const u8) Allocator.Error!void { try destination.ensureUnusedCapacity(alloc, strings.len); for (strings) |string| { destination.appendAssumeCapacity(try alloc.dupe(u8, string)); } } pub const contract_name = "fx.shared_model_context.v1"; pub const Fragment = enum { workspace_identity, repo_identity, scoped_instructions, available_tools, permission_mode, environment_metadata, session_metadata, fn label(self: Fragment) []const u8 { return switch (self) { .workspace_identity => "workspace_identity", .repo_identity => "repo_identity", .scoped_instructions => "scoped_instructions", .available_tools => "available_tools", .permission_mode => "permission_mode", .environment_metadata => "environment_metadata", .session_metadata => "session_metadata", }; } fn contractText(self: Fragment) []const u8 { return switch (self) { .workspace_identity => "workspace root and current directory", .repo_identity => "git branch, worktree state, and sanitized GitHub origin when known", .scoped_instructions => "bounded global, workspace, and nested scoped rules plus the bounded visible skill catalog", .available_tools => "gateway-advertised tool schemas after permission, deferred MCP discovery, and entrypoint filtering", .permission_mode => "captured ask, auto, or yolo baseline for the active turn without rule or grant contents", .environment_metadata => "OS, shell, date, home directory, sandbox, and live/non-live background runtime hints", .session_metadata => "model, step budget, max tool-result bytes, conversation history, grants, loaded skills, and persisted session id when present", }; } }; pub const EntryPoint = enum { interactive, ask, acp, subagent, fn label(self: EntryPoint) []const u8 { return switch (self) { .interactive => "interactive", .ask => "fx ask", .acp => "ACP", .subagent => "subagent", }; } }; pub const DriftStatus = enum { intentional, temporary, phase12_follow_up, fn label(self: DriftStatus) []const u8 { return switch (self) { .intentional => "intentional", .temporary => "temporary", .phase12_follow_up => "phase12_follow_up", }; } }; pub const EntrypointInventory = struct { entrypoint: EntryPoint, assembly_path: []const u8, static_context: []const u8, transient_context: []const u8, tools: []const u8, permission: []const u8, session: []const u8, drift_status: DriftStatus, drift: []const u8, }; const required_fragments = [_]Fragment{ .workspace_identity, .repo_identity, .scoped_instructions, .available_tools, .permission_mode, .environment_metadata, .session_metadata, }; const current_inventory = [_]EntrypointInventory{ .{ .entrypoint = .interactive, .assembly_path = "main.App.enqueuePrompt -> app_agent_runtime.processQueuedPrompt -> agent_runtime dependencies", .static_context = "builtins/context captures one global/root/ancestor/applicable AGENTS.md snapshot before enqueue, then adds scoped deltas from effective structured tool targets", .transient_context = "tool_runtime transient context each model step: env_context, captured permission mode, sandbox, background runtime, non-live background history", .tools = "App.snapshotGatewayToolProjection pairs full tool advertisement with included custom-provider guidance after permission and deferred MCP discovery", .permission = "PermissionEngine mode, persistent rules, and session grants are enforced by interactive permission prompts", .session = "live SessionRuntime history plus persisted session/log/artifact stores when enabled", .drift_status = .intentional, .drift = "live terminal approvals, clarification UI, and full interactive tool surface differ from headless entrypoints by design", }, .{ .entrypoint = .ask, .assembly_path = "cli_ask.runPromptInternal -> agent_runtime dependencies", .static_context = "builtins/context captures one global/root/ancestor/applicable AGENTS.md snapshot before the prompt, then adds scoped deltas from effective structured tool targets", .transient_context = "tool_runtime transient context each model step with captured permission mode, noninteractive output callbacks, and no live user question path", .tools = "mode-filtered paired tool advertisement and included custom-provider guidance with permission rules and deferred MCP discovery", .permission = "ask, --auto, or --yolo mode; approval-required actions fail with a noninteractive blocker instead of prompting unless yolo bypasses Fx policy", .session = "fresh headless SessionRuntime, optional persisted session id, empty prior history, no local approval grants", .drift_status = .intentional, .drift = "noninteractive permission blockers and absent live clarification UI differ from interactive by design", }, .{ .entrypoint = .acp, .assembly_path = "acp.prompt.handlePrompt -> agent_runtime dependencies", .static_context = "builtins/context captures one global/root/ancestor/applicable AGENTS.md snapshot from accepted local resources before each ACP prompt, then adds scoped tool-target deltas", .transient_context = "tool_runtime transient context each model step using the prompt-captured permission mode, ACP session state, and noninteractive tool/update callbacks", .tools = "mode-filtered paired tool advertisement and included custom-provider guidance with ACP session permission rules, deferred MCP discovery", .permission = "ACP session mode ask/auto; approval-required actions map to refusal or policy decisions instead of terminal prompts", .session = "active ACP session id, selected model, mode, persisted history on load, and session runtime history", .drift_status = .intentional, .drift = "ACP JSON-RPC session updates and refusal mapping differ from terminal output by protocol contract", }, .{ .entrypoint = .subagent, .assembly_path = "subagent.agent_adapter.run -> execution.runNormalAgentTurn -> agent_runtime dependencies", .static_context = "reuses the launching surface's project-context snapshot bytes and its system and skill prompt sections; child delivery state starts empty so scoped tool-target deltas are re-evaluated for the child",