diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index 6c1b2233c0f..36131932bb4 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"; @@ -557,6 +558,26 @@ 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. + // 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, { + 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({ 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;