1044 lines · 44.4 KB · Zig
RawDownload
const std = @import("std");const app_lifecycle = @import("app_lifecycle.zig");const app_input_runtime = @import("app_input_runtime.zig");const app_permission_runtime = @import("app_permission_runtime.zig");const app_render_runtime = @import("app_render_runtime.zig");const app_runtime_setup = @import("app_runtime_setup.zig");const app_session_runtime = @import("app_session_runtime.zig");const auth_runtime = @import("../auth/auth_runtime.zig");const credentials = @import("../auth/credentials.zig");const config_runtime = @import("../config/config_runtime.zig");const host = @import("../hosts/host.zig");const debug_trace = @import("../shared/debug_trace.zig");const record_tape = @import("../workspace/record_tape.zig");const shared_io = @import("../shared/io.zig");const mcp_runtime = @import("../mcp/mcp_runtime.zig");const permissions = @import("../permissions/permissions.zig");const sandbox = @import("../permissions/sandbox.zig");const skill_contract = @import("../skills/skill_contract.zig");const skill_runtime = @import("../skills/skill_runtime.zig");const types = @import("../shared/types.zig");const worker_runtime = @import("../agent/worker_runtime.zig");const auto_upgrade = @import("../upgrade/auto_upgrade.zig");const update_target = @import("../upgrade/update_target.zig");const core_input_runtime = @import("../input/runtime.zig");const ui_render = @import("../../ui/render.zig");const ui_input = @import("../../ui/input/runtime.zig");const shell_runtime = @import("../../ui/shell_runtime.zig");const transcript_runtime = @import("../../ui/transcript/runtime.zig"); const Allocator = std.mem.Allocator; pub const CapabilityProviders = struct {    load_mcp_runtime: mcp_runtime.LoadRuntimeFn,    skill_root_policy: skill_contract.RootPolicy,    terminal_title: host.TerminalTitle,}; fn BootstrapDeps(comptime App: type) type {    return struct {        const BootstrapInteractiveAppFn = *const fn (app_lifecycle.BootstrapConfig) anyerror!app_lifecycle.StartupState;        const ConfigureSessionPreferencesFn = *const fn (            *App,            []const u8,            config_runtime.ModelSource,            []const u8,            types.ReasoningEffort,            bool,        ) anyerror!void;        const InitializePersistenceFn = *const fn (*App, bool) anyerror!void;        const StageRequestedResumeViewFn = *const fn (*App) app_session_runtime.ResumeViewStage;        const PublishStagedResumeViewFn = *const fn (*App, u32) anyerror!void;        const LoadSkillsFn = *const fn (            Allocator,            []const u8,            skill_contract.RootPolicy,        ) app_runtime_setup.LoadSkillsError!app_runtime_setup.LoadedSkills;        const WelcomeMessageFn = *const fn (Allocator) anyerror![]u8;        const BeginFreshPersistedSessionFn = *const fn (*App) anyerror!void;        const EnableSessionStoresFn = *const fn (*App) void;        bootstrap_interactive_app: BootstrapInteractiveAppFn,        configure_session_preferences: ConfigureSessionPreferencesFn,        initialize_persistence: InitializePersistenceFn,        stage_requested_resume_view: StageRequestedResumeViewFn,        publish_staged_resume_view: PublishStagedResumeViewFn,        load_mcp_runtime: mcp_runtime.LoadRuntimeFn,        load_skills: LoadSkillsFn,        skill_root_policy: skill_contract.RootPolicy,        welcome_message: WelcomeMessageFn,        begin_fresh_persisted_session: BeginFreshPersistedSessionFn,        enable_session_stores: EnableSessionStoresFn,        terminal_title: host.TerminalTitle,    };} pub fn Runtime(comptime App: type) type {    return struct {        pub fn bootstrap(            app: *App,            footer_rows: u16,            default_model: []const u8,            default_agent_step_limit: usize,            resize_handler: app_lifecycle.ResizeHandler,            record_requested: bool,            capability_providers: CapabilityProviders,        ) !void {            try bootstrapWithDeps(                app,                footer_rows,                default_model,                default_agent_step_limit,                resize_handler,                record_requested,                defaultDeps(capability_providers),            );        }         fn defaultDeps(capability_providers: CapabilityProviders) BootstrapDeps(App) {            return .{                .bootstrap_interactive_app = bootstrapInteractiveAppDefault,                .configure_session_preferences = configureSessionPreferencesDefault,                .initialize_persistence = initializePersistenceDefault,                .stage_requested_resume_view = stageRequestedResumeViewDefault,                .publish_staged_resume_view = publishStagedResumeViewDefault,                .load_mcp_runtime = capability_providers.load_mcp_runtime,                .load_skills = app_runtime_setup.loadSkills,                .skill_root_policy = capability_providers.skill_root_policy,                .welcome_message = welcomeMessageDefault,                .begin_fresh_persisted_session = beginFreshPersistedSessionDefault,                .enable_session_stores = enableSessionStoresDefault,                .terminal_title = capability_providers.terminal_title,            };        }         fn bootstrapInteractiveAppDefault(cfg: app_lifecycle.BootstrapConfig) !app_lifecycle.StartupState {            return app_lifecycle.bootstrapInteractiveApp(cfg);        }         fn initializePersistenceDefault(            app: *App,            required: bool,        ) !void {            try app_session_runtime.Runtime(App).initializePersistence(                app,                required,            );        }         fn stageRequestedResumeViewDefault(app: *App) app_session_runtime.ResumeViewStage {            return app_session_runtime.Runtime(App).stageRequestedResumeView(app);        }         fn publishStagedResumeViewDefault(app: *App, entry_id: u32) !void {            try app_session_runtime.Runtime(App).publishStagedResumeView(app, entry_id);        }         fn configureSessionPreferencesDefault(            app: *App,            configured_model: []const u8,            model_source: config_runtime.ModelSource,            selected_model: []const u8,            effort: types.ReasoningEffort,            fast_mode: bool,        ) !void {            try app_session_runtime.Runtime(App).configureStartupPreferences(                app,                configured_model,                model_source,                selected_model,                effort,                fast_mode,            );        }         fn welcomeMessageDefault(alloc: Allocator) ![]u8 {            return ui_render.welcomeMessage(alloc);        }         fn beginFreshPersistedSessionDefault(app: *App) !void {            try app_session_runtime.Runtime(App).beginFreshPersistedSession(app);        }         fn enableSessionStoresDefault(app: *App) void {            app_session_runtime.Runtime(App).enableSessionStores(app);        }         // Neutral one-line summary inline; the full detail stays behind Ctrl+O.        fn writeCollapsedStartupNotice(app: *App, topic: []const u8, summary_lead: []const u8, detail: []const u8) !void {            const summary = try std.fmt.allocPrint(app.alloc, "{s} (ctrl o to view)", .{summary_lead});            defer app.alloc.free(summary);            try app.writeDomainNotice(.{ .topic = topic, .tone = .neutral, .body = summary }, true);            try app.writeDomainNotice(.{ .topic = topic, .tone = .neutral, .body = detail, .visibility = .full_only }, true);        }         fn bootstrapWithDeps(            app: *App,            footer_rows: u16,            default_model: []const u8,            default_agent_step_limit: usize,            resize_handler: app_lifecycle.ResizeHandler,            record_requested: bool,            deps: BootstrapDeps(App),        ) !void {            errdefer app.deinit();             var startup = try deps.bootstrap_interactive_app(.{                .alloc = app.alloc,                .terminal = &app.terminal,                .shell = &app.shell,                .metrics = &app.metrics,                .terminal_title = deps.terminal_title,                .footer_rows = footer_rows,                .startup_min_body_rows = ui_render.welcome_message_reserved_rows,                .default_model = default_model,                .default_agent_step_limit = default_agent_step_limit,                .secret_store = if (comptime @hasDecl(App, "secretStore"))                    app.secretStore()                else                    host.unavailable_secret_store,                .resize_handler = resize_handler,                .fx_version = App.app_version,                .record_requested = record_requested,            });            defer startup.deinit(app.alloc);             app.workspace_root = startup.takeWorkspaceRoot();            if (comptime @hasDecl(App, "adoptWorkspaceAccess")) {                app.adoptWorkspaceAccess(startup.takeWorkspaceAccess());            }            if (startup.takeCredential()) |credential_value| {                var credential = credential_value;                defer credential.deinit(app.alloc);                _ = app.auth.adoptCredential(app.alloc, &credential);            }            app.auth.recordStartupStatus(                startup.stored_key_status,                startup.credential_onboarding_skipped,            );            const startup_auth_view = app.auth.view();            if (startup_auth_view.active_source == null and !startup_auth_view.onboarding_skipped) {                try app.auth.refreshSourceInventory(app.alloc);                app.auth.openOnboardingPicker(app.alloc);            }            if (comptime @hasField(App, "terminal_input_runtime") and @hasField(App, "terminal")) {                // Own theme protocol bytes even under FX_THEME; probing stays gated.                app.terminal_input_runtime.terminal_theme_monitor.start();                if (startup.theme_monitor_enabled) {                    app.terminal.enableThemeNotifications() catch |err| {                        debug_trace.logf("theme", "theme_notification_enable_failed err={s}", .{@errorName(err)});                    };                    app.terminal.requestThemeColorScheme() catch |err| {                        debug_trace.logf("theme", "theme_color_scheme_query_failed err={s}", .{@errorName(err)});                    };                }            }            var prompt_history_unavailable = false;            if (comptime @hasField(App, "prompt_history")) {                prompt_history_unavailable =                    (try app.prompt_history.initialize(                        app.alloc,                        shared_io.getenv("HOME"),                        startup.prompt_history_enabled,                        startup.prompt_history_store_allowed,                    )) == .unavailable;            }            if (comptime @hasField(App, "session") and                @hasDecl(@TypeOf(app.session), "initializeProfileUsage"))            {                _ = try app.session.initializeProfileUsage(                    app.alloc,                    shared_io.getenv("HOME"),                );            }             const selected_model = startup.takeSelectedModel();            defer if (selected_model.len > 0) app.alloc.free(selected_model);            try app.selected_model.appendSlice(app.alloc, selected_model);            try deps.configure_session_preferences(                app,                startup.configured_model,                startup.model_source,                selected_model,                startup.effort,                startup.fast_mode,            );            app.permission_engine.mode = startup.permission_mode;            app.permission_engine.replaceRules(app.alloc, startup.takePermissionRules());            app.agent_step_limit = startup.agent_step_limit;            app.worker.agent_turn_settings.max_tool_result_bytes = startup.max_tool_result_bytes;            if (comptime @hasField(App, "context_limits")) app.context_limits = startup.context_limits;            app.worker.agent_turn_settings.first_call_tool_choice = startup.first_call_tool_choice;            app.worker.agent_turn_settings.fast_mode = startup.fast_mode;            app.worker.agent_turn_settings.effort = startup.effort;            app.context_enabled = startup.context_enabled;            app.fast_mode = startup.fast_mode;            app.input_runtime.input_appearance = startup.input_appearance;            app.input_runtime.slash_menu_categories = startup.slash_menu_categories;            app.shell.maxxing_mode = startup.maxxing_mode;            app.auto_upgrade_enabled = startup.auto_upgrade;            app.upgrader.configure_channel(startup.update_channel);            app.effort = startup.effort;            app.shell.setCommandOutputRenderPolicy(                app_render_runtime.Runtime(App).shellStyles(),            );            app.permission_state.sandbox_backend = startup.sandbox_backend;            app.permission_state.yolo_acknowledged = startup.yolo_acknowledged;            app_permission_runtime.Runtime(App).initializeYoloWarning(app);            app.statusline_sandbox = startup.statusline_sandbox;            app.statusline_context = startup.statusline_context;            app.statusline_session = startup.statusline_session;            if (comptime @hasDecl(App, "setNotificationPreferences")) {                app.setNotificationPreferences(                    startup.notification_turn_end,                    startup.notification_attention_required,                    startup.notification_max,                );            }            try deps.initialize_persistence(                app,                app.requested_resume != null,            );            const staged_resume_view = if (app.requested_resume != null)                deps.stage_requested_resume_view(app)            else                app_session_runtime.ResumeViewStage.none;            const profile_mcp = try deps.load_mcp_runtime(app.alloc, .{ .form = true, .url = true });            if (comptime @hasDecl(App, "installInitialMcpRuntime")) {                app.installInitialMcpRuntime(profile_mcp);            } else {                app.mcp_runtime = profile_mcp;            }             const loaded = try deps.load_skills(                std.heap.c_allocator,                app.workspace_root,                deps.skill_root_policy,            );            skill_runtime.traceDiagnostics("interactive_startup", loaded.diagnostics);            app.skills.replaceLoaded(std.heap.c_allocator, loaded.dir, loaded.skills, loaded.diagnostics);             if (app.requested_resume == null) {                const welcome_message = try deps.welcome_message(app.alloc);                defer app.alloc.free(welcome_message);                var welcome_preview_buf: [96]u8 = undefined;                const welcome_preview = debug_trace.terminalPreview(welcome_preview_buf[0..], welcome_message);                debug_trace.logf(                    "paint",                    "welcome_write bytes={d} reserved_rows={d} has_version={s} has_help={s} has_feedback={s} preview=\"{s}\"",                    .{                        welcome_message.len,                        ui_render.welcome_message_reserved_rows,                        if (std.mem.find(u8, welcome_message, " v") != null) "true" else "false",                        if (std.mem.find(u8, welcome_message, "Run /help") != null) "true" else "false",                        if (std.mem.find(u8, welcome_message, "Feedback?") != null) "true" else "false",                        welcome_preview,                    },                );                try app.writeTranscriptClassified(welcome_message, true, .welcome);            }            if (app.skills.diagnostics.len > 0) {                var notice_writer: std.Io.Writer.Allocating = .init(app.alloc);                defer notice_writer.deinit();                skill_runtime.writeDiagnosticSummary(app.alloc, &notice_writer.writer, app.skills.diagnostics) catch return error.OutOfMemory;                const skills_body = notice_writer.toOwnedSlice() catch return error.OutOfMemory;                defer app.alloc.free(skills_body);                if (comptime @hasField(App, "session") and @hasDecl(@TypeOf(app.session), "claimContextNotice")) {                    _ = try app.session.claimContextNotice(app.alloc, skills_body);                }                 const skills_summary = try std.fmt.allocPrint(app.alloc, "{d} discovery issue{s}; some skills may be missing", .{                    app.skills.diagnostics.len,                    if (app.skills.diagnostics.len == 1) "" else "s",                });                defer app.alloc.free(skills_summary);                try writeCollapsedStartupNotice(app, "skills", skills_summary, skills_body);            }            if (comptime @hasField(App, "auth")) {                const auth_view = app.auth.view();                if (auth_view.active_source == null and auth_view.stored_key_status == .unavailable) {                    debug_trace.logf("keychain", "interactive read skipped", .{});                    try app.writeDomainNotice(.{                        .topic = "keychain",                        .tone = .warning,                        .body = "fx could not access " ++ credentials.stored_key_backend_label ++ ". Continuing without an API key.",                    }, true);                }            }            var recording = try record_tape.captureStatus(app.alloc);            defer recording.deinit(app.alloc);            if (recording == .active) {                const recording_body = try std.fmt.allocPrint(                    app.alloc,                    "visual terminal capture: {s}\nvisible terminal content, including typed prompt text, is recorded",                    .{recording.active},                );                defer app.alloc.free(recording_body);                try app.writeDomainNotice(.{                    .topic = "recording",                    .tone = .warning,                    .body = recording_body,                }, true);            }            {                var detail_writer: std.Io.Writer.Allocating = .init(app.alloc);                defer detail_writer.deinit();                var reported: usize = 0;                for (startup.config_diagnostics) |diagnostic| {                    if (!diagnostic.reportAtStartup()) continue;                    if (reported > 0) try detail_writer.writer.writeByte('\n');                    try detail_writer.writer.print(                        "{s}: {s}",                        .{ @tagName(diagnostic.layer), @tagName(diagnostic.cause) },                    );                    try config_runtime.writeDiagnosticMetadata(&detail_writer.writer, diagnostic);                    reported += 1;                }                if (reported > 0) {                    const summary = try std.fmt.allocPrint(app.alloc, "{d} configuration issue{s}", .{                        reported,                        if (reported == 1) "" else "s",                    });                    defer app.alloc.free(summary);                    try writeCollapsedStartupNotice(app, "config", summary, detail_writer.written());                }            }            if (comptime @hasField(App, "prompt_history")) {                if (prompt_history_unavailable) {                    try app.writeDomainNotice(.{                        .topic = "history",                        .tone = .warning,                        .body = "durable prompt history unavailable",                    }, true);                } else {                    app_input_runtime.Runtime(App).loadAndInstallPromptHistory(                        app,                    ) catch |err| {                        app.prompt_history.writes_available = false;                        debug_trace.logf(                            "prompt_history",                            "startup load failed err={s}",                            .{@errorName(err)},                        );                        const history_body = try std.fmt.allocPrint(                            app.alloc,                            "failed to load durable prompt history ({s})",                            .{@errorName(err)},                        );                        defer app.alloc.free(history_body);                        try app.writeDomainNotice(.{                            .topic = "history",                            .tone = .warning,                            .body = history_body,                        }, true);                    };                }            }             if (app.requested_resume == null) {                try deps.begin_fresh_persisted_session(app);                deps.enable_session_stores(app);                deps.terminal_title.setModel(app.selected_model.items);            }             switch (staged_resume_view) {                .none => {},                .ready => |entry_id| try deps.publish_staged_resume_view(app, entry_id),            }            app.shell.render_requests.request(.first_frame);        }    };} const TestCapture = struct {    alloc: Allocator,    bootstrap_calls: usize = 0,    footer_rows: u16 = 0,    default_model: []const u8 = "",    default_agent_step_limit: usize = 0,    fx_version: []const u8 = "",    configured_model: [64]u8 = undefined,    configured_model_len: usize = 0,    configured_model_source: config_runtime.ModelSource = .compiled_default,    runtime_model: [64]u8 = undefined,    runtime_model_len: usize = 0,    configured_effort: types.ReasoningEffort = .auto,    configured_fast_mode: bool = false,    initialize_required: bool = false,    load_skills_workspace: []const u8 = "",    load_skills_workspace_root_count: usize = 0,    load_skills_global_root_count: usize = 0,    transcript_recorded: bool = false,    begin_calls: usize = 0,    enable_calls: usize = 0,    title: [64]u8 = undefined,    title_len: usize = 0,    bootstrap_error: ?anyerror = null,    bootstrap_init_backing_before_error: bool = false,    load_skills_error: ?app_runtime_setup.LoadSkillsError = null,    emit_skill_diagnostic: bool = false,    emit_config_diagnostics: bool = false,    startup_with_credential: bool = true,    onboarding_skipped: bool = true,    early_notice_palette_initialized: bool = false,    events: [16][]const u8 = undefined,    event_count: usize = 0,     fn init(alloc: Allocator) TestCapture {        return .{ .alloc = alloc };    }     fn titleText(self: *const TestCapture) []const u8 {        return self.title[0..self.title_len];    }     fn configuredModel(self: *const TestCapture) []const u8 {        return self.configured_model[0..self.configured_model_len];    }     fn runtimeModel(self: *const TestCapture) []const u8 {        return self.runtime_model[0..self.runtime_model_len];    }

Lines 1–500 of 1044.