fix: harden memory indexing and embedded session locks

This commit is contained in:
Peter Steinberger
2026-01-18 04:29:42 +00:00
parent b7575a889e
commit cf8b3ed988
2 changed files with 66 additions and 69 deletions

View File

@@ -146,83 +146,78 @@ const readSessionMessages = async (sessionFile: string) => {
}; };
describe("runEmbeddedPiAgent", () => { describe("runEmbeddedPiAgent", () => {
it( it("appends new user + assistant after existing transcript entries", { timeout: 90_000 }, async () => {
"appends new user + assistant after existing transcript entries", const { SessionManager } = await import("@mariozechner/pi-coding-agent");
{ timeout: 90_000 },
async () => {
const { SessionManager } = await import("@mariozechner/pi-coding-agent");
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-agent-")); const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-agent-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-workspace-")); const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-workspace-"));
const sessionFile = path.join(workspaceDir, "session.jsonl"); const sessionFile = path.join(workspaceDir, "session.jsonl");
const sessionManager = SessionManager.open(sessionFile); const sessionManager = SessionManager.open(sessionFile);
sessionManager.appendMessage({ sessionManager.appendMessage({
role: "user", role: "user",
content: [{ type: "text", text: "seed user" }], content: [{ type: "text", text: "seed user" }],
}); });
sessionManager.appendMessage({ sessionManager.appendMessage({
role: "assistant", role: "assistant",
content: [{ type: "text", text: "seed assistant" }], content: [{ type: "text", text: "seed assistant" }],
stopReason: "stop", stopReason: "stop",
api: "openai-responses", api: "openai-responses",
provider: "openai", provider: "openai",
model: "mock-1", model: "mock-1",
usage: { usage: {
input: 1, input: 1,
output: 1, output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: {
input: 0,
output: 0,
cacheRead: 0, cacheRead: 0,
cacheWrite: 0, cacheWrite: 0,
totalTokens: 2, total: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
}, },
timestamp: Date.now(), },
}); timestamp: Date.now(),
});
const cfg = makeOpenAiConfig(["mock-1"]); const cfg = makeOpenAiConfig(["mock-1"]);
await ensureModels(cfg, agentDir); await ensureModels(cfg, agentDir);
await runEmbeddedPiAgent({ await runEmbeddedPiAgent({
sessionId: "session:test", sessionId: "session:test",
sessionKey: testSessionKey, sessionKey: testSessionKey,
sessionFile, sessionFile,
workspaceDir, workspaceDir,
config: cfg, config: cfg,
prompt: "hello", prompt: "hello",
provider: "openai", provider: "openai",
model: "mock-1", model: "mock-1",
timeoutMs: 5_000, timeoutMs: 5_000,
agentDir, agentDir,
enqueue: immediateEnqueue, enqueue: immediateEnqueue,
}); });
const messages = await readSessionMessages(sessionFile); const messages = await readSessionMessages(sessionFile);
const seedUserIndex = messages.findIndex( const seedUserIndex = messages.findIndex(
(message) => message?.role === "user" && textFromContent(message.content) === "seed user", (message) => message?.role === "user" && textFromContent(message.content) === "seed user",
); );
const seedAssistantIndex = messages.findIndex( const seedAssistantIndex = messages.findIndex(
(message) => (message) =>
message?.role === "assistant" && textFromContent(message.content) === "seed assistant", message?.role === "assistant" && textFromContent(message.content) === "seed assistant",
); );
const newUserIndex = messages.findIndex( const newUserIndex = messages.findIndex(
(message) => message?.role === "user" && textFromContent(message.content) === "hello", (message) => message?.role === "user" && textFromContent(message.content) === "hello",
); );
const newAssistantIndex = messages.findIndex( const newAssistantIndex = messages.findIndex(
(message, index) => index > newUserIndex && message?.role === "assistant", (message, index) => index > newUserIndex && message?.role === "assistant",
); );
expect(seedUserIndex).toBeGreaterThanOrEqual(0); expect(seedUserIndex).toBeGreaterThanOrEqual(0);
expect(seedAssistantIndex).toBeGreaterThan(seedUserIndex); expect(seedAssistantIndex).toBeGreaterThan(seedUserIndex);
expect(newUserIndex).toBeGreaterThan(seedAssistantIndex); expect(newUserIndex).toBeGreaterThan(seedAssistantIndex);
expect(newAssistantIndex).toBeGreaterThan(newUserIndex); expect(newAssistantIndex).toBeGreaterThan(newUserIndex);
}, });
45_000,
);
it("persists multi-turn user/assistant ordering across runs", async () => { it("persists multi-turn user/assistant ordering across runs", async () => {
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-agent-")); const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-agent-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-workspace-")); const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-workspace-"));

View File

@@ -1,4 +1,5 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path";
type LockFilePayload = { type LockFilePayload = {
pid: number; pid: number;
@@ -46,6 +47,7 @@ export async function acquireSessionWriteLock(params: {
const staleMs = params.staleMs ?? 30 * 60 * 1000; const staleMs = params.staleMs ?? 30 * 60 * 1000;
const sessionFile = params.sessionFile; const sessionFile = params.sessionFile;
const lockPath = `${sessionFile}.lock`; const lockPath = `${sessionFile}.lock`;
await fs.mkdir(path.dirname(lockPath), { recursive: true });
const held = HELD_LOCKS.get(sessionFile); const held = HELD_LOCKS.get(sessionFile);
if (held) { if (held) {