From d070faddea096fe7b7dc21497b18b1900ddef0e5 Mon Sep 17 00:00:00 2001 From: Lance Woodson <65194841+woodsonl@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:31:50 -0500 Subject: [PATCH 1/2] fix(compression): honor the Auto-Clarity toggle on the output-styles path The dashboard stores cavemanOutputMode.autoClarity and shows it on two settings pages. The only code that reads it is the gate in applyCavemanOutputMode, which chatCore stopped calling in #4694 (v3.8.35). applyOutputStyles, the path chatCore uses, runs shouldBypassCavemanOutputMode unconditionally, so with Auto-Clarity off a security, irreversible-action, clarification, or order-sensitive turn still dropped every selected style. applyOutputStyles takes { autoClarity } and skips the content bypass when it is false, the same test outputMode.ts applies; chatCore passes the stored toggle. The default is on, so installs that leave the toggle alone keep today's behavior. --- open-sse/handlers/chatCore.ts | 4 +- .../compression/outputStyles/apply.ts | 13 ++- .../chatcore-compression-integration.test.ts | 92 +++++++++++++++++++ .../compression/output-styles-apply.test.ts | 14 +++ 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 38feddfc7de5..14b4f50bcb62 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1664,6 +1664,7 @@ export async function handleChatCore({ } } // Phase 4A: unified output styles (supersedes cavemanOutputMode via the back-compat shim). + // The Auto-Clarity toggle is read from cavemanOutputMode.autoClarity. let outputStyleResult: import("../services/compression/outputStyles/apply.ts").OutputStylesResult | null = null; if (config.enabled && compressionHeader?.trim().toLowerCase() !== "off") { @@ -1681,7 +1682,8 @@ export async function handleChatCore({ outputStyleResult = applyOutputStyles( body as Parameters[0], selection, - outputStyleLanguage + outputStyleLanguage, + { autoClarity: config.cavemanOutputMode?.autoClarity } ); if (outputStyleResult.applied) { body = outputStyleResult.body as typeof body; diff --git a/open-sse/services/compression/outputStyles/apply.ts b/open-sse/services/compression/outputStyles/apply.ts index f932d4b9d050..45c12be768d1 100644 --- a/open-sse/services/compression/outputStyles/apply.ts +++ b/open-sse/services/compression/outputStyles/apply.ts @@ -122,11 +122,13 @@ function buildStyleInstructions(resolved: OutputStyleSelectionEntry[], language: * - SHARED_BOUNDARIES applied once at the end (not per style). * - Single idempotency marker; re-applying is a no-op. * - Content bypass runs once across the whole turn (all-or-nothing); reason recorded. + * `options.autoClarity: false` (the Auto-Clarity Bypass toggle) skips it. */ export function applyOutputStyles( body: ChatRequestBody, selection: OutputStyleSelectionEntry[], - language = "en" + language = "en", + options: { autoClarity?: boolean } = {} ): OutputStylesResult { const resolved = resolveStyles(selection ?? [], language); if (resolved.length === 0) { @@ -172,9 +174,12 @@ export function applyOutputStyles( ); if (alreadyApplied) return { body, applied: false, skippedReason: "already_applied" }; - // Content bypass (all-or-nothing for the turn): reuse the existing rules verbatim. - const bypass = shouldBypassCavemanOutputMode(messages); - if (bypass) return { body, applied: false, skippedReason: bypass }; + // Content bypass (all-or-nothing for the turn): reuse the existing rules verbatim, + // gated on the Auto-Clarity toggle the same way applyCavemanOutputMode gates it. + if (options.autoClarity !== false) { + const bypass = shouldBypassCavemanOutputMode(messages); + if (bypass) return { body, applied: false, skippedReason: bypass }; + } return { body: { ...body, ...placeSystemInstruction(messages, body.system, instruction) }, diff --git a/tests/integration/chatcore-compression-integration.test.ts b/tests/integration/chatcore-compression-integration.test.ts index cb0ac06cec53..bb0dcc2b17ad 100644 --- a/tests/integration/chatcore-compression-integration.test.ts +++ b/tests/integration/chatcore-compression-integration.test.ts @@ -1115,3 +1115,95 @@ test("chatCore integration: caveman output mode injected when both compression a globalThis.fetch = originalFetch; } }); + +async function styleInstructionReachesUpstream( + autoClarity: boolean, + outputStyles?: Array<{ id: string; level: "lite" | "full" | "ultra" }> +) { + const provider = "openai"; + const model = "gpt-4"; + + await compressionDb.updateCompressionSettings({ + enabled: true, + defaultMode: "off", + autoTriggerTokens: 0, + ...(outputStyles ? { outputStyles } : {}), + cavemanOutputMode: { + enabled: !outputStyles, + intensity: "full", + autoClarity, + }, + }); + + const connection = await providersDb.createProviderConnection({ + provider, + apiKey: "test-key", + isActive: true, + }); + + let capturedBody = null as { messages?: Array<{ role?: string; content?: string }> } | null; + globalThis.fetch = async (_url: string | URL | Request, init?: RequestInit) => { + if (init?.body) { + capturedBody = JSON.parse(init.body as string); + } + return new Response( + JSON.stringify({ + choices: [{ message: { role: "assistant", content: "ok" } }], + usage: { prompt_tokens: 20, completion_tokens: 4, total_tokens: 24 }, + }), + { + status: 200, + headers: { "content-type": "application/json" }, + } + ); + }; + + try { + const result = await handleChatCore({ + body: { + model, + stream: false, + messages: [{ role: "user", content: "Explain this security vulnerability in detail." }], + }, + modelInfo: { provider, model }, + credentials: { apiKey: "test-key" }, + log: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }, + clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Map() }, + connectionId: connection.id, + onCredentialsRefreshed: () => {}, + onRequestSuccess: () => {}, + onStreamFailure: () => {}, + onDisconnect: () => {}, + userAgent: "test-agent", + comboName: null, + }); + + assert.ok(result.success, "Request should succeed"); + assert.ok(capturedBody, "the upstream request was captured"); + return ( + capturedBody.messages?.some( + (message) => message.role === "system" && /Output Styles/.test(message.content ?? "") + ) ?? false + ); + } finally { + globalThis.fetch = originalFetch; + } +} + +test("chatCore integration: output styles stay on a security-topic turn when Auto-Clarity is off", async () => { + assert.equal(await styleInstructionReachesUpstream(false), true); +}); + +test("chatCore integration: Auto-Clarity on keeps output styles off a security-topic turn", async () => { + assert.equal(await styleInstructionReachesUpstream(true), false); +}); + +test("chatCore integration: styles picked in the Output Styles panel stay on a security-topic turn when Auto-Clarity is off", async () => { + assert.equal( + await styleInstructionReachesUpstream(false, [ + { id: "terse-prose", level: "full" }, + { id: "less-code", level: "full" }, + ]), + true + ); +}); diff --git a/tests/unit/compression/output-styles-apply.test.ts b/tests/unit/compression/output-styles-apply.test.ts index a9a64d8213f3..10d836b6c880 100644 --- a/tests/unit/compression/output-styles-apply.test.ts +++ b/tests/unit/compression/output-styles-apply.test.ts @@ -79,6 +79,20 @@ test("content bypass is all-or-nothing across every selected style", () => { assert.equal(r.body.messages?.at(-1)?.role, "user"); // untouched }); +test("autoClarity: false keeps every selected style on a turn the bypass would skip", () => { + const r = applyOutputStyles( + { messages: [{ role: "user", content: "Explain this security vulnerability in detail." }] }, + sel(["terse-prose", "full"], ["less-code", "full"]), + "en", + { autoClarity: false } + ); + assert.equal(r.applied, true); + assert.deepEqual( + r.appliedStyles?.map((s) => s.id), + ["terse-prose", "less-code"] + ); +}); + test("no styles selected → body untouched", () => { const body = { messages: [{ role: "user", content: "Tell me a joke." }] }; const r = applyOutputStyles(body, []); From c91069a6babea3f1b68f11805eb09a315a629ff0 Mon Sep 17 00:00:00 2001 From: Lance Woodson <65194841+woodsonl@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:19:54 -0500 Subject: [PATCH 2/2] docs(changelog): add the #14551 fragment Changelog entry for the Auto-Clarity toggle fix on the output-styles path, named by PR number per changelog.d/README.md. --- changelog.d/fixes/14551-output-styles-auto-clarity.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/fixes/14551-output-styles-auto-clarity.md diff --git a/changelog.d/fixes/14551-output-styles-auto-clarity.md b/changelog.d/fixes/14551-output-styles-auto-clarity.md new file mode 100644 index 000000000000..d8d15389f829 --- /dev/null +++ b/changelog.d/fixes/14551-output-styles-auto-clarity.md @@ -0,0 +1 @@ +- **fix(compression):** the output-styles path honors the dashboard's Auto-Clarity Bypass toggle, so turning it off keeps the selected styles on security, irreversible-action, clarification, and order-sensitive turns ([#14551](https://github.com/diegosouzapw/OmniRoute/pull/14551)) — thanks @woodsonl