refactor: harden session store updates
Co-authored-by: Tyler Yust <tyler6204@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
loadSessionStore,
|
||||
resolveStorePath,
|
||||
type SessionEntry,
|
||||
saveSessionStore,
|
||||
updateSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
import { resolveCommandAuthorization } from "../command-auth.js";
|
||||
@@ -90,7 +90,13 @@ export async function tryFastAbortFromMessage(params: {
|
||||
entry.abortedLastRun = true;
|
||||
entry.updatedAt = Date.now();
|
||||
store[key] = entry;
|
||||
await saveSessionStore(storePath, store);
|
||||
await updateSessionStore(storePath, (nextStore) => {
|
||||
const nextEntry = nextStore[key] ?? entry;
|
||||
if (!nextEntry) return;
|
||||
nextEntry.abortedLastRun = true;
|
||||
nextEntry.updatedAt = Date.now();
|
||||
nextStore[key] = nextEntry;
|
||||
});
|
||||
} else if (abortKey) {
|
||||
setAbortMemory(abortKey, true);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
resolveAgentIdFromSessionKey,
|
||||
resolveSessionTranscriptPath,
|
||||
type SessionEntry,
|
||||
saveSessionStore,
|
||||
updateSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
||||
@@ -383,6 +383,7 @@ export async function runAgentTurnWithFallback(params: {
|
||||
params.activeSessionStore &&
|
||||
params.storePath
|
||||
) {
|
||||
const sessionKey = params.sessionKey;
|
||||
const corruptedSessionId = params.getActiveSessionEntry()?.sessionId;
|
||||
defaultRuntime.error(
|
||||
`Session history corrupted (Gemini function call ordering). Resetting session: ${params.sessionKey}`,
|
||||
@@ -399,9 +400,10 @@ export async function runAgentTurnWithFallback(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove session entry from store
|
||||
delete params.activeSessionStore[params.sessionKey];
|
||||
await saveSessionStore(params.storePath, params.activeSessionStore);
|
||||
// Remove session entry from store using a fresh, locked snapshot.
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
delete store[sessionKey];
|
||||
});
|
||||
} catch (cleanupErr) {
|
||||
defaultRuntime.error(
|
||||
`Failed to reset corrupted session ${params.sessionKey}: ${String(cleanupErr)}`,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
resolveAgentIdFromSessionKey,
|
||||
resolveSessionTranscriptPath,
|
||||
type SessionEntry,
|
||||
saveSessionStore,
|
||||
updateSessionStore,
|
||||
updateSessionStoreEntry,
|
||||
} from "../../config/sessions.js";
|
||||
import type { TypingMode } from "../../config/types.js";
|
||||
@@ -156,7 +156,9 @@ export async function runReplyAgent(params: {
|
||||
activeSessionEntry.updatedAt = Date.now();
|
||||
activeSessionStore[sessionKey] = activeSessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, activeSessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = activeSessionEntry as SessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
typing.cleanup();
|
||||
@@ -170,7 +172,9 @@ export async function runReplyAgent(params: {
|
||||
activeSessionEntry.updatedAt = Date.now();
|
||||
activeSessionStore[sessionKey] = activeSessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, activeSessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = activeSessionEntry as SessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
typing.cleanup();
|
||||
@@ -224,7 +228,9 @@ export async function runReplyAgent(params: {
|
||||
nextEntry.sessionFile = nextSessionFile;
|
||||
activeSessionStore[sessionKey] = nextEntry;
|
||||
try {
|
||||
await saveSessionStore(storePath, activeSessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = nextEntry;
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`Failed to persist session reset after compaction failure (${sessionKey}): ${String(err)}`,
|
||||
@@ -280,7 +286,9 @@ export async function runReplyAgent(params: {
|
||||
activeSessionEntry.updatedAt = Date.now();
|
||||
activeSessionStore[sessionKey] = activeSessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, activeSessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = activeSessionEntry as SessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { saveSessionStore } from "../../config/sessions.js";
|
||||
import { updateSessionStore } from "../../config/sessions.js";
|
||||
import { setAbortMemory } from "./abort.js";
|
||||
|
||||
export async function applySessionHints(params: {
|
||||
@@ -23,7 +23,16 @@ export async function applySessionHints(params: {
|
||||
params.sessionEntry.updatedAt = Date.now();
|
||||
params.sessionStore[params.sessionKey] = params.sessionEntry;
|
||||
if (params.storePath) {
|
||||
await saveSessionStore(params.storePath, params.sessionStore);
|
||||
const sessionKey = params.sessionKey;
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
const entry = store[sessionKey] ?? params.sessionEntry;
|
||||
if (!entry) return;
|
||||
store[sessionKey] = {
|
||||
...entry,
|
||||
abortedLastRun: false,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
});
|
||||
}
|
||||
} else if (params.abortKey) {
|
||||
setAbortMemory(params.abortKey, false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { saveSessionStore } from "../../config/sessions.js";
|
||||
import { updateSessionStore } from "../../config/sessions.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { scheduleGatewaySigusr1Restart, triggerClawdbotRestart } from "../../infra/restart.js";
|
||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
@@ -71,7 +71,9 @@ export const handleActivationCommand: CommandHandler = async (params, allowTextC
|
||||
params.sessionEntry.updatedAt = Date.now();
|
||||
params.sessionStore[params.sessionKey] = params.sessionEntry;
|
||||
if (params.storePath) {
|
||||
await saveSessionStore(params.storePath, params.sessionStore);
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
store[params.sessionKey] = params.sessionEntry as SessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -107,7 +109,9 @@ export const handleSendPolicyCommand: CommandHandler = async (params, allowTextC
|
||||
params.sessionEntry.updatedAt = Date.now();
|
||||
params.sessionStore[params.sessionKey] = params.sessionEntry;
|
||||
if (params.storePath) {
|
||||
await saveSessionStore(params.storePath, params.sessionStore);
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
store[params.sessionKey] = params.sessionEntry as SessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
const label =
|
||||
@@ -190,7 +194,9 @@ export const handleStopCommand: CommandHandler = async (params, allowTextCommand
|
||||
abortTarget.entry.updatedAt = Date.now();
|
||||
params.sessionStore[abortTarget.key] = abortTarget.entry;
|
||||
if (params.storePath) {
|
||||
await saveSessionStore(params.storePath, params.sessionStore);
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
store[abortTarget.key] = abortTarget.entry as SessionEntry;
|
||||
});
|
||||
}
|
||||
} else if (params.command.abortKey) {
|
||||
setAbortMemory(params.command.abortKey, true);
|
||||
@@ -215,7 +221,9 @@ export const handleAbortTrigger: CommandHandler = async (params, allowTextComman
|
||||
abortTarget.entry.updatedAt = Date.now();
|
||||
params.sessionStore[abortTarget.key] = abortTarget.entry;
|
||||
if (params.storePath) {
|
||||
await saveSessionStore(params.storePath, params.sessionStore);
|
||||
await updateSessionStore(params.storePath, (store) => {
|
||||
store[abortTarget.key] = abortTarget.entry as SessionEntry;
|
||||
});
|
||||
}
|
||||
} else if (params.command.abortKey) {
|
||||
setAbortMemory(params.command.abortKey, true);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope
|
||||
import type { ModelAliasIndex } from "../../agents/model-selection.js";
|
||||
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
|
||||
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import { applyVerboseOverride } from "../../sessions/level-overrides.js";
|
||||
import { formatThinkingLevels, formatXHighModelHint, supportsXHighThinking } from "../thinking.js";
|
||||
@@ -288,7 +288,9 @@ export async function handleDirectiveOnly(params: {
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = sessionEntry;
|
||||
});
|
||||
}
|
||||
if (modelSelection) {
|
||||
const nextLabel = `${modelSelection.provider}/${modelSelection.model}`;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
resolveModelRefFromString,
|
||||
} from "../../agents/model-selection.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
|
||||
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import { applyVerboseOverride } from "../../sessions/level-overrides.js";
|
||||
import { resolveProfileOverride } from "./directive-handling.auth.js";
|
||||
@@ -184,7 +184,9 @@ export async function persistInlineDirectives(params: {
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = sessionEntry;
|
||||
});
|
||||
}
|
||||
if (elevatedChanged) {
|
||||
const nextElevated = (sessionEntry.elevatedLevel ?? "off") as ElevatedLevel;
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import {
|
||||
resolveSessionFilePath,
|
||||
type SessionEntry,
|
||||
saveSessionStore,
|
||||
updateSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { clearCommandLane, getQueueSize } from "../../process/command-queue.js";
|
||||
@@ -276,7 +276,9 @@ export async function runPreparedReply(
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = sessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
resolveThinkingDefault,
|
||||
} from "../../agents/model-selection.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
|
||||
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
|
||||
import type { ThinkLevel } from "./directives.js";
|
||||
|
||||
export type ModelDirectiveSelection = {
|
||||
@@ -189,7 +189,9 @@ export async function createModelSelectionState(params: {
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = sessionEntry;
|
||||
});
|
||||
}
|
||||
resetModelOverride = true;
|
||||
}
|
||||
@@ -218,7 +220,9 @@ export async function createModelSelectionState(params: {
|
||||
sessionEntry.updatedAt = Date.now();
|
||||
sessionStore[sessionKey] = sessionEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = sessionEntry;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
|
||||
|
||||
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
|
||||
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
|
||||
import { buildChannelSummary } from "../../infra/channel-summary.js";
|
||||
import { drainSystemEventEntries } from "../../infra/system-events.js";
|
||||
|
||||
@@ -111,7 +111,9 @@ export async function ensureSkillSnapshot(params: {
|
||||
};
|
||||
sessionStore[sessionKey] = { ...sessionStore[sessionKey], ...nextEntry };
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = { ...store[sessionKey], ...nextEntry };
|
||||
});
|
||||
}
|
||||
systemSent = true;
|
||||
}
|
||||
@@ -143,7 +145,9 @@ export async function ensureSkillSnapshot(params: {
|
||||
};
|
||||
sessionStore[sessionKey] = { ...sessionStore[sessionKey], ...nextEntry };
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = { ...store[sessionKey], ...nextEntry };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +172,13 @@ export async function incrementCompactionCount(params: {
|
||||
updatedAt: now,
|
||||
};
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[sessionKey] = {
|
||||
...store[sessionKey],
|
||||
compactionCount: nextCount,
|
||||
updatedAt: now,
|
||||
};
|
||||
});
|
||||
}
|
||||
return nextCount;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
resolveStorePath,
|
||||
type SessionEntry,
|
||||
type SessionScope,
|
||||
saveSessionStore,
|
||||
updateSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { normalizeMainKey } from "../../routing/session-key.js";
|
||||
import { resolveCommandAuthorization } from "../command-auth.js";
|
||||
@@ -188,6 +188,11 @@ export async function initSessionState(params: {
|
||||
}
|
||||
|
||||
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
|
||||
// Track the originating channel/to for announce routing (subagent announce-back).
|
||||
const lastChannel =
|
||||
(ctx.OriginatingChannel as string | undefined)?.trim() || baseEntry?.lastChannel;
|
||||
const lastTo = ctx.OriginatingTo?.trim() || ctx.To?.trim() || baseEntry?.lastTo;
|
||||
const lastAccountId = ctx.AccountId?.trim() || baseEntry?.lastAccountId;
|
||||
sessionEntry = {
|
||||
...baseEntry,
|
||||
sessionId,
|
||||
@@ -212,6 +217,10 @@ export async function initSessionState(params: {
|
||||
subject: baseEntry?.subject,
|
||||
room: baseEntry?.room,
|
||||
space: baseEntry?.space,
|
||||
// Track originating channel for subagent announce routing.
|
||||
lastChannel,
|
||||
lastTo,
|
||||
lastAccountId,
|
||||
};
|
||||
if (groupResolution?.channel) {
|
||||
const channel = groupResolution.channel;
|
||||
@@ -270,7 +279,15 @@ export async function initSessionState(params: {
|
||||
);
|
||||
}
|
||||
sessionStore[sessionKey] = { ...sessionStore[sessionKey], ...sessionEntry };
|
||||
await saveSessionStore(storePath, sessionStore);
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
if (groupResolution?.legacyKey && groupResolution.legacyKey !== sessionKey) {
|
||||
if (store[groupResolution.legacyKey] && !store[sessionKey]) {
|
||||
store[sessionKey] = store[groupResolution.legacyKey];
|
||||
}
|
||||
delete store[groupResolution.legacyKey];
|
||||
}
|
||||
store[sessionKey] = { ...store[sessionKey], ...sessionEntry };
|
||||
});
|
||||
|
||||
const sessionCtx: TemplateContext = {
|
||||
...ctx,
|
||||
|
||||
Reference in New Issue
Block a user