Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"generate:expo-schema": "node scripts/fetch-expo-schema.ts"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.276",
"@anthropic-ai/claude-agent-sdk": "^0.3.284",
"@cursor/sdk": "1.0.38",
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,34 @@ vi.mock("@anthropic-ai/claude-agent-sdk", { spy: true });

const FORK_SESSION_ID = "11111111-1111-4111-8111-111111111111";

const writeClaudeSession = Effect.fnUntraced(function* (configDir: string) {
const sessionEntry = (uuid: string, parentUuid: string | null, message: unknown) => ({
type: parentUuid === null ? "user" : "assistant",
uuid,
parentUuid,
sessionId: FORK_SESSION_ID,
cwd: "/repo",
isSidechain: false,
timestamp: "2026-10-01T00:00:00.000Z",
message,
});

const writeClaudeSession = Effect.fnUntraced(function* (
configDir: string,
entries: ReadonlyArray<unknown> = [
sessionEntry("aaaaaaaa-0000-4000-8000-000000000001", null, { role: "user", content: "hello" }),
sessionEntry("aaaaaaaa-0000-4000-8000-000000000002", "aaaaaaaa-0000-4000-8000-000000000001", {
role: "assistant",
content: [{ type: "text", text: "hi" }],
}),
],
) {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const projectDir = path.join(configDir, "projects", "-repo");
const entry = (uuid: string, parentUuid: string | null, message: unknown) => ({
type: parentUuid === null ? "user" : "assistant",
uuid,
parentUuid,
sessionId: FORK_SESSION_ID,
cwd: "/repo",
isSidechain: false,
timestamp: "2026-10-01T00:00:00.000Z",
message,
});
yield* fileSystem.makeDirectory(projectDir, { recursive: true });
yield* fileSystem.writeFileString(
path.join(projectDir, `${FORK_SESSION_ID}.jsonl`),
[
entry("aaaaaaaa-0000-4000-8000-000000000001", null, { role: "user", content: "hello" }),
entry("aaaaaaaa-0000-4000-8000-000000000002", "aaaaaaaa-0000-4000-8000-000000000001", {
role: "assistant",
content: [{ type: "text", text: "hi" }],
}),
]
.map((line) => JSON.stringify(line))
.join("\n"),
entries.map((line) => JSON.stringify(line)).join("\n"),
);
return projectDir;
});
Expand Down Expand Up @@ -111,6 +113,93 @@ it.effect("forks a session stored under the instance's own CLAUDE_CONFIG_DIR", (
}).pipe(Effect.provide(forkRunnerLayer)),
).pipe(Effect.provide(NodeServices.layer)),
);
it.effect("forked Claude sessions keep their compaction chain", () =>
Effect.scoped(
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const configDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-claude-fork-compact-",
});
const uuid = (n: number) => `bbbbbbbb-0000-4000-8000-00000000000${n}`;
const turn = (id: number, parent: string | null, role: "user" | "assistant") => ({
...sessionEntry(uuid(id), parent, {
role,
content: role === "user" ? `question ${id}` : [{ type: "text", text: `answer ${id}` }],
}),
type: role,
});
const projectDir = yield* writeClaudeSession(configDir, [
turn(1, null, "user"),
turn(2, uuid(1), "assistant"),
turn(3, uuid(2), "user"),
turn(4, uuid(3), "assistant"),
{
...sessionEntry(uuid(5), null, undefined),
type: "system",
subtype: "compact_boundary",
content: "Conversation compacted",
logicalParentUuid: uuid(4),
compactMetadata: {
trigger: "manual",
preTokens: 19979,
preservedSegment: { headUuid: uuid(3), anchorUuid: uuid(6), tailUuid: uuid(4) },
preservedMessages: {
anchorUuid: uuid(6),
uuids: [uuid(3), uuid(4)],
allUuids: [uuid(3), uuid(4)],
},
postTokens: 3589,
},
},
{
...turn(6, uuid(5), "user"),
isCompactSummary: true,
isVisibleInTranscriptOnly: true,
},
turn(7, uuid(4), "user"),
turn(8, uuid(7), "assistant"),
]);

const runner = yield* ClaudeAgentSdkQueryRunner;
const forked = yield* runner.forkSession({
sessionId: FORK_SESSION_ID,
options: { dir: "/repo" },
environment: { ...process.env, CLAUDE_CONFIG_DIR: configDir },
threadId: ThreadId.make("thread-fork-compaction"),
providerSessionId: ProviderSessionId.make("session-fork-compaction"),
});

const entries: ReadonlyArray<{
readonly uuid?: string;
readonly subtype?: string;
readonly compactMetadata?: {
readonly preservedSegment: Record<"headUuid" | "anchorUuid" | "tailUuid", string>;
readonly preservedMessages: {
readonly anchorUuid: string;
readonly uuids: string[];
readonly allUuids: string[];
};
};
}> = (yield* fileSystem.readFileString(path.join(projectDir, `${forked.sessionId}.jsonl`)))
.trim()
.split("\n")
.map((line) => JSON.parse(line));
const forkedUuids = new Set(entries.map((entry) => entry.uuid));
const boundary = entries.find((entry) => entry.subtype === "compact_boundary");
assert.isDefined(boundary?.compactMetadata);
const { preservedSegment, preservedMessages } = boundary!.compactMetadata!;
const danglingRefs = [
...Object.values(preservedSegment),
preservedMessages.anchorUuid,
...preservedMessages.uuids,
...preservedMessages.allUuids,
].filter((ref) => !forkedUuids.has(ref));
assert.deepEqual(danglingRefs, [], "compaction refs missing from the forked transcript");
}).pipe(Effect.provide(forkRunnerLayer)),
).pipe(Effect.provide(NodeServices.layer)),
);

const realQueryRunner = layerQueryRunner.pipe(
Layer.provide(PortScanner.layerTest),
Layer.provide(
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ minimumReleaseAgeExclude:
- expo-sqlite@58.0.13
- expo-symbols@58.0.4
- electron@44.10.0
- "@anthropic-ai/claude-agent-sdk@0.3.276"
- "@shadcn/lint@0.1.5"
- cn@0.3.2
- uniwind@1.13.0
Expand Down
Loading