fix: harden memory indexing and embedded session locks
This commit is contained in:
@@ -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-"));
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user