Files
pi_harness/packages/agent/test/harness/repo.test.ts
T
ArcadiaLin 7198e78f99 feat(agent): support custom metadata in jsonl session headers (#6417)
Allow callers to attach an opaque JSON object to the session header so
application context needed to rebuild a harness (for example an agent
profile reference) is readable from the first line alone, without
scanning session entries. The field is optional and ignored by readers
that do not use it; fork inherits the source metadata unless overridden.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:18:43 +02:00

93 lines
4.8 KiB
TypeScript

import { existsSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { JsonlSessionRepo } from "../../src/harness/session/jsonl-repo.ts";
import { InMemorySessionRepo } from "../../src/harness/session/memory-repo.ts";
import { createAssistantMessage, createTempDir, createUserMessage } from "./session-test-utils.ts";
describe("InMemorySessionRepo", () => {
it("opens, deletes, and forks by metadata", async () => {
const repo = new InMemorySessionRepo();
const session = await repo.create({ id: "session-1" });
const metadata = await session.getMetadata();
const user1 = await session.appendMessage(createUserMessage("one"));
const assistant1 = await session.appendMessage(createAssistantMessage("two"));
const user2 = await session.appendMessage(createUserMessage("three"));
expect(await repo.open(metadata)).toBe(session);
expect((await repo.list()).map((info) => info.id)).toEqual(["session-1"]);
const fork = await repo.fork(metadata, { entryId: user2, id: "session-2" });
expect((await fork.getEntries()).map((entry) => entry.id)).toEqual([user1, assistant1]);
const fullFork = await repo.fork(metadata, { id: "session-3" });
expect((await fullFork.getEntries()).map((entry) => entry.id)).toEqual([user1, assistant1, user2]);
await repo.delete(metadata);
await expect(repo.open(metadata)).rejects.toThrow("Session not found: session-1");
});
});
describe("JsonlSessionRepo", () => {
it("stores sessions below encoded cwd directories and lists by cwd", async () => {
const root = createTempDir();
const env = new NodeExecutionEnv({ cwd: root });
const cwd = "/tmp/my-project";
const otherCwd = "/tmp/other-project";
const repo = new JsonlSessionRepo({ fs: env, sessionsRoot: root });
const session = await repo.create({ cwd, id: "019de8c2-de29-73e9-ae0c-e134db34c447" });
const otherSession = await repo.create({ cwd: otherCwd, id: "other-session" });
const metadata = await session.getMetadata();
const otherMetadata = await otherSession.getMetadata();
expect(metadata.path).toContain("--tmp-my-project--");
expect(otherMetadata.path).toContain("--tmp-other-project--");
expect(existsSync(metadata.path)).toBe(true);
expect((await repo.list({ cwd })).map((sessionMetadata) => sessionMetadata.id)).toEqual([metadata.id]);
expect((await repo.list()).map((sessionMetadata) => sessionMetadata.id).sort()).toEqual(
[metadata.id, otherMetadata.id].sort(),
);
});
it("opens, deletes, and forks by metadata", async () => {
const root = createTempDir();
const env = new NodeExecutionEnv({ cwd: root });
const repo = new JsonlSessionRepo({ fs: env, sessionsRoot: root });
const source = await repo.create({ cwd: "/tmp/source", id: "source-session" });
const sourceMetadata = await source.getMetadata();
const user1 = await source.appendMessage(createUserMessage("one"));
const assistant1 = await source.appendMessage(createAssistantMessage("two"));
const user2 = await source.appendMessage(createUserMessage("three"));
await expect((await repo.open(sourceMetadata)).getMetadata()).resolves.toEqual(sourceMetadata);
const fork = await repo.fork(sourceMetadata, { cwd: "/tmp/target", id: "fork-session", entryId: user2 });
const forkMetadata = await fork.getMetadata();
expect(forkMetadata.cwd).toBe("/tmp/target");
expect(forkMetadata.parentSessionPath).toBe(sourceMetadata.path);
expect((await fork.getEntries()).map((entry) => entry.id)).toEqual([user1, assistant1]);
const fullFork = await repo.fork(sourceMetadata, { cwd: "/tmp/target", id: "full-fork-session" });
expect((await fullFork.getEntries()).map((entry) => entry.id)).toEqual([user1, assistant1, user2]);
await repo.delete(sourceMetadata);
expect(existsSync(sourceMetadata.path)).toBe(false);
await expect(repo.open(sourceMetadata)).rejects.toThrow("Session not found");
});
it("persists header metadata through create, list, and fork", async () => {
const root = createTempDir();
const env = new NodeExecutionEnv({ cwd: root });
const repo = new JsonlSessionRepo({ fs: env, sessionsRoot: root });
const source = await repo.create({
cwd: "/tmp/source",
id: "source-session",
metadata: { profile: "reviewer" },
});
const sourceMetadata = await source.getMetadata();
expect(sourceMetadata.metadata).toEqual({ profile: "reviewer" });
expect((await repo.list({ cwd: "/tmp/source" })).map((listed) => listed.metadata)).toEqual([
{ profile: "reviewer" },
]);
const fork = await repo.fork(sourceMetadata, { cwd: "/tmp/target", id: "fork-session" });
expect((await fork.getMetadata()).metadata).toEqual({ profile: "reviewer" });
const overridden = await repo.fork(sourceMetadata, {
cwd: "/tmp/target",
id: "overridden-session",
metadata: { profile: "writer" },
});
expect((await overridden.getMetadata()).metadata).toEqual({ profile: "writer" });
});
});