const std = @import("std");const builtin = @import("builtin");const app_permission_runtime = @import("../app/app_permission_runtime.zig");const app_session_runtime = @import("../app/app_session_runtime.zig");const auth_runtime = @import("../auth/auth_runtime.zig");const collections = @import("../shared/collections.zig");const config_runtime = @import("../config/config_runtime.zig");const debug_trace = @import("../shared/debug_trace.zig");const host = @import("../hosts/host.zig");const io_mod = @import("../shared/io.zig");const model_capabilities = @import("../config/model_capabilities.zig");const output_contracts = @import("../output/output_contracts.zig");const permissions = @import("../permissions/permissions.zig");const sandbox = @import("../permissions/sandbox.zig");const worker_runtime = @import("../agent/worker_runtime.zig");const prompt_history_runtime = @import("../app/prompt_history_runtime.zig");const tool_dispatch = @import("../tooling/tool_dispatch.zig");const text_utils = @import("../shared/text_utils.zig");const types = @import("../shared/types.zig");const render_request = @import("../../ui/render_request.zig"); const freeStringList = collections.freeStringList;const containsIgnoreCase = text_utils.containsIgnoreCase;const permissions_usage = "usage: /permissions [ask|auto|yolo|reset]\n /permissions remember <allow|deny> <tool-name> <arguments-json>\n /permissions revoke <rule-id>"; pub fn reportUserSettingsCommit( app: anytype, label: []const u8, patch: config_runtime.UserSettingsPatch, outcome: config_runtime.CommitOutcome, session_error: ?anyerror, // False when the caller announces state; drops the redundant "saved" line. announce_commit: bool,) !?config_runtime.ConfigSources { var detailed = loadDetailedSettingsForNotice(app) catch |err| { debug_trace.logf( "config", "{s} post-commit source resolution failed err={s}", .{ label, @errorName(err) }, ); try writeUserSettingsCommitUnknownSource( app, label, outcome, session_error, err, ); return null; }; defer detailed.deinit(app.alloc); if (postCommitResolutionError(detailed)) |err| { debug_trace.logf( "config", "{s} post-commit source resolution diagnostic err={s}", .{ label, @errorName(err) }, ); try writeUserSettingsCommitUnknownSource( app, label, outcome, session_error, err, ); return null; } var out: std.Io.Writer.Allocating = .init(app.alloc); defer out.deinit(); const benign_commit = "saved to user settings (scope=user)"; try out.writer.writeAll(benign_commit); try appendShadowedUserSources(&out.writer, patch, detailed.sources); try appendCommitCleanup(&out.writer, outcome); if (session_error) |err| { try out.writer.print("; current-session recovery degraded ({s})", .{@errorName(err)}); } const text = try out.toOwnedSlice(); defer app.alloc.free(text); // Keep the line when any warning suffix was appended. if (announce_commit or !std.mem.eql(u8, text, benign_commit)) { try app.writeDomainNotice(.{ .topic = label, .tone = .neutral, .body = text }, true); } return detailed.sources;} fn writeUserSettingsCommitUnknownSource( app: anytype, label: []const u8, outcome: config_runtime.CommitOutcome, session_error: ?anyerror, resolution_error: anyerror,) !void { var out: std.Io.Writer.Allocating = .init(app.alloc); defer out.deinit(); try out.writer.print( "saved to user settings (scope=user); next-startup source unknown ({s})", .{@errorName(resolution_error)}, ); try appendCommitCleanup(&out.writer, outcome); if (session_error) |err| { try out.writer.print("; current-session recovery degraded ({s})", .{@errorName(err)}); } const text = try out.toOwnedSlice(); defer app.alloc.free(text); try app.writeDomainNotice(.{ .topic = label, .tone = .warning, .body = text }, true);} pub fn reportUserSettingsFailure( app: anytype, label: []const u8, err: anyerror, cleanup: config_runtime.LegacyCleanup, runtime_changed: bool,) !void { var out: std.Io.Writer.Allocating = .init(app.alloc); defer out.deinit(); if (err == error.SettingsCommitIndeterminate) { try out.writer.print( "user settings persistence uncertain (scope=user, error={s})", .{@errorName(err)}, ); } else { try out.writer.print( "{s}not saved to user settings ({s})", .{ if (runtime_changed) "active for this process but " else "", @errorName(err) }, ); } try appendLegacyCleanup(&out.writer, cleanup); const text = try out.toOwnedSlice(); defer app.alloc.free(text); try app.writeDomainNotice(.{ .topic = label, .tone = .@"error", .body = text }, true);} fn loadDetailedSettingsForNotice(app: anytype) !config_runtime.DetailedSettings { if (comptime @hasDecl(@TypeOf(app.*), "loadDetailedSettingsForNotice")) { return app.loadDetailedSettingsForNotice(); } return config_runtime.loadMergedSettingsDetailed(app.alloc, app.workspace_root);} fn postCommitResolutionError( detailed: config_runtime.DetailedSettings,) ?anyerror { for (detailed.diagnostics) |diagnostic| { if (diagnostic.layer != .user) continue; switch (diagnostic.cause) { .malformed_settings => return error.InvalidSettingsFormat, .settings_too_large => return error.SettingsPrimaryTooLarge, .durable_path_unsafe => return error.DurablePathUnsafe, .private_state_permissions_unsupported => return error.PrivateStatePermissionsUnsupported, .invalid_model_id => return error.InvalidModelValue, .retired_skill_match_fuzzy => return error.InvalidSettingsFormat, .invalid_context_limits => return error.InvalidSettingsFormat, .invalid_additional_directories => return error.InvalidSettingsFormat, .ignored_project_user_only_setting, .legacy_workspace_preferences, .manual_backup_available, => {}, } } return null;} fn appendShadowedUserSources( writer: *std.Io.Writer, patch: config_runtime.UserSettingsPatch, sources: config_runtime.ConfigSources,) !void { var wrote_header = false; try appendShadowedUserSource(writer, "model", patch.model != null, sources.model, &wrote_header); try appendShadowedUserSource(writer, "permission_mode", patch.permission_mode != null, sources.permission_mode, &wrote_header); try appendShadowedUserSource(writer, "effort", patch.effort != null, sources.effort, &wrote_header); try appendShadowedUserSource(writer, "fast_mode", patch.fast_mode != null, sources.fast_mode, &wrote_header); try appendShadowedUserSource(writer, "startup_scrollback", patch.startup_scrollback != null, sources.startup_scrollback, &wrote_header); try appendShadowedUserSource(writer, "input_appearance", patch.input_appearance != null, sources.input_appearance, &wrote_header); try appendShadowedUserSource(writer, "prompt_history", patch.prompt_history_enabled != null, sources.prompt_history_enabled, &wrote_header); if (patch.statusline_item) |item| { const source = switch (item.item) { .sandbox => sources.statusline_sandbox, .context => sources.statusline_context, .session => sources.statusline_session, }; const field = switch (item.item) { .sandbox => "statusLine.sandbox", .context => "statusLine.context", .session => "statusLine.session", }; try appendShadowedUserSource(writer, field, true, source, &wrote_header); } try appendShadowedUserSource( writer, "notifications.turn_end", patch.notification_turn_end != null, sources.notification_turn_end, &wrote_header, ); try appendShadowedUserSource( writer, "notifications.attention_required", patch.notification_attention_required != null, sources.notification_attention_required, &wrote_header, ); try appendShadowedUserSource( writer, "notifications.max", patch.notification_max != null, sources.notification_max, &wrote_header, );} fn appendShadowedUserSource( writer: *std.Io.Writer, field: []const u8, present: bool, source: config_runtime.ConfigSource, wrote_header: *bool,) !void { if (!present or source == .user_global) return; if (!wrote_header.*) { try writer.writeAll("; fresh sessions here use higher-precedence "); wrote_header.* = true; } else { try writer.writeAll(", "); } try writer.print("{s}={s}", .{ field, @tagName(source) });} fn appendCommitCleanup(writer: *std.Io.Writer, outcome: config_runtime.CommitOutcome) !void { switch (outcome) { .unchanged => {}, .committed => |committed| try appendLegacyCleanup(writer, committed.cleanup), }} fn appendLegacyCleanup(writer: *std.Io.Writer, cleanup: config_runtime.LegacyCleanup) !void { if (cleanup.fields_removed > 0) { try writer.print( "; normalized {d} legacy value{s} across {d} workspace{s}", .{ cleanup.fields_removed, if (cleanup.fields_removed == 1) "" else "s", cleanup.workspaces_changed, if (cleanup.workspaces_changed == 1) "" else "s", }, ); } for (cleanup.recovery_paths) |path| { try writer.print("; recovery={s}", .{path}); }} pub fn Commands(comptime App: type) type { return struct { fn terminalTitle(app: *App) host.TerminalTitle { if (comptime @hasDecl(App, "terminalTitle")) { return app.terminalTitle(); } if (comptime builtin.is_test) return host.unavailable_terminal_title; @compileError("interactive session commands require a terminal title host capability"); } fn update_channel_label(app: *App) []const u8 { if (comptime @hasField(App, "upgrader")) { const Upgrader = @TypeOf(app.upgrader); if (comptime @hasDecl(Upgrader, "channel")) { return app.upgrader.channel().label(); } } return "stable"; } pub fn showStatus(app: *App) !void { const auth = app.auth.statusSnapshot(); const text = try (output_contracts.StatusSnapshot{ .model = app.selected_model.items, .update_channel = update_channel_label(app), .build_channel = if (@hasDecl(App, "build_update_channel")) App.build_update_channel.label() else "stable", .build_revision = if (@hasDecl(App, "build_revision")) App.build_revision else "", .auth = auth, .auth_help = auth.missingHelp(.interactive), .permission_mode = app.permission_engine.mode, .sandbox_backend = sandbox.effectiveBackend( app.permission_engine.mode, app.permission_state.sandbox_backend, ), .workspace_root = app.workspace_root, .history_turns = app.session.historyLen(), .session_permission_grants = app.permission_engine.grants.items.len, .agent_step_limit = app.agent_step_limit, }).renderInteractiveBody(app.alloc); defer app.alloc.free(text); try app.writeDomainNotice(.{ .topic = "status", .tone = .neutral, .body = text }, true); } pub fn handleSettings(app: *App, rest: []const u8) !void { const trimmed = std.mem.trim(u8, rest, " \t"); if (trimmed.len == 0) { try writeSettingsStatus(app); return; } const first = splitFirstWord(trimmed) orelse { try writeSettingsUsage(app); return; }; if (!std.ascii.eqlIgnoreCase(first.word, "startup-scrollback")) { try writeSettingsUsage(app); return; } const value_split = splitFirstWord(first.rest); if (value_split) |split| { if (split.rest.len != 0) { try writeSettingsUsage(app); return; } if (std.ascii.eqlIgnoreCase(split.word, "on")) { try saveStartupScrollbackSetting(app, true); return; } if (std.ascii.eqlIgnoreCase(split.word, "off")) { try saveStartupScrollbackSetting(app, false); return; } try writeSettingsUsage(app); return; } var settings = config_runtime.loadMergedSettings(app.alloc, app.workspace_root) catch |err| { try writeSettingsLoadError(app, err); return; }; defer settings.deinit(app.alloc); try saveStartupScrollbackSetting(app, !(settings.startup_scrollback orelse true)); } pub fn handleModel(app: *App, query: []const u8) !void { if (query.len == 0) { try app.writeDomainNotice(.{ .topic = "model", .tone = .neutral, .body = app.selected_model.items }, true); return; } const resolved = try resolveModelQuery(app, query); defer app.alloc.free(resolved); try setResolvedModel(app, resolved, true); } pub fn handlePermissions(app: *App, rest: []const u8) !void { if (rest.len == 0) { try writePermissionsStatus(app); return; } if (std.ascii.eqlIgnoreCase(rest, "ask")) { try app_permission_runtime.Runtime(App).selectMode(app, .ask); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .neutral, .body = "mode set to ask" }, true); return; } if (std.ascii.eqlIgnoreCase(rest, "auto")) { try app_permission_runtime.Runtime(App).selectMode(app, .auto); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .neutral, .body = "mode set to auto" }, true); return; } if (std.ascii.eqlIgnoreCase(rest, "yolo")) { try app_permission_runtime.Runtime(App).selectMode(app, .yolo); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .warning, .body = "mode set to yolo" }, true); return; } if (std.ascii.eqlIgnoreCase(rest, "reset")) { try app_permission_runtime.Runtime(App).reset(app); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .neutral, .body = "permissions reset to ask, session grants cleared" }, true); return; } try app.writeDomainNotice(.{ .topic = "permissions", .tone = .@"error", .body = permissions_usage }, true); } pub fn handleAllowlist(app: *App, rest: []const u8) !void { const trimmed = std.mem.trim(u8, rest, " \t"); if (trimmed.len == 0) { try writeAllowlistStatus(app, .effective); return; } const first = splitFirstWord(trimmed).?; if (std.ascii.eqlIgnoreCase(first.word, "view")) { const view = parseAllowlistView(first.rest) orelse { try writeAllowlistUsage(app); return; }; try writeAllowlistStatus(app, view); return; } var permission_scope: config_runtime.PermissionScope = .local; var action = first; if (std.ascii.eqlIgnoreCase(first.word, "user") or std.ascii.eqlIgnoreCase(first.word, "local")) { permission_scope = if (std.ascii.eqlIgnoreCase(first.word, "user")) .user else .local; action = splitFirstWord(first.rest) orelse { try writeAllowlistUsage(app); return; }; } if (std.ascii.eqlIgnoreCase(action.word, "add")) { try handleAllowlistAdd(app, permission_scope, action.rest); return; } if (std.ascii.eqlIgnoreCase(action.word, "remove")) { try handleAllowlistRemove(app, permission_scope, action.rest); return; } if (std.ascii.eqlIgnoreCase(action.word, "reset")) { try handleAllowlistReset(app, permission_scope, action.rest); return; } try writeAllowlistUsage(app); } pub fn handleHistory(app: *App, rest: []const u8) !void { const trimmed = std.mem.trim(u8, rest, " \t"); if (std.ascii.eqlIgnoreCase(trimmed, "off")) { app.prompt_history.disable(); var attempt = config_runtime.attemptUserPreferences( app.alloc, .{ .prompt_history_enabled = false }, ); defer attempt.deinit(app.alloc); switch (attempt) { .failure => |failure| { try reportUserSettingsFailure( app, "history", failure.err, failure.cleanup, true, ); return; }, .outcome => |outcome| _ = try reportUserSettingsCommit( app, "history", .{ .prompt_history_enabled = false }, outcome, null, true, ), } return; } if (std.ascii.eqlIgnoreCase(trimmed, "on")) { var attempt = config_runtime.attemptUserPreferences( app.alloc, .{ .prompt_history_enabled = true }, ); defer attempt.deinit(app.alloc); switch (attempt) { .failure => |failure| { app.prompt_history.disable(); try reportUserSettingsFailure( app, "history", failure.err, failure.cleanup, false, ); return; }, .outcome => |outcome| { app.prompt_history.enable(); _ = try reportUserSettingsCommit( app, "history", .{ .prompt_history_enabled = true }, outcome, null, true, ); }, } return; } try app.writeDomainNotice(.{ .topic = "history", .tone = .@"error", .body = "prompt history options: on, off" }, true); } Lines 1–500 of 3154. Load more
const std = @import("std"); const builtin = @import("builtin"); const app_permission_runtime = @import("../app/app_permission_runtime.zig"); const app_session_runtime = @import("../app/app_session_runtime.zig"); const auth_runtime = @import("../auth/auth_runtime.zig"); const collections = @import("../shared/collections.zig"); const config_runtime = @import("../config/config_runtime.zig"); const debug_trace = @import("../shared/debug_trace.zig"); const host = @import("../hosts/host.zig"); const io_mod = @import("../shared/io.zig"); const model_capabilities = @import("../config/model_capabilities.zig"); const output_contracts = @import("../output/output_contracts.zig"); const permissions = @import("../permissions/permissions.zig"); const sandbox = @import("../permissions/sandbox.zig"); const worker_runtime = @import("../agent/worker_runtime.zig"); const prompt_history_runtime = @import("../app/prompt_history_runtime.zig"); const tool_dispatch = @import("../tooling/tool_dispatch.zig"); const text_utils = @import("../shared/text_utils.zig"); const types = @import("../shared/types.zig"); const render_request = @import("../../ui/render_request.zig"); const freeStringList = collections.freeStringList; const containsIgnoreCase = text_utils.containsIgnoreCase; const permissions_usage = "usage: /permissions [ask|auto|yolo|reset]\n /permissions remember <allow|deny> <tool-name> <arguments-json>\n /permissions revoke <rule-id>"; pub fn reportUserSettingsCommit( app: anytype, label: []const u8, patch: config_runtime.UserSettingsPatch, outcome: config_runtime.CommitOutcome, session_error: ?anyerror, // False when the caller announces state; drops the redundant "saved" line. announce_commit: bool, ) !?config_runtime.ConfigSources { var detailed = loadDetailedSettingsForNotice(app) catch |err| { debug_trace.logf( "config", "{s} post-commit source resolution failed err={s}", .{ label, @errorName(err) }, ); try writeUserSettingsCommitUnknownSource( app, label, outcome, session_error, err, ); return null; }; defer detailed.deinit(app.alloc); if (postCommitResolutionError(detailed)) |err| { debug_trace.logf( "config", "{s} post-commit source resolution diagnostic err={s}", .{ label, @errorName(err) }, ); try writeUserSettingsCommitUnknownSource( app, label, outcome, session_error, err, ); return null; } var out: std.Io.Writer.Allocating = .init(app.alloc); defer out.deinit(); const benign_commit = "saved to user settings (scope=user)"; try out.writer.writeAll(benign_commit); try appendShadowedUserSources(&out.writer, patch, detailed.sources); try appendCommitCleanup(&out.writer, outcome); if (session_error) |err| { try out.writer.print("; current-session recovery degraded ({s})", .{@errorName(err)}); } const text = try out.toOwnedSlice(); defer app.alloc.free(text); // Keep the line when any warning suffix was appended. if (announce_commit or !std.mem.eql(u8, text, benign_commit)) { try app.writeDomainNotice(.{ .topic = label, .tone = .neutral, .body = text }, true); } return detailed.sources; } fn writeUserSettingsCommitUnknownSource( app: anytype, label: []const u8, outcome: config_runtime.CommitOutcome, session_error: ?anyerror, resolution_error: anyerror, ) !void { var out: std.Io.Writer.Allocating = .init(app.alloc); defer out.deinit(); try out.writer.print( "saved to user settings (scope=user); next-startup source unknown ({s})", .{@errorName(resolution_error)}, ); try appendCommitCleanup(&out.writer, outcome); if (session_error) |err| { try out.writer.print("; current-session recovery degraded ({s})", .{@errorName(err)}); } const text = try out.toOwnedSlice(); defer app.alloc.free(text); try app.writeDomainNotice(.{ .topic = label, .tone = .warning, .body = text }, true); } pub fn reportUserSettingsFailure( app: anytype, label: []const u8, err: anyerror, cleanup: config_runtime.LegacyCleanup, runtime_changed: bool, ) !void { var out: std.Io.Writer.Allocating = .init(app.alloc); defer out.deinit(); if (err == error.SettingsCommitIndeterminate) { try out.writer.print( "user settings persistence uncertain (scope=user, error={s})", .{@errorName(err)}, ); } else { try out.writer.print( "{s}not saved to user settings ({s})", .{ if (runtime_changed) "active for this process but " else "", @errorName(err) }, ); } try appendLegacyCleanup(&out.writer, cleanup); const text = try out.toOwnedSlice(); defer app.alloc.free(text); try app.writeDomainNotice(.{ .topic = label, .tone = .@"error", .body = text }, true); } fn loadDetailedSettingsForNotice(app: anytype) !config_runtime.DetailedSettings { if (comptime @hasDecl(@TypeOf(app.*), "loadDetailedSettingsForNotice")) { return app.loadDetailedSettingsForNotice(); } return config_runtime.loadMergedSettingsDetailed(app.alloc, app.workspace_root); } fn postCommitResolutionError( detailed: config_runtime.DetailedSettings, ) ?anyerror { for (detailed.diagnostics) |diagnostic| { if (diagnostic.layer != .user) continue; switch (diagnostic.cause) { .malformed_settings => return error.InvalidSettingsFormat, .settings_too_large => return error.SettingsPrimaryTooLarge, .durable_path_unsafe => return error.DurablePathUnsafe, .private_state_permissions_unsupported => return error.PrivateStatePermissionsUnsupported, .invalid_model_id => return error.InvalidModelValue, .retired_skill_match_fuzzy => return error.InvalidSettingsFormat, .invalid_context_limits => return error.InvalidSettingsFormat, .invalid_additional_directories => return error.InvalidSettingsFormat, .ignored_project_user_only_setting, .legacy_workspace_preferences, .manual_backup_available, => {}, } } return null; } fn appendShadowedUserSources( writer: *std.Io.Writer, patch: config_runtime.UserSettingsPatch, sources: config_runtime.ConfigSources, ) !void { var wrote_header = false; try appendShadowedUserSource(writer, "model", patch.model != null, sources.model, &wrote_header); try appendShadowedUserSource(writer, "permission_mode", patch.permission_mode != null, sources.permission_mode, &wrote_header); try appendShadowedUserSource(writer, "effort", patch.effort != null, sources.effort, &wrote_header); try appendShadowedUserSource(writer, "fast_mode", patch.fast_mode != null, sources.fast_mode, &wrote_header); try appendShadowedUserSource(writer, "startup_scrollback", patch.startup_scrollback != null, sources.startup_scrollback, &wrote_header); try appendShadowedUserSource(writer, "input_appearance", patch.input_appearance != null, sources.input_appearance, &wrote_header); try appendShadowedUserSource(writer, "prompt_history", patch.prompt_history_enabled != null, sources.prompt_history_enabled, &wrote_header); if (patch.statusline_item) |item| { const source = switch (item.item) { .sandbox => sources.statusline_sandbox, .context => sources.statusline_context, .session => sources.statusline_session, }; const field = switch (item.item) { .sandbox => "statusLine.sandbox", .context => "statusLine.context", .session => "statusLine.session", }; try appendShadowedUserSource(writer, field, true, source, &wrote_header); } try appendShadowedUserSource( writer, "notifications.turn_end", patch.notification_turn_end != null, sources.notification_turn_end, &wrote_header, ); try appendShadowedUserSource( writer, "notifications.attention_required", patch.notification_attention_required != null, sources.notification_attention_required, &wrote_header, ); try appendShadowedUserSource( writer, "notifications.max", patch.notification_max != null, sources.notification_max, &wrote_header, ); } fn appendShadowedUserSource( writer: *std.Io.Writer, field: []const u8, present: bool, source: config_runtime.ConfigSource, wrote_header: *bool, ) !void { if (!present or source == .user_global) return; if (!wrote_header.*) { try writer.writeAll("; fresh sessions here use higher-precedence "); wrote_header.* = true; } else { try writer.writeAll(", "); } try writer.print("{s}={s}", .{ field, @tagName(source) }); } fn appendCommitCleanup(writer: *std.Io.Writer, outcome: config_runtime.CommitOutcome) !void { switch (outcome) { .unchanged => {}, .committed => |committed| try appendLegacyCleanup(writer, committed.cleanup), } } fn appendLegacyCleanup(writer: *std.Io.Writer, cleanup: config_runtime.LegacyCleanup) !void { if (cleanup.fields_removed > 0) { try writer.print( "; normalized {d} legacy value{s} across {d} workspace{s}", .{ cleanup.fields_removed, if (cleanup.fields_removed == 1) "" else "s", cleanup.workspaces_changed, if (cleanup.workspaces_changed == 1) "" else "s", }, ); } for (cleanup.recovery_paths) |path| { try writer.print("; recovery={s}", .{path}); } } pub fn Commands(comptime App: type) type { return struct { fn terminalTitle(app: *App) host.TerminalTitle { if (comptime @hasDecl(App, "terminalTitle")) { return app.terminalTitle(); } if (comptime builtin.is_test) return host.unavailable_terminal_title; @compileError("interactive session commands require a terminal title host capability"); } fn update_channel_label(app: *App) []const u8 { if (comptime @hasField(App, "upgrader")) { const Upgrader = @TypeOf(app.upgrader); if (comptime @hasDecl(Upgrader, "channel")) { return app.upgrader.channel().label(); } } return "stable"; } pub fn showStatus(app: *App) !void { const auth = app.auth.statusSnapshot(); const text = try (output_contracts.StatusSnapshot{ .model = app.selected_model.items, .update_channel = update_channel_label(app), .build_channel = if (@hasDecl(App, "build_update_channel")) App.build_update_channel.label() else "stable", .build_revision = if (@hasDecl(App, "build_revision")) App.build_revision else "", .auth = auth, .auth_help = auth.missingHelp(.interactive), .permission_mode = app.permission_engine.mode, .sandbox_backend = sandbox.effectiveBackend( app.permission_engine.mode, app.permission_state.sandbox_backend, ), .workspace_root = app.workspace_root, .history_turns = app.session.historyLen(), .session_permission_grants = app.permission_engine.grants.items.len, .agent_step_limit = app.agent_step_limit, }).renderInteractiveBody(app.alloc); defer app.alloc.free(text); try app.writeDomainNotice(.{ .topic = "status", .tone = .neutral, .body = text }, true); } pub fn handleSettings(app: *App, rest: []const u8) !void { const trimmed = std.mem.trim(u8, rest, " \t"); if (trimmed.len == 0) { try writeSettingsStatus(app); return; } const first = splitFirstWord(trimmed) orelse { try writeSettingsUsage(app); return; }; if (!std.ascii.eqlIgnoreCase(first.word, "startup-scrollback")) { try writeSettingsUsage(app); return; } const value_split = splitFirstWord(first.rest); if (value_split) |split| { if (split.rest.len != 0) { try writeSettingsUsage(app); return; } if (std.ascii.eqlIgnoreCase(split.word, "on")) { try saveStartupScrollbackSetting(app, true); return; } if (std.ascii.eqlIgnoreCase(split.word, "off")) { try saveStartupScrollbackSetting(app, false); return; } try writeSettingsUsage(app); return; } var settings = config_runtime.loadMergedSettings(app.alloc, app.workspace_root) catch |err| { try writeSettingsLoadError(app, err); return; }; defer settings.deinit(app.alloc); try saveStartupScrollbackSetting(app, !(settings.startup_scrollback orelse true)); } pub fn handleModel(app: *App, query: []const u8) !void { if (query.len == 0) { try app.writeDomainNotice(.{ .topic = "model", .tone = .neutral, .body = app.selected_model.items }, true); return; } const resolved = try resolveModelQuery(app, query); defer app.alloc.free(resolved); try setResolvedModel(app, resolved, true); } pub fn handlePermissions(app: *App, rest: []const u8) !void { if (rest.len == 0) { try writePermissionsStatus(app); return; } if (std.ascii.eqlIgnoreCase(rest, "ask")) { try app_permission_runtime.Runtime(App).selectMode(app, .ask); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .neutral, .body = "mode set to ask" }, true); return; } if (std.ascii.eqlIgnoreCase(rest, "auto")) { try app_permission_runtime.Runtime(App).selectMode(app, .auto); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .neutral, .body = "mode set to auto" }, true); return; } if (std.ascii.eqlIgnoreCase(rest, "yolo")) { try app_permission_runtime.Runtime(App).selectMode(app, .yolo); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .warning, .body = "mode set to yolo" }, true); return; } if (std.ascii.eqlIgnoreCase(rest, "reset")) { try app_permission_runtime.Runtime(App).reset(app); try app.writeDomainNotice(.{ .topic = "permissions", .tone = .neutral, .body = "permissions reset to ask, session grants cleared" }, true); return; } try app.writeDomainNotice(.{ .topic = "permissions", .tone = .@"error", .body = permissions_usage }, true); } pub fn handleAllowlist(app: *App, rest: []const u8) !void { const trimmed = std.mem.trim(u8, rest, " \t"); if (trimmed.len == 0) { try writeAllowlistStatus(app, .effective); return; } const first = splitFirstWord(trimmed).?; if (std.ascii.eqlIgnoreCase(first.word, "view")) { const view = parseAllowlistView(first.rest) orelse { try writeAllowlistUsage(app); return; }; try writeAllowlistStatus(app, view); return; } var permission_scope: config_runtime.PermissionScope = .local; var action = first; if (std.ascii.eqlIgnoreCase(first.word, "user") or std.ascii.eqlIgnoreCase(first.word, "local")) { permission_scope = if (std.ascii.eqlIgnoreCase(first.word, "user")) .user else .local; action = splitFirstWord(first.rest) orelse { try writeAllowlistUsage(app); return; }; } if (std.ascii.eqlIgnoreCase(action.word, "add")) { try handleAllowlistAdd(app, permission_scope, action.rest); return; } if (std.ascii.eqlIgnoreCase(action.word, "remove")) { try handleAllowlistRemove(app, permission_scope, action.rest); return; } if (std.ascii.eqlIgnoreCase(action.word, "reset")) { try handleAllowlistReset(app, permission_scope, action.rest); return; } try writeAllowlistUsage(app); } pub fn handleHistory(app: *App, rest: []const u8) !void { const trimmed = std.mem.trim(u8, rest, " \t"); if (std.ascii.eqlIgnoreCase(trimmed, "off")) { app.prompt_history.disable(); var attempt = config_runtime.attemptUserPreferences( app.alloc, .{ .prompt_history_enabled = false }, ); defer attempt.deinit(app.alloc); switch (attempt) { .failure => |failure| { try reportUserSettingsFailure( app, "history", failure.err, failure.cleanup, true, ); return; }, .outcome => |outcome| _ = try reportUserSettingsCommit( app, "history", .{ .prompt_history_enabled = false }, outcome, null, true, ), } return; } if (std.ascii.eqlIgnoreCase(trimmed, "on")) { var attempt = config_runtime.attemptUserPreferences( app.alloc, .{ .prompt_history_enabled = true }, ); defer attempt.deinit(app.alloc); switch (attempt) { .failure => |failure| { app.prompt_history.disable(); try reportUserSettingsFailure( app, "history", failure.err, failure.cleanup, false, ); return; }, .outcome => |outcome| { app.prompt_history.enable(); _ = try reportUserSettingsCommit( app, "history", .{ .prompt_history_enabled = true }, outcome, null, true, ); }, } return; } try app.writeDomainNotice(.{ .topic = "history", .tone = .@"error", .body = "prompt history options: on, off" }, true); }