fix(marketplace): close client contract gaps

This commit is contained in:
2026-08-29 23:21:26 +08:00
parent e4676977b9
commit c4120dd847
9 changed files with 236 additions and 29 deletions

View File

@@ -782,8 +782,8 @@ function parseV2Tool(
if (permissions.some((permission) => !hostedPermissionMatchesPlugin(permission, pluginId))) {
fail(filePath, `tools[${index}].permissions`, 'permission is outside the plugin hosted namespace');
}
if (tool.executionMode !== 'synchronous' && tool.executionMode !== 'accepted') {
fail(filePath, `tools[${index}].executionMode`, 'must be synchronous or accepted');
if (tool.executionMode !== 'synchronous' && tool.executionMode !== 'job') {
fail(filePath, `tools[${index}].executionMode`, 'must be synchronous or job');
}
validateV2SchemaNode(tool.inputSchema, filePath, `tools[${index}].inputSchema`);
validateV2SchemaNode(tool.outputSchema, filePath, `tools[${index}].outputSchema`);

View File

@@ -1040,43 +1040,50 @@ export class PluginPackageStore {
const selected = currentSelection.current[validated]
? records.find((record) => record.releaseId === currentSelection.current[validated])
: undefined;
if (!selected) {
return {
status: 'kept',
pluginId: validated,
reason: 'current_selection_missing',
};
}
const protectedIds = new Set([
...this.accountCache.referencedReleaseIds(),
...(this.activeWorkerReleaseIds() ?? []),
...this.activeWorkers,
]);
const removable = mode === 'explicit'
? records.filter((record) => !protectedIds.has(record.releaseId))
: records.length === 1 && !protectedIds.has(selected.releaseId)
let removable: InstalledReleaseRecord[];
if (mode === 'explicit') {
removable = records.filter((record) => !protectedIds.has(record.releaseId));
} else {
if (!selected) {
return {
status: 'kept',
pluginId: validated,
reason: 'current_selection_missing',
};
}
removable = records.length === 1 && !protectedIds.has(selected.releaseId)
? records
: records.filter((record) => (
record.releaseId !== selected.releaseId && !protectedIds.has(record.releaseId)
));
if (removable.length === 0) {
return {
status: 'kept',
pluginId: validated,
releaseId: selected.releaseId,
version: selected.version,
...(selected.channel === undefined ? {} : { channel: selected.channel }),
};
if (removable.length === 0) {
return {
status: 'kept',
pluginId: validated,
releaseId: selected.releaseId,
version: selected.version,
...(selected.channel === undefined ? {} : { channel: selected.channel }),
};
}
}
const remaining = index.releases.filter((record) => !removable.includes(record));
if (binding) this.assertBinding(binding);
try {
await this.writeIndex(this.indexPath, serializeIndex({ schema_version: INDEX_SCHEMA_VERSION, releases: remaining }));
} catch {
throw new PluginPackageStoreError('plugin_install_failed', 'package index cleanup failed');
if (removable.length > 0) {
try {
await this.writeIndex(this.indexPath, serializeIndex({ schema_version: INDEX_SCHEMA_VERSION, releases: remaining }));
} catch {
throw new PluginPackageStoreError('plugin_install_failed', 'package index cleanup failed');
}
}
const nextCurrent = { ...currentSelection.current };
if (nextCurrent[validated] && !remaining.some((record) => (
if (mode === 'explicit') {
delete nextCurrent[validated];
} else if (nextCurrent[validated] && !remaining.some((record) => (
record.pluginId === validated && record.releaseId === nextCurrent[validated]
))) delete nextCurrent[validated];
await this.writeCurrentSelection({