mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 18:02:17 +03:00
fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388) (#8404)
This commit is contained in:
committed by
GitHub
parent
09e9ecef97
commit
1cafd328c7
1
changelog.d/fixes/8388-compression-detail-persist.md
Normal file
1
changelog.d/fixes/8388-compression-detail-persist.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388)
|
||||
@@ -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,
|
||||
|
||||
26
open-sse/services/compression/stepDetailConfig.ts
Normal file
26
open-sse/services/compression/stepDetailConfig.ts
Normal 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 {};
|
||||
}
|
||||
}
|
||||
@@ -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 } : {}),
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
70
src/lib/db/compressionDetailNormalizers.ts
Normal file
70
src/lib/db/compressionDetailNormalizers.ts
Normal 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);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
64
tests/unit/8388-compression-detail-persist.test.ts
Normal file
64
tests/unit/8388-compression-detail-persist.test.ts
Normal 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 });
|
||||
});
|
||||
Reference in New Issue
Block a user