fix(cron): reuse existing sessionId for webhook/cron sessions

When a webhook or cron job provides a stable sessionKey, the session
should maintain conversation history across invocations. Previously,
resolveCronSession always generated a new sessionId and hardcoded
isNewSession: true, preventing any conversation continuity.

Changes:
- Check if existing entry has a valid sessionId
- Evaluate freshness using configured reset policy
- Reuse sessionId and set isNewSession: false when fresh
- Add forceNew parameter to override reuse behavior
- Spread existing entry to preserve conversation context

This enables persistent, stateful conversations for webhook-driven
agent endpoints when allowRequestSessionKey is configured.

Fixes #18027
This commit is contained in:
Operative-001
2026-02-16 13:08:40 +01:00
committed by Peter Steinberger
parent 952db1a3e2
commit 57c8f62396
2 changed files with 150 additions and 5 deletions

View File

@@ -1,12 +1,19 @@
import crypto from "node:crypto";
import type { OpenClawConfig } from "../../config/config.js";
import { loadSessionStore, resolveStorePath, type SessionEntry } from "../../config/sessions.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, {
@@ -14,12 +21,50 @@ export function resolveCronSession(params: {
});
const store = loadSessionStore(storePath);
const entry = store[params.sessionKey];
const sessionId = crypto.randomUUID();
const systemSent = false;
// 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 = {
// Spread existing entry to preserve conversation context when reusing
...(isNewSession ? undefined : entry),
sessionId,
updatedAt: params.nowMs,
systemSent,
// Preserve user preferences from existing entry
thinkingLevel: entry?.thinkingLevel,
verboseLevel: entry?.verboseLevel,
model: entry?.model,
@@ -34,5 +79,5 @@ export function resolveCronSession(params: {
displayName: entry?.displayName,
skillsSnapshot: entry?.skillsSnapshot,
};
return { storePath, store, sessionEntry, systemSent, isNewSession: true };
return { storePath, store, sessionEntry, systemSent, isNewSession };
}