fix(agents): enforce single-writer session files
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user