fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388) (#8404)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-24 20:37:37 -03:00
committed by GitHub
parent 09e9ecef97
commit 1cafd328c7
10 changed files with 239 additions and 11 deletions

View File

@@ -0,0 +1 @@
- fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388)

View File

@@ -186,7 +186,8 @@
"open-sse/handlers/videoGeneration.ts": 1275,
"_rebaseline_2026_07_22_8010_codex_responses_engine": "PR #8010 (@JxnLexn) own growth: open-sse/mcp-server/schemas/tools.ts 1497->1505 (+8 = threading the new \"codex-responses\" literal into the compressionConfigureInput strategy/autoTriggerMode Zod enums and setCompressionEngineInput engine enum, mirroring the existing rtk/omniglyph enum entries; no new tool). open-sse/services/compression/strategySelector.ts 1043->1054 (+11 = one new `if (mode === \"codex-responses\")` dispatch branch in runCompression that delegates 100% to the new codexResponsesEngine.apply, mirroring the existing rtk single-mode dispatch, plus threading config.codexResponsesConfig.preserveToolNames into the shared adaptBodyForCompression call at the 3 existing call sites). src/lib/db/compression.ts (untracked, new-file cap 800) 794->845 (+51 = normalizeCodexResponsesConfig, mirroring the existing normalizeRtkConfig normalizer, plus registering \"codex-responses\" in the COMPRESSION_MODES/STACKED_PIPELINE_ENGINE_IDS/SINGLE_MODE_ENGINE sets and the getCompressionSettings load/save switch) — added to the baseline at its current size. All three are cohesive dispatch/normalizer wiring at existing chokepoints (mirroring the prior compression-mode rebaselines #6534/#6556), not extractable without hiding the mode-dispatch boundary. Covered by tests/unit/compression/codex-responses.test.ts (6) + omniglyph-registries.test.ts/types.test.ts (22, updated for the new mode).",
"_rebaseline_2026_07_22_8034_compression_exclusions_persistence": "#8034 (compression exclusions) own growth: src/lib/db/compression.ts 845->850 (+5 = threading the new compressionExclusions field through the existing getCompressionSettings/saveCompressionSettings load/save switch over the shared key_value compression namespace — no new table, no raw SQL). Mirrors the prior compression-field rebaselines (#8010 codex-responses normalizer at the same chokepoint); the load/save switch is a single dispatch boundary, not extractable without hiding it. Covered by the PR's 8 node:test + 3 vitest cases.",
"src/lib/db/compression.ts": 866,
"_rebaseline_2026_07_24_8388_compression_detail_persist": "#8388 (compression engine DETAIL settings — Headroom/session-dedup/CCR — dropped on save) own growth: src/lib/db/compression.ts 866->872 (+6 = irreducible call-site wiring at the existing getCompressionSettings/updateCompressionSettings chokepoint: one import line, one `...buildDetailConfigDefaults()` spread in the seed config, and one `case \"sessionDedup\": case \"ccr\": applyDetailConfigUpdate(config, key, parsed); break;` load-switch case, mirroring the existing headroom/#8056 case immediately above it). The actual normalizer logic (normalizeSessionDedupConfig/normalizeCcrConfig, matching SESSION_DEDUP_SCHEMA/CCR_SCHEMA bounds) was EXTRACTED into a new leaf src/lib/db/compressionDetailNormalizers.ts (well under cap) so this frozen file only carries the minimal dispatch wiring. Covered by tests/unit/8388-compression-detail-persist.test.ts (schema-accept + full DB save->reload round-trip for both new sub-objects, plus a no-regression assertion on the existing headroom round-trip).",
"src/lib/db/compression.ts": 872,
"open-sse/mcp-server/schemas/tools.ts": 1505,
"open-sse/mcp-server/server.ts": 1555,
"open-sse/mcp-server/tools/advancedTools.ts": 1120,

View File

@@ -0,0 +1,26 @@
// Resolves the persisted per-engine DETAIL sub-object (settings.headroom / .sessionDedup /
// .ccr) for a stacked-pipeline step. Extracted out of strategySelector.ts (frozen at cap by
// file-size-baseline.json — see scripts/check/check-file-size.mjs) rather than growing that
// file inline.
//
// #8056 wired settings.headroom.minRows into buildStepOptions so the dashboard value takes
// effect even when the stacked-pipeline step itself carries no config. #8388 extends the same
// merge to session-dedup and ccr, whose detail settings previously had nowhere to persist to
// (see compressionDetailNormalizers.ts on the DB write side of the same gap).
import type { CompressionConfig, CompressionPipelineStep } from "./types.ts";
export function resolveStepDetailConfig(
engine: CompressionPipelineStep["engine"],
config: CompressionConfig | undefined
): Record<string, unknown> {
switch (engine) {
case "headroom":
return (config?.headroom as Record<string, unknown> | undefined) ?? {};
case "session-dedup":
return (config?.sessionDedup as Record<string, unknown> | undefined) ?? {};
case "ccr":
return (config?.ccr as Record<string, unknown> | undefined) ?? {};
default:
return {};
}
}

View File

@@ -28,6 +28,7 @@ import {
decideStep,
mergeStackStep,
} from "./stackedStepCore.ts";
import { resolveStepDetailConfig } from "./stepDetailConfig.ts";
import { registerBuiltinCompressionEngines } from "./engines/index.ts";
import { getCompressionEngine, getEngineEntry } from "./engines/registry.ts";
import { codexResponsesEngine } from "./engines/codexResponses/index.ts";
@@ -736,13 +737,12 @@ function buildStepOptions(
step: CompressionPipelineStep,
options?: StackOptions
): CompressionEngineApplyOptions {
// Headroom detail (minRows) lives on settings.headroom, not only on step.config.
// Merge it so the stacked runner honors the dashboard value (#8056). Explicit
// step.config still wins so combo pipelines can override per step.
const headroomDetail =
step.engine === "headroom" ? (options?.config?.headroom ?? {}) : {};
// Detail sub-objects (headroom.minRows #8056; sessionDedup/ccr #8388) live on
// settings.<engine>, not only on step.config. Merge them so the stacked runner
// honors the dashboard value. Explicit step.config still wins so combo pipelines
// can override per step. See resolveStepDetailConfig (stepDetailConfig.ts).
const stepConfig: Record<string, unknown> = {
...headroomDetail,
...resolveStepDetailConfig(step.engine, options?.config),
...(step.config ?? {}),
...(step.intensity ? { intensity: step.intensity } : {}),
};

View File

@@ -220,6 +220,10 @@ export interface CompressionConfig {
ultra?: UltraConfig;
/** Headroom SmartCrusher detail settings (minRows gate). */
headroom?: HeadroomConfig;
/** Session Dedup detail settings (minBlockChars / fuzzy, #8388). */
sessionDedup?: SessionDedupConfig;
/** CCR (context-cache-retrieval) detail settings (minChars / retrievalRampFactor, #8388). */
ccr?: CcrConfig;
/** Provider-delegated context editing (Claude/Anthropic only). */
contextEditing?: ContextEditingConfig;
/** Opt-in cache-aligned live-zone compression (default disabled). */
@@ -563,6 +567,39 @@ export const DEFAULT_HEADROOM_CONFIG: HeadroomConfig = {
minRows: 8,
};
// ─── Session Dedup detail settings ───────────────────────────────────────────
// Persisted under compression settings key `sessionDedup` (#8388 — was previously
// rendered on the detail page but had no sub-object to save into).
/** Configuration for the Session Dedup engine detail page. */
export interface SessionDedupConfig {
/** Minimum character count for a suffix block to be a dedup candidate. Matches DEFAULT_MIN_BLOCK_CHARS=80. */
minBlockChars: number;
/** Opt-in fuzzy near-duplicate dedup (replaces ~85%+ similar messages with a CCR marker). */
fuzzy: boolean;
}
export const DEFAULT_SESSION_DEDUP_CONFIG: SessionDedupConfig = {
minBlockChars: 80,
fuzzy: false,
};
// ─── CCR (context-cache-retrieval) detail settings ───────────────────────────
// Persisted under compression settings key `ccr` (#8388 — same gap as session-dedup).
/** Configuration for the CCR engine detail page. */
export interface CcrConfig {
/** Minimum character count for a block to be a CCR candidate. Matches DEFAULT_MIN_CHARS=600. */
minChars: number;
/** How steeply frequently-retrieved blocks resist compression; 1 disables the ramp. */
retrievalRampFactor: number;
}
export const DEFAULT_CCR_CONFIG: CcrConfig = {
minChars: 600,
retrievalRampFactor: 2,
};
export type { McpAccessibilityConfig } from "./engines/mcpAccessibility/constants.ts";
export {
DEFAULT_MCP_ACCESSIBILITY_CONFIG,

View File

@@ -40,6 +40,7 @@ import {
normalizePreserveSystemPromptMode,
} from "@omniroute/open-sse/services/compression/preserveSystemPromptMode.ts";
import { maybePrewarmUltraSlmOnConfig } from "@omniroute/open-sse/services/compression/ultra.ts";
import { applyDetailConfigUpdate, buildDetailConfigDefaults } from "./compressionDetailNormalizers";
const NAMESPACE = "compression";
const COMPRESSION_MODES = new Set<CompressionMode>([
@@ -612,6 +613,7 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
aggressive: normalizeAggressiveConfig(undefined),
ultra: normalizeUltraConfig(undefined),
headroom: normalizeHeadroomConfig(undefined),
...buildDetailConfigDefaults(),
contextBudget: normalizeContextBudgetConfig(undefined),
contextEditing: { ...DEFAULT_CONTEXT_EDITING_CONFIG },
liveZone: { enabled: false },
@@ -724,6 +726,10 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
case "headroomConfig":
config.headroom = normalizeHeadroomConfig(parsed);
break;
case "sessionDedup":
case "ccr":
applyDetailConfigUpdate(config, key, parsed);
break;
case "contextBudget":
config.contextBudget = normalizeContextBudgetConfig(parsed);
break;

View File

@@ -0,0 +1,70 @@
// Normalizers for the compression engine DETAIL settings sub-objects that persist to a
// single key_value row each (settings.sessionDedup / settings.ccr). Extracted out of
// src/lib/db/compression.ts (frozen at cap by file-size-baseline.json — see
// scripts/check/check-file-size.mjs) rather than growing that file inline.
//
// #8388: session-dedup and ccr detail fields (minBlockChars/fuzzy, minChars/
// retrievalRampFactor) were editable on the EngineConfigPage detail form but had no
// persisted sub-object — mirrors the #8056 headroom/minRows fix (normalizeHeadroomConfig
// in compression.ts), extended to the two engines #8056 left uncovered.
import {
DEFAULT_CCR_CONFIG,
DEFAULT_SESSION_DEDUP_CONFIG,
type CcrConfig,
type CompressionConfig,
type SessionDedupConfig,
} from "@omniroute/open-sse/services/compression/types.ts";
function toRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
}
function boundedInt(value: unknown, fallback: number, min: number, max: number): number {
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
return Math.min(max, Math.max(min, Math.floor(value)));
}
/** Matches SESSION_DEDUP_SCHEMA bounds (engines/session-dedup/index.ts). */
export function normalizeSessionDedupConfig(value: unknown): SessionDedupConfig {
const record = toRecord(value);
return {
...DEFAULT_SESSION_DEDUP_CONFIG,
minBlockChars: boundedInt(
record.minBlockChars,
DEFAULT_SESSION_DEDUP_CONFIG.minBlockChars,
1,
100000
),
fuzzy: typeof record.fuzzy === "boolean" ? record.fuzzy : DEFAULT_SESSION_DEDUP_CONFIG.fuzzy,
};
}
/** Matches CCR_SCHEMA bounds (engines/ccr/index.ts). */
export function normalizeCcrConfig(value: unknown): CcrConfig {
const record = toRecord(value);
return {
...DEFAULT_CCR_CONFIG,
minChars: boundedInt(record.minChars, DEFAULT_CCR_CONFIG.minChars, 100, 1_000_000),
retrievalRampFactor: boundedInt(
record.retrievalRampFactor,
DEFAULT_CCR_CONFIG.retrievalRampFactor,
1,
100
),
};
}
/** Default sub-objects spread into getCompressionSettings' seed config. */
export function buildDetailConfigDefaults(): Pick<CompressionConfig, "sessionDedup" | "ccr"> {
return { sessionDedup: normalizeSessionDedupConfig(undefined), ccr: normalizeCcrConfig(undefined) };
}
/** Applies a stored sessionDedup/ccr row onto config during getCompressionSettings' row scan. */
export function applyDetailConfigUpdate(
config: CompressionConfig,
key: "sessionDedup" | "ccr",
parsed: unknown
): void {
if (key === "sessionDedup") config.sessionDedup = normalizeSessionDedupConfig(parsed);
else config.ccr = normalizeCcrConfig(parsed);
}

View File

@@ -21,14 +21,17 @@ interface EngineEntry {
// Engines whose detailed config has a dedicated sub-object in the compression
// settings store. The on/off + level for ALL engines now live in the panel
// (/dashboard/context/settings, the `engines` map); only these have a place to
// persist the extra per-engine fields edited on this page. Other structural
// engines (lite, session-dedup, ccr, llmlingua, relevance) still have no
// dedicated sub-object — their page keeps the detail form + preview but has
// nothing extra to persist yet.
// persist the extra per-engine fields edited on this page. session-dedup and ccr
// joined headroom in #8388 (they previously rendered a real, editable detail form
// with no Save affordance — edits vanished on reload). Other structural engines
// (lite, llmlingua, relevance) still have no dedicated sub-object — their page
// keeps the detail form + preview but has nothing extra to persist yet.
const SETTINGS_SUBOBJECT: Record<string, string> = {
aggressive: "aggressive",
ultra: "ultra",
headroom: "headroom",
"session-dedup": "sessionDedup",
ccr: "ccr",
};
interface CompressionSettings {

View File

@@ -161,6 +161,24 @@ export const headroomConfigSchema = z
})
.strict();
// Session Dedup / CCR detail settings (#8388 — sibling gap to headroom/#8056: the
// EngineConfigPage detail form was renderable but PUT bodies had no slot to persist
// into). Ranges mirror SESSION_DEDUP_SCHEMA / CCR_SCHEMA (engines/session-dedup,
// engines/ccr) so the validation layer stays in lockstep with the engine's own bounds.
export const sessionDedupConfigSchema = z
.object({
minBlockChars: z.number().int().min(1).max(100000).optional(),
fuzzy: z.boolean().optional(),
})
.strict();
export const ccrConfigSchema = z
.object({
minChars: z.number().int().min(100).max(1_000_000).optional(),
retrievalRampFactor: z.number().min(1).max(100).optional(),
})
.strict();
const noConfigSchema = z.object({}).strict();
// Structural engines (session-dedup / ccr / headroom / relevance / llmlingua) do not
@@ -344,6 +362,8 @@ export const compressionSettingsUpdateSchema = z
aggressive: aggressiveConfigSchema.optional(),
ultra: ultraConfigSchema.optional(),
headroom: headroomConfigSchema.optional(),
sessionDedup: sessionDedupConfigSchema.optional(),
ccr: ccrConfigSchema.optional(),
contextBudget: contextBudgetConfigSchema.optional(),
contextEditing: contextEditingConfigSchema.optional(),
liveZone: z.object({ enabled: z.boolean() }).strict().optional(),

View File

@@ -0,0 +1,64 @@
// #8388 — Compression engine DETAIL settings (Headroom / session dedup / CCR) do not
// persist on save. Root cause was a two-layer gap on origin/release/v3.8.49:
// (1) the .strict() Zod schema (compressionSettingsUpdateSchema) had no `sessionDedup`
// / `ccr` top-level keys, so a PUT body carrying either was rejected outright;
// (2) even past validation, src/lib/db/compression.ts had no normalizer/switch-case
// wired for those two sub-objects (only `headroom` got the #8056 treatment), so a
// set → save → reload round-trip would silently drop the values.
// This test asserts the FULL round-trip end-to-end (schema parse -> DB write -> DB read),
// not just schema-level parsing, per the plan-file's explicit instruction.
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// Isolate DATA_DIR so this test never touches a real installed DB (see MEMORY: "teste sem
// isolateDataDir → DB REAL"). Must be set BEFORE importing anything that resolves getDbInstance().
const tmpDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-8388-"));
process.env.DATA_DIR = tmpDataDir;
const { compressionSettingsUpdateSchema } = await import(
"../../src/shared/validation/compressionConfigSchemas.ts"
);
const { resetDbInstance } = await import("../../src/lib/db/core.ts");
const { getCompressionSettings, updateCompressionSettings } = await import(
"../../src/lib/db/compression.ts"
);
test.after(() => {
resetDbInstance();
fs.rmSync(tmpDataDir, { recursive: true, force: true });
});
test("#8388: PUT body carrying ccr detail (minChars/retrievalRampFactor) is ACCEPTED by the schema", () => {
const parsed = compressionSettingsUpdateSchema.safeParse({
ccr: { minChars: 5000, retrievalRampFactor: 10 },
});
assert.equal(parsed.success, true);
});
test("#8388: PUT body carrying session-dedup detail (minBlockChars/fuzzy) is ACCEPTED by the schema", () => {
const parsed = compressionSettingsUpdateSchema.safeParse({
sessionDedup: { minBlockChars: 200, fuzzy: true },
});
assert.equal(parsed.success, true);
});
test("#8388: session-dedup detail round-trips through save -> reload (DB layer)", async () => {
await updateCompressionSettings({ sessionDedup: { minBlockChars: 321, fuzzy: true } });
const reloaded = await getCompressionSettings();
assert.deepEqual(reloaded.sessionDedup, { minBlockChars: 321, fuzzy: true });
});
test("#8388: ccr detail round-trips through save -> reload (DB layer)", async () => {
await updateCompressionSettings({ ccr: { minChars: 4242, retrievalRampFactor: 7 } });
const reloaded = await getCompressionSettings();
assert.deepEqual(reloaded.ccr, { minChars: 4242, retrievalRampFactor: 7 });
});
test("#8388: headroom minRows STILL round-trips (proves #8056 fix stays intact, no regression)", async () => {
await updateCompressionSettings({ headroom: { minRows: 5 } });
const reloaded = await getCompressionSettings();
assert.deepEqual(reloaded.headroom, { minRows: 5 });
});