






















@@ -346,6 +346,80 @@ describe("syncMemoryWikiBridgeSources", () => {
346346await expect(fs.readFile(externalTarget, "utf8")).resolves.toBe("external target\n");
347347});
348348349+async function createDirectoryCollisionFixture(params: {
350+workspaceName: string;
351+vaultName: string;
352+populateDirectory?: boolean;
353+}) {
354+const workspaceDir = await createBridgeWorkspace(params.workspaceName);
355+const { rootDir: vaultDir, config } = await createVault({
356+rootDir: nextCaseRoot(params.vaultName),
357+config: {
358+vaultMode: "bridge",
359+bridge: {
360+enabled: true,
361+readMemoryArtifacts: true,
362+indexMemoryRoot: true,
363+},
364+},
365+});
366+const memoryPath = path.join(workspaceDir, "MEMORY.md");
367+await fs.writeFile(memoryPath, "# Durable Memory\n", "utf8");
368+registerBridgeArtifacts([
369+{
370+kind: "memory-root",
371+ workspaceDir,
372+relativePath: "MEMORY.md",
373+absolutePath: memoryPath,
374+agentIds: ["main"],
375+contentType: "markdown",
376+},
377+]);
378+const appConfig: OpenClawConfig = {
379+agents: {
380+list: [{ id: "main", default: true, workspace: workspaceDir }],
381+},
382+};
383+const first = await syncMemoryWikiBridgeSources({ config, appConfig });
384+const pagePath = first.pagePaths[0] ?? "";
385+const pageAbsPath = path.join(vaultDir, pagePath);
386+await fs.rm(pageAbsPath);
387+await fs.mkdir(pageAbsPath);
388+if (params.populateDirectory) {
389+await fs.writeFile(path.join(pageAbsPath, "child.md"), "blocking child\n", "utf8");
390+}
391+await fs.writeFile(memoryPath, "# Updated Durable Memory\n", "utf8");
392+return { appConfig, config, pageAbsPath };
393+}
394+395+it("reports non-symlink bridge source write safety failures without symlink wording", async () => {
396+const { appConfig, config } = await createDirectoryCollisionFixture({
397+workspaceName: "not-file-workspace",
398+vaultName: "not-file-vault",
399+populateDirectory: true,
400+});
401+402+const second = syncMemoryWikiBridgeSources({ config, appConfig });
403+await expect(second).rejects.toThrow(
404+/Refusing to write imported source page \((not-empty|not-file|path-mismatch)\): sources\//u,
405+);
406+await expect(second).rejects.not.toThrow("through symlink");
407+});
408+409+it("does not remove empty directory bridge source collisions as hardlinks", async () => {
410+const { appConfig, config, pageAbsPath } = await createDirectoryCollisionFixture({
411+workspaceName: "empty-directory-workspace",
412+vaultName: "empty-directory-vault",
413+});
414+415+const second = syncMemoryWikiBridgeSources({ config, appConfig });
416+await expect(second).rejects.toThrow(
417+/Refusing to write imported source page \((not-file|path-mismatch)\): sources\//u,
418+);
419+await expect(second).rejects.not.toThrow("through symlink");
420+await expect(fs.stat(pageAbsPath)).resolves.toSatisfy((stat) => stat.isDirectory());
421+});
422+349423it("replaces bridge source page hardlinks without clobbering their target", async () => {
350424const workspaceDir = await createBridgeWorkspace("hardlink-workspace");
351425const { rootDir: vaultDir, config } = await createVault({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。