Merge pull request 'Implement unique filename generation to prevent overwriting existing session files' (#4) from no-overrides into main
Reviewed-on: #4 Reviewed-by: Luna <clawy@reversed.dev>
This commit was merged in pull request #4.
This commit is contained in:
3
HOOK.md
3
HOOK.md
@@ -17,6 +17,9 @@ metadata:
|
||||
Writes full user/assistant transcript context from the previous session into
|
||||
`<workspace>/memory/YYYY-MM-DD-slug.md` when `/new` or `/reset` runs.
|
||||
|
||||
This hook avoids overwriting existing files by choosing a unique filename
|
||||
when a collision occurs (it appends `-1`, `-2`, etc. to the base name).
|
||||
|
||||
Differences from bundled session-memory:
|
||||
|
||||
- Uses full session context in output file
|
||||
|
||||
@@ -14,6 +14,8 @@ Custom OpenClaw session-memory hook.
|
||||
- Saves the **full** user/assistant session context into `workspace/memory/`
|
||||
- Uses `agents.defaults.userTimezone` for date/time formatting
|
||||
- Uses a slugged filename, with slug generation based on a small excerpt
|
||||
- Avoids overwriting existing files: if a filename already exists the hook
|
||||
appends `-1`, `-2`, ... to produce a unique filename.
|
||||
|
||||
## Disable the bundled hook
|
||||
|
||||
@@ -42,3 +44,8 @@ Or set it false in config:
|
||||
## Notes
|
||||
|
||||
This repo is intentionally lightweight, it exists so the custom hook can live in the managed hooks directory and stay separate from the bundled one.
|
||||
|
||||
If you prefer atomic creation to fully eliminate a race window (two processes
|
||||
creating the same new filename simultaneously), replace the simple existence
|
||||
check with an atomic open/write using `fs.open(..., 'wx')` or write to a
|
||||
temporary file and `fs.rename()` it into place.
|
||||
|
||||
22
handler.ts
22
handler.ts
@@ -197,6 +197,26 @@ function buildSlugExcerpt(full: string): string {
|
||||
return tail.slice(-3000);
|
||||
}
|
||||
|
||||
async function getUniqueFilePath(dir: string, filename: string): Promise<string> {
|
||||
const candidate = path.join(dir, filename);
|
||||
try {
|
||||
await fs.access(candidate);
|
||||
} catch {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
const parsed = path.parse(filename);
|
||||
for (let i = 1; ; i += 1) {
|
||||
const name = `${parsed.name}-${i}${parsed.ext}`;
|
||||
const p = path.join(dir, name);
|
||||
try {
|
||||
await fs.access(p);
|
||||
} catch {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function generateSlug(params: {
|
||||
excerpt: string;
|
||||
cfg: AnyObj;
|
||||
@@ -274,7 +294,7 @@ const handler = async (event: {
|
||||
await fs.mkdir(memoryDir, { recursive: true });
|
||||
|
||||
const filename = `${dateStr}-${slug}.md`;
|
||||
const memoryFilePath = path.join(memoryDir, filename);
|
||||
const memoryFilePath = await getUniqueFilePath(memoryDir, filename);
|
||||
|
||||
const source = typeof context.commandSource === "string" ? context.commandSource : "unknown";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user