mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 07:21:23 +00:00
* fix: clear delivery routing state when creating isolated cron sessions When `resolveCronSession()` creates a new session (forceNew / isolated), the `...entry` spread preserves `lastThreadId`, `lastTo`, `lastChannel`, and `lastAccountId` from the prior session. This causes announce-mode cron deliveries to post as thread replies instead of channel top-level messages when `delivery.to` matches the channel of a prior conversation. Clear delivery routing metadata on new session creation so isolated cron sessions start with a clean delivery state. Closes #27751 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved) * fix: also clear deliveryContext to prevent lastThreadId repopulation normalizeSessionEntryDelivery (called on store writes) repopulates lastThreadId from deliveryContext.threadId. Clearing only the last* fields is insufficient — deliveryContext must also be cleared when creating a new isolated session. ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
84 lines
2.6 KiB
TypeScript
84 lines
2.6 KiB
TypeScript
import crypto from "node:crypto";
|
|
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;
|
|
}
|
|
|
|
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 };
|
|
}
|