Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 379 additions & 0 deletions electron/ai-edition/agent-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1640,3 +1640,382 @@ describe("getCursorTrack", () => {
expect(isMutatingTool("getCursorTrack")).toBe(false);
});
});

// ─── D-ANCHOR: a zoom's focus is something a result can be held to ──────────
//
// `focus` was the one argument of a zoom write that no result ever mentioned. A
// span covering no clip is refused, a depth off the table is refused, and the
// span that actually landed is reported — but a focus ON the pointer and a focus
// half a frame away from it produced byte-identical results, so nothing
// downstream could tell the two apart. These tests hold both halves: the
// measurement is present and correct where the runtime can make it, and ABSENT
// everywhere it would be a claim the runtime has no standing to make.

/** A pointer parked at one place over a source window, sampled at 10 Hz — the
* shape a zoom's focus is usually aimed at, and the one whose spread is 0 so a
* test can assert the reported position exactly. */
function parkedSamples(fromSec: number, toSec: number, cx: number, cy: number) {
const out: Array<{ timeMs: number; cx: number; cy: number }> = [];
for (let ms = Math.round(fromSec * 1000); ms <= Math.round(toSec * 1000); ms += 100) {
out.push({ timeMs: ms, cx, cy });
}
return out;
}

function withTrack(
samples: Array<{ timeMs: number; cx: number; cy: number }>,
assetId = "asset_1",
) {
return { cursorTelemetry: { load: { status: "ok" as const, assetId, samples } } };
}

/** Two recordings, laid back to back. Their clips cover DIFFERENT ruler spans
* from IDENTICAL source windows, which is what makes reading one asset's track
* as if it described the other silently plausible. */
function twoAssetDocument(): AxcutDocument {
const base = createEmptyDocument({ title: "Two", projectId: "proj_two" });
return documentSchema.parse({
...base,
project: { ...base.project, primaryAssetId: "asset_1" },
assets: [
{ id: "asset_1", kind: "video", label: "Screen", originalPath: "C:/a.mp4", durationSec: 10 },
{ id: "asset_2", kind: "video", label: "B-roll", originalPath: "C:/b.mp4", durationSec: 10 },
],
timeline: {
...base.timeline,
clips: [
{
id: "clip_a",
assetId: "asset_1",
sourceStartSec: 0,
sourceEndSec: 10,
timelineStartSec: 0,
timelineEndSec: 10,
wordRefs: [],
origin: "user",
reason: "",
},
{
id: "clip_b",
assetId: "asset_2",
sourceStartSec: 0,
sourceEndSec: 10,
timelineStartSec: 10,
timelineEndSec: 20,
wordRefs: [],
origin: "user",
reason: "",
},
],
},
});
}

/** Zoom ids are fresh uuids, so two runs of the same write differ in the one
* place that carries no meaning. Everything else must match byte for byte. */
function withoutIds(document: AxcutDocument | undefined): string {
return JSON.stringify(document).replace(/zoom_[0-9a-f-]+/g, "zoom_x");
}

describe("addZoom answers for the focus it was given", () => {
it("reports where the pointer really was, beside the focus it received", () => {
// The samples outside the span are the other half of the assertion: a
// report that averaged the whole track would land nowhere near (0.8, 0.7).
const samples = [...parkedSamples(2, 6, 0.8, 0.7), ...parkedSamples(20, 25, 0.1, 0.2)];
const result = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 2, endSec: 6, focus: { cx: 0.2, cy: 0.1 } }),
withTrack(samples),
);
const anchor = JSON.parse(result.resultJson).cursorAnchor;

expect(result.ok).toBe(true);
expect(anchor.available).toBe(true);
expect(anchor.focus).toEqual({ cx: 0.2, cy: 0.1 });
expect(anchor.cursor).toEqual({ cx: 0.8, cy: 0.7 });
expect(anchor.offset).toBeCloseTo(Math.hypot(0.6, 0.6), 3);
// A parked pointer has nowhere to stray: `spread` is what tells a reader
// whether the single position above stands for anything.
expect(anchor.spread).toBe(0);
expect(anchor.samples).toBe(41);
});

it("echoes the default focus a call never set", () => {
// The case a caller cannot reconstruct from its own arguments: it sent no
// focus, so it has no record that it asked for the centre of the frame.
const result = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 2, endSec: 6 }),
withTrack(parkedSamples(2, 6, 0.8, 0.7)),
);
const anchor = JSON.parse(result.resultJson).cursorAnchor;

expect(anchor.focus).toEqual({ cx: 0.5, cy: 0.5 });
expect(anchor.cursor).not.toEqual(anchor.focus);
});

it("reports; it does not place — the document is identical either way", () => {
// THE invariant. This is a reporting change: a zoom written by a runtime
// that can read telemetry and one written by a runtime that cannot must be
// the same zoom, in the same place, described to the user the same way.
const args = JSON.stringify({ startSec: 2, endSec: 6, focus: { cx: 0.2, cy: 0.1 } });
const blind = executeAgentTool(fixtureDocument(), "addZoom", args);
const seeing = executeAgentTool(
fixtureDocument(),
"addZoom",
args,
withTrack(parkedSamples(2, 6, 0.8, 0.7)),
);

expect(withoutIds(seeing.document)).toEqual(withoutIds(blind.document));
expect(seeing.summary).toEqual(blind.summary);
// …and the ONLY difference is in the report.
expect(seeing.resultJson).toContain("cursorAnchor");
expect(blind.resultJson).not.toContain("cursorAnchor");
});

it("measures the span that LANDED, not the span that was asked for", () => {
// CLAMP. `addZoom {20,40}` on a 24.70 s clip stores 20–24.704. Measuring the
// requested window would let 152 samples the zoom never covers outvote the
// 48 it does, and answer (0.1, 0.1) for a pointer that sat at (0.9, 0.9)
// throughout the zoom.
const samples = [...parkedSamples(20, 24.7, 0.9, 0.9), ...parkedSamples(24.8, 40, 0.1, 0.1)];
const result = executeAgentTool(
shortSingleClip(),
"addZoom",
JSON.stringify({ startSec: 20, endSec: 40 }),
withTrack(samples),
);
const payload = JSON.parse(result.resultJson);

expect(payload.clamped).toBe(true);
expect(payload.cursorAnchor.cursor).toEqual({ cx: 0.9, cy: 0.9 });
expect(payload.cursorAnchor.samples).toBe(48);
});

it("spans every fragment when a zoom is split across two clips", () => {
// FRAGMENTATION. Two fragments, two source windows, one answer: reporting
// only the first would describe half the zoom and say nothing about it.
const samples = [
...parkedSamples(25, 29.9, 0.7, 0.3),
...parkedSamples(30, 35, 0.7, 0.3),
...parkedSamples(36, 40, 0.1, 0.9),
];
const result = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 25, endSec: 35 }),
withTrack(samples),
);
const payload = JSON.parse(result.resultJson);

expect(payload.fragments).toBe(2);
// 50 from the first fragment and 51 from the second: the first alone would
// be 50, and the decoy after the zoom ends would drag the position off
// (0.7, 0.3) if the window were the ruler span rather than the anchors.
expect(payload.cursorAnchor.samples).toBe(101);
expect(payload.cursorAnchor.cursor).toEqual({ cx: 0.7, cy: 0.3 });
});

it("has no window to report on when nothing was placed", () => {
// NOTHING PLACED. The span covers no clip, so the write is refused and
// there is no landed window to measure — the refusal must not grow a
// measurement of a zoom that does not exist.
const telemetry = withTrack(parkedSamples(0, 30, 0.8, 0.7));
const nowhere = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 90, endSec: 95 }),
telemetry,
);
expect(nowhere.ok).toBe(false);
expect(nowhere.resultJson).toContain("covers no clip");
expect(nowhere.resultJson).not.toContain("cursorAnchor");

// Same telemetry, a span that does cover a clip: the field is there.
const somewhere = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 2, endSec: 6 }),
telemetry,
);
expect(somewhere.resultJson).toContain("cursorAnchor");
});

it("stays silent when the runtime could not look, and never calls that an absence", () => {
// Blindness is not evidence. A runtime with no reader wired, and an asset
// whose sidecar was checked and is genuinely missing, both leave the field
// OFF — an absent field claims nothing, while a present one saying "no
// cursor here" would put our limit into the answer as their fact.
const args = JSON.stringify({ startSec: 2, endSec: 6 });
const blind = [
undefined,
{ cursorTelemetry: {} },
{ cursorTelemetry: { load: { status: "unavailable" as const, assetId: "asset_1" } } },
{ cursorTelemetry: { load: { status: "no-sidecar" as const, assetId: "asset_1" } } },
];
for (const options of blind) {
const result = executeAgentTool(fixtureDocument(), "addZoom", args, options);
expect(result.ok).toBe(true);
expect(result.resultJson).not.toContain("cursorAnchor");
expect(result.resultJson).not.toMatch(/cursor|pointer|telemetry/i);
}
// The control: the same call, on a runtime that could read the track.
expect(
executeAgentTool(fixtureDocument(), "addZoom", args, withTrack(parkedSamples(2, 6, 0.8, 0.7)))
.resultJson,
).toContain("cursorAnchor");
});

it("says the SPAN carries no sample, not that the recording carries none", () => {
// The track was read and it simply does not reach here — a finding about
// this window, and the note has to keep it that size.
const result = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 20, endSec: 25 }),
withTrack(parkedSamples(0, 5, 0.8, 0.7)),
);
const anchor = JSON.parse(result.resultJson).cursorAnchor;

expect(anchor.available).toBe(false);
expect(anchor.reason).toBe("no-samples");
expect(anchor.note).toMatch(/fact about this span/i);
expect(anchor.note).not.toMatch(/no cursor data|has no cursor|no pointer data/i);

// One sample inside the span is enough to turn it into a measurement: the
// reason is about coverage, never about the recording.
const covered = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 20, endSec: 25 }),
withTrack([...parkedSamples(0, 5, 0.8, 0.7), { timeMs: 22_000, cx: 0.4, cy: 0.6 }]),
);
expect(JSON.parse(covered.resultJson).cursorAnchor).toMatchObject({
available: true,
cursor: { cx: 0.4, cy: 0.6 },
samples: 1,
});
});

it("will not read a position off frames a trim cuts out of playback", () => {
// The fixture trims source 10–12. Those instants are recorded and never
// seen, so a focus argued from them would describe a frame no viewer
// reaches — the same untruth as reporting the span that was requested
// instead of the one that was stored.
const samples = [...parkedSamples(10, 12, 0.9, 0.1), ...parkedSamples(12.1, 14, 0.3, 0.6)];
const result = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 10, endSec: 14 }),
withTrack(samples),
);
const anchor = JSON.parse(result.resultJson).cursorAnchor;

expect(anchor.cursor).toEqual({ cx: 0.3, cy: 0.6 });
expect(anchor.samples).toBe(20);

// And when the trim takes ALL of them, that is said rather than averaged in.
const allCut = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 10, endSec: 12 }),
withTrack(parkedSamples(10, 12, 0.9, 0.1)),
);
expect(JSON.parse(allCut.resultJson).cursorAnchor).toMatchObject({
available: false,
reason: "trimmed-out",
});
});

it("never measures a zoom against another recording's track", () => {
// Both clips draw source 0–10 from their own asset, so asset_1's samples
// fit asset_2's window arithmetically and would be read as a plausible
// answer about footage they do not describe. Silence is the only honest
// reply, and it is not a claim that asset_2 has no telemetry.
const telemetry = withTrack(parkedSamples(0, 10, 0.8, 0.7), "asset_1");
const otherAsset = executeAgentTool(
twoAssetDocument(),
"addZoom",
JSON.stringify({ startSec: 12, endSec: 18 }),
telemetry,
);
expect(otherAsset.ok).toBe(true);
expect(otherAsset.resultJson).not.toContain("cursorAnchor");

const ownAsset = executeAgentTool(
twoAssetDocument(),
"addZoom",
JSON.stringify({ startSec: 2, endSec: 8 }),
telemetry,
);
expect(JSON.parse(ownAsset.resultJson).cursorAnchor).toMatchObject({
available: true,
cursor: { cx: 0.8, cy: 0.7 },
});
});

it("gives every region of a batch its own answer", () => {
const result = executeAgentTool(
fixtureDocument(),
"addZooms",
JSON.stringify({
regions: [
{ startSec: 2, endSec: 6 },
{ startSec: 20, endSec: 24 },
],
}),
withTrack(parkedSamples(2, 6, 0.8, 0.7)),
);
const applied = JSON.parse(result.resultJson).applied;

expect(applied[0].cursorAnchor).toMatchObject({
available: true,
cursor: { cx: 0.8, cy: 0.7 },
});
// One measured, one not: a batch answer that leaned on the first region
// would describe the second with a position taken from somewhere else.
expect(applied[1].cursorAnchor).toMatchObject({ available: false, reason: "no-samples" });
});
});

describe("setZoom answers for the focus it kept", () => {
function seededZoom(): { document: AxcutDocument; zoomId: string } {
const document = executeAgentTool(
fixtureDocument(),
"addZoom",
JSON.stringify({ startSec: 2, endSec: 6, focus: { cx: 0.2, cy: 0.1 } }),
).document as AxcutDocument;
return { document, zoomId: document.zoomRanges[0].id };
}

it("measures the moved span against the focus the call did not touch", () => {
// Reshaping a zoom is mostly a question about where it now points, and the
// focus is read back off the document rather than off the arguments —
// exactly as `renderedScale` already is.
const { document, zoomId } = seededZoom();
const result = executeAgentTool(
document,
"setZoom",
JSON.stringify({ zoomId, startSec: 20, endSec: 24 }),
withTrack(parkedSamples(20, 24, 0.55, 0.45)),
);
const anchor = JSON.parse(result.resultJson).cursorAnchor;

expect(anchor.focus).toEqual({ cx: 0.2, cy: 0.1 });
expect(anchor.cursor).toEqual({ cx: 0.55, cy: 0.45 });
});

it("says nothing about the pointer when no track was read", () => {
const { document, zoomId } = seededZoom();
const result = executeAgentTool(
document,
"setZoom",
JSON.stringify({ zoomId, startSec: 20, endSec: 24 }),
);
expect(result.ok).toBe(true);
expect(result.resultJson).not.toContain("cursorAnchor");
});
});
Loading
Loading