fix(agents): enforce single-writer session files

This commit is contained in:
Peter Steinberger
2026-01-11 02:24:25 +00:00
parent 3a113b7752
commit 6668805aca
4 changed files with 220 additions and 69 deletions

View File

@@ -68,6 +68,7 @@ import {
} from "./model-auth.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
import type { MessagingToolSend } from "./pi-embedded-messaging.js";
import { acquireSessionWriteLock } from "./session-write-lock.js";
export type { MessagingToolSend } from "./pi-embedded-messaging.js";
@@ -952,70 +953,79 @@ export async function compactEmbeddedPiSession(params: {
});
const systemPrompt = createSystemPromptOverride(appendPrompt);
// Pre-warm session file to bring it into OS page cache
await prewarmSessionFile(params.sessionFile);
const sessionManager = SessionManager.open(params.sessionFile);
trackSessionManagerAccess(params.sessionFile);
const settingsManager = SettingsManager.create(
effectiveWorkspace,
agentDir,
);
const pruning = buildContextPruningExtension({
cfg: params.config,
sessionManager,
provider,
modelId,
model,
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
});
const additionalExtensionPaths = pruning.additionalExtensionPaths;
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: !!sandbox?.enabled,
});
let session: Awaited<ReturnType<typeof createAgentSession>>["session"];
({ session } = await createAgentSession({
cwd: resolvedWorkspace,
agentDir,
authStorage,
modelRegistry,
model,
thinkingLevel: mapThinkingLevel(params.thinkLevel),
systemPrompt,
tools: builtInTools,
customTools,
sessionManager,
settingsManager,
skills: [],
contextFiles: [],
additionalExtensionPaths,
}));
try {
const prior = await sanitizeSessionHistory({
messages: session.messages,
modelApi: model.api,
// Pre-warm session file to bring it into OS page cache
await prewarmSessionFile(params.sessionFile);
const sessionManager = SessionManager.open(params.sessionFile);
trackSessionManagerAccess(params.sessionFile);
const settingsManager = SettingsManager.create(
effectiveWorkspace,
agentDir,
);
const pruning = buildContextPruningExtension({
cfg: params.config,
sessionManager,
sessionId: params.sessionId,
provider,
modelId,
model,
});
const validated = validateGeminiTurns(prior);
if (validated.length > 0) {
session.agent.replaceMessages(validated);
const additionalExtensionPaths = pruning.additionalExtensionPaths;
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: !!sandbox?.enabled,
});
let session: Awaited<
ReturnType<typeof createAgentSession>
>["session"];
({ session } = await createAgentSession({
cwd: resolvedWorkspace,
agentDir,
authStorage,
modelRegistry,
model,
thinkingLevel: mapThinkingLevel(params.thinkLevel),
systemPrompt,
tools: builtInTools,
customTools,
sessionManager,
settingsManager,
skills: [],
contextFiles: [],
additionalExtensionPaths,
}));
try {
const prior = await sanitizeSessionHistory({
messages: session.messages,
modelApi: model.api,
sessionManager,
sessionId: params.sessionId,
});
const validated = validateGeminiTurns(prior);
if (validated.length > 0) {
session.agent.replaceMessages(validated);
}
const result = await session.compact(params.customInstructions);
return {
ok: true,
compacted: true,
result: {
summary: result.summary,
firstKeptEntryId: result.firstKeptEntryId,
tokensBefore: result.tokensBefore,
details: result.details,
},
};
} finally {
session.dispose();
}
const result = await session.compact(params.customInstructions);
return {
ok: true,
compacted: true,
result: {
summary: result.summary,
firstKeptEntryId: result.firstKeptEntryId,
tokensBefore: result.tokensBefore,
details: result.details,
},
};
} finally {
session.dispose();
await sessionLock.release();
}
} catch (err) {
return {
@@ -1333,6 +1343,9 @@ export async function runEmbeddedPiAgent(params: {
});
const systemPrompt = createSystemPromptOverride(appendPrompt);
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
});
// Pre-warm session file to bring it into OS page cache
await prewarmSessionFile(params.sessionFile);
const sessionManager = SessionManager.open(params.sessionFile);
@@ -1390,6 +1403,7 @@ export async function runEmbeddedPiAgent(params: {
}
} catch (err) {
session.dispose();
await sessionLock.release();
throw err;
}
let aborted = Boolean(params.abortSignal?.aborted);
@@ -1419,6 +1433,7 @@ export async function runEmbeddedPiAgent(params: {
});
} catch (err) {
session.dispose();
await sessionLock.release();
throw err;
}
const {
@@ -1515,6 +1530,7 @@ export async function runEmbeddedPiAgent(params: {
notifyEmbeddedRunEnded(params.sessionId);
}
session.dispose();
await sessionLock.release();
params.abortSignal?.removeEventListener?.("abort", onAbort);
}
if (promptError && !aborted) {