fix(marketplace): preserve release sync semantics

This commit is contained in:
2026-08-29 14:17:39 +08:00
parent d04b031040
commit 227b8214a8
8 changed files with 270 additions and 14 deletions

View File

@@ -497,7 +497,7 @@ export function createCodingProjectPluginService(
if (effective && effectiveReason?.code === 'project_disabled') state = 'disabled';
else if (effective && !hasEffectiveSkill && effectiveReason
&& ['account_required', 'library_required', 'library_unavailable', 'release_not_installed',
'release_invalid', 'client_incompatible', 'runtime_suspended', 'policy_unavailable',
'release_invalid', 'client_incompatible', 'skill_owner_conflict', 'runtime_suspended', 'policy_unavailable',
'policy_unsupported']
.includes(effectiveReason.code)) state = 'unavailable';
return {

View File

@@ -46,6 +46,7 @@ export type PluginUnavailableReasonCode =
| 'client_incompatible'
| 'project_disabled'
| 'skill_unassigned'
| 'skill_owner_conflict'
| 'runtime_suspended'
| 'policy_unavailable'
| 'policy_unsupported'
@@ -301,7 +302,14 @@ export class EffectivePluginResolver {
}
for (const { definition, installed, unavailableReason } of definitions) {
if (blockedMarketplacePlugins.has(definition.id)) continue;
if (blockedMarketplacePlugins.has(definition.id)) {
unavailableReasons.push(unavailable(
definition.id,
'skill_owner_conflict',
'Plugin Skill ID conflicts with an existing owner',
));
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'));
@@ -331,7 +339,7 @@ export class EffectivePluginResolver {
unavailableReasons.push(unavailable(definition.id, 'account_required', 'Marketplace account is required'));
continue;
}
if (!library || library.stale) {
if (!library || (library.stale && definition.runtimeKind !== 'skill_only')) {
unavailableReasons.push(unavailable(definition.id, 'library_unavailable', 'Marketplace Library is unavailable'));
continue;
}

View File

@@ -1,5 +1,5 @@
import { Buffer } from 'node:buffer';
import { createHash } from 'node:crypto';
import { createHash, randomUUID } from 'node:crypto';
import { WORKS_SQUARE_CONFIG } from '../api/works-config';
import {
getValidWorksSquareAccessToken,
@@ -733,7 +733,9 @@ function prepareResolveInput(input: ResolveRequest): {
const digest = input.resolveRequestDigest ?? derivedDigest;
if (!SHA256_PATTERN.test(digest)) fail('marketplace_request_invalid', 'invalid resolveRequestDigest');
const requestedId = input.resolveRequestId ?? input.requestId;
const requestId = validateRequestId(requestedId ?? `makelore-resolve-${derivedDigest}`);
// The digest identifies request content; the ID identifies one logical sync.
// Deriving both from content would replay an expired Admission forever.
const requestId = validateRequestId(requestedId ?? `makelore-resolve-${randomUUID()}`);
return {
requestId,
digest,

View File

@@ -756,7 +756,7 @@ export class PluginPackageStore {
makeloreVersion: input.makeloreVersion,
channel,
installed,
resolveRequestId: input.resolveRequestId,
resolveRequestId: input.resolveRequestId ?? `makelore-resolve-${randomUUID()}`,
resolveRequestDigest: input.resolveRequestDigest,
};
let resolved: MarketplaceResolveSnapshot;