





















@@ -1,6 +1,8 @@
11import crypto from "node:crypto";
22import path from "node:path";
3+import { stableStringify } from "../agents/stable-stringify.js";
34import type { OpenClawConfig } from "../config/types.openclaw.js";
5+import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
46import { getSkillsSnapshotVersion } from "./refresh-state.js";
57import { buildSkillIndex, skillIndexEntries, type SkillIndex } from "./registry.js";
68import type { SkillEligibilityContext, SkillSnapshot } from "./types.js";
@@ -31,15 +33,24 @@ export type SkillSnapshotBuildOptions = {
31333234export class SkillsService {
3335private readonly cache = new Map<string, SkillIndex>();
36+private readonly cacheScopes = new Map<string, string>();
34373538getIndex(request: SkillIndexRequest): SkillIndex {
36-const cacheKey = buildSkillIndexCacheKey(request);
39+const snapshotVersion =
40+request.snapshotVersion ?? getSkillsSnapshotVersion(request.workspaceDir);
41+if (!shouldCacheSkillIndex(snapshotVersion)) {
42+return this.loadIndex(request, buildUncachedSkillIndexCacheKey(request, snapshotVersion));
43+}
44+const cacheKeyParts = buildSkillIndexCacheKeyParts(request, snapshotVersion);
45+const cacheKey = stringifyCacheKeyParts(cacheKeyParts);
3746const cached = this.cache.get(cacheKey);
3847if (cached) {
3948return cached;
4049}
4150const index = this.loadIndex(request, cacheKey);
51+this.pruneScope(cacheKeyParts.scope, cacheKey);
4252this.cache.set(cacheKey, index);
53+this.cacheScopes.set(cacheKey, cacheKeyParts.scope);
4354return index;
4455}
4556@@ -62,15 +73,26 @@ export class SkillsService {
6273pluginSkillsDir: opts?.pluginSkillsDir,
6374snapshotVersion: opts?.snapshotVersion,
6475};
65-const index =
66- opts?.snapshotVersion === undefined
67- ? this.loadIndex(request, buildSkillIndexCacheKey(request))
68- : this.getIndex(request);
76+const snapshotVersion = request.snapshotVersion ?? getSkillsSnapshotVersion(workspaceDir);
77+const index = shouldCacheSkillIndex(snapshotVersion)
78+ ? this.getIndex(request)
79+ : this.loadIndex(request, buildUncachedSkillIndexCacheKey(request, snapshotVersion));
6980return buildSkillSnapshotFromIndex(workspaceDir, index, opts);
7081}
71827283invalidate(): void {
7384this.cache.clear();
85+this.cacheScopes.clear();
86+}
87+88+private pruneScope(scope: string, keepKey: string): void {
89+for (const [key, cachedScope] of this.cacheScopes) {
90+if (key === keepKey || cachedScope !== scope) {
91+continue;
92+}
93+this.cache.delete(key);
94+this.cacheScopes.delete(key);
95+}
7496}
7597}
7698@@ -98,13 +120,8 @@ export function buildWorkspaceSkillSnapshot(
98120return skillsService.buildSnapshot(workspaceDir, opts);
99121}
100122101-function stableConfigHash(config?: OpenClawConfig): string {
102-const skillsConfig = config?.skills ?? {};
103-return crypto
104-.createHash("sha256")
105-.update(JSON.stringify(skillsConfig))
106-.digest("hex")
107-.slice(0, 16);
123+function stableHash(value: unknown): string {
124+return crypto.createHash("sha256").update(stableStringify(value)).digest("hex").slice(0, 16);
108125}
109126110127function normalizedOptionalPath(value?: string): string {
@@ -113,12 +130,82 @@ function normalizedOptionalPath(value?: string): string {
113130114131export function buildSkillIndexCacheKey(request: SkillIndexRequest): string {
115132const snapshotVersion = request.snapshotVersion ?? getSkillsSnapshotVersion(request.workspaceDir);
116-return JSON.stringify({
133+return stringifyCacheKeyParts(buildSkillIndexCacheKeyParts(request, snapshotVersion));
134+}
135+136+type SkillIndexCacheKeyParts = {
137+scope: string;
138+snapshotVersion: number;
139+};
140+141+function shouldCacheSkillIndex(snapshotVersion: number | undefined): boolean {
142+return typeof snapshotVersion === "number" && snapshotVersion > 0;
143+}
144+145+function buildUncachedSkillIndexCacheKey(
146+request: SkillIndexRequest,
147+snapshotVersion: number,
148+): string {
149+return stableStringify({
150+uncached: true,
151+workspaceDir: path.resolve(request.workspaceDir),
152+ snapshotVersion,
153+});
154+}
155+156+function buildSkillIndexCacheKeyParts(
157+request: SkillIndexRequest,
158+snapshotVersion: number,
159+): SkillIndexCacheKeyParts {
160+const scope = stableStringify({
117161workspaceDir: path.resolve(request.workspaceDir),
118162managedSkillsDir: normalizedOptionalPath(request.managedSkillsDir),
119163bundledSkillsDir: normalizedOptionalPath(request.bundledSkillsDir),
120164pluginSkillsDir: normalizedOptionalPath(request.pluginSkillsDir),
121-skillsConfig: stableConfigHash(request.config),
122- snapshotVersion,
165+config: stableHash(request.config ?? {}),
166+pluginDiscovery: resolvePluginSkillDiscoveryFingerprint(request),
167+});
168+return { scope, snapshotVersion };
169+}
170+171+function stringifyCacheKeyParts(parts: SkillIndexCacheKeyParts): string {
172+return stableStringify(parts);
173+}
174+175+function resolvePluginSkillDiscoveryFingerprint(request: SkillIndexRequest): string {
176+const snapshot = resolvePluginMetadataSnapshot({
177+workspaceDir: request.workspaceDir,
178+config: request.config ?? {},
179+env: process.env,
180+allowWorkspaceScopedCurrent: true,
181+});
182+return stableHash({
183+policyHash: snapshot.policyHash,
184+configFingerprint: snapshot.configFingerprint ?? null,
185+registrySource: snapshot.registrySource ?? null,
186+index: {
187+hostContractVersion: snapshot.index.hostContractVersion,
188+compatRegistryVersion: snapshot.index.compatRegistryVersion,
189+migrationVersion: snapshot.index.migrationVersion,
190+policyHash: snapshot.index.policyHash,
191+installRecords: snapshot.index.installRecords,
192+plugins: snapshot.index.plugins.map((plugin) => ({
193+pluginId: plugin.pluginId,
194+enabled: plugin.enabled,
195+enabledByDefault: plugin.enabledByDefault ?? null,
196+manifestHash: plugin.manifestHash,
197+manifestPath: plugin.manifestPath,
198+origin: plugin.origin,
199+rootDir: plugin.rootDir,
200+})),
201+},
202+manifestPlugins: snapshot.manifestRegistry.plugins.map((plugin) => ({
203+id: plugin.id,
204+enabledByDefault: plugin.enabledByDefault ?? null,
205+kind: plugin.kind ?? null,
206+origin: plugin.origin,
207+rootDir: plugin.rootDir,
208+skills: plugin.skills,
209+})),
123210});
124211}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。