fix: complete marketplace client remediation

This commit is contained in:
2026-08-28 23:14:41 +08:00
parent 2c3baf6dff
commit 11d0af0166
18 changed files with 1007 additions and 90 deletions

View File

@@ -92,14 +92,15 @@ export class AccountPluginCache {
library: MarketplaceLibrarySnapshot | null;
resolves: Map<string, MarketplaceResolveSnapshot>;
}>();
private readonly libraryIntents = new Map<string, number>();
getLibrary(binding: AccountBinding): MarketplaceLibrarySnapshot | null {
const record = this.records.get(bindingId(binding));
return record?.library ? clone(record.library) : null;
private nextLibraryIntent(key: string): number {
const next = (this.libraryIntents.get(key) ?? 0) + 1;
this.libraryIntents.set(key, next);
return next;
}
setLibrary(binding: AccountBinding, snapshot: MarketplaceLibrarySnapshot): void {
const key = bindingId(binding);
private setLibrarySnapshot(key: string, binding: AccountBinding, snapshot: MarketplaceLibrarySnapshot): void {
const record = this.records.get(key) ?? {
binding: { accountKey: binding.accountKey, epoch: binding.epoch },
library: null,
@@ -109,8 +110,37 @@ export class AccountPluginCache {
this.records.set(key, record);
}
markLibraryStale(binding: AccountBinding): MarketplaceLibrarySnapshot | null {
getLibrary(binding: AccountBinding): MarketplaceLibrarySnapshot | null {
const record = this.records.get(bindingId(binding));
return record?.library ? clone(record.library) : null;
}
setLibrary(binding: AccountBinding, snapshot: MarketplaceLibrarySnapshot): void {
const key = bindingId(binding);
this.nextLibraryIntent(key);
this.setLibrarySnapshot(key, binding, snapshot);
}
/** Reserve the commit slot before an async Library read or mutation starts. */
beginLibraryIntent(binding: AccountBinding): number {
return this.nextLibraryIntent(bindingId(binding));
}
/** Commit only the latest read/mutation intent for this account binding. */
commitLibrary(
binding: AccountBinding,
intent: number,
snapshot: MarketplaceLibrarySnapshot,
): boolean {
const key = bindingId(binding);
if (this.libraryIntents.get(key) !== intent) return false;
this.setLibrarySnapshot(key, binding, snapshot);
return true;
}
markLibraryStale(binding: AccountBinding, intent?: number): MarketplaceLibrarySnapshot | null {
const key = bindingId(binding);
if (intent !== undefined && this.libraryIntents.get(key) !== intent) return null;
const record = this.records.get(key);
if (!record?.library) return null;
record.library = { ...record.library, stale: true };
@@ -193,12 +223,16 @@ export class AccountPluginCache {
clearAccount(binding: AccountBinding): void {
assertBinding(binding);
for (const [key, record] of this.records.entries()) {
if (record.binding.accountKey === binding.accountKey) this.records.delete(key);
if (record.binding.accountKey === binding.accountKey) {
this.records.delete(key);
this.libraryIntents.delete(key);
}
}
}
invalidateAll(): void {
this.records.clear();
this.libraryIntents.clear();
}
clear(): void {

View File

@@ -43,6 +43,7 @@ export type PluginUnavailableReasonCode =
| 'library_unavailable'
| 'release_not_installed'
| 'release_invalid'
| 'client_incompatible'
| 'project_disabled'
| 'skill_unassigned'
| 'runtime_suspended'
@@ -119,6 +120,7 @@ export interface EffectivePluginSkillSource {
interface DefinitionRecord {
readonly definition: CodingPluginDefinition;
readonly installed: boolean;
readonly unavailableReason?: PluginUnavailableReasonCode;
}
const EMPTY_POLICY_STATE: PluginPolicyClientState = {
@@ -210,6 +212,26 @@ function validateBinding(value: AccountBinding | null): AccountBinding | null {
return { accountKey: value.accountKey, epoch: value.epoch };
}
/**
* A raw Skill ID is the public assignment key. Marketplace packages cannot
* shadow a core Skill or an already accepted package owner; the project
* assignment is retained, but the later package contributes no resources.
*/
function marketplaceSkillConflicts(records: readonly DefinitionRecord[]): ReadonlySet<string> {
const blocked = new Set<string>();
const owned = new Set<string>(CORE_CODING_SKILL_IDS);
for (const { definition } of records) {
if (definition.provenance.source !== 'marketplace') continue;
const conflicts = definition.skills.some(({ id }) => owned.has(id));
if (conflicts) {
blocked.add(definition.id);
continue;
}
for (const { id } of definition.skills) owned.add(id);
}
return blocked;
}
/**
* Resolve a worker snapshot from separated Marketplace, Package Store,
* project, assignment, and policy state. This module deliberately performs
@@ -222,6 +244,7 @@ export class EffectivePluginResolver {
async resolve(input: EffectivePluginResolverInput): Promise<EffectivePluginSnapshot> {
const definitions = await this.definitionRecords();
const blockedMarketplacePlugins = marketplaceSkillConflicts(definitions);
const assigned = normalizeIds(input.assignedSkillIds);
const coreIds = new Set<string>(CORE_CODING_SKILL_IDS);
@@ -273,7 +296,8 @@ export class EffectivePluginResolver {
policyState = this.options.policyClient.getState();
}
for (const { definition, installed } of definitions) {
for (const { definition, installed, unavailableReason } of definitions) {
if (blockedMarketplacePlugins.has(definition.id)) continue;
const selectedSkills = definition.skills.filter(({ id }) => assigned.includes(id));
if (selectedSkills.length === 0) {
unavailableReasons.push(unavailable(definition.id, 'skill_unassigned', 'Plugin Skill is not assigned'));
@@ -284,6 +308,16 @@ export class EffectivePluginResolver {
unavailableReasons.push(unavailable(definition.id, 'release_not_installed', 'Plugin Release is not installed'));
continue;
}
if (unavailableReason) {
unavailableReasons.push(unavailable(
definition.id,
unavailableReason,
unavailableReason === 'client_incompatible'
? 'Plugin Release is incompatible with this MakeLore client'
: 'Plugin Release is unavailable',
));
continue;
}
if (!enabled.has(definition.id)) {
unavailableReasons.push(unavailable(definition.id, 'project_disabled', 'Plugin is not enabled for this project'));
continue;
@@ -386,12 +420,14 @@ export class EffectivePluginResolver {
async getSkillSources(): Promise<readonly EffectivePluginSkillSource[]> {
const definitions = await this.definitionRecords();
const blockedMarketplacePlugins = marketplaceSkillConflicts(definitions);
// Bundled plugin Skills already have a trusted resource source supplied by
// composition. Returning their manifest-relative `packageRoot` here would
// replace that source with a cwd-relative path in the product projection;
// this seam is exclusively for immutable user-installed package roots.
return Object.freeze(definitions.flatMap(({ definition, installed }) => (
!installed || definition.acquisitionMode !== 'user_acquired'
return Object.freeze(definitions.flatMap(({ definition, installed, unavailableReason }) => (
!installed || unavailableReason || blockedMarketplacePlugins.has(definition.id)
|| definition.acquisitionMode !== 'user_acquired'
? []
: definition.skills.map((skill) => ({
id: skill.id,
@@ -427,19 +463,38 @@ export class EffectivePluginResolver {
for (const definition of [...records.values()].map(({ definition }) => definition)) {
if (definition.acquisitionMode !== 'user_acquired') continue;
let installed = (this.options.installedDefinitions ?? []).some(({ id }) => id === definition.id);
if (installedIds.has(definition.id)) installed = Boolean(await this.options.packageStore?.getInstalled(definition.id));
if (this.options.getInstalled) installed = Boolean(await this.options.getInstalled(definition.id));
let unavailableReason: PluginUnavailableReasonCode | undefined;
if (installedIds.has(definition.id)) {
const installedRelease = await this.options.packageStore?.getInstalled(definition.id);
installed = Boolean(installedRelease);
if (installedRelease?.unavailableReason === 'plugin_incompatible_client') {
unavailableReason = 'client_incompatible';
}
}
if (this.options.getInstalled) {
const installedRelease = await this.options.getInstalled(definition.id);
installed = Boolean(installedRelease);
if (installedRelease?.unavailableReason === 'plugin_incompatible_client') {
unavailableReason = 'client_incompatible';
}
}
if (!this.options.packageStore && !this.options.getInstalled
&& !(this.options.installedDefinitions ?? []).some(({ id }) => id === definition.id)) {
installed = false;
}
records.set(definition.id, { definition, installed });
records.set(definition.id, { definition, installed, unavailableReason });
}
if (this.options.packageStore) {
for (const pluginId of installedIds) {
if (records.has(pluginId)) continue;
const installed = await this.options.packageStore.getInstalled(pluginId);
if (installed) records.set(pluginId, { definition: installed.definition, installed: true });
if (installed) records.set(pluginId, {
definition: installed.definition,
installed: true,
...(installed.unavailableReason === 'plugin_incompatible_client'
? { unavailableReason: 'client_incompatible' as const }
: {}),
});
}
}
return [...records.values()];

View File

@@ -924,6 +924,14 @@ class MarketplaceClientImpl implements MarketplaceClient {
async readLibrary(): Promise<MarketplaceLibrarySnapshot> {
const binding = this.requireBinding();
const intent = this.accountCache.beginLibraryIntent(binding);
return this.readLibraryForIntent(binding, intent);
}
private async readLibraryForIntent(
binding: AccountBinding,
intent: number,
): Promise<MarketplaceLibrarySnapshot> {
const previous = this.accountCache.getLibrary(binding);
try {
const result = await this.requestJson('/api/plugin-marketplace/v1/library', { auth: 'required' }, parseLibrary);
@@ -934,14 +942,16 @@ class MarketplaceClientImpl implements MarketplaceClient {
fetchedAt: this.now(),
};
this.assertBinding(binding);
this.accountCache.setLibrary(binding, snapshot);
if (!this.accountCache.commitLibrary(binding, intent, snapshot)) {
return clone(this.accountCache.getLibrary(binding) ?? snapshot);
}
return clone(snapshot);
} catch (error) {
if (!this.bindingMatches(binding)) {
fail('marketplace_account_changed', 'Marketplace account changed while the request was active');
}
if (previous && canServeStale(error)) {
const stale = this.accountCache.markLibraryStale(binding);
const stale = this.accountCache.markLibraryStale(binding, intent);
if (stale) return clone(stale);
}
throw normalizeError(error);
@@ -1073,6 +1083,8 @@ class MarketplaceClientImpl implements MarketplaceClient {
private async mutateLibrary(method: 'PUT' | 'DELETE', pluginId: string): Promise<MarketplaceLibrarySnapshot> {
const validated = idValue(pluginId, 'pluginId');
const binding = this.requireBinding();
const intent = this.accountCache.beginLibraryIntent(binding);
const previous = this.accountCache.getLibrary(binding);
const result = await this.requestJson(
`/api/plugin-marketplace/v1/library/${encodeURIComponent(validated)}`,
{ auth: 'required', method },
@@ -1081,13 +1093,12 @@ class MarketplaceClientImpl implements MarketplaceClient {
const entry = result.value!;
this.assertBinding(binding);
try {
return await this.readLibrary();
return await this.readLibraryForIntent(binding, intent);
} catch (error) {
if (!this.bindingMatches(binding)) {
fail('marketplace_account_changed', 'Marketplace account changed while the request was active');
}
if (!canServeStale(error)) throw normalizeError(error);
const previous = this.accountCache.getLibrary(binding);
if (previous) {
const items = [entry, ...previous.items.filter((item) => item.pluginId !== entry.pluginId)];
const snapshot: MarketplaceLibrarySnapshot = {
@@ -1096,8 +1107,8 @@ class MarketplaceClientImpl implements MarketplaceClient {
stale: true,
fetchedAt: previous.fetchedAt,
};
this.accountCache.setLibrary(binding, snapshot);
return clone(snapshot);
if (this.accountCache.commitLibrary(binding, intent, snapshot)) return clone(snapshot);
return clone(this.accountCache.getLibrary(binding) ?? snapshot);
}
throw normalizeError(error);
}

View File

@@ -20,6 +20,7 @@ import {
import type { CodingPluginDefinition } from '../../shared/coding-plugins';
import {
buildPluginReleaseDescriptor,
compareSemVer,
isMakeLoreVersionCompatible,
isValidSemVer,
type PluginReleaseDescriptor,
@@ -70,6 +71,9 @@ const INDEX_RELEASE_KEYS = new Set([
'sha256',
'size_bytes',
'installed_at',
'channel',
'min_makelore_version',
'max_makelore_version',
]);
const CURRENT_SELECTION_ROOT_KEYS = new Set(['schema_version', 'current']);
@@ -88,11 +92,16 @@ export interface InstalledReleaseRecord {
readonly sha256: string;
readonly sizeBytes: number;
readonly installedAt: string;
/** Channel and verified client range are immutable facts of this install. */
readonly channel?: 'stable' | 'beta';
readonly minMakeloreVersion?: string;
readonly maxMakeloreVersion?: string | null;
}
export interface InstalledRelease extends InstalledReleaseRecord {
readonly packageRoot: string;
readonly definition: CodingPluginDefinition;
readonly unavailableReason?: 'plugin_incompatible_client';
}
export type InstallationStatus = 'installed' | 'kept' | 'removed' | 'unavailable';
@@ -102,6 +111,8 @@ export interface InstallationSnapshot {
readonly pluginId: string;
readonly releaseId?: string;
readonly version?: string;
/** The immutable channel selected for this device package. */
readonly channel?: 'stable' | 'beta';
readonly packageRoot?: string;
readonly definition?: CodingPluginDefinition;
readonly reason?: string;
@@ -306,6 +317,20 @@ function parseIndexDocument(value: unknown): IndexDocument {
}
const installedAt = boundedText(record.installed_at, `release ${index}.installed_at`, 80);
if (!Number.isFinite(Date.parse(installedAt))) fail('plugin_store_index_invalid', `invalid release ${index}.installed_at`);
const minMakeloreVersion = record.min_makelore_version === undefined
? undefined
: validVersion(record.min_makelore_version, `release ${index}.min_makelore_version`);
const maxMakeloreVersion = record.max_makelore_version === undefined || record.max_makelore_version === null
? record.max_makelore_version as string | null | undefined
: validVersion(record.max_makelore_version, `release ${index}.max_makelore_version`);
if (minMakeloreVersion && maxMakeloreVersion
&& compareSemVer(maxMakeloreVersion, minMakeloreVersion) < 0) {
fail('plugin_store_index_invalid', `invalid release ${index}.max_makelore_version`);
}
const channel = record.channel === undefined ? undefined : record.channel;
if (channel !== undefined && channel !== 'stable' && channel !== 'beta') {
fail('plugin_store_index_invalid', `invalid release ${index}.channel`);
}
return Object.freeze({
pluginId,
releaseId,
@@ -316,6 +341,9 @@ function parseIndexDocument(value: unknown): IndexDocument {
sha256: validSha(record.sha256, `release ${index}.sha256`, 'plugin_store_index_invalid'),
sizeBytes: positiveInteger(record.size_bytes, `release ${index}.size_bytes`, DEFAULT_MAX_ARCHIVE_BYTES),
installedAt,
...(channel === undefined ? {} : { channel }),
...(minMakeloreVersion === undefined ? {} : { minMakeloreVersion }),
...(maxMakeloreVersion === undefined ? {} : { maxMakeloreVersion }),
});
});
return Object.freeze({ schema_version: INDEX_SCHEMA_VERSION, releases: Object.freeze(releases) });
@@ -334,6 +362,9 @@ function serializeIndex(document: IndexDocument): Uint8Array {
sha256: record.sha256,
size_bytes: record.sizeBytes,
installed_at: record.installedAt,
...(record.channel === undefined ? {} : { channel: record.channel }),
...(record.minMakeloreVersion === undefined ? {} : { min_makelore_version: record.minMakeloreVersion }),
...(record.maxMakeloreVersion === undefined ? {} : { max_makelore_version: record.maxMakeloreVersion }),
})),
})}\n`, 'utf8');
}
@@ -687,6 +718,7 @@ export class PluginPackageStore {
pluginId,
releaseId: current.releaseId,
version: current.version,
...(current.channel === undefined ? {} : { channel: current.channel }),
packageRoot: current.packageRoot,
definition: current.definition,
};
@@ -705,7 +737,7 @@ export class PluginPackageStore {
const existingRecord = index.releases.find((record) => record.pluginId === pluginId && record.releaseId === item.releaseId);
if (existingRecord && existingRecord.sha256 === item.sha256 && await isDirectoryPath(this.releaseDirectory(existingRecord))) {
const existing = await this.getInstalledFromIndex(index, pluginId, item.releaseId);
if (existing) {
if (existing && !existing.unavailableReason) {
await this.setCurrentSelection(
await this.readCurrentSelection(),
pluginId,
@@ -716,6 +748,7 @@ export class PluginPackageStore {
pluginId,
releaseId: existing.releaseId,
version: existing.version,
...(existing.channel === undefined ? {} : { channel: existing.channel }),
packageRoot: existing.packageRoot,
definition: existing.definition,
};
@@ -738,6 +771,7 @@ export class PluginPackageStore {
packageRoot,
pluginId,
grant,
channel,
binding,
});
}
@@ -781,7 +815,7 @@ export class PluginPackageStore {
await writeFile(path.join(extractedPath, ORPHAN_ARCHIVE_FILE), toBuffer(artifact), { flag: 'wx' });
await rename(extractedPath, packageRoot);
moved = true;
const record = this.installedRecord(grant);
const record = this.installedRecord(grant, channel);
const records = index.releases.filter((candidate) => !(candidate.pluginId === pluginId && candidate.releaseId === grant.releaseId));
try {
await this.writeIndex(this.indexPath, serializeIndex({ schema_version: INDEX_SCHEMA_VERSION, releases: [...records, record] }));
@@ -794,6 +828,7 @@ export class PluginPackageStore {
pluginId,
releaseId: record.releaseId,
version: record.version,
channel: record.channel,
packageRoot,
definition,
};
@@ -817,6 +852,7 @@ export class PluginPackageStore {
readonly packageRoot: string;
readonly pluginId: string;
readonly grant: DownloadGrant;
readonly channel: 'stable' | 'beta';
readonly binding: AccountBinding;
}): Promise<InstallationSnapshot> {
const existingRecord = input.index.releases.find((record) => (
@@ -832,7 +868,7 @@ export class PluginPackageStore {
const descriptor = this.verifyArtifact(artifact, input.grant);
const definition = await this.loadDefinition(input.packageRoot, input.grant, descriptor);
this.assertBinding(input.binding);
const record = this.installedRecord(input.grant);
const record = this.installedRecord(input.grant, input.channel);
try {
await this.writeIndex(this.indexPath, serializeIndex({
schema_version: INDEX_SCHEMA_VERSION,
@@ -847,6 +883,7 @@ export class PluginPackageStore {
pluginId: input.pluginId,
releaseId: record.releaseId,
version: record.version,
channel: record.channel,
packageRoot: input.packageRoot,
definition,
};
@@ -879,7 +916,7 @@ export class PluginPackageStore {
return descriptor;
}
private installedRecord(grant: DownloadGrant): InstalledReleaseRecord {
private installedRecord(grant: DownloadGrant, channel: 'stable' | 'beta'): InstalledReleaseRecord {
return Object.freeze({
pluginId: grant.pluginId,
releaseId: grant.releaseId,
@@ -890,6 +927,9 @@ export class PluginPackageStore {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
installedAt: new Date(this.now()).toISOString(),
channel,
minMakeloreVersion: grant.minMakeloreVersion,
maxMakeloreVersion: grant.maxMakeloreVersion,
});
}
@@ -908,22 +948,23 @@ export class PluginPackageStore {
}
return { status: 'removed', pluginId: validated, reason: 'none' };
}
const latest = records
.map((record, index) => ({ record, index }))
.sort((left, right) => right.record.installedAt.localeCompare(left.record.installedAt) || right.index - left.index)[0]
?.record;
if (!latest) return { status: 'removed', pluginId: validated, reason: 'none' };
const selected = currentSelection.current[validated]
? records.find((record) => record.releaseId === currentSelection.current[validated])
: undefined;
const reported = selected ?? latest;
const protectedIds = new Set([
...this.accountCache.referencedReleaseIds(),
...(this.activeWorkerReleaseIds() ?? []),
...this.activeWorkers,
]);
const removable = records.filter((record) => !protectedIds.has(record.releaseId));
if (removable.length === 0) return { status: 'kept', pluginId: validated, releaseId: reported.releaseId, version: reported.version };
const removable = selected && records.length > 1
? records.filter((record) => record.releaseId !== selected.releaseId && !protectedIds.has(record.releaseId))
: records.filter((record) => !protectedIds.has(record.releaseId));
if (removable.length === 0) {
const reported = selected ?? records.find((record) => protectedIds.has(record.releaseId));
return reported
? { status: 'kept', pluginId: validated, releaseId: reported.releaseId, version: reported.version }
: { status: 'kept', pluginId: validated, reason: 'current_selection_missing' };
}
const remaining = index.releases.filter((record) => !removable.includes(record));
try {
await this.writeIndex(this.indexPath, serializeIndex({ schema_version: INDEX_SCHEMA_VERSION, releases: remaining }));
@@ -933,15 +974,7 @@ export class PluginPackageStore {
const nextCurrent = { ...currentSelection.current };
if (nextCurrent[validated] && !remaining.some((record) => (
record.pluginId === validated && record.releaseId === nextCurrent[validated]
))) {
const fallback = remaining
.filter((record) => record.pluginId === validated)
.map((record, index) => ({ record, index }))
.sort((left, right) => right.record.installedAt.localeCompare(left.record.installedAt) || right.index - left.index)[0]
?.record;
if (fallback) nextCurrent[validated] = fallback.releaseId;
else delete nextCurrent[validated];
}
))) delete nextCurrent[validated];
await this.writeCurrentSelection({
schema_version: CURRENT_SELECTION_SCHEMA_VERSION,
current: Object.freeze(nextCurrent),
@@ -952,8 +985,24 @@ export class PluginPackageStore {
const currentAfter = nextCurrent[validated]
? remaining.find((record) => record.pluginId === validated && record.releaseId === nextCurrent[validated])
: undefined;
const reportedAfter = currentAfter ?? latest;
return { status: 'removed', pluginId: validated, releaseId: reportedAfter.releaseId, version: reportedAfter.version };
const selectedAfter = selected && remaining.some((record) => (
record.pluginId === validated && record.releaseId === selected.releaseId
)) ? selected : undefined;
const reportedAfter = currentAfter ?? selectedAfter ?? remaining.find((record) => record.pluginId === validated);
if (reportedAfter) {
return {
status: 'kept',
pluginId: validated,
releaseId: reportedAfter.releaseId,
version: reportedAfter.version,
...(reportedAfter.channel === undefined ? {} : { channel: reportedAfter.channel }),
};
}
return {
status: 'removed',
pluginId: validated,
...(selected ? { releaseId: selected.releaseId, version: selected.version } : { reason: 'none' }),
};
}
private buildDescriptor(grant: DownloadGrant): PluginReleaseDescriptor {
@@ -1073,9 +1122,11 @@ export class PluginPackageStore {
.filter(({ record }) => record.pluginId === pluginId && (releaseId === undefined || record.releaseId === releaseId))
.sort((left, right) => right.record.installedAt.localeCompare(left.record.installedAt) || right.index - left.index)
.map(({ record }) => record);
const selected = pointedReleaseId === undefined
const selected = releaseId !== undefined
? records
: records.filter((record) => record.releaseId === pointedReleaseId);
: pointedReleaseId === undefined
? []
: records.filter((record) => record.releaseId === pointedReleaseId);
for (const record of selected) {
const packageRoot = this.releaseDirectory(record);
if (!await isDirectoryPath(packageRoot)) continue;
@@ -1090,7 +1141,18 @@ export class PluginPackageStore {
|| definition.contractVersion !== record.contractVersion || definition.runtimeKind !== record.runtimeKind) {
throw new PluginPackageStoreError('plugin_manifest_invalid', 'installed package does not match its immutable index record');
}
return { ...record, packageRoot, definition };
const compatible = record.minMakeloreVersion !== undefined
&& isMakeLoreVersionCompatible(
this.clientVersion,
record.minMakeloreVersion,
record.maxMakeloreVersion ?? null,
);
return {
...record,
packageRoot,
definition,
...(compatible ? {} : { unavailableReason: 'plugin_incompatible_client' as const }),
};
} catch (error) {
if (error instanceof PluginPackageStoreError) throw error;
throw new PluginPackageStoreError('plugin_manifest_invalid', 'installed package manifest is invalid');

View File

@@ -17,6 +17,7 @@ import type {
import type {
EffectivePluginResolver,
EffectivePluginSnapshot,
EffectivePluginSkillSource,
} from './effective-resolver';
const MAX_REQUEST_ID = 128;
@@ -391,21 +392,30 @@ export class CodingCapabilityRegistryImpl implements CodingCapabilityRegistryPor
const effectiveSkills = new Set(snapshot.effectiveSkillIds);
const pluginIds = new Set<string>(snapshot.runtimePolicies.map(({ pluginId }) => pluginId));
const skillRoots = new Set<string>();
const sourcesBySkillId = new Map<string, EffectivePluginSkillSource>();
for (const source of sources) {
if (effectiveSkills.has(source.id)) {
pluginIds.add(source.pluginId);
skillRoots.add(source.packageRoot);
if (effectiveSkills.has(source.id) && !sourcesBySkillId.has(source.id)) {
sourcesBySkillId.set(source.id, source);
}
}
const skillEntries = snapshot.skillEntries.map(({ id, entryPath, packageRoot }) => {
const source = sourcesBySkillId.get(id);
const pairedRoot = packageRoot ?? source?.packageRoot;
if (source && pairedRoot) {
pluginIds.add(source.pluginId);
skillRoots.add(pairedRoot);
}
return {
id,
entryPath,
...(pairedRoot ? { packageRoot: pairedRoot } : {}),
};
});
return {
catalogRevision: this.options.effectiveResolver.getPolicyState().revision,
pluginIds: [...pluginIds],
effectiveSkillIds: [...snapshot.effectiveSkillIds],
skillEntries: snapshot.skillEntries.map(({ id, entryPath, packageRoot }) => ({
id,
entryPath,
...(packageRoot ? { packageRoot } : {}),
})),
skillEntries,
tools: snapshot.toolDefinitions.map((tool) => structuredClone(tool)),
effectiveSnapshot: snapshot,
skillRoots: [...skillRoots],