diff --git a/src/exec/runner.ts b/src/exec/runner.ts index bbb3f2a39..74da365cd 100644 --- a/src/exec/runner.ts +++ b/src/exec/runner.ts @@ -611,7 +611,7 @@ export async function runExec(config: Config): Promise { if (config.skipPermissionsFromSettings) { stderr.write( - `${savedSkipPermissionsWarning(config.globalSettingsPath)}\n`, + `${savedSkipPermissionsWarning(config.globalSettingsPath, "exec")}\n`, ); } diff --git a/src/permission/saved-skip-warning.test.ts b/src/permission/saved-skip-warning.test.ts new file mode 100644 index 000000000..0c6b47a29 --- /dev/null +++ b/src/permission/saved-skip-warning.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, test } from "bun:test"; +import { mkdtemp, symlink } from "node:fs/promises"; +import { homedir, tmpdir } from "node:os"; +import { join } from "node:path"; +import { SETTINGS_DIR_NAME } from "../branding.js"; +import { savedSkipPermissionsWarning } from "./saved-skip-warning.js"; + +describe("savedSkipPermissionsWarning", () => { + test("TUI default machine-wide source appends the /yolo off hint", () => { + const source = join(homedir(), SETTINGS_DIR_NAME, "settings.json"); + + const warning = savedSkipPermissionsWarning(source, "tui"); + + expect(warning).toContain(source); + expect(warning).toContain("edit that file to re-enable"); + expect(warning).toContain("/yolo off"); + }); + + test("TUI symlinked-home alias of the default source still appends the hint", async () => { + const sandbox = await mkdtemp(join(tmpdir(), "corbits-skip-warning-")); + const homeLink = join(sandbox, "home"); + await symlink(homedir(), homeLink); + const aliased = join(homeLink, SETTINGS_DIR_NAME, "settings.json"); + + const warning = savedSkipPermissionsWarning(aliased, "tui"); + + expect(warning).toContain(aliased); + expect(warning).toContain("edit that file to re-enable"); + expect(warning).toContain("/yolo off"); + }); + + test("exec default source stays path-only with no slash", () => { + const source = join(homedir(), SETTINGS_DIR_NAME, "settings.json"); + + const warning = savedSkipPermissionsWarning(source, "exec"); + + expect(warning).toContain(source); + expect(warning).toContain("edit that file to re-enable"); + expect(warning).not.toContain("/yolo"); + expect(warning).toBe( + `Warning: permission prompts are disabled by saved settings at ${source}; edit that file to re-enable.`, + ); + }); + + test("exec symlinked-home alias of the default source stays path-only", async () => { + const sandbox = await mkdtemp(join(tmpdir(), "corbits-skip-warning-")); + const homeLink = join(sandbox, "home"); + await symlink(homedir(), homeLink); + const aliased = join(homeLink, SETTINGS_DIR_NAME, "settings.json"); + + const warning = savedSkipPermissionsWarning(aliased, "exec"); + + expect(warning).toContain(aliased); + expect(warning).toContain("edit that file to re-enable"); + expect(warning).not.toContain("/yolo"); + }); + + test("custom source keeps file-path wording with no false provenance on both surfaces", () => { + for (const surface of ["tui", "exec"] as const) { + const warning = savedSkipPermissionsWarning( + "/tmp/custom-corbits-settings.json", + surface, + ); + + expect(warning).toContain("/tmp/custom-corbits-settings.json"); + expect(warning).toContain("edit that file to re-enable"); + expect(warning).not.toMatch(/machine-wide|saved default|\/yolo off/i); + } + }); +}); diff --git a/src/permission/saved-skip-warning.ts b/src/permission/saved-skip-warning.ts index 307357acf..41830e826 100644 --- a/src/permission/saved-skip-warning.ts +++ b/src/permission/saved-skip-warning.ts @@ -1,5 +1,22 @@ +import { + globalSettingsPath as defaultGlobalSettingsPath, + isProgrammaticSettingsOverride, +} from "../config/settings.js"; + +function isDefaultSettingsSource(sourcePath: string): boolean { + return !isProgrammaticSettingsOverride( + sourcePath, + defaultGlobalSettingsPath(), + ); +} + export function savedSkipPermissionsWarning( globalSettingsPath: string, + surface: "tui" | "exec", ): string { - return `Warning: permission prompts are disabled by saved settings at ${globalSettingsPath}; edit that file to re-enable.`; + const base = `Warning: permission prompts are disabled by saved settings at ${globalSettingsPath}; edit that file to re-enable`; + if (surface === "tui" && isDefaultSettingsSource(globalSettingsPath)) { + return `${base} (/yolo off to re-enable).`; + } + return `${base}.`; } diff --git a/src/tui/runner/wiring.skip-permissions-warning.test.ts b/src/tui/runner/wiring.skip-permissions-warning.test.ts index d5fa9962a..c2e0863f1 100644 --- a/src/tui/runner/wiring.skip-permissions-warning.test.ts +++ b/src/tui/runner/wiring.skip-permissions-warning.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test"; +import { globalSettingsPath } from "../../config/settings.js"; import { createAppShell } from "../shell/index.js"; import { shellInternals } from "../shell/internals.js"; import { withTestRenderer } from "../harness.js"; @@ -34,12 +35,12 @@ describe("saved skip-permissions startup warning", () => { expect(warning).not.toMatch(/machine-wide|saved default|\/yolo off/i); }); - test("identifies the default settings path", async () => { - const warning = await surfacedWarning( - "/home/operator/.corbits/settings.json", - ); + test("appends the /yolo off hint for the default settings path", async () => { + const source = globalSettingsPath(); + const warning = await surfacedWarning(source); - expect(warning).toContain("/home/operator/.corbits/settings.json"); + expect(warning).toContain(source); expect(warning).toContain("edit that file to re-enable"); + expect(warning).toContain("/yolo off"); }); }); diff --git a/src/tui/runner/wiring.ts b/src/tui/runner/wiring.ts index fd6aa0837..2ff3104dc 100644 --- a/src/tui/runner/wiring.ts +++ b/src/tui/runner/wiring.ts @@ -96,7 +96,7 @@ export function surfaceSavedSkipPermissionsWarning( if (!config.skipPermissionsFromSettings) return; surfaceSystemNotice( shell, - savedSkipPermissionsWarning(config.globalSettingsPath), + savedSkipPermissionsWarning(config.globalSettingsPath, "tui"), ); } diff --git a/tests/unit/exec/runner.test.ts b/tests/unit/exec/runner.test.ts index 264f9382f..043633580 100644 --- a/tests/unit/exec/runner.test.ts +++ b/tests/unit/exec/runner.test.ts @@ -470,7 +470,11 @@ describe("runExec", () => { async () => { const { runExec: runExecUnderMock } = await import("../../../src/exec/runner.js"); - const defaultSettingsPath = join(home, "settings.json"); + const defaultSettingsPath = join( + home, + ".corbits", + "settings.json", + ); const result = await runExecUnderMock({ ...bareConfig("do the thing"), cwd,