From 2e3e516a4a6b688eb45f85d1207910806f4e3959 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 20 Mar 2026 12:18:34 +0530 Subject: [PATCH 1/6] fix: trigger session-memory hook on idle and daily session resets --- src/auto-reply/reply/session.ts | 18 +++++++++++++++++- src/hooks/bundled/session-memory/HOOK.md | 8 ++++---- src/hooks/bundled/session-memory/handler.ts | 21 ++++++++++++++------- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index 6c1b2233c0f..ce53dee0571 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -5,7 +5,7 @@ import { normalizeConversationText, parseTelegramChatIdFromTarget, } from "../../acp/conversation-id.js"; -import { resolveSessionAgentId } from "../../agents/agent-scope.js"; +import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "../../agents/agent-scope.js"; import { clearBootstrapSnapshotOnSessionRollover } from "../../agents/bootstrap-cache.js"; import { normalizeChatType } from "../../channels/chat-type.js"; import type { OpenClawConfig } from "../../config/config.js"; @@ -30,6 +30,7 @@ import { } from "../../config/sessions.js"; import type { TtsAutoMode } from "../../config/types.tts.js"; import { archiveSessionTranscripts } from "../../gateway/session-utils.fs.js"; +import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js"; import { resolveConversationIdFromTargets } from "../../infra/outbound/conversation-id.js"; import { deliverSessionMaintenanceWarning } from "../../infra/session-maintenance-warning.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; @@ -585,6 +586,21 @@ export async function initSessionState(params: { IsNewSession: isNewSession ? "true" : "false", }; + // When a session is auto-reset (idle timeout or daily reset), emit a + // session:reset internal hook so the session-memory hook can persist + // the outgoing session context — just as it does for manual /new and /reset. + if (isNewSession && !resetTriggered && previousSessionEntry) { + const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); + const autoResetEvent = createInternalHookEvent("session", "reset", sessionKey, { + sessionEntry, + previousSessionEntry, + commandSource: "auto-reset", + workspaceDir, + cfg, + }); + void triggerInternalHook(autoResetEvent).catch(() => {}); + } + // Run session plugin hooks (fire-and-forget) const hookRunner = getGlobalHookRunner(); if (hookRunner && isNewSession) { diff --git a/src/hooks/bundled/session-memory/HOOK.md b/src/hooks/bundled/session-memory/HOOK.md index c963e17b76c..f99070b43a8 100644 --- a/src/hooks/bundled/session-memory/HOOK.md +++ b/src/hooks/bundled/session-memory/HOOK.md @@ -1,13 +1,13 @@ --- name: session-memory -description: "Save session context to memory when /new or /reset command is issued" +description: "Save session context to memory when session is reset (manual or automatic)" homepage: https://docs.openclaw.ai/automation/hooks#session-memory metadata: { "openclaw": { "emoji": "💾", - "events": ["command:new", "command:reset"], + "events": ["command:new", "command:reset", "session:reset"], "requires": { "config": ["workspace.dir"] }, "install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }], }, @@ -16,11 +16,11 @@ metadata: # Session Memory Hook -Automatically saves session context to your workspace memory when you issue `/new` or `/reset`. +Automatically saves session context to your workspace memory when a session is reset, whether manually (`/new`, `/reset`) or automatically (idle timeout, daily reset). ## What It Does -When you run `/new` or `/reset` to start a fresh session: +When a session is reset (manually via `/new`/`/reset` or automatically via idle timeout or daily reset): 1. **Finds the previous session** - Uses the pre-reset session entry to locate the correct transcript 2. **Extracts conversation** - Reads the last N user/assistant messages from the session (default: 15, configurable) diff --git a/src/hooks/bundled/session-memory/handler.ts b/src/hooks/bundled/session-memory/handler.ts index 32fc36b23f0..d073ee5cc81 100644 --- a/src/hooks/bundled/session-memory/handler.ts +++ b/src/hooks/bundled/session-memory/handler.ts @@ -1,8 +1,10 @@ /** * Session memory hook handler * - * Saves session context to memory when /new or /reset command is triggered - * Creates a new dated memory file with LLM-generated slug + * Saves session context to memory when a session is reset. + * Handles both manual resets (/new, /reset) and automatic resets + * (idle timeout, daily reset). Creates a new dated memory file + * with LLM-generated slug. */ import fs from "node:fs/promises"; @@ -194,17 +196,22 @@ async function findPreviousSessionFile(params: { } /** - * Save session context to memory when /new or /reset command is triggered + * Save session context to memory when session is reset. + * + * Triggers on: + * - command:new / command:reset (manual /new or /reset commands) + * - session:reset (automatic idle-timeout or daily resets) */ const saveSessionToMemory: HookHandler = async (event) => { - // Only trigger on reset/new commands - const isResetCommand = event.action === "new" || event.action === "reset"; - if (event.type !== "command" || !isResetCommand) { + const isManualReset = + event.type === "command" && (event.action === "new" || event.action === "reset"); + const isAutoReset = event.type === "session" && event.action === "reset"; + if (!isManualReset && !isAutoReset) { return; } try { - log.debug("Hook triggered for reset/new command", { action: event.action }); + log.debug("Hook triggered for session reset", { type: event.type, action: event.action }); const context = event.context || {}; const cfg = context.cfg as OpenClawConfig | undefined; From a54be14070e944869d2071a22e723c9264c0618e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 20 Mar 2026 12:59:36 +0530 Subject: [PATCH 2/6] fix: add debug logging for session:reset hook errors --- src/auto-reply/reply/session.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index ce53dee0571..bfe2f8fd5c7 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -598,7 +598,9 @@ export async function initSessionState(params: { workspaceDir, cfg, }); - void triggerInternalHook(autoResetEvent).catch(() => {}); + void triggerInternalHook(autoResetEvent).catch((err) => { + log.debug("session:reset hook error (auto-reset)", { error: String(err) }); + }); } // Run session plugin hooks (fire-and-forget) From 9cabdeff38e5882ae095157885d66b981d0f2a4d Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 20 Mar 2026 13:03:33 +0530 Subject: [PATCH 3/6] fix: emit session:reset hook before transcript archival --- src/auto-reply/reply/session.ts | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index bfe2f8fd5c7..b5f005335ae 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -558,6 +558,25 @@ export async function initSessionState(params: { }, ); + // When a session is auto-reset (idle timeout or daily reset), emit a + // session:reset internal hook so the session-memory hook can persist + // the outgoing session context — just as it does for manual /new and /reset. + // This MUST fire before archiveSessionTranscripts() so the handler can still + // read the original transcript file (which archival renames). + if (isNewSession && !resetTriggered && previousSessionEntry) { + const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); + const autoResetEvent = createInternalHookEvent("session", "reset", sessionKey, { + sessionEntry, + previousSessionEntry, + commandSource: "auto-reset", + workspaceDir, + cfg, + }); + void triggerInternalHook(autoResetEvent).catch((err) => { + log.debug("session:reset hook error (auto-reset)", { error: String(err) }); + }); + } + // Archive old transcript so it doesn't accumulate on disk (#14869). if (previousSessionEntry?.sessionId) { archiveSessionTranscripts({ @@ -586,23 +605,6 @@ export async function initSessionState(params: { IsNewSession: isNewSession ? "true" : "false", }; - // When a session is auto-reset (idle timeout or daily reset), emit a - // session:reset internal hook so the session-memory hook can persist - // the outgoing session context — just as it does for manual /new and /reset. - if (isNewSession && !resetTriggered && previousSessionEntry) { - const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); - const autoResetEvent = createInternalHookEvent("session", "reset", sessionKey, { - sessionEntry, - previousSessionEntry, - commandSource: "auto-reset", - workspaceDir, - cfg, - }); - void triggerInternalHook(autoResetEvent).catch((err) => { - log.debug("session:reset hook error (auto-reset)", { error: String(err) }); - }); - } - // Run session plugin hooks (fire-and-forget) const hookRunner = getGlobalHookRunner(); if (hookRunner && isNewSession) { From f4903052b2a96196e136f59e2af8ce13aa1d381d Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 20 Mar 2026 13:33:47 +0530 Subject: [PATCH 4/6] fix: correct comment to describe fire-and-forget reset fallback behavior --- src/auto-reply/reply/session.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index b5f005335ae..36131932bb4 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -561,8 +561,9 @@ export async function initSessionState(params: { // When a session is auto-reset (idle timeout or daily reset), emit a // session:reset internal hook so the session-memory hook can persist // the outgoing session context — just as it does for manual /new and /reset. - // This MUST fire before archiveSessionTranscripts() so the handler can still - // read the original transcript file (which archival renames). + // Note: this is fire-and-forget; if archival renames the transcript before + // the handler reads it, getRecentSessionContentWithResetFallback() will + // locate it via the .reset.* sibling naming convention. if (isNewSession && !resetTriggered && previousSessionEntry) { const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); const autoResetEvent = createInternalHookEvent("session", "reset", sessionKey, { From 93599cb1872a4063261e7c8c664bd5adbe31b047 Mon Sep 17 00:00:00 2001 From: Saurabh Mishra Date: Fri, 20 Mar 2026 21:25:53 +0530 Subject: [PATCH 5/6] Update src/auto-reply/reply/session.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/auto-reply/reply/session.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index 36131932bb4..a70ff6909d6 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -558,6 +558,7 @@ export async function initSessionState(params: { }, ); + // When a session is auto-reset (idle timeout or daily reset), emit a // When a session is auto-reset (idle timeout or daily reset), emit a // session:reset internal hook so the session-memory hook can persist // the outgoing session context — just as it does for manual /new and /reset. @@ -565,17 +566,6 @@ export async function initSessionState(params: { // the handler reads it, getRecentSessionContentWithResetFallback() will // locate it via the .reset.* sibling naming convention. if (isNewSession && !resetTriggered && previousSessionEntry) { - const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); - const autoResetEvent = createInternalHookEvent("session", "reset", sessionKey, { - sessionEntry, - previousSessionEntry, - commandSource: "auto-reset", - workspaceDir, - cfg, - }); - void triggerInternalHook(autoResetEvent).catch((err) => { - log.debug("session:reset hook error (auto-reset)", { error: String(err) }); - }); } // Archive old transcript so it doesn't accumulate on disk (#14869). From ce8f0c2de1bb6a816c0bbc180e1da0aafc7c7961 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 20 Mar 2026 22:50:09 +0530 Subject: [PATCH 6/6] Revert "Update src/auto-reply/reply/session.ts" This reverts commit 93599cb1872a4063261e7c8c664bd5adbe31b047. --- src/auto-reply/reply/session.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index a70ff6909d6..36131932bb4 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -558,7 +558,6 @@ export async function initSessionState(params: { }, ); - // When a session is auto-reset (idle timeout or daily reset), emit a // When a session is auto-reset (idle timeout or daily reset), emit a // session:reset internal hook so the session-memory hook can persist // the outgoing session context — just as it does for manual /new and /reset. @@ -566,6 +565,17 @@ export async function initSessionState(params: { // the handler reads it, getRecentSessionContentWithResetFallback() will // locate it via the .reset.* sibling naming convention. if (isNewSession && !resetTriggered && previousSessionEntry) { + const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); + const autoResetEvent = createInternalHookEvent("session", "reset", sessionKey, { + sessionEntry, + previousSessionEntry, + commandSource: "auto-reset", + workspaceDir, + cfg, + }); + void triggerInternalHook(autoResetEvent).catch((err) => { + log.debug("session:reset hook error (auto-reset)", { error: String(err) }); + }); } // Archive old transcript so it doesn't accumulate on disk (#14869).