fix: Create memory directory and symlink identity files during workspace setup
The memory search system expects files in ~/clawd/memory/ but workspace setup only created identity files (SOUL.md, USER.md, AGENTS.md, IDENTITY.md) at the workspace root. This caused the memory index to report "no memory files found" even though identity files existed. Changes: - Create memory/ directory during ensureAgentWorkspace when ensureBootstrapFiles is true - Symlink identity files (SOUL.md, USER.md, AGENTS.md, IDENTITY.md) into memory/ so they are discoverable by the memory search indexer - Add memoryDir to the return type of ensureAgentWorkspace - Gracefully handle symlink creation failures (e.g., Windows without privileges) - Skip symlink creation if target file already exists (preserves user customizations) Fixes issue where clawdbot would claim "I have no memories" despite having identity files, because the memory search index was empty. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f662039c47
commit
2f9bcb6a71
@ -1,8 +1,16 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
DEFAULT_AGENTS_FILENAME,
|
||||
DEFAULT_IDENTITY_FILENAME,
|
||||
DEFAULT_MEMORY_ALT_FILENAME,
|
||||
DEFAULT_MEMORY_DIR,
|
||||
DEFAULT_MEMORY_FILENAME,
|
||||
DEFAULT_SOUL_FILENAME,
|
||||
DEFAULT_USER_FILENAME,
|
||||
ensureAgentWorkspace,
|
||||
loadWorkspaceBootstrapFiles,
|
||||
} from "./workspace.js";
|
||||
import { makeTempWorkspace, writeWorkspaceFile } from "../test-helpers/workspace.js";
|
||||
@ -47,3 +55,62 @@ describe("loadWorkspaceBootstrapFiles", () => {
|
||||
expect(memoryEntries).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureAgentWorkspace", () => {
|
||||
it("creates memory directory with symlinks to identity files", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-workspace-");
|
||||
|
||||
const result = await ensureAgentWorkspace({
|
||||
dir: tempDir,
|
||||
ensureBootstrapFiles: true,
|
||||
});
|
||||
|
||||
// Check memory directory was created
|
||||
expect(result.memoryDir).toBe(path.join(tempDir, DEFAULT_MEMORY_DIR));
|
||||
const memoryDirStat = await fs.stat(result.memoryDir!);
|
||||
expect(memoryDirStat.isDirectory()).toBe(true);
|
||||
|
||||
// Check symlinks exist in memory directory
|
||||
const memoryFiles = await fs.readdir(result.memoryDir!);
|
||||
expect(memoryFiles).toContain(DEFAULT_SOUL_FILENAME);
|
||||
expect(memoryFiles).toContain(DEFAULT_USER_FILENAME);
|
||||
expect(memoryFiles).toContain(DEFAULT_AGENTS_FILENAME);
|
||||
expect(memoryFiles).toContain(DEFAULT_IDENTITY_FILENAME);
|
||||
|
||||
// Check symlinks point to correct files
|
||||
const soulLink = await fs.readlink(path.join(result.memoryDir!, DEFAULT_SOUL_FILENAME));
|
||||
expect(soulLink).toBe(`../${DEFAULT_SOUL_FILENAME}`);
|
||||
});
|
||||
|
||||
it("does not create memory directory when ensureBootstrapFiles is false", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-workspace-");
|
||||
|
||||
const result = await ensureAgentWorkspace({
|
||||
dir: tempDir,
|
||||
ensureBootstrapFiles: false,
|
||||
});
|
||||
|
||||
expect(result.memoryDir).toBeUndefined();
|
||||
const memoryDirPath = path.join(tempDir, DEFAULT_MEMORY_DIR);
|
||||
await expect(fs.access(memoryDirPath)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("does not overwrite existing files in memory directory", async () => {
|
||||
const tempDir = await makeTempWorkspace("moltbot-workspace-");
|
||||
const memoryDir = path.join(tempDir, DEFAULT_MEMORY_DIR);
|
||||
await fs.mkdir(memoryDir, { recursive: true });
|
||||
|
||||
// Create an existing file in memory directory
|
||||
const existingContent = "existing content";
|
||||
await fs.writeFile(path.join(memoryDir, DEFAULT_SOUL_FILENAME), existingContent);
|
||||
|
||||
await ensureAgentWorkspace({
|
||||
dir: tempDir,
|
||||
ensureBootstrapFiles: true,
|
||||
});
|
||||
|
||||
// Check existing file was not overwritten
|
||||
const content = await fs.readFile(path.join(memoryDir, DEFAULT_SOUL_FILENAME), "utf-8");
|
||||
expect(content).toBe(existingContent);
|
||||
});
|
||||
});
|
||||
|
||||
@ -28,6 +28,7 @@ export const DEFAULT_HEARTBEAT_FILENAME = "HEARTBEAT.md";
|
||||
export const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
|
||||
export const DEFAULT_MEMORY_FILENAME = "MEMORY.md";
|
||||
export const DEFAULT_MEMORY_ALT_FILENAME = "memory.md";
|
||||
export const DEFAULT_MEMORY_DIR = "memory";
|
||||
|
||||
const TEMPLATE_DIR = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
@ -120,6 +121,7 @@ export async function ensureAgentWorkspace(params?: {
|
||||
ensureBootstrapFiles?: boolean;
|
||||
}): Promise<{
|
||||
dir: string;
|
||||
memoryDir?: string;
|
||||
agentsPath?: string;
|
||||
soulPath?: string;
|
||||
toolsPath?: string;
|
||||
@ -174,10 +176,39 @@ export async function ensureAgentWorkspace(params?: {
|
||||
if (isBrandNewWorkspace) {
|
||||
await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
|
||||
}
|
||||
|
||||
// Ensure memory/ directory exists and symlink identity files for memory search indexing
|
||||
const memoryDir = path.join(dir, DEFAULT_MEMORY_DIR);
|
||||
await fs.mkdir(memoryDir, { recursive: true });
|
||||
|
||||
// Symlink identity files into memory/ so they are indexed by memory search
|
||||
const identityFilesToSymlink = [
|
||||
{ source: soulPath, target: path.join(memoryDir, DEFAULT_SOUL_FILENAME) },
|
||||
{ source: userPath, target: path.join(memoryDir, DEFAULT_USER_FILENAME) },
|
||||
{ source: agentsPath, target: path.join(memoryDir, DEFAULT_AGENTS_FILENAME) },
|
||||
{ source: identityPath, target: path.join(memoryDir, DEFAULT_IDENTITY_FILENAME) },
|
||||
];
|
||||
|
||||
for (const { source, target } of identityFilesToSymlink) {
|
||||
try {
|
||||
await fs.access(target);
|
||||
// Target already exists, skip
|
||||
} catch {
|
||||
// Target doesn't exist, create symlink
|
||||
try {
|
||||
const relativePath = path.relative(memoryDir, source);
|
||||
await fs.symlink(relativePath, target);
|
||||
} catch {
|
||||
// Symlink creation failed (e.g., on Windows without privileges), skip silently
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ensureGitRepo(dir, isBrandNewWorkspace);
|
||||
|
||||
return {
|
||||
dir,
|
||||
memoryDir,
|
||||
agentsPath,
|
||||
soulPath,
|
||||
toolsPath,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user