feat(plugins): move game resources behind marketplace
This commit is contained in:
@@ -12,7 +12,10 @@ import {
|
||||
DataServiceCloudClient,
|
||||
type DataServiceOperations,
|
||||
} from '../../electron/services/data-service-client';
|
||||
import { DATA_SERVICE_PLUGIN_DEFINITION } from '../../shared/coding-plugins';
|
||||
import {
|
||||
DATA_SERVICE_PLUGIN_DEFINITION,
|
||||
type CodingPluginDefinition,
|
||||
} from '../../shared/coding-plugins';
|
||||
import { productToolDetailsOfResult } from '../../shared/coding-conversation-product-tool-protocol';
|
||||
import type { DataServiceErrorContext, DataServiceHostResult } from '../../shared/data-service';
|
||||
import type {
|
||||
@@ -203,6 +206,131 @@ describe('CodingCapabilityRegistry', () => {
|
||||
expect(JSON.stringify(invalidIdentity)).not.toMatch(/[0-9a-f]{8}-[0-9a-f]{4}/i);
|
||||
});
|
||||
|
||||
it('dispatches a Marketplace-hosted tool from the frozen effective snapshot without a static tool list', async () => {
|
||||
const hostedDefinition: CodingPluginDefinition = {
|
||||
id: 'makelore.game-resource', version: '1.0.0', contractVersion: 1,
|
||||
displayName: 'Game Resource', description: 'Hosted game resources',
|
||||
runtimeKind: 'platform_hosted', acquisitionMode: 'user_acquired',
|
||||
releaseId: 'release-game-1',
|
||||
provenance: { source: 'marketplace', packageRoot: 'C:/packages/game-resource' },
|
||||
scope: 'project', adapterId: '', requiresBackend: true,
|
||||
skills: [{
|
||||
id: 'game-resource', entryPath: 'skills/game-resource/SKILL.md',
|
||||
grants: ['game-resource.generate'],
|
||||
}],
|
||||
tools: [{
|
||||
name: 'game_resource_generate', label: 'Generate', description: 'Generate an asset',
|
||||
capabilityId: 'game-resource.generate', operation: 'generate', roles: ['parent'],
|
||||
mutation: 'write', projectWriteLease: false,
|
||||
permissions: ['hosted.game-resource.generate'], executionMode: 'job',
|
||||
inputSchema: {
|
||||
type: 'object', additionalProperties: false, required: ['kind'],
|
||||
properties: { kind: { type: 'string' } },
|
||||
},
|
||||
}],
|
||||
operations: [{
|
||||
capabilityId: 'game-resource.generate', operation: 'generate', toolName: 'game_resource_generate',
|
||||
}],
|
||||
surfaces: {},
|
||||
};
|
||||
const hostedPolicy: PluginPolicyClientState = {
|
||||
status: 'current', revision: 9, lastVerifiedAt: 1,
|
||||
catalog: {
|
||||
schema_version: 1, catalog_version: 'hosted-1', pricing_version: 'pricing-1',
|
||||
plugins: [{
|
||||
plugin_id: hostedDefinition.id, supported_contract_versions: [1], status: 'active',
|
||||
capabilities: [{
|
||||
capability_id: 'game-resource.generate',
|
||||
operations: [{
|
||||
operation: 'generate',
|
||||
billing: {
|
||||
mode: 'platform_metered', entitlement_scope: 'plugin_usage', notice: 'Metered',
|
||||
unit_name: 'generation', unit_size: 1, rate_points: '1.00',
|
||||
minimum_charge_points: '1.00', rounding_mode: 'ceil',
|
||||
},
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
},
|
||||
};
|
||||
const frozenSnapshot: EffectivePluginSnapshot = {
|
||||
accountSessionId: 'account-a\u00001', projectId: context.projectId,
|
||||
pluginReleaseIds: ['release-game-1'], effectiveSkillIds: ['game-resource'],
|
||||
skillEntries: [{
|
||||
id: 'game-resource', entryPath: 'skills/game-resource/SKILL.md',
|
||||
packageRoot: 'C:/packages/game-resource',
|
||||
}],
|
||||
toolDefinitions: hostedDefinition.tools,
|
||||
runtimePolicies: [{
|
||||
pluginId: hostedDefinition.id, pluginVersion: hostedDefinition.version,
|
||||
releaseId: hostedDefinition.releaseId, contractVersion: 1,
|
||||
capabilityId: 'game-resource.generate', operation: 'generate',
|
||||
billing: hostedPolicy.catalog!.plugins[0]!.capabilities[0]!.operations[0]!.billing,
|
||||
}],
|
||||
unavailableReasons: [],
|
||||
};
|
||||
const resolve = vi.fn(async () => frozenSnapshot);
|
||||
const effectiveResolver = {
|
||||
resolve,
|
||||
getSkillSources: vi.fn(async () => []),
|
||||
getPolicyState: vi.fn(() => hostedPolicy),
|
||||
getInstalledDefinition: vi.fn(async () => hostedDefinition),
|
||||
} as unknown as EffectivePluginResolver;
|
||||
const invoke = vi.fn(async () => ({
|
||||
success: true as const, status: 202, code: null, error: null, retryable: false as const,
|
||||
payload_schema: 'game-resource.v1', data: { executionId: 'execution-a', status: 'accepted' },
|
||||
billing: {
|
||||
mode: 'platform_metered' as const, status: 'dispatched' as const,
|
||||
reserved_points: '2.00', usage_amount: 1, unit: 'generation',
|
||||
},
|
||||
}));
|
||||
const capabilityRegistry = registry({
|
||||
definitions: [], effectiveResolver,
|
||||
adapters: [{ pluginId: hostedDefinition.id, inspect: async () => ({ status: 'ready' }), invoke }],
|
||||
policyClient: { getState: () => hostedPolicy, refresh: vi.fn() },
|
||||
getEnabledPluginIds: async () => [hostedDefinition.id],
|
||||
});
|
||||
|
||||
const result = await capabilityRegistry.invoke({
|
||||
toolName: 'game_resource_generate',
|
||||
context: { ...context, skillIds: ['game-resource'], effectiveSnapshot: frozenSnapshot },
|
||||
workerRole: 'parent', effectiveSkillIds: ['game-resource'], value: { kind: 'pixel' },
|
||||
});
|
||||
|
||||
expect(effectiveResolver.getInstalledDefinition).toHaveBeenCalledWith(
|
||||
hostedDefinition.id,
|
||||
hostedDefinition.releaseId,
|
||||
);
|
||||
expect(invoke).toHaveBeenCalledWith(expect.objectContaining({
|
||||
requestId: 'pi:run-a:resource-a', workerRole: 'parent', effectiveSkillIds: ['game-resource'],
|
||||
pluginReleaseId: hostedDefinition.releaseId,
|
||||
}), hostedDefinition.tools[0], { kind: 'pixel' });
|
||||
expect(result.details).toMatchObject({
|
||||
schema: 'makelore-capability.v1', plugin_id: hostedDefinition.id,
|
||||
capability_id: 'game-resource.generate', operation: 'generate',
|
||||
request_id: 'pi:run-a:resource-a', success: true, status: 202,
|
||||
billing: { mode: 'platform_metered', status: 'dispatched', reserved_points: '2.00' },
|
||||
payload_schema: 'game-resource.v1',
|
||||
});
|
||||
|
||||
resolve.mockResolvedValue({
|
||||
...frozenSnapshot,
|
||||
pluginReleaseIds: ['release-game-2'],
|
||||
runtimePolicies: [{
|
||||
...frozenSnapshot.runtimePolicies[0]!,
|
||||
pluginVersion: '2.0.0',
|
||||
releaseId: 'release-game-2',
|
||||
}],
|
||||
});
|
||||
const stale = await capabilityRegistry.invoke({
|
||||
toolName: 'game_resource_generate',
|
||||
context: { ...context, skillIds: ['game-resource'], effectiveSnapshot: frozenSnapshot },
|
||||
workerRole: 'parent', effectiveSkillIds: ['game-resource'], value: { kind: 'pixel' },
|
||||
});
|
||||
expect(stale.details).toMatchObject({ success: false, code: 'plugin_runtime_stale' });
|
||||
expect(invoke).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('refuses a new plugin action from an old worker after lifecycle invalidation', async () => {
|
||||
const frozenSnapshot: EffectivePluginSnapshot = {
|
||||
accountSessionId: 'account-a\u00001',
|
||||
|
||||
Reference in New Issue
Block a user