3154 lines · 128.6 KB · Zig
RawDownload
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.