
















@@ -300,6 +300,97 @@ describe("syncMemoryWikiBridgeSources", () => {
300300});
301301});
302302303+it("refuses to overwrite bridge source pages through vault symlinks", async () => {
304+const workspaceDir = await createBridgeWorkspace("symlink-workspace");
305+const { rootDir: vaultDir, config } = await createVault({
306+rootDir: nextCaseRoot("symlink-vault"),
307+config: {
308+vaultMode: "bridge",
309+bridge: {
310+enabled: true,
311+readMemoryArtifacts: true,
312+indexMemoryRoot: true,
313+},
314+},
315+});
316+const memoryPath = path.join(workspaceDir, "MEMORY.md");
317+await fs.writeFile(memoryPath, "# Durable Memory\n", "utf8");
318+registerBridgeArtifacts([
319+{
320+kind: "memory-root",
321+ workspaceDir,
322+relativePath: "MEMORY.md",
323+absolutePath: memoryPath,
324+agentIds: ["main"],
325+contentType: "markdown",
326+},
327+]);
328+const appConfig: OpenClawConfig = {
329+agents: {
330+list: [{ id: "main", default: true, workspace: workspaceDir }],
331+},
332+};
333+const first = await syncMemoryWikiBridgeSources({ config, appConfig });
334+const pagePath = first.pagePaths[0] ?? "";
335+const pageAbsPath = path.join(vaultDir, pagePath);
336+const externalTarget = path.join(workspaceDir, "outside.md");
337+await fs.writeFile(externalTarget, "external target\n", "utf8");
338+await fs.rm(pageAbsPath);
339+await fs.symlink(externalTarget, pageAbsPath);
340+await fs.writeFile(memoryPath, "# Updated Durable Memory\n", "utf8");
341+342+await expect(syncMemoryWikiBridgeSources({ config, appConfig })).rejects.toThrow(
343+"Refusing to write imported source page through symlink",
344+);
345+await expect(fs.readFile(externalTarget, "utf8")).resolves.toBe("external target\n");
346+});
347+348+it("replaces bridge source page hardlinks without clobbering their target", async () => {
349+const workspaceDir = await createBridgeWorkspace("hardlink-workspace");
350+const { rootDir: vaultDir, config } = await createVault({
351+rootDir: nextCaseRoot("hardlink-vault"),
352+config: {
353+vaultMode: "bridge",
354+bridge: {
355+enabled: true,
356+readMemoryArtifacts: true,
357+indexMemoryRoot: true,
358+},
359+},
360+});
361+const memoryPath = path.join(workspaceDir, "MEMORY.md");
362+await fs.writeFile(memoryPath, "# Durable Memory\n", "utf8");
363+registerBridgeArtifacts([
364+{
365+kind: "memory-root",
366+ workspaceDir,
367+relativePath: "MEMORY.md",
368+absolutePath: memoryPath,
369+agentIds: ["main"],
370+contentType: "markdown",
371+},
372+]);
373+const appConfig: OpenClawConfig = {
374+agents: {
375+list: [{ id: "main", default: true, workspace: workspaceDir }],
376+},
377+};
378+const first = await syncMemoryWikiBridgeSources({ config, appConfig });
379+const pagePath = first.pagePaths[0] ?? "";
380+const pageAbsPath = path.join(vaultDir, pagePath);
381+const externalTarget = path.join(workspaceDir, "outside-hardlink.md");
382+await fs.writeFile(externalTarget, "external target\n", "utf8");
383+await fs.rm(pageAbsPath);
384+await fs.link(externalTarget, pageAbsPath);
385+await fs.writeFile(memoryPath, "# Updated Durable Memory\n", "utf8");
386+387+const second = await syncMemoryWikiBridgeSources({ config, appConfig });
388+389+expect(second.updatedCount).toBe(1);
390+await expect(fs.readFile(externalTarget, "utf8")).resolves.toBe("external target\n");
391+await expect(fs.readFile(pageAbsPath, "utf8")).resolves.toContain("# Updated Durable Memory");
392+});
393+303394it("caps composed bridge source filenames to the filesystem component limit", async () => {
304395const workspaceDir = await createBridgeWorkspace(`${"漢".repeat(50)}-workspace`);
305396const { rootDir: vaultDir, config } = await createVault({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。