feat(session): Explicit session id naming (#5076)

This commit is contained in:
Armin Ronacher
2026-05-27 21:32:10 +02:00
committed by GitHub
parent 7c02a55632
commit 52dc08c1f7
7 changed files with 307 additions and 27 deletions

View File

@@ -1,6 +1,6 @@
import { mkdtempSync, writeFileSync } from "node:fs";
import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { basename, join } from "node:path";
import { describe, expect, it } from "vitest";
import { SessionManager } from "../../src/core/session-manager.ts";
@@ -13,6 +13,23 @@ describe("SessionManager.newSession with custom id", () => {
expect(session.getSessionId()).toBe("my-custom-id");
});
it("allows alphanumeric session ids with interior punctuation", () => {
const session = SessionManager.inMemory();
session.newSession({ id: "abc-123_def.456" });
expect(session.getSessionId()).toBe("abc-123_def.456");
});
it("rejects invalid custom session ids", () => {
const invalidIds = ["", "-abc", "abc-", "_abc", "abc_", ".abc", "abc.", "abc/def", "abc\\def", "abc def"];
for (const id of invalidIds) {
const session = SessionManager.inMemory();
expect(() => session.newSession({ id })).toThrow(
"Session id must be non-empty, contain only alphanumeric characters",
);
}
});
it("generates a UUIDv7 id when no id is provided", () => {
const session = SessionManager.inMemory();
session.newSession();
@@ -46,6 +63,18 @@ describe("SessionManager.newSession with custom id", () => {
expect(session.getHeader()!.id).toBe(session.getSessionId());
});
it("uses the provided id when creating a persisted session", () => {
const tempDir = mkdtempSync(join(tmpdir(), "pi-session-manager-"));
const session = SessionManager.create(tempDir, tempDir, { id: "created-session-id" });
expect(session.getSessionId()).toBe("created-session-id");
expect(session.getHeader()!.id).toBe("created-session-id");
const sessionFile = session.getSessionFile()!;
expect(sessionFile).toContain("created-session-id");
expect(basename(sessionFile)).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_created-session-id\.jsonl$/);
expect(existsSync(sessionFile)).toBe(false);
});
it("generates a UUIDv7 id when creating a branched session", () => {
const session = SessionManager.inMemory();
const firstId = session.appendMessage({
@@ -106,4 +135,28 @@ describe("SessionManager.newSession with custom id", () => {
expect(header!.id).toMatch(UUID_V7_RE);
expect(header!.parentSession).toBe(sourcePath);
});
it("uses the provided id when forking from another session file", () => {
const tempDir = mkdtempSync(join(tmpdir(), "pi-session-manager-"));
const sourcePath = join(tempDir, "source.jsonl");
writeFileSync(
sourcePath,
`${JSON.stringify({
type: "session",
version: 3,
id: "source-session-id",
timestamp: new Date().toISOString(),
cwd: tempDir,
})}\n`,
);
const forked = SessionManager.forkFrom(sourcePath, tempDir, tempDir, { id: "forked-session-id" });
const header = forked.getHeader();
expect(header).not.toBeNull();
expect(header!.id).toBe("forked-session-id");
expect(header!.parentSession).toBe(sourcePath);
const sessionFile = forked.getSessionFile()!;
expect(sessionFile).toContain("forked-session-id");
expect(basename(sessionFile)).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z_forked-session-id\.jsonl$/);
});
});