const std = @import("std");const io_mod = @import("../shared/io.zig");const agent_steps = @import("../config/agent_steps.zig");const config_runtime = @import("../config/config_runtime.zig");const auth_runtime = @import("../auth/auth_runtime.zig");const credentials = @import("../auth/credentials.zig");const host = @import("../hosts/host.zig");const oauth_transport = @import("../auth/oauth_transport.zig");const input_appearance = @import("../config/input_appearance.zig");const model_capabilities = @import("../config/model_capabilities.zig");const debug_trace = @import("../shared/debug_trace.zig");const record_tape = @import("../workspace/record_tape.zig");const workspace_access = @import("../workspace/workspace_access.zig");const update_target = @import("../upgrade/update_target.zig");const sandbox = @import("../permissions/sandbox.zig");const notification_sound = @import("../notifications/sound.zig");const tool_result_limits = @import("../tooling/tool_result_limits.zig");const types = @import("../shared/types.zig");const ui_render = @import("../../ui/render.zig");const presentation_mode = @import("../config/presentation_mode.zig");const transcript_presentation = @import("../output/transcript_presentation.zig");const shell_runtime = @import("../../ui/shell_runtime.zig");const ui_terminal = @import("../../ui/terminal/terminal.zig");const terminal_diff = @import("../../ui/render_engine/terminal_diff.zig");const transcript_runtime = @import("../../ui/transcript/runtime.zig"); const Allocator = std.mem.Allocator;const Metrics = types.Metrics;const PermissionMode = types.PermissionMode;const CursorPosition = shell_runtime.CursorPosition;const TerminalState = shell_runtime.TerminalState;const TranscriptRuntime = transcript_runtime.TranscriptRuntime;pub const ResizeHandler = shell_runtime.ResizeHandler;pub const default_permission_mode = config_runtime.default_permission_mode; /// Compile-time terminal restoration for one async-signal-safe `write(2)` call./// Resets terminal modes and ends with a newline before the next shell prompt.const abnormal_exit_restore_prefix = "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1004l\x1b[?1006l\x1b[?1l\x1b>\x1b[?1049l\x1b[?7h\x1b[4l\x1b[?6l\x1b[0m\x1b[?25h\x1b[?2031l\x1b[?2004l";const abnormal_exit_restore = abnormal_exit_restore_prefix ++ "\x1b[<u\x1b[>4;0m\n";const tmux_abnormal_exit_restore = abnormal_exit_restore_prefix ++ "\x1b[>4;0m\n";const normal_exit_restore_prefix = ui_terminal.theme_notification_disable_sequence ++ "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1004l\x1b[?1006l\x1b[?1l\x1b>\x1b[4l\x1b[?6l\x1b[?2004l";const normal_exit_restore = normal_exit_restore_prefix ++ "\x1b[<u\x1b[>4;0m";const tmux_normal_exit_restore = normal_exit_restore_prefix ++ "\x1b[>4;0m";const alternate_screen_enter = "\x1b[?1049h";const alternate_mouse_tracking_enter = "\x1b[?1000h\x1b[?1006h";const terminal_takeover_reset = "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1004l\x1b[?1006l\x1b[?1l\x1b>\x1b[?2004l\x1b[<u\x1b[>4;0m\x1b[4l\x1b[?6l\x1b[?7h\x1b[0m\x1b[?25h"; /// Original handlers, written at bootstrap and restored at shutdown./// Signal context never mutates them.var old_sigterm_action: ?std.posix.Sigaction = null;var old_sighup_action: ?std.posix.Sigaction = null; /// Restores terminal state, installs the default disposition, and re-raises./// Must remain async-signal-safe: only `write(2)`, `sigaction(2)`, and `raise(3)`.fn abnormalExitHandlerWithRestore(comptime restore: []const u8, sig: std.posix.SIG) void { // Signal context cannot recover from a failed async-signal-safe write. _ = std.c.write( std.posix.STDOUT_FILENO, restore.ptr, restore.len, ); const default_action: std.posix.Sigaction = .{ .handler = .{ .handler = std.posix.SIG.DFL }, .mask = std.posix.sigemptyset(), .flags = 0, }; std.posix.sigaction(sig, &default_action, null); _ = std.c.raise(sig);} fn abnormalExitHandler(sig: std.posix.SIG) callconv(.c) void { abnormalExitHandlerWithRestore(abnormal_exit_restore, sig);} fn tmuxAbnormalExitHandler(sig: std.posix.SIG) callconv(.c) void { abnormalExitHandlerWithRestore(tmux_abnormal_exit_restore, sig);} /// Install handlers for SIGTERM and SIGHUP so an externally-terminated/// fx restores terminal state before dying. SIGINT is not included/// because raw mode disables terminal-generated SIGINT.pub fn installAbnormalExitHandlers(tmux: ?[]const u8) void { if (!shell_runtime.supports_resize_signal) return; const handler: std.posix.Sigaction.handler_fn = if (tmux == null) abnormalExitHandler else tmuxAbnormalExitHandler; const act: std.posix.Sigaction = .{ .handler = .{ .handler = handler }, .mask = std.posix.sigemptyset(), .flags = 0, }; var old_term: std.posix.Sigaction = undefined; std.posix.sigaction(std.posix.SIG.TERM, &act, &old_term); old_sigterm_action = old_term; var old_hup: std.posix.Sigaction = undefined; std.posix.sigaction(std.posix.SIG.HUP, &act, &old_hup); old_sighup_action = old_hup;} pub fn uninstallAbnormalExitHandlers() void { if (!shell_runtime.supports_resize_signal) return; if (old_sigterm_action) |old| { std.posix.sigaction(std.posix.SIG.TERM, &old, null); old_sigterm_action = null; } if (old_sighup_action) |old| { std.posix.sigaction(std.posix.SIG.HUP, &old, null); old_sighup_action = null; }} pub const StartupState = struct { workspace_root: []u8 = &.{}, workspace_access: workspace_access.WorkspaceAccess = .{}, credential: ?credentials.Credential = null, credential_onboarding_skipped: bool = false, stored_key_status: credentials.StoredKeyReadStatus = .not_attempted, selected_model: []u8 = &.{}, configured_model: []u8 = &.{}, model_source: config_runtime.ModelSource = .compiled_default, permission_mode: PermissionMode = default_permission_mode, yolo_acknowledged: bool = false, permission_rules: types.PermissionRuleSet = .{}, agent_step_limit: usize, max_tool_result_bytes: usize = tool_result_limits.default_max_tool_result_bytes, context_limits: config_runtime.context_limits.Values = .{}, context_enabled: bool = true, fast_mode: bool = false, input_appearance: input_appearance.InputAppearance = .default, maxxing_mode: presentation_mode.MaxxingMode = presentation_mode.MaxxingMode.default, slash_menu_categories: bool = true, auto_upgrade: bool = true, update_channel: update_target.Channel = .stable, startup_scrollback: bool = true, prompt_history_enabled: bool = true, prompt_history_store_allowed: bool = true, config_diagnostics: []config_runtime.ConfigDiagnostic = &.{}, effort: types.ReasoningEffort = .auto, first_call_tool_choice: types.ToolChoice = .auto, sandbox_backend: sandbox.BackendKind = .none, statusline_sandbox: bool = false, statusline_context: bool = false, statusline_session: bool = false, notification_turn_end: bool = false, notification_attention_required: bool = false, notification_max: bool = false, theme_monitor_enabled: bool = false, pub fn deinit(self: *StartupState, alloc: Allocator) void { self.workspace_access.deinit(alloc); if (self.workspace_root.len > 0) alloc.free(self.workspace_root); if (self.credential) |*credential| credential.deinit(alloc); if (self.selected_model.len > 0) alloc.free(self.selected_model); if (self.configured_model.len > 0) alloc.free(self.configured_model); self.permission_rules.deinit(alloc); if (self.config_diagnostics.len > 0) { for (self.config_diagnostics) |*diagnostic| diagnostic.deinit(alloc); alloc.free(self.config_diagnostics); } self.* = .{ .agent_step_limit = self.agent_step_limit }; } pub fn takeWorkspaceRoot(self: *StartupState) []u8 { const value = self.workspace_root; self.workspace_root = &.{}; return value; } pub fn takeWorkspaceAccess(self: *StartupState) workspace_access.WorkspaceAccess { const value = self.workspace_access; self.workspace_access = .{}; return value; } pub fn apiKey(self: *const StartupState) ?[]const u8 { const credential = self.credential orelse return null; if (credential.needsRefreshAt(io_mod.milliTimestamp())) return null; return credential.token; } pub fn modelCatalogAccess(self: *const StartupState) credentials.CatalogAccess { return credentials.catalogAccessAt(self.credential, io_mod.milliTimestamp()); } pub fn gatewayTeam(self: *const StartupState) ?[]const u8 { const credential = self.credential orelse return null; if (credential.needsRefreshAt(io_mod.milliTimestamp())) return null; return credential.gatewayTeam(); } pub fn takeCredential(self: *StartupState) ?credentials.Credential { const value = self.credential; self.credential = null; return value; } pub fn takeSelectedModel(self: *StartupState) []u8 { const value = self.selected_model; self.selected_model = &.{}; return value; } pub fn takePermissionRules(self: *StartupState) types.PermissionRuleSet { const value = self.permission_rules; self.permission_rules = .{}; return value; }}; pub const StartupStatus = struct { workspace_root: []u8, selected_model: []const u8, owned_selected_model: ?[]u8 = null, auth: auth_runtime.StatusSnapshot = .{}, permission_mode: PermissionMode, sandbox_backend: sandbox.BackendKind = .none, agent_step_limit: usize, update_channel: update_target.Channel = .stable, config_diagnostics: []config_runtime.ConfigDiagnostic = &.{}, pub fn deinit(self: *StartupStatus, alloc: Allocator) void { alloc.free(self.workspace_root); if (self.owned_selected_model) |model| alloc.free(model); self.auth.deinit(alloc); if (self.config_diagnostics.len > 0) { for (self.config_diagnostics) |*diagnostic| diagnostic.deinit(alloc); alloc.free(self.config_diagnostics); } self.* = undefined; }}; const StartupStatusModel = struct { value: []const u8, owned: ?[]u8 = null,}; pub const BootstrapConfig = struct { alloc: Allocator, terminal: *TerminalState, shell: *TranscriptRuntime, metrics: *Metrics, terminal_title: host.TerminalTitle, footer_rows: u16, startup_min_body_rows: u16 = 0, default_model: []const u8, default_agent_step_limit: usize, secret_store: host.SecretStore, resize_handler: ResizeHandler, fx_version: []const u8 = "", record_requested: bool = false,}; pub fn loadStartupState( alloc: Allocator, transport: oauth_transport.Provider, secret_store: host.SecretStore, default_model: []const u8, default_agent_step_limit: usize,) !StartupState { const workspace_root = try io_mod.realpathAlloc(alloc, "."); return loadStartupStateFromOwnedWorkspace(alloc, transport, secret_store, workspace_root, default_model, default_agent_step_limit, null, .refresh_if_needed);} pub fn loadStartupStateWithoutCredentials(alloc: Allocator, default_model: []const u8, default_agent_step_limit: usize) !StartupState { const workspace_root = try io_mod.realpathAlloc(alloc, "."); return loadStartupStateFromOwnedWorkspace(alloc, oauth_transport.unavailable_provider, host.unavailable_secret_store, workspace_root, default_model, default_agent_step_limit, null, null);} pub fn loadEmbeddedStartupState( alloc: Allocator, home_dir: []const u8, workspace_root: []const u8, default_model: []const u8, default_agent_step_limit: usize,) !StartupState { const owned_workspace_root = try io_mod.realpathAlloc(alloc, workspace_root); return loadStartupStateFromOwnedWorkspace( alloc, oauth_transport.unavailable_provider, host.unavailable_secret_store, owned_workspace_root, default_model, default_agent_step_limit, home_dir, null, );} pub fn loadCatalogStartupState( alloc: Allocator, secret_store: host.SecretStore, default_model: []const u8, default_agent_step_limit: usize,) !StartupState { const workspace_root = try io_mod.realpathAlloc(alloc, "."); return loadStartupStateFromOwnedWorkspace(alloc, oauth_transport.unavailable_provider, secret_store, workspace_root, default_model, default_agent_step_limit, null, .stored);} pub fn loadStartupStatus( alloc: Allocator, secret_store: host.SecretStore, default_model: []const u8, default_agent_step_limit: usize,) !StartupStatus { const workspace_root = try io_mod.realpathAlloc(alloc, "."); errdefer alloc.free(workspace_root); debug_trace.configureFromEnv(alloc, workspace_root); var detailed = try config_runtime.loadMergedSettingsDetailed(alloc, workspace_root); defer detailed.deinit(alloc); const settings = &detailed.settings; const selected_model = try loadStartupStatusModel(alloc, default_model, settings.model); errdefer if (selected_model.owned) |model| alloc.free(model); var auth_status = try auth_runtime.loadStatusSnapshot(alloc, secret_store, settings.credential_source); errdefer auth_status.deinit(alloc); const result = StartupStatus{ .workspace_root = workspace_root, .selected_model = selected_model.value, .owned_selected_model = selected_model.owned, .auth = auth_status, .permission_mode = loadPermissionMode(settings.permission_mode), .sandbox_backend = sandbox.backendFromConfig(settings.sandbox), .agent_step_limit = loadAgentStepLimit(default_agent_step_limit, settings.max_agent_steps), .update_channel = settings.update_channel orelse .stable, .config_diagnostics = detailed.diagnostics, }; auth_status.owned_team = null; detailed.diagnostics = &.{}; return result;} pub fn applyWorkspaceLaunch( self: *StartupState, alloc: Allocator, command_line_directories: []const []const u8, saved_suppressed: bool,) !void { try self.workspace_access.applyLaunch( alloc, self.workspace_root, command_line_directories, saved_suppressed, );} fn loadStartupStateForWorkspace(alloc: Allocator, workspace_root: []const u8, default_model: []const u8, default_agent_step_limit: usize) !StartupState { const owned_workspace_root = try alloc.dupe(u8, workspace_root); return loadStartupStateFromOwnedWorkspace(alloc, oauth_transport.unavailable_provider, host.unavailable_secret_store, owned_workspace_root, default_model, default_agent_step_limit, null, null);} const CredentialLoadMode = credentials.LoadMode; fn loadStartupStateFromOwnedWorkspace( alloc: Allocator, transport: oauth_transport.Provider, secret_store: host.SecretStore, owned_workspace_root: []u8, default_model: []const u8, default_agent_step_limit: usize, profile_home: ?[]const u8, credential_mode: ?CredentialLoadMode,) !StartupState { var state = StartupState{ .agent_step_limit = default_agent_step_limit }; errdefer state.deinit(alloc); state.workspace_root = owned_workspace_root; debug_trace.configureFromEnv(alloc, state.workspace_root); var detailed = if (profile_home) |home_dir| try config_runtime.loadMergedSettingsDetailedFromHome(alloc, home_dir, state.workspace_root) else try config_runtime.loadMergedSettingsDetailed(alloc, state.workspace_root); defer detailed.deinit(alloc); const settings = &detailed.settings; state.workspace_access = try workspace_access.WorkspaceAccess.init( alloc, state.workspace_root, detailed.additional_directory_sources orelse &.{}, &.{}, false, ); state.configured_model = try alloc.dupe(u8, settings.model orelse default_model); state.model_source = detailed.model_source orelse .compiled_default; state.selected_model = try loadInitialModel(alloc, default_model, settings.model); if (hasProcessModelOverride()) state.model_source = .process_override; state.config_diagnostics = detailed.diagnostics; detailed.diagnostics = &.{}; state.prompt_history_enabled = settings.prompt_history_enabled orelse true; state.prompt_history_store_allowed = detailed.prompt_history_store_allowed; if (credential_mode) |mode| { const resolution = try credentials.resolvePreferring(alloc, transport, secret_store, mode, settings.credential_source); state.credential = resolution.credential; state.stored_key_status = resolution.stored_key_status; } state.permission_mode = loadPermissionMode(settings.permission_mode); state.yolo_acknowledged = settings.yolo_acknowledged orelse false; state.permission_rules = try types.dupePermissionRuleSet(alloc, settings.permission_rules); state.agent_step_limit = loadAgentStepLimit(default_agent_step_limit, settings.max_agent_steps); state.max_tool_result_bytes = tool_result_limits.resolveMaxToolResultBytes(settings.max_tool_result_bytes, tool_result_limits.default_max_tool_result_bytes); state.context_limits = config_runtime.resolveContextLimits(settings, &.{}); state.context_enabled = settings.context orelse true; state.fast_mode = settings.fast_mode orelse false; state.input_appearance = initialInputAppearance(settings.input_appearance); state.maxxing_mode = initialMaxxingMode(settings.maxxing_mode); state.slash_menu_categories = settings.slash_menu_categories orelse true; state.auto_upgrade = settings.auto_upgrade orelse true; state.update_channel = settings.update_channel orelse .stable; state.startup_scrollback = settings.startup_scrollback orelse true; state.effort = settings.effort orelse .auto; state.first_call_tool_choice = settings.first_call_tool_choice orelse .auto; state.sandbox_backend = sandbox.backendFromConfig(settings.sandbox); state.statusline_sandbox = settings.statusline_sandbox orelse false; state.statusline_context = settings.statusline_context orelse false; state.statusline_session = settings.statusline_session orelse false; const sound_override = soundEnvOverride(); const sound_on_override: ?bool = if (sound_override) |level| level != .off else null; const max_override: ?bool = if (sound_override) |level| level == .max else null; state.notification_turn_end = sound_on_override orelse settings.notification_turn_end orelse notification_sound.default_enabled; state.notification_attention_required = sound_on_override orelse settings.notification_attention_required orelse notification_sound.default_enabled; state.notification_max = max_override orelse settings.notification_max orelse false; return state;} pub fn bootstrapInteractiveApp(cfg: BootstrapConfig) !StartupState { try cfg.terminal.ensureInteractive(); try cfg.terminal.captureOriginalTermios(); cfg.shell.sync_updates_enabled = shell_runtime.detectSyncUpdatesEnabled(cfg.alloc); cfg.shell.history_reset_uses_ris = shell_runtime.detectHistoryResetUsesRis(cfg.alloc); cfg.shell.layout = minimalLayout(); try cfg.shell.initBacking(cfg.alloc); var state = try loadCatalogStartupState( cfg.alloc, cfg.secret_store, cfg.default_model, cfg.default_agent_step_limit, ); errdefer state.deinit(cfg.alloc); state.credential_onboarding_skipped = credentialOnboardingDisabled(); errdefer shutdownInteractiveShell( cfg.terminal, cfg.shell, cfg.metrics, cfg.terminal_title, ); try cfg.terminal.enableRawMode(); cfg.terminal.installResizeSignal(cfg.resize_handler); installAbnormalExitHandlers(io_mod.getenv("TMUX")); cfg.shell.layout = try cfg.terminal.queryLayout(cfg.footer_rows); record_tape.configureFromEnv( cfg.alloc, state.workspace_root, cfg.shell.layout.cols, cfg.shell.layout.rows, cfg.fx_version, cfg.record_requested, ) catch |err| { debug_trace.logf("record", "startup recording failed err={s}", .{@errorName(err)}); return error.RecordingStartFailed; }; // The shadow VT is load-bearing: frame commit diffs the target // surface against it and records the accepted terminal state. try cfg.shell.enableShadowVt(cfg.alloc); ui_render.setTruecolorSupport(ui_render.truecolorSupportedForValues( io_mod.getenv("COLORTERM"), io_mod.getenv("TERM_PROGRAM"), )); const theme = ui_render.detectTheme(cfg.alloc, cfg.terminal); ui_render.initTheme(theme.light, theme.rgb); state.theme_monitor_enabled = ui_render.explicitThemeOverride() == null; const cursor = cfg.terminal.queryCursorPosition() catch blk: { break :blk CursorPosition{ .row = cfg.shell.layout.content_bottom, .col = 1, }; }; const start_row = blk: { var row = cursor.row; if (cursor.col > 1 and row < cfg.shell.layout.rows) row += 1; break :blk row;Lines 1–500 of 2398. Load more
const std = @import("std"); const io_mod = @import("../shared/io.zig"); const agent_steps = @import("../config/agent_steps.zig"); const config_runtime = @import("../config/config_runtime.zig"); const auth_runtime = @import("../auth/auth_runtime.zig"); const credentials = @import("../auth/credentials.zig"); const host = @import("../hosts/host.zig"); const oauth_transport = @import("../auth/oauth_transport.zig"); const input_appearance = @import("../config/input_appearance.zig"); const model_capabilities = @import("../config/model_capabilities.zig"); const debug_trace = @import("../shared/debug_trace.zig"); const record_tape = @import("../workspace/record_tape.zig"); const workspace_access = @import("../workspace/workspace_access.zig"); const update_target = @import("../upgrade/update_target.zig"); const sandbox = @import("../permissions/sandbox.zig"); const notification_sound = @import("../notifications/sound.zig"); const tool_result_limits = @import("../tooling/tool_result_limits.zig"); const types = @import("../shared/types.zig"); const ui_render = @import("../../ui/render.zig"); const presentation_mode = @import("../config/presentation_mode.zig"); const transcript_presentation = @import("../output/transcript_presentation.zig"); const shell_runtime = @import("../../ui/shell_runtime.zig"); const ui_terminal = @import("../../ui/terminal/terminal.zig"); const terminal_diff = @import("../../ui/render_engine/terminal_diff.zig"); const transcript_runtime = @import("../../ui/transcript/runtime.zig"); const Allocator = std.mem.Allocator; const Metrics = types.Metrics; const PermissionMode = types.PermissionMode; const CursorPosition = shell_runtime.CursorPosition; const TerminalState = shell_runtime.TerminalState; const TranscriptRuntime = transcript_runtime.TranscriptRuntime; pub const ResizeHandler = shell_runtime.ResizeHandler; pub const default_permission_mode = config_runtime.default_permission_mode; /// Compile-time terminal restoration for one async-signal-safe `write(2)` call. /// Resets terminal modes and ends with a newline before the next shell prompt. const abnormal_exit_restore_prefix = "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1004l\x1b[?1006l\x1b[?1l\x1b>\x1b[?1049l\x1b[?7h\x1b[4l\x1b[?6l\x1b[0m\x1b[?25h\x1b[?2031l\x1b[?2004l"; const abnormal_exit_restore = abnormal_exit_restore_prefix ++ "\x1b[<u\x1b[>4;0m\n"; const tmux_abnormal_exit_restore = abnormal_exit_restore_prefix ++ "\x1b[>4;0m\n"; const normal_exit_restore_prefix = ui_terminal.theme_notification_disable_sequence ++ "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1004l\x1b[?1006l\x1b[?1l\x1b>\x1b[4l\x1b[?6l\x1b[?2004l"; const normal_exit_restore = normal_exit_restore_prefix ++ "\x1b[<u\x1b[>4;0m"; const tmux_normal_exit_restore = normal_exit_restore_prefix ++ "\x1b[>4;0m"; const alternate_screen_enter = "\x1b[?1049h"; const alternate_mouse_tracking_enter = "\x1b[?1000h\x1b[?1006h"; const terminal_takeover_reset = "\x1b[?2026l\x1b[?1000l\x1b[?1002l\x1b[?1004l\x1b[?1006l\x1b[?1l\x1b>\x1b[?2004l\x1b[<u\x1b[>4;0m\x1b[4l\x1b[?6l\x1b[?7h\x1b[0m\x1b[?25h"; /// Original handlers, written at bootstrap and restored at shutdown. /// Signal context never mutates them. var old_sigterm_action: ?std.posix.Sigaction = null; var old_sighup_action: ?std.posix.Sigaction = null; /// Restores terminal state, installs the default disposition, and re-raises. /// Must remain async-signal-safe: only `write(2)`, `sigaction(2)`, and `raise(3)`. fn abnormalExitHandlerWithRestore(comptime restore: []const u8, sig: std.posix.SIG) void { // Signal context cannot recover from a failed async-signal-safe write. _ = std.c.write( std.posix.STDOUT_FILENO, restore.ptr, restore.len, ); const default_action: std.posix.Sigaction = .{ .handler = .{ .handler = std.posix.SIG.DFL }, .mask = std.posix.sigemptyset(), .flags = 0, }; std.posix.sigaction(sig, &default_action, null); _ = std.c.raise(sig); } fn abnormalExitHandler(sig: std.posix.SIG) callconv(.c) void { abnormalExitHandlerWithRestore(abnormal_exit_restore, sig); } fn tmuxAbnormalExitHandler(sig: std.posix.SIG) callconv(.c) void { abnormalExitHandlerWithRestore(tmux_abnormal_exit_restore, sig); } /// Install handlers for SIGTERM and SIGHUP so an externally-terminated /// fx restores terminal state before dying. SIGINT is not included /// because raw mode disables terminal-generated SIGINT. pub fn installAbnormalExitHandlers(tmux: ?[]const u8) void { if (!shell_runtime.supports_resize_signal) return; const handler: std.posix.Sigaction.handler_fn = if (tmux == null) abnormalExitHandler else tmuxAbnormalExitHandler; const act: std.posix.Sigaction = .{ .handler = .{ .handler = handler }, .mask = std.posix.sigemptyset(), .flags = 0, }; var old_term: std.posix.Sigaction = undefined; std.posix.sigaction(std.posix.SIG.TERM, &act, &old_term); old_sigterm_action = old_term; var old_hup: std.posix.Sigaction = undefined; std.posix.sigaction(std.posix.SIG.HUP, &act, &old_hup); old_sighup_action = old_hup; } pub fn uninstallAbnormalExitHandlers() void { if (!shell_runtime.supports_resize_signal) return; if (old_sigterm_action) |old| { std.posix.sigaction(std.posix.SIG.TERM, &old, null); old_sigterm_action = null; } if (old_sighup_action) |old| { std.posix.sigaction(std.posix.SIG.HUP, &old, null); old_sighup_action = null; } } pub const StartupState = struct { workspace_root: []u8 = &.{}, workspace_access: workspace_access.WorkspaceAccess = .{}, credential: ?credentials.Credential = null, credential_onboarding_skipped: bool = false, stored_key_status: credentials.StoredKeyReadStatus = .not_attempted, selected_model: []u8 = &.{}, configured_model: []u8 = &.{}, model_source: config_runtime.ModelSource = .compiled_default, permission_mode: PermissionMode = default_permission_mode, yolo_acknowledged: bool = false, permission_rules: types.PermissionRuleSet = .{}, agent_step_limit: usize, max_tool_result_bytes: usize = tool_result_limits.default_max_tool_result_bytes, context_limits: config_runtime.context_limits.Values = .{}, context_enabled: bool = true, fast_mode: bool = false, input_appearance: input_appearance.InputAppearance = .default, maxxing_mode: presentation_mode.MaxxingMode = presentation_mode.MaxxingMode.default, slash_menu_categories: bool = true, auto_upgrade: bool = true, update_channel: update_target.Channel = .stable, startup_scrollback: bool = true, prompt_history_enabled: bool = true, prompt_history_store_allowed: bool = true, config_diagnostics: []config_runtime.ConfigDiagnostic = &.{}, effort: types.ReasoningEffort = .auto, first_call_tool_choice: types.ToolChoice = .auto, sandbox_backend: sandbox.BackendKind = .none, statusline_sandbox: bool = false, statusline_context: bool = false, statusline_session: bool = false, notification_turn_end: bool = false, notification_attention_required: bool = false, notification_max: bool = false, theme_monitor_enabled: bool = false, pub fn deinit(self: *StartupState, alloc: Allocator) void { self.workspace_access.deinit(alloc); if (self.workspace_root.len > 0) alloc.free(self.workspace_root); if (self.credential) |*credential| credential.deinit(alloc); if (self.selected_model.len > 0) alloc.free(self.selected_model); if (self.configured_model.len > 0) alloc.free(self.configured_model); self.permission_rules.deinit(alloc); if (self.config_diagnostics.len > 0) { for (self.config_diagnostics) |*diagnostic| diagnostic.deinit(alloc); alloc.free(self.config_diagnostics); } self.* = .{ .agent_step_limit = self.agent_step_limit }; } pub fn takeWorkspaceRoot(self: *StartupState) []u8 { const value = self.workspace_root; self.workspace_root = &.{}; return value; } pub fn takeWorkspaceAccess(self: *StartupState) workspace_access.WorkspaceAccess { const value = self.workspace_access; self.workspace_access = .{}; return value; } pub fn apiKey(self: *const StartupState) ?[]const u8 { const credential = self.credential orelse return null; if (credential.needsRefreshAt(io_mod.milliTimestamp())) return null; return credential.token; } pub fn modelCatalogAccess(self: *const StartupState) credentials.CatalogAccess { return credentials.catalogAccessAt(self.credential, io_mod.milliTimestamp()); } pub fn gatewayTeam(self: *const StartupState) ?[]const u8 { const credential = self.credential orelse return null; if (credential.needsRefreshAt(io_mod.milliTimestamp())) return null; return credential.gatewayTeam(); } pub fn takeCredential(self: *StartupState) ?credentials.Credential { const value = self.credential; self.credential = null; return value; } pub fn takeSelectedModel(self: *StartupState) []u8 { const value = self.selected_model; self.selected_model = &.{}; return value; } pub fn takePermissionRules(self: *StartupState) types.PermissionRuleSet { const value = self.permission_rules; self.permission_rules = .{}; return value; } }; pub const StartupStatus = struct { workspace_root: []u8, selected_model: []const u8, owned_selected_model: ?[]u8 = null, auth: auth_runtime.StatusSnapshot = .{}, permission_mode: PermissionMode, sandbox_backend: sandbox.BackendKind = .none, agent_step_limit: usize, update_channel: update_target.Channel = .stable, config_diagnostics: []config_runtime.ConfigDiagnostic = &.{}, pub fn deinit(self: *StartupStatus, alloc: Allocator) void { alloc.free(self.workspace_root); if (self.owned_selected_model) |model| alloc.free(model); self.auth.deinit(alloc); if (self.config_diagnostics.len > 0) { for (self.config_diagnostics) |*diagnostic| diagnostic.deinit(alloc); alloc.free(self.config_diagnostics); } self.* = undefined; } }; const StartupStatusModel = struct { value: []const u8, owned: ?[]u8 = null, }; pub const BootstrapConfig = struct { alloc: Allocator, terminal: *TerminalState, shell: *TranscriptRuntime, metrics: *Metrics, terminal_title: host.TerminalTitle, footer_rows: u16, startup_min_body_rows: u16 = 0, default_model: []const u8, default_agent_step_limit: usize, secret_store: host.SecretStore, resize_handler: ResizeHandler, fx_version: []const u8 = "", record_requested: bool = false, }; pub fn loadStartupState( alloc: Allocator, transport: oauth_transport.Provider, secret_store: host.SecretStore, default_model: []const u8, default_agent_step_limit: usize, ) !StartupState { const workspace_root = try io_mod.realpathAlloc(alloc, "."); return loadStartupStateFromOwnedWorkspace(alloc, transport, secret_store, workspace_root, default_model, default_agent_step_limit, null, .refresh_if_needed); } pub fn loadStartupStateWithoutCredentials(alloc: Allocator, default_model: []const u8, default_agent_step_limit: usize) !StartupState { const workspace_root = try io_mod.realpathAlloc(alloc, "."); return loadStartupStateFromOwnedWorkspace(alloc, oauth_transport.unavailable_provider, host.unavailable_secret_store, workspace_root, default_model, default_agent_step_limit, null, null); } pub fn loadEmbeddedStartupState( alloc: Allocator, home_dir: []const u8, workspace_root: []const u8, default_model: []const u8, default_agent_step_limit: usize, ) !StartupState { const owned_workspace_root = try io_mod.realpathAlloc(alloc, workspace_root); return loadStartupStateFromOwnedWorkspace( alloc, oauth_transport.unavailable_provider, host.unavailable_secret_store, owned_workspace_root, default_model, default_agent_step_limit, home_dir, null, ); } pub fn loadCatalogStartupState( alloc: Allocator, secret_store: host.SecretStore, default_model: []const u8, default_agent_step_limit: usize, ) !StartupState { const workspace_root = try io_mod.realpathAlloc(alloc, "."); return loadStartupStateFromOwnedWorkspace(alloc, oauth_transport.unavailable_provider, secret_store, workspace_root, default_model, default_agent_step_limit, null, .stored); } pub fn loadStartupStatus( alloc: Allocator, secret_store: host.SecretStore, default_model: []const u8, default_agent_step_limit: usize, ) !StartupStatus { const workspace_root = try io_mod.realpathAlloc(alloc, "."); errdefer alloc.free(workspace_root); debug_trace.configureFromEnv(alloc, workspace_root); var detailed = try config_runtime.loadMergedSettingsDetailed(alloc, workspace_root); defer detailed.deinit(alloc); const settings = &detailed.settings; const selected_model = try loadStartupStatusModel(alloc, default_model, settings.model); errdefer if (selected_model.owned) |model| alloc.free(model); var auth_status = try auth_runtime.loadStatusSnapshot(alloc, secret_store, settings.credential_source); errdefer auth_status.deinit(alloc); const result = StartupStatus{ .workspace_root = workspace_root, .selected_model = selected_model.value, .owned_selected_model = selected_model.owned, .auth = auth_status, .permission_mode = loadPermissionMode(settings.permission_mode), .sandbox_backend = sandbox.backendFromConfig(settings.sandbox), .agent_step_limit = loadAgentStepLimit(default_agent_step_limit, settings.max_agent_steps), .update_channel = settings.update_channel orelse .stable, .config_diagnostics = detailed.diagnostics, }; auth_status.owned_team = null; detailed.diagnostics = &.{}; return result; } pub fn applyWorkspaceLaunch( self: *StartupState, alloc: Allocator, command_line_directories: []const []const u8, saved_suppressed: bool, ) !void { try self.workspace_access.applyLaunch( alloc, self.workspace_root, command_line_directories, saved_suppressed, ); } fn loadStartupStateForWorkspace(alloc: Allocator, workspace_root: []const u8, default_model: []const u8, default_agent_step_limit: usize) !StartupState { const owned_workspace_root = try alloc.dupe(u8, workspace_root); return loadStartupStateFromOwnedWorkspace(alloc, oauth_transport.unavailable_provider, host.unavailable_secret_store, owned_workspace_root, default_model, default_agent_step_limit, null, null); } const CredentialLoadMode = credentials.LoadMode; fn loadStartupStateFromOwnedWorkspace( alloc: Allocator, transport: oauth_transport.Provider, secret_store: host.SecretStore, owned_workspace_root: []u8, default_model: []const u8, default_agent_step_limit: usize, profile_home: ?[]const u8, credential_mode: ?CredentialLoadMode, ) !StartupState { var state = StartupState{ .agent_step_limit = default_agent_step_limit }; errdefer state.deinit(alloc); state.workspace_root = owned_workspace_root; debug_trace.configureFromEnv(alloc, state.workspace_root); var detailed = if (profile_home) |home_dir| try config_runtime.loadMergedSettingsDetailedFromHome(alloc, home_dir, state.workspace_root) else try config_runtime.loadMergedSettingsDetailed(alloc, state.workspace_root); defer detailed.deinit(alloc); const settings = &detailed.settings; state.workspace_access = try workspace_access.WorkspaceAccess.init( alloc, state.workspace_root, detailed.additional_directory_sources orelse &.{}, &.{}, false, ); state.configured_model = try alloc.dupe(u8, settings.model orelse default_model); state.model_source = detailed.model_source orelse .compiled_default; state.selected_model = try loadInitialModel(alloc, default_model, settings.model); if (hasProcessModelOverride()) state.model_source = .process_override; state.config_diagnostics = detailed.diagnostics; detailed.diagnostics = &.{}; state.prompt_history_enabled = settings.prompt_history_enabled orelse true; state.prompt_history_store_allowed = detailed.prompt_history_store_allowed; if (credential_mode) |mode| { const resolution = try credentials.resolvePreferring(alloc, transport, secret_store, mode, settings.credential_source); state.credential = resolution.credential; state.stored_key_status = resolution.stored_key_status; } state.permission_mode = loadPermissionMode(settings.permission_mode); state.yolo_acknowledged = settings.yolo_acknowledged orelse false; state.permission_rules = try types.dupePermissionRuleSet(alloc, settings.permission_rules); state.agent_step_limit = loadAgentStepLimit(default_agent_step_limit, settings.max_agent_steps); state.max_tool_result_bytes = tool_result_limits.resolveMaxToolResultBytes(settings.max_tool_result_bytes, tool_result_limits.default_max_tool_result_bytes); state.context_limits = config_runtime.resolveContextLimits(settings, &.{}); state.context_enabled = settings.context orelse true; state.fast_mode = settings.fast_mode orelse false; state.input_appearance = initialInputAppearance(settings.input_appearance); state.maxxing_mode = initialMaxxingMode(settings.maxxing_mode); state.slash_menu_categories = settings.slash_menu_categories orelse true; state.auto_upgrade = settings.auto_upgrade orelse true; state.update_channel = settings.update_channel orelse .stable; state.startup_scrollback = settings.startup_scrollback orelse true; state.effort = settings.effort orelse .auto; state.first_call_tool_choice = settings.first_call_tool_choice orelse .auto; state.sandbox_backend = sandbox.backendFromConfig(settings.sandbox); state.statusline_sandbox = settings.statusline_sandbox orelse false; state.statusline_context = settings.statusline_context orelse false; state.statusline_session = settings.statusline_session orelse false; const sound_override = soundEnvOverride(); const sound_on_override: ?bool = if (sound_override) |level| level != .off else null; const max_override: ?bool = if (sound_override) |level| level == .max else null; state.notification_turn_end = sound_on_override orelse settings.notification_turn_end orelse notification_sound.default_enabled; state.notification_attention_required = sound_on_override orelse settings.notification_attention_required orelse notification_sound.default_enabled; state.notification_max = max_override orelse settings.notification_max orelse false; return state; } pub fn bootstrapInteractiveApp(cfg: BootstrapConfig) !StartupState { try cfg.terminal.ensureInteractive(); try cfg.terminal.captureOriginalTermios(); cfg.shell.sync_updates_enabled = shell_runtime.detectSyncUpdatesEnabled(cfg.alloc); cfg.shell.history_reset_uses_ris = shell_runtime.detectHistoryResetUsesRis(cfg.alloc); cfg.shell.layout = minimalLayout(); try cfg.shell.initBacking(cfg.alloc); var state = try loadCatalogStartupState( cfg.alloc, cfg.secret_store, cfg.default_model, cfg.default_agent_step_limit, ); errdefer state.deinit(cfg.alloc); state.credential_onboarding_skipped = credentialOnboardingDisabled(); errdefer shutdownInteractiveShell( cfg.terminal, cfg.shell, cfg.metrics, cfg.terminal_title, ); try cfg.terminal.enableRawMode(); cfg.terminal.installResizeSignal(cfg.resize_handler); installAbnormalExitHandlers(io_mod.getenv("TMUX")); cfg.shell.layout = try cfg.terminal.queryLayout(cfg.footer_rows); record_tape.configureFromEnv( cfg.alloc, state.workspace_root, cfg.shell.layout.cols, cfg.shell.layout.rows, cfg.fx_version, cfg.record_requested, ) catch |err| { debug_trace.logf("record", "startup recording failed err={s}", .{@errorName(err)}); return error.RecordingStartFailed; }; // The shadow VT is load-bearing: frame commit diffs the target // surface against it and records the accepted terminal state. try cfg.shell.enableShadowVt(cfg.alloc); ui_render.setTruecolorSupport(ui_render.truecolorSupportedForValues( io_mod.getenv("COLORTERM"), io_mod.getenv("TERM_PROGRAM"), )); const theme = ui_render.detectTheme(cfg.alloc, cfg.terminal); ui_render.initTheme(theme.light, theme.rgb); state.theme_monitor_enabled = ui_render.explicitThemeOverride() == null; const cursor = cfg.terminal.queryCursorPosition() catch blk: { break :blk CursorPosition{ .row = cfg.shell.layout.content_bottom, .col = 1, }; }; const start_row = blk: { var row = cursor.row; if (cursor.col > 1 and row < cfg.shell.layout.rows) row += 1; break :blk row;