mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
fix(core): guard against constructing an undecorated context resolver
TurnRepoContextResolver was still freely constructible, so a future call site could silently lose elision. The factory now accepts an injectable policy loader (also keeps tests off the machine's real context.json), the raw class carries a do-not-construct note, and factory-level tests pin both the decorator wiring and the default policy behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
331a5eda4e
commit
8a952a4b7e
3 changed files with 36 additions and 0 deletions
|
|
@ -2,7 +2,9 @@ import { describe, expect, it } from "vitest";
|
|||
import type { z } from "zod";
|
||||
import type { TurnContext, TurnEvent } from "@x/shared/dist/turns.js";
|
||||
import {
|
||||
DEFAULT_ELISION_POLICY,
|
||||
ElidingContextResolver,
|
||||
createContextResolver,
|
||||
elideHistoricImages,
|
||||
elideHistoricMiddlePaneContent,
|
||||
elideHistoricToolResults,
|
||||
|
|
@ -307,6 +309,32 @@ const POLICY_OFF = {
|
|||
middlePaneContent: false,
|
||||
};
|
||||
|
||||
describe("createContextResolver", () => {
|
||||
// The factory is the app's one sanctioned constructor (DI container and
|
||||
// inspect CLI). If this stops returning the eliding decorator, every
|
||||
// construction site silently regresses to full historic replay.
|
||||
it("wraps the repo resolver in the eliding decorator", () => {
|
||||
const resolver = createContextResolver({
|
||||
turnRepo: new InMemoryTurnRepo(),
|
||||
});
|
||||
expect(resolver).toBeInstanceOf(ElidingContextResolver);
|
||||
});
|
||||
|
||||
it("applies the default policy through the decorated resolver", async () => {
|
||||
const repo = new InMemoryTurnRepo();
|
||||
repo.seed(toolTurnLog(T1, [], "x".repeat(5000)));
|
||||
// Pinned to the shipped defaults (not the machine's context.json) so
|
||||
// the test is hermetic while still exercising the full wiring.
|
||||
const resolved = await createContextResolver({
|
||||
turnRepo: repo,
|
||||
loadPolicy: () => DEFAULT_ELISION_POLICY,
|
||||
}).resolve({ previousTurnId: T1 });
|
||||
const tool = resolved.find((m) => m.role === "tool");
|
||||
expect(tool?.content).toContain("elided");
|
||||
expect(tool?.content.length).toBeLessThan(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ElidingContextResolver", () => {
|
||||
function resolver(
|
||||
repo: InMemoryTurnRepo,
|
||||
|
|
|
|||
|
|
@ -230,10 +230,14 @@ export class ElidingContextResolver implements IContextResolver {
|
|||
// the loop transmits.
|
||||
export function createContextResolver({
|
||||
turnRepo,
|
||||
loadPolicy,
|
||||
}: {
|
||||
turnRepo: ITurnRepo;
|
||||
// Injectable for tests; the app default reads config/context.json.
|
||||
loadPolicy?: () => ElisionPolicy;
|
||||
}): IContextResolver {
|
||||
return new ElidingContextResolver({
|
||||
inner: new TurnRepoContextResolver({ turnRepo }),
|
||||
...(loadPolicy ? { loadPolicy } : {}),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ export interface IContextResolver {
|
|||
): Promise<z.infer<typeof ResolvedAgent>>;
|
||||
}
|
||||
|
||||
// NOTE: app code must not construct this directly — use createContextResolver
|
||||
// (context-elision.ts), which wraps it in the eliding decorator. A raw
|
||||
// instance silently transmits full historic tool results, frames, and note
|
||||
// snapshots. Direct construction is for tests of the raw resolution only.
|
||||
export class TurnRepoContextResolver implements IContextResolver {
|
||||
private readonly turnRepo: ITurnRepo;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue