mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
fix(hooks): avoid session memory filename collisions
Add collision suffixes for session-memory fallback filenames so repeated same-minute reset/new captures do not overwrite earlier archives.
This commit is contained in:
@@ -73,6 +73,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/status: show recent supervisor restart handoffs in `openclaw gateway status --deep`, including JSON details, so clean service-managed restarts are reported as restart handoffs instead of opaque stopped-service diagnostics. Thanks @shakkernerd.
|
||||
- Providers/Fireworks: expose Kimi models as thinking-off-only and keep K2.5/K2.6 requests on `thinking: disabled`, so manual model switches do not send Fireworks-rejected `reasoning*` parameters. Refs #74289. Thanks @frankekn.
|
||||
- WhatsApp responsiveness: stop only verified stale local TUI clients when they degrade the Gateway event loop and delay replies. Thanks @vincentkoc.
|
||||
- Hooks/session-memory: add collision suffixes to fallback memory filenames so repeated `/new` or `/reset` captures in the same minute do not overwrite the earlier session archive. Thanks @vincentkoc.
|
||||
- Video generation: wait up to 20 minutes for slow fal/MiniMax queue-backed jobs, stop forwarding unsupported Google Veo generated-audio options, and normalize MiniMax `720P` requests to its supported `768P` resolution with the usual override warning/details instead of failing fallback.
|
||||
- Video generation: accept provider-specific aspect-ratio and resolution hints at the tool boundary, normalize `720P` to MiniMax's supported `768P`, and stop sending Google `generateAudio` on Gemini video requests so provider fallback can recover from model-specific parameter differences. Thanks @vincentkoc.
|
||||
- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc.
|
||||
|
||||
@@ -413,6 +413,41 @@ describe("session-memory hook", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps same-minute fallback timestamp captures by adding a filename suffix", async () => {
|
||||
await withEnvAsync({ TZ: "UTC" }, async () => {
|
||||
const tempDir = await createCaseWorkspace("workspace");
|
||||
const timestamp = new Date("2026-01-01T04:30:15.000Z");
|
||||
|
||||
await runNewWithPreviousSessionEntry({
|
||||
tempDir,
|
||||
timestamp,
|
||||
previousSessionEntry: {
|
||||
sessionId: "first-session",
|
||||
},
|
||||
});
|
||||
await runNewWithPreviousSessionEntry({
|
||||
tempDir,
|
||||
timestamp,
|
||||
previousSessionEntry: {
|
||||
sessionId: "second-session",
|
||||
},
|
||||
});
|
||||
|
||||
const memoryDir = path.join(tempDir, "memory");
|
||||
const files = await fs.readdir(memoryDir);
|
||||
expect(files).toHaveLength(2);
|
||||
expect(files).toContain("2026-01-01-0430.md");
|
||||
expect(files).toContain("2026-01-01-0430-2.md");
|
||||
|
||||
await expect(
|
||||
fs.readFile(path.join(memoryDir, "2026-01-01-0430.md"), "utf-8"),
|
||||
).resolves.toContain("- **Session ID**: first-session");
|
||||
await expect(
|
||||
fs.readFile(path.join(memoryDir, "2026-01-01-0430-2.md"), "utf-8"),
|
||||
).resolves.toContain("- **Session ID**: second-session");
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers workspaceDir from hook context when sessionKey points at main", async () => {
|
||||
const mainWorkspace = await createCaseWorkspace("workspace-main");
|
||||
const naviWorkspace = await createCaseWorkspace("workspace-navi");
|
||||
|
||||
@@ -85,6 +85,28 @@ function formatLocalSessionTimestamp(date: Date): {
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveAvailableMemoryFilename(params: {
|
||||
memoryDir: string;
|
||||
dateStr: string;
|
||||
slug: string;
|
||||
}): Promise<string> {
|
||||
const basename = `${params.dateStr}-${params.slug}`;
|
||||
let suffix = 1;
|
||||
|
||||
while (true) {
|
||||
const filename = suffix === 1 ? `${basename}.md` : `${basename}-${suffix}.md`;
|
||||
try {
|
||||
await fs.access(path.join(params.memoryDir, filename));
|
||||
suffix += 1;
|
||||
} catch (err) {
|
||||
if ((err as { code?: string }).code === "ENOENT") {
|
||||
return filename;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDisplaySessionKey(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
@@ -223,7 +245,7 @@ async function saveSessionMemoryNow(event: Parameters<HookHandler>[0]): Promise<
|
||||
}
|
||||
|
||||
// Create filename with date and slug
|
||||
const filename = `${dateStr}-${slug}.md`;
|
||||
const filename = await resolveAvailableMemoryFilename({ memoryDir, dateStr, slug });
|
||||
const memoryFilePath = path.join(memoryDir, filename);
|
||||
log.debug("Memory file path resolved", {
|
||||
filename,
|
||||
|
||||
Reference in New Issue
Block a user