拐爷&&老拐瘦 2e31aead39
fix(gateway): invalidate bootstrap cache on session rollover (openclaw#38535)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: yfge <1186273+yfge@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-06 23:46:02 -06:00

90 lines
2.8 KiB
TypeScript

import crypto from "node:crypto";
import { clearBootstrapSnapshotOnSessionRollover } from "../../agents/bootstrap-cache.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
evaluateSessionFreshness,
loadSessionStore,
resolveSessionResetPolicy,
resolveStorePath,
type SessionEntry,
} from "../../config/sessions.js";
export function resolveCronSession(params: {
cfg: OpenClawConfig;
sessionKey: string;
nowMs: number;
agentId: string;
forceNew?: boolean;
}) {
const sessionCfg = params.cfg.session;
const storePath = resolveStorePath(sessionCfg?.store, {
agentId: params.agentId,
});
const store = loadSessionStore(storePath);
const entry = store[params.sessionKey];
// Check if we can reuse an existing session
let sessionId: string;
let isNewSession: boolean;
let systemSent: boolean;
if (!params.forceNew && entry?.sessionId) {
// Evaluate freshness using the configured reset policy
// Cron/webhook sessions use "direct" reset type (1:1 conversation style)
const resetPolicy = resolveSessionResetPolicy({
sessionCfg,
resetType: "direct",
});
const freshness = evaluateSessionFreshness({
updatedAt: entry.updatedAt,
now: params.nowMs,
policy: resetPolicy,
});
if (freshness.fresh) {
// Reuse existing session
sessionId = entry.sessionId;
isNewSession = false;
systemSent = entry.systemSent ?? false;
} else {
// Session expired, create new
sessionId = crypto.randomUUID();
isNewSession = true;
systemSent = false;
}
} else {
// No existing session or forced new
sessionId = crypto.randomUUID();
isNewSession = true;
systemSent = false;
}
clearBootstrapSnapshotOnSessionRollover({
sessionKey: params.sessionKey,
previousSessionId: isNewSession ? entry?.sessionId : undefined,
});
const sessionEntry: SessionEntry = {
// Preserve existing per-session overrides even when rolling to a new sessionId.
...entry,
// Always update these core fields
sessionId,
updatedAt: params.nowMs,
systemSent,
// When starting a fresh session (forceNew / isolated), clear delivery routing
// state inherited from prior sessions. Without this, lastThreadId leaks into
// the new session and causes announce-mode cron deliveries to post as thread
// replies instead of channel top-level messages.
// deliveryContext must also be cleared because normalizeSessionEntryDelivery
// repopulates lastThreadId from deliveryContext.threadId on store writes.
...(isNewSession && {
lastChannel: undefined,
lastTo: undefined,
lastAccountId: undefined,
lastThreadId: undefined,
deliveryContext: undefined,
}),
};
return { storePath, store, sessionEntry, systemSent, isNewSession };
}