mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 17:24:46 +00:00
fix(skills): replace generated plugin skill directories
This commit is contained in:
@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugin skills: replace generated Windows plugin-skill directories before publishing the current skill link, avoiding repeated `EINVAL` warnings from stale non-symlink entries. Fixes #81432. (#81446) Thanks @hclsys and @vincentkoc.
|
||||
- Channels/config: treat channel entries with only `enabled: true` as configured state so plugin-backed channels can auto-enable from an explicit on switch. Fixes #81323. (#81331) Thanks @EvanYao826 and @vincentkoc.
|
||||
- CLI/update: add an update finalization path for externally swapped core runtimes, running update-time doctor repair and plugin convergence from post-doctor config and install-record state before reporting completion. Thanks @shakkernerd.
|
||||
- Agents/WebChat: stop a successful assistant turn whose stale `errorMessage` matches a billing, auth, or rate-limit pattern from rotating profiles, falling back, or surfacing a hard `FailoverError` unless the current attempt has a real failover failure. (#70900) Thanks @truffle-dev.
|
||||
|
||||
@@ -473,6 +473,22 @@ describe("publishPluginSkills", () => {
|
||||
expect(fsSync.readlinkSync(path.join(managedDir, "my-skill"))).toBe(dir2);
|
||||
});
|
||||
|
||||
it("replaces generated Windows directory entries before publishing a current skill", async () => {
|
||||
const skillParent = await tempDirs.make("plugin-skills-");
|
||||
const managedDir = await tempDirs.make("managed-skills-");
|
||||
|
||||
const dir = await writeSkillDir(skillParent, "my-skill");
|
||||
const existingDir = path.join(managedDir, "my-skill");
|
||||
await fs.mkdir(existingDir, { recursive: true });
|
||||
await fs.writeFile(path.join(existingDir, "stale.txt"), "stale");
|
||||
|
||||
withPlatform("win32", () => {
|
||||
publishPluginSkills([dir], { pluginSkillsDir: managedDir });
|
||||
});
|
||||
|
||||
expect(fsSync.readlinkSync(existingDir)).toBe(dir);
|
||||
});
|
||||
|
||||
it("cleans up stale symlinks whose targets still exist", async () => {
|
||||
const skillParent = await tempDirs.make("plugin-skills-");
|
||||
const managedDir = await tempDirs.make("managed-skills-");
|
||||
|
||||
@@ -220,11 +220,19 @@ function publishPluginSkills(skillDirs: string[], opts?: { pluginSkillsDir?: str
|
||||
// best-effort; symlink will fail below if dir is truly unusable
|
||||
}
|
||||
try {
|
||||
const existingTarget = fs.readlinkSync(linkPath);
|
||||
if (existingTarget === target) {
|
||||
const existingEntry = fs.lstatSync(linkPath);
|
||||
if (existingEntry.isSymbolicLink()) {
|
||||
const existingTarget = fs.readlinkSync(linkPath);
|
||||
if (existingTarget === target) {
|
||||
continue;
|
||||
}
|
||||
removeGeneratedPluginSkillEntry(linkPath);
|
||||
} else if (isGeneratedPluginSkillEntry(existingEntry)) {
|
||||
removeGeneratedPluginSkillEntry(linkPath);
|
||||
} else {
|
||||
log.warn(`plugin skill entry is not a generated symlink: ${linkPath}`);
|
||||
continue;
|
||||
}
|
||||
removeGeneratedPluginSkillEntry(linkPath);
|
||||
} catch (err) {
|
||||
if (!isNotFoundError(err)) {
|
||||
log.warn(`failed to inspect plugin skill symlink "${linkPath}": ${String(err)}`);
|
||||
@@ -259,7 +267,10 @@ function publishPluginSkills(skillDirs: string[], opts?: { pluginSkillsDir?: str
|
||||
}
|
||||
}
|
||||
|
||||
function isGeneratedPluginSkillEntry(entry: fs.Dirent): boolean {
|
||||
function isGeneratedPluginSkillEntry(
|
||||
entry: Pick<fs.Dirent, "isDirectory" | "isSymbolicLink">,
|
||||
): boolean {
|
||||
// Windows directory symlinks are junctions and lstat reports them as directories.
|
||||
return entry.isSymbolicLink() || (process.platform === "win32" && entry.isDirectory());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user