




















@@ -384,6 +384,35 @@ describe("stageBundledPluginRuntime", () => {
384384expect(fs.readFileSync(runtimePackagePath, "utf8")).toContain('"extensions": [');
385385});
386386387+it("copies bundled plugin skill trees into the runtime overlay", () => {
388+const repoRoot = makeRepoRoot("openclaw-stage-bundled-runtime-skills-");
389+createDistPluginDir(repoRoot, "feishu");
390+setupRepoFiles(repoRoot, {
391+[bundledDistPluginFile("feishu", "index.js")]: "export default {}\n",
392+[bundledDistPluginFile("feishu", "skills/feishu-doc/SKILL.md")]:
393+"---\nname: feishu-doc\n---\n",
394+[bundledDistPluginFile("feishu", "skills/feishu-doc/fixture.txt")]: "# Feishu Doc\n",
395+});
396+397+stageBundledPluginRuntime({ repoRoot });
398+399+const runtimeRoot = path.join(repoRoot, "dist-runtime");
400+const runtimeSkillPath = path.join(
401+runtimeRoot,
402+"extensions",
403+"feishu",
404+"skills",
405+"feishu-doc",
406+"fixture.txt",
407+);
408+409+expect(fs.lstatSync(runtimeSkillPath).isSymbolicLink()).toBe(false);
410+expect(fs.readFileSync(runtimeSkillPath, "utf8")).toBe("# Feishu Doc\n");
411+expect(path.relative(fs.realpathSync(runtimeRoot), fs.realpathSync(runtimeSkillPath))).toBe(
412+path.join("extensions", "feishu", "skills", "feishu-doc", "fixture.txt"),
413+);
414+});
415+387416it("preserves package metadata needed for bundled plugin discovery from dist-runtime", () => {
388417const repoRoot = makeRepoRoot("openclaw-stage-bundled-runtime-discovery-");
389418const runtimeExtensionsDir = path.join(repoRoot, "dist-runtime", "extensions");
@@ -480,13 +509,13 @@ describe("stageBundledPluginRuntime", () => {
480509createDistPluginDir(repoRoot, "feishu");
481510setupRepoFiles(repoRoot, {
482511[bundledDistPluginFile("feishu", "index.js")]: "export default {}\n",
483-[bundledDistPluginFile("feishu", "skills/feishu-doc/fixture.txt")]: "# Feishu Doc\n",
512+[bundledDistPluginFile("feishu", "assets/fixture.txt")]: "# Feishu Doc\n",
484513});
485514486515const realSymlinkSync = fs.symlinkSync.bind(fs);
487516const symlinkSpy = vi.spyOn(fs, "symlinkSync").mockImplementation(((target, link, type) => {
488517const linkPath = String(link);
489-if (linkPath.endsWith(path.join("skills", "feishu-doc", "fixture.txt"))) {
518+if (linkPath.endsWith(path.join("assets", "fixture.txt"))) {
490519const err = Object.assign(new Error("file already exists"), { code: "EEXIST" });
491520realSymlinkSync(String(target), linkPath, type);
492521throw err;
@@ -496,18 +525,55 @@ describe("stageBundledPluginRuntime", () => {
496525497526expect(() => stageBundledPluginRuntime({ repoRoot })).not.toThrow();
498527499-const runtimeSkillPath = path.join(
528+const runtimeAssetPath = path.join(
500529repoRoot,
501530"dist-runtime",
502531"extensions",
503532"feishu",
504-"skills",
505-"feishu-doc",
533+"assets",
506534"fixture.txt",
507535);
508-expect(fs.lstatSync(runtimeSkillPath).isSymbolicLink()).toBe(true);
509-expect(fs.readFileSync(runtimeSkillPath, "utf8")).toBe("# Feishu Doc\n");
536+expect(fs.lstatSync(runtimeAssetPath).isSymbolicLink()).toBe(true);
537+expect(fs.readFileSync(runtimeAssetPath, "utf8")).toBe("# Feishu Doc\n");
510538511539symlinkSpy.mockRestore();
512540});
541+542+it.each(["EACCES", "ENOSYS"] as const)(
543+"falls back to copying runtime assets when Windows symlink creation fails with %s",
544+(code) => {
545+const repoRoot = makeRepoRoot("openclaw-stage-bundled-runtime-win-copy-");
546+createDistPluginDir(repoRoot, "feishu");
547+setupRepoFiles(repoRoot, {
548+[bundledDistPluginFile("feishu", "index.js")]: "export default {}\n",
549+[bundledDistPluginFile("feishu", "assets/fixture.txt")]: "# Feishu Doc\n",
550+});
551+552+const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
553+const realSymlinkSync = fs.symlinkSync.bind(fs);
554+const symlinkSpy = vi.spyOn(fs, "symlinkSync").mockImplementation(((target, link, type) => {
555+const linkPath = String(link);
556+if (linkPath.endsWith(path.join("assets", "fixture.txt"))) {
557+throw Object.assign(new Error("symlink failed"), { code });
558+}
559+return realSymlinkSync(String(target), linkPath, type);
560+}) as typeof fs.symlinkSync);
561+562+stageBundledPluginRuntime({ repoRoot });
563+564+const runtimeAssetPath = path.join(
565+repoRoot,
566+"dist-runtime",
567+"extensions",
568+"feishu",
569+"assets",
570+"fixture.txt",
571+);
572+expect(fs.lstatSync(runtimeAssetPath).isSymbolicLink()).toBe(false);
573+expect(fs.readFileSync(runtimeAssetPath, "utf8")).toBe("# Feishu Doc\n");
574+575+symlinkSpy.mockRestore();
576+platformSpy.mockRestore();
577+},
578+);
513579});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。