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 { z } from "zod";
|
||||||
import type { TurnContext, TurnEvent } from "@x/shared/dist/turns.js";
|
import type { TurnContext, TurnEvent } from "@x/shared/dist/turns.js";
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_ELISION_POLICY,
|
||||||
ElidingContextResolver,
|
ElidingContextResolver,
|
||||||
|
createContextResolver,
|
||||||
elideHistoricImages,
|
elideHistoricImages,
|
||||||
elideHistoricMiddlePaneContent,
|
elideHistoricMiddlePaneContent,
|
||||||
elideHistoricToolResults,
|
elideHistoricToolResults,
|
||||||
|
|
@ -307,6 +309,32 @@ const POLICY_OFF = {
|
||||||
middlePaneContent: false,
|
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", () => {
|
describe("ElidingContextResolver", () => {
|
||||||
function resolver(
|
function resolver(
|
||||||
repo: InMemoryTurnRepo,
|
repo: InMemoryTurnRepo,
|
||||||
|
|
|
||||||
|
|
@ -230,10 +230,14 @@ export class ElidingContextResolver implements IContextResolver {
|
||||||
// the loop transmits.
|
// the loop transmits.
|
||||||
export function createContextResolver({
|
export function createContextResolver({
|
||||||
turnRepo,
|
turnRepo,
|
||||||
|
loadPolicy,
|
||||||
}: {
|
}: {
|
||||||
turnRepo: ITurnRepo;
|
turnRepo: ITurnRepo;
|
||||||
|
// Injectable for tests; the app default reads config/context.json.
|
||||||
|
loadPolicy?: () => ElisionPolicy;
|
||||||
}): IContextResolver {
|
}): IContextResolver {
|
||||||
return new ElidingContextResolver({
|
return new ElidingContextResolver({
|
||||||
inner: new TurnRepoContextResolver({ turnRepo }),
|
inner: new TurnRepoContextResolver({ turnRepo }),
|
||||||
|
...(loadPolicy ? { loadPolicy } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ export interface IContextResolver {
|
||||||
): Promise<z.infer<typeof ResolvedAgent>>;
|
): 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 {
|
export class TurnRepoContextResolver implements IContextResolver {
|
||||||
private readonly turnRepo: ITurnRepo;
|
private readonly turnRepo: ITurnRepo;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue