mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-10 22:20:27 +00:00
compaction: Settings overrides + GUI divider (OPE-27 3/4)
Settings -> Models grows a Context compaction card next to Token savings: the trigger % of the context window (10-95), the absolute token cap (clamped 10k-2M), and the summarizer-model pin (default: the session's own model). POST /v1/settings/compaction persists them; engines read the knobs live per check, so changes apply to running sessions immediately. The "context compacted" divider rides the existing notice machinery: the persisted `compacted` notice replays on reload (itemsFromMessages) and the live COMPACTED event appends the same info notice mid-turn. The transcript itself stays intact - outbound-only by construction. Covered by vitest (marker replay), a settings-card e2e (defaults + clamped POSTs + model pin), and a mid-session divider e2e driven by the fixtures' scripted `compacted` event.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// OPE-27 — auto-compaction GUI: the Settings card's two overrides + summarizer-model
|
||||
// pin POST through, and the "context compacted" divider renders inline mid-session
|
||||
// (driven by the fixtures' scripted `compacted` event) without touching the transcript.
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "./fixtures";
|
||||
|
||||
test("Settings: Context compaction card edits threshold, cap, and summarizer model", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
await page.getByTestId("account-row").click();
|
||||
await page.getByRole("button", { name: "Settings", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Models", exact: true }).click();
|
||||
|
||||
const card = page.getByTestId("compaction-card");
|
||||
await expect(card).toBeVisible();
|
||||
await expect(card.getByText("Context compaction")).toBeVisible();
|
||||
|
||||
// Defaults render when the backend doesn't send the fields (older-backend robustness).
|
||||
await expect(card.getByTestId("compaction-threshold")).toHaveValue("80");
|
||||
await expect(card.getByTestId("compaction-cap")).toHaveValue("250000");
|
||||
await expect(card.getByTestId("compaction-model")).toHaveValue("");
|
||||
|
||||
// Threshold edits POST as a fraction, clamped to 10–95%.
|
||||
const [req] = await Promise.all([
|
||||
page.waitForRequest(
|
||||
(r) => r.url().endsWith("/v1/settings/compaction") && r.method() === "POST",
|
||||
),
|
||||
card.getByTestId("compaction-threshold").fill("70"),
|
||||
]);
|
||||
expect(req.postDataJSON()).toEqual({ compaction_threshold_pct: 0.7 });
|
||||
|
||||
const [req2] = await Promise.all([
|
||||
page.waitForRequest(
|
||||
(r) => r.url().endsWith("/v1/settings/compaction") && r.method() === "POST",
|
||||
),
|
||||
card.getByTestId("compaction-cap").fill("100000"),
|
||||
]);
|
||||
expect(req2.postDataJSON()).toEqual({ compaction_cap_tokens: 100000 });
|
||||
|
||||
// Summarizer pin: the picker offers the session-default plus the configured models.
|
||||
const [req3] = await Promise.all([
|
||||
page.waitForRequest(
|
||||
(r) => r.url().endsWith("/v1/settings/compaction") && r.method() === "POST",
|
||||
),
|
||||
card.getByTestId("compaction-model").selectOption("gpt-4o-mini"),
|
||||
]);
|
||||
expect(req3.postDataJSON()).toEqual({ compaction_model: "gpt-4o-mini" });
|
||||
});
|
||||
|
||||
test("the compacted divider renders mid-session and the transcript stays intact", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
await page.getByText("Draft the launch note").first().click();
|
||||
const box = page.getByPlaceholder(/Ask the coworker/);
|
||||
|
||||
// An earlier exchange that must survive the compaction marker (transcript intact).
|
||||
await box.fill("remember the launch date");
|
||||
await box.press("Enter");
|
||||
await expect(page.getByText("Echo: remember the launch date").first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
|
||||
await box.fill("compact the context");
|
||||
await box.press("Enter");
|
||||
await expect(
|
||||
page.getByText("Context compacted — earlier turns were summarized").first(),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
await expect(
|
||||
page.getByText("Still on it — continuing where I left off.").first(),
|
||||
).toBeVisible();
|
||||
// Outbound-only: everything before the divider is still on screen.
|
||||
await expect(page.getByText("Echo: remember the launch date").first()).toBeVisible();
|
||||
});
|
||||
@@ -681,6 +681,14 @@ export async function mockApi(page: import("@playwright/test").Page) {
|
||||
}, 120);
|
||||
return;
|
||||
}
|
||||
// Auto-compaction (OPE-27): the server compacts mid-run and emits the marker,
|
||||
// then the turn continues normally — the divider must render inline.
|
||||
if (/compact the context/i.test(msg.text)) {
|
||||
send("compacted", { text: "Context compacted — earlier turns were summarized" });
|
||||
send("assistant_message", { text: "Still on it — continuing where I left off." });
|
||||
send("turn_done");
|
||||
return;
|
||||
}
|
||||
// A turn that dies on a provider error; the follow-up {type:"retry"} recovers.
|
||||
if (/fail the turn/i.test(msg.text)) {
|
||||
send("error", { error: "model unreachable" });
|
||||
|
||||
Reference in New Issue
Block a user