fix(core): refresh cached remote skills (#34059)

This commit is contained in:
Shoubhit Dash
2026-06-26 17:42:50 +05:30
committed by GitHub
parent ae853561cd
commit 971518c6d9
4 changed files with 215 additions and 24 deletions
+47 -10
View File
@@ -13,6 +13,7 @@ const fileConcurrency = 8
class IndexSkill extends Schema.Class<IndexSkill>("IndexSkill")({
name: Schema.String,
files: Schema.Array(Schema.String),
version: Schema.optional(Schema.String),
}) {}
class Index extends Schema.Class<Index>("Index")({
@@ -76,17 +77,53 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | Htt
(skill) =>
Effect.gen(function* () {
const root = path.join(cache, skill.name)
const versionFile = path.join(root, ".opencode-version")
const version = skill.version
const current =
version === undefined
? undefined
: yield* fs.readFileStringSafe(versionFile).pipe(Effect.catch(() => Effect.succeed(undefined)))
yield* Effect.forEach(
skill.files,
(file) => download(new URL(file, `${host}/${skill.name}/`).href, path.join(root, file)),
{
concurrency: fileConcurrency,
},
)
const md = path.join(root, "SKILL.md")
return (yield* fs.exists(md).pipe(Effect.orDie)) ? root : null
if (version === undefined || current === version) {
yield* Effect.forEach(
skill.files,
(file) => download(new URL(file, `${host}/${skill.name}/`).href, path.join(root, file)),
{ concurrency: fileConcurrency, discard: true },
)
} else {
const token = crypto.randomUUID()
const staging = `${root}.tmp-${token}`
const backup = `${root}.old-${token}`
yield* Effect.gen(function* () {
const downloaded = yield* Effect.forEach(
skill.files,
(file) => download(new URL(file, `${host}/${skill.name}/`).href, path.join(staging, file)),
{ concurrency: fileConcurrency },
)
if (!downloaded.every(Boolean)) return
if (!(yield* fs.exists(path.join(staging, "SKILL.md")).pipe(Effect.orDie))) return
yield* fs.writeFileString(path.join(staging, ".opencode-version"), version)
yield* Effect.uninterruptible(
Effect.gen(function* () {
const cached = yield* fs.exists(root).pipe(Effect.orDie)
if (cached) yield* fs.rename(root, backup)
yield* fs.rename(staging, root).pipe(
Effect.catch((error) =>
Effect.gen(function* () {
if (cached) yield* fs.rename(backup, root).pipe(Effect.ignore)
return yield* Effect.fail(error)
}),
),
)
if (cached) yield* fs.remove(backup, { recursive: true, force: true }).pipe(Effect.ignore)
}),
)
}).pipe(
Effect.catch((error) => Effect.logError("failed to refresh skill", { skill: skill.name, error })),
Effect.ensuring(fs.remove(staging, { recursive: true, force: true }).pipe(Effect.ignore)),
)
}
return (yield* fs.exists(path.join(root, "SKILL.md")).pipe(Effect.orDie)) ? root : null
}),
{ concurrency: skillConcurrency },
)