mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 20:16:14 +00:00
fix(session-memory): harden reset transcript recovery
This commit is contained in:
@@ -8,14 +8,14 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import type { HookHandler } from "../../hooks.js";
|
||||
import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
|
||||
import { resolveStateDir } from "../../../config/paths.js";
|
||||
import { createSubsystemLogger } from "../../../logging/subsystem.js";
|
||||
import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
|
||||
import { hasInterSessionUserProvenance } from "../../../sessions/input-provenance.js";
|
||||
import { resolveHookConfig } from "../../config.js";
|
||||
import type { HookHandler } from "../../hooks.js";
|
||||
import { generateSlugViaLLM } from "../../llm-slug-generator.js";
|
||||
|
||||
const log = createSubsystemLogger("hooks/session-memory");
|
||||
@@ -107,6 +107,65 @@ async function getRecentSessionContentWithResetFallback(
|
||||
}
|
||||
}
|
||||
|
||||
function stripResetSuffix(fileName: string): string {
|
||||
const resetIndex = fileName.indexOf(".reset.");
|
||||
return resetIndex === -1 ? fileName : fileName.slice(0, resetIndex);
|
||||
}
|
||||
|
||||
async function findPreviousSessionFile(params: {
|
||||
sessionsDir: string;
|
||||
currentSessionFile?: string;
|
||||
sessionId?: string;
|
||||
}): Promise<string | undefined> {
|
||||
try {
|
||||
const files = await fs.readdir(params.sessionsDir);
|
||||
const fileSet = new Set(files);
|
||||
|
||||
const baseFromReset = params.currentSessionFile
|
||||
? stripResetSuffix(path.basename(params.currentSessionFile))
|
||||
: undefined;
|
||||
if (baseFromReset && fileSet.has(baseFromReset)) {
|
||||
return path.join(params.sessionsDir, baseFromReset);
|
||||
}
|
||||
|
||||
const trimmedSessionId = params.sessionId?.trim();
|
||||
if (trimmedSessionId) {
|
||||
const canonicalFile = `${trimmedSessionId}.jsonl`;
|
||||
if (fileSet.has(canonicalFile)) {
|
||||
return path.join(params.sessionsDir, canonicalFile);
|
||||
}
|
||||
|
||||
const topicVariants = files
|
||||
.filter(
|
||||
(name) =>
|
||||
name.startsWith(`${trimmedSessionId}-topic-`) &&
|
||||
name.endsWith(".jsonl") &&
|
||||
!name.includes(".reset."),
|
||||
)
|
||||
.toSorted()
|
||||
.toReversed();
|
||||
if (topicVariants.length > 0) {
|
||||
return path.join(params.sessionsDir, topicVariants[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.currentSessionFile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const nonResetJsonl = files
|
||||
.filter((name) => name.endsWith(".jsonl") && !name.includes(".reset."))
|
||||
.toSorted()
|
||||
.toReversed();
|
||||
if (nonResetJsonl.length > 0) {
|
||||
return path.join(params.sessionsDir, nonResetJsonl[0]);
|
||||
}
|
||||
} catch {
|
||||
// Ignore directory read errors.
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save session context to memory when /new command is triggered
|
||||
*/
|
||||
@@ -133,12 +192,36 @@ const saveSessionToMemory: HookHandler = async (event) => {
|
||||
const dateStr = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
|
||||
// Generate descriptive slug from session using LLM
|
||||
// Prefer previousSessionEntry (old session before /new) over current (which may be empty)
|
||||
const sessionEntry = (context.previousSessionEntry || context.sessionEntry || {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const currentSessionId = sessionEntry.sessionId as string;
|
||||
const currentSessionFile = sessionEntry.sessionFile as string;
|
||||
let currentSessionFile = (sessionEntry.sessionFile as string) || undefined;
|
||||
|
||||
// If sessionFile is empty or looks like a new/reset file, try to find the previous session file.
|
||||
if (!currentSessionFile || currentSessionFile.includes(".reset.")) {
|
||||
const sessionsDirs = new Set<string>();
|
||||
if (currentSessionFile) {
|
||||
sessionsDirs.add(path.dirname(currentSessionFile));
|
||||
}
|
||||
sessionsDirs.add(path.join(workspaceDir, "sessions"));
|
||||
|
||||
for (const sessionsDir of sessionsDirs) {
|
||||
const recoveredSessionFile = await findPreviousSessionFile({
|
||||
sessionsDir,
|
||||
currentSessionFile,
|
||||
sessionId: currentSessionId,
|
||||
});
|
||||
if (!recoveredSessionFile) {
|
||||
continue;
|
||||
}
|
||||
currentSessionFile = recoveredSessionFile;
|
||||
log.debug("Found previous session file", { file: currentSessionFile });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Session context resolved", {
|
||||
sessionId: currentSessionId,
|
||||
|
||||
Reference in New Issue
Block a user