PranitInitial commit439f83c3 months agoHistory
3671 lines · 159.8 KB · Zig
RawDownload
const std = @import("std");const agent_steps = @import("agent_steps.zig");const debug_trace = @import("../shared/debug_trace.zig");const host = @import("../hosts/host.zig");const io_mod = @import("../shared/io.zig");const profile_paths = @import("../shared/profile_paths.zig");const tool_result_limits = @import("../tooling/tool_result_limits.zig");const types = @import("../shared/types.zig");const sandbox = @import("../permissions/sandbox.zig");const workspace_access = @import("../workspace/workspace_access.zig");const settings_store = @import("settings_store.zig");const update_target = @import("../upgrade/update_target.zig");pub const context_limits = @import("context_limits.zig"); const Allocator = std.mem.Allocator;const max_settings_bytes: usize = 64 * 1024;pub const default_permission_mode: types.PermissionMode = .auto; pub const Paths = struct {    home_dir: ?[]u8 = null,    user_settings: ?[]u8 = null,    workspace_settings: []u8,    home_fx_dir: ?[]u8 = null,    sessions_dir: ?[]u8 = null,    workspace_root: []u8,     pub fn deinit(self: *Paths, alloc: Allocator) void {        if (self.home_dir) |path| alloc.free(path);        if (self.user_settings) |path| alloc.free(path);        alloc.free(self.workspace_settings);        if (self.home_fx_dir) |path| alloc.free(path);        if (self.sessions_dir) |path| alloc.free(path);        alloc.free(self.workspace_root);        self.* = undefined;    }}; pub const Settings = struct {    model: ?[]u8 = null,    permission_mode: ?types.PermissionMode = null,    credential_source: ?types.CredentialSource = null,    yolo_acknowledged: ?bool = null,    max_agent_steps: ?usize = null,    max_tool_result_bytes: ?usize = null,    context_limits: context_limits.Overrides = .{},    first_call_tool_choice: ?types.ToolChoice = null,    context: ?bool = null,    fast_mode: ?bool = null,    input_appearance: ?[]u8 = null,    maxxing_mode: ?[]u8 = null,    slash_menu_categories: ?bool = null,    auto_upgrade: ?bool = null,    update_channel: ?update_target.Channel = null,    startup_scrollback: ?bool = null,    prompt_history_enabled: ?bool = null,    effort: ?types.ReasoningEffort = null,    sandbox: ?[]u8 = null,    statusline_sandbox: ?bool = null,    statusline_context: ?bool = null,    statusline_session: ?bool = null,    notification_turn_end: ?bool = null,    notification_attention_required: ?bool = null,    notification_max: ?bool = null,    permission_rules: types.PermissionRuleSet = .{},    has_permission_rules: bool = false,     pub fn deinit(self: *Settings, alloc: Allocator) void {        if (self.model) |value| alloc.free(value);        if (self.input_appearance) |value| alloc.free(value);        if (self.maxxing_mode) |value| alloc.free(value);        if (self.sandbox) |value| alloc.free(value);        self.permission_rules.deinit(alloc);        self.* = .{};    }}; pub const StartupStatusSettings = struct {    model: ?[]u8 = null,    permission_mode: ?types.PermissionMode = null,    sandbox: ?[]u8 = null,    max_agent_steps: ?usize = null,     pub fn deinit(self: *StartupStatusSettings, alloc: Allocator) void {        if (self.model) |value| alloc.free(value);        if (self.sandbox) |value| alloc.free(value);        self.* = .{};    }}; pub const ConfigLayer = enum {    user,    project,}; pub const ConfigSource = enum {    compiled_default,    user_global,    project,    user_workspace,    process_override,}; pub const ModelSource = ConfigSource; pub const ConfigSources = struct {    model: ConfigSource = .compiled_default,    permission_mode: ConfigSource = .compiled_default,    effort: ConfigSource = .compiled_default,    fast_mode: ConfigSource = .compiled_default,    input_appearance: ConfigSource = .compiled_default,    maxxing_mode: ConfigSource = .compiled_default,    slash_menu_categories: ConfigSource = .compiled_default,    startup_scrollback: ConfigSource = .compiled_default,    prompt_history_enabled: ConfigSource = .compiled_default,    statusline_sandbox: ConfigSource = .compiled_default,    statusline_context: ConfigSource = .compiled_default,    statusline_session: ConfigSource = .compiled_default,    notification_turn_end: ConfigSource = .compiled_default,    notification_attention_required: ConfigSource = .compiled_default,    notification_max: ConfigSource = .compiled_default,    sandbox: ConfigSource = .compiled_default,}; pub fn resolveContextLimits(settings: *const Settings, command_line: []const context_limits.Override) context_limits.Values {    var values = context_limits.Values{};    values.apply(settings.context_limits);    values.applyCommandLine(command_line);    return values;} pub const PermissionSourceViews = struct {    user: types.PermissionRuleSet = .{},    local: types.PermissionRuleSet = .{},    user_shadowed_by_local: bool = false,     pub fn deinit(self: *PermissionSourceViews, alloc: Allocator) void {        self.user.deinit(alloc);        self.local.deinit(alloc);        self.* = .{};    }}; pub const ConfigDiagnosticCause = enum {    malformed_settings,    settings_too_large,    durable_path_unsafe,    private_state_permissions_unsupported,    invalid_model_id,    ignored_project_user_only_setting,    legacy_workspace_preferences,    manual_backup_available,    retired_skill_match_fuzzy,    invalid_context_limits,    invalid_additional_directories,}; pub const ConfigDiagnostic = struct {    layer: ConfigLayer,    cause: ConfigDiagnosticCause,    setting_key: ?[]u8 = null,    recovery_path: ?[]u8 = null,     pub fn reportAtStartup(self: ConfigDiagnostic) bool {        return self.cause != .legacy_workspace_preferences;    }     pub fn deinit(self: *ConfigDiagnostic, alloc: Allocator) void {        if (self.setting_key) |key| alloc.free(key);        if (self.recovery_path) |path| alloc.free(path);        self.* = undefined;    }}; pub fn writeDiagnosticMetadata(writer: *std.Io.Writer, diagnostic: ConfigDiagnostic) !void {    if (diagnostic.setting_key) |key| try writer.print("; key={s}", .{key});    if (diagnostic.recovery_path) |path| try writer.print("; recovery={s}", .{path});    if (diagnostic.cause == .retired_skill_match_fuzzy) {        try writer.writeAll("; remove skill_match_fuzzy; skills now load only through explicit invocation or the skill tool");    }    if (diagnostic.cause == .invalid_context_limits) {        try writer.writeAll("; context_limits keys must be documented limit names with a non-negative integer or \"off\" value");    }    if (diagnostic.cause == .invalid_additional_directories) {        try writer.print(            "; additional_directories must be an array of at most {d} unique absolute directory paths for the current primary workspace",            .{workspace_access.max_additional_directories},        );    }} pub const DetailedSettings = struct {    settings: Settings,    diagnostics: []ConfigDiagnostic = &.{},    model_source: ?ModelSource = null,    sources: ConfigSources = .{},    permission_sources: PermissionSourceViews = .{},    prompt_history_store_allowed: bool = true,    additional_directories: ?[][]u8 = null,    additional_directory_sources: ?[][]u8 = null,     pub fn deinit(self: *DetailedSettings, alloc: Allocator) void {        self.settings.deinit(alloc);        self.permission_sources.deinit(alloc);        if (self.additional_directories) |paths| freeStringSlice(alloc, paths);        if (self.additional_directory_sources) |paths| freeStringSlice(alloc, paths);        if (self.diagnostics.len > 0) {            for (self.diagnostics) |*diagnostic| diagnostic.deinit(alloc);            alloc.free(self.diagnostics);        }        self.* = undefined;    }}; pub fn discoverPaths(alloc: Allocator, workspace_root: []const u8) !Paths {    return discoverPathsWithOptionalHome(alloc, io_mod.getenv("HOME"), workspace_root);} pub fn discoverPathsFromHome(alloc: Allocator, home_dir: []const u8, workspace_root: []const u8) !Paths {    return discoverPathsWithOptionalHome(alloc, home_dir, workspace_root);} pub fn loadMergedSettings(alloc: Allocator, workspace_root: []const u8) !Settings {    var paths = try discoverPaths(alloc, workspace_root);    defer paths.deinit(alloc);    return loadMergedSettingsFromPaths(alloc, paths);} pub fn loadMergedSettingsFromHome(alloc: Allocator, home_dir: []const u8, workspace_root: []const u8) !Settings {    var paths = try discoverPathsFromHome(alloc, home_dir, workspace_root);    defer paths.deinit(alloc);    return loadMergedSettingsFromPaths(alloc, paths);} pub fn loadMergedSettingsDetailedFromHome(    alloc: Allocator,    home_dir: []const u8,    workspace_root: []const u8,) !DetailedSettings {    return loadMergedSettingsDetailedWithOptionalHome(alloc, home_dir, workspace_root);} pub fn loadMergedSettingsDetailed(alloc: Allocator, workspace_root: []const u8) !DetailedSettings {    return loadMergedSettingsDetailedWithOptionalHome(alloc, io_mod.getenv("HOME"), workspace_root);} fn loadMergedSettingsDetailedWithOptionalHome(    alloc: Allocator,    home_dir: ?[]const u8,    workspace_root: []const u8,) !DetailedSettings {    var settings = Settings{};    errdefer settings.deinit(alloc);    var diagnostics: std.ArrayList(ConfigDiagnostic) = .empty;    errdefer {        for (diagnostics.items) |*diagnostic| diagnostic.deinit(alloc);        diagnostics.deinit(alloc);    }    var sources = ConfigSources{};    var permission_sources = PermissionSourceViews{};    errdefer permission_sources.deinit(alloc);    var prompt_history_store_allowed = home_dir != null;    var additional_directories: ?[][]u8 = null;    errdefer if (additional_directories) |paths| freeStringSlice(alloc, paths);    var additional_directory_sources: ?[][]u8 = null;    errdefer if (additional_directory_sources) |paths| freeStringSlice(alloc, paths);    const detailed_merge_state: DetailedSettingsMergeState = .{        .settings = &settings,        .sources = &sources,        .permission_sources = &permission_sources,        .diagnostics = &diagnostics,        .prompt_history_store_allowed = &prompt_history_store_allowed,    };     var user_root: ?std.json.Parsed(std.json.Value) = null;    defer if (user_root) |*parsed| parsed.deinit();     if (home_dir) |home| {        var store: ?settings_store.Store = settings_store.Store.initFromHome(alloc, home, .read_only) catch |err| blk: {            prompt_history_store_allowed = false;            try diagnostics.append(alloc, .{                .layer = .user,                .cause = diagnosticCauseForUserStoreError(err),            });            break :blk null;        };        if (store) |*usable_store| {            defer usable_store.deinit(alloc);            var primary = usable_store.loadPrimary(alloc) catch |err| blk: {                prompt_history_store_allowed = false;                try diagnostics.append(alloc, .{                    .layer = .user,                    .cause = diagnosticCauseForUserStoreError(err),                });                break :blk settings_store.PrimaryLoad.absent;            };            defer primary.deinit(alloc);            switch (primary) {                .absent => {},                .invalid => {                    prompt_history_store_allowed = false;                    try diagnostics.append(alloc, .{ .layer = .user, .cause = .malformed_settings });                    if (usable_store.newestValidBackupPath(alloc) catch null) |recovery_path| {                        try diagnostics.append(alloc, .{                            .layer = .user,                            .cause = .manual_backup_available,                            .recovery_path = recovery_path,                        });                    }                },                .oversized => {                    prompt_history_store_allowed = false;                    try diagnostics.append(alloc, .{ .layer = .user, .cause = .settings_too_large });                    if (usable_store.newestValidBackupPath(alloc) catch null) |recovery_path| {                        try diagnostics.append(alloc, .{                            .layer = .user,                            .cause = .manual_backup_available,                            .recovery_path = recovery_path,                        });                    }                },                .valid => |bytes| {                    user_root = try std.json.parseFromSlice(std.json.Value, alloc, bytes, .{ .allocate = .alloc_always });                    if (user_root.?.value != .object) {                        prompt_history_store_allowed = false;                        try diagnostics.append(alloc, .{ .layer = .user, .cause = .malformed_settings });                        user_root.?.deinit();                        user_root = null;                    }                },            }        }    }     const project_path = try std.fs.path.join(alloc, &.{ workspace_root, ".fx.json" });    defer alloc.free(project_path);    const project_text = readOptionalFile(alloc, project_path) catch |err| blk: {        if (err == error.OutOfMemory) return err;        try diagnostics.append(alloc, .{            .layer = .project,            .cause = switch (err) {                error.StreamTooLong => .settings_too_large,                error.DurablePathUnsafe => .durable_path_unsafe,                else => .malformed_settings,            },        });        break :blk null;    };    defer if (project_text) |text| alloc.free(text);    if (project_text) |text| {        if (std.json.parseFromSlice(std.json.Value, alloc, text, .{})) |parsed_value| {            var parsed = parsed_value;            defer parsed.deinit();            if (parsed.value != .object) {                try diagnostics.append(alloc, .{ .layer = .project, .cause = .malformed_settings });            } else {                try appendIgnoredProjectProfileSettingDiagnostics(alloc, &diagnostics, parsed.value);                try mergeDetailedSettingsLayer(                    alloc,                    &detailed_merge_state,                    parsed.value,                    .project,                    false,                    .project,                    .project,                    .none,                );            }        } else |err| {            if (err == error.OutOfMemory) return err;            try diagnostics.append(alloc, .{ .layer = .project, .cause = .malformed_settings });        }    }     if (user_root) |parsed| {        if (parsed.value.object.contains("additional_directories")) {            try diagnostics.append(alloc, .{                .layer = .user,                .cause = .invalid_additional_directories,                .setting_key = try alloc.dupe(u8, "additional_directories"),            });        }        if (hasLegacyWorkspacePreferences(parsed.value)) {            try diagnostics.append(alloc, .{                .layer = .user,                .cause = .legacy_workspace_preferences,            });        }        try mergeDetailedSettingsLayer(            alloc,            &detailed_merge_state,            parsed.value,            .profile,            false,            .user,            .user_global,            .user,        );    }     if (user_root) |parsed| {        const workspaces = parsed.value.object.get("workspaces");        if (workspaces) |workspaces_value| {            if (workspaces_value != .object) {                try diagnostics.append(alloc, .{ .layer = .user, .cause = .malformed_settings });            } else if (workspaces_value.object.get(workspace_root)) |workspace_value| {                if (workspace_value != .object) {                    try diagnostics.append(alloc, .{ .layer = .user, .cause = .malformed_settings });                } else {                    if (workspace_value.object.get("additional_directories")) |value| {                        const parsed_directories = parseAdditionalDirectories(                            alloc,                            workspace_root,                            value,                        ) catch |err| blk: {                            if (err == error.OutOfMemory) return err;                            try diagnostics.append(alloc, .{                                .layer = .user,                                .cause = .invalid_additional_directories,                                .setting_key = try alloc.dupe(u8, "additional_directories"),                            });                            break :blk null;                        };                        if (parsed_directories) |directories| {                            additional_directories = directories.canonical;                            additional_directory_sources = directories.sources;                        }                    }                    try mergeDetailedSettingsLayer(                        alloc,                        &detailed_merge_state,                        workspace_value,                        .profile,                        true,                        .user,                        .user_workspace,                        .local,                    );                }            }        }    }     if (io_mod.getenv("FX_MODEL")) |model_override| {        if (std.mem.trim(u8, model_override, " \t\r\n").len > 0) {            sources.model = .process_override;        }    }     return .{        .settings = settings,        .diagnostics = try diagnostics.toOwnedSlice(alloc),        .model_source = sources.model,        .sources = sources,        .permission_sources = permission_sources,        .prompt_history_store_allowed = prompt_history_store_allowed,        .additional_directories = additional_directories,        .additional_directory_sources = additional_directory_sources,    };} const ParsedAdditionalDirectories = struct {    canonical: [][]u8,    sources: [][]u8,}; fn parseAdditionalDirectories(    alloc: Allocator,    workspace_root: []const u8,    value: std.json.Value,) !ParsedAdditionalDirectories {    if (value != .array) return error.InvalidAdditionalDirectories;     var raw_paths: std.ArrayList([]const u8) = .empty;    defer raw_paths.deinit(alloc);    for (value.array.items) |item| {        if (item != .string) return error.InvalidAdditionalDirectories;        if (raw_paths.items.len >= workspace_access.max_additional_directories) {            return error.InvalidAdditionalDirectories;        }        for (raw_paths.items) |path| {            if (std.mem.eql(u8, path, item.string)) return error.InvalidAdditionalDirectories;        }        try raw_paths.append(alloc, item.string);    }     var access = workspace_access.WorkspaceAccess.init(        alloc,        workspace_root,        raw_paths.items,        &.{},        false,    ) catch |err| switch (err) {        error.OutOfMemory => return error.OutOfMemory,        else => return error.InvalidAdditionalDirectories,    };    defer access.deinit(alloc);    const canonical = try access.savedDirectoriesAlloc(alloc);    errdefer freeStringSlice(alloc, canonical);    return .{        .canonical = canonical,

Lines 1–500 of 3671.