feat: integrate automatic game resource delivery

This commit is contained in:
2026-09-06 09:50:49 +08:00
parent f721966f3c
commit 4df4bc9624
20 changed files with 1667 additions and 191 deletions

View File

@@ -320,6 +320,115 @@ describe('Makelore Pi extension bundle', () => {
}
});
it('streams job-tool progress through the authenticated product bridge', async () => {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-product-stream-'));
roots.push(root);
const host = new PiManagedExtensionHost();
hosts.push(host);
const details = (phase: string) => ({
schema: 'makelore-capability.v1' as const,
plugin_id: 'makelore.game-resource',
plugin_version: '1.0.0',
capability_id: 'game-resource.generate',
operation: 'generate',
request_id: 'pi:stream-run:stream-tool',
success: true,
status: phase === 'saved' ? 200 : 202,
code: null,
error: null,
retryable: false,
billing: {
mode: 'platform_metered' as const,
status: phase === 'saved' ? 'settled' as const : 'dispatched' as const,
reserved_points: '2.00',
...(phase === 'saved' ? { actual_points: '2.00' } : {}),
usage_amount: 1,
unit: 'generation',
},
payload_schema: 'game-resource.v1',
data: { phase, executionId: 'execution-a' },
});
const invoke = vi.fn(async (input: {
onUpdate?: (result: { content: []; details: ReturnType<typeof details> }) => void;
}) => {
input.onUpdate?.({ content: [], details: details('generating') });
input.onUpdate?.({ content: [], details: details('saving') });
return { content: [], details: details('saved') };
});
host.configureProductTools(new PiProductTools({
browser: {} as AgentBrowserModule,
attachments: new CodingAttachmentStore(path.join(root, 'attachments')),
bundledSkillsDir: path.resolve('resources/coding-skills'),
capabilityRegistry: { invoke } as unknown as CodingCapabilityRegistryImpl,
}));
const jobTool: CodingPluginToolDefinition = {
name: 'game_resource_generate',
label: 'Generate game resource',
description: 'Generate and save a game resource.',
capabilityId: 'game-resource.generate',
operation: 'generate',
roles: ['parent'],
mutation: 'write',
projectWriteLease: false,
permissions: ['hosted.game-resource.generate'],
executionMode: 'job',
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
};
const registration = await host.registerWorker({
conversationId: 'stream-conversation', generation: 1, projectId: 'stream-project',
projectPath: root, extensionsDir: root, tools: [jobTool],
});
await host.bindRun('stream-conversation', 1, 'stream-run');
const previous = {
bridge: process.env.MAKELORE_PI_BRIDGE_URL,
token: process.env.MAKELORE_PI_WORKER_TOKEN,
context: process.env.MAKELORE_PI_CONTEXT_FILE,
role: process.env.MAKELORE_PI_WORKER_ROLE,
};
Object.assign(process.env, registration.env);
try {
const module = await import(
/* @vite-ignore */ `${pathToFileURL(registration.extensionPath).href}?stream=${Date.now()}`
) as {
default(factory: {
registerTool(tool: ExtensionTool): void;
on(event: string, handler: ExtensionHandler): void;
}): void | Promise<void>;
};
const tools = new Map<string, ExtensionTool>();
await module.default({
registerTool: (tool) => tools.set(tool.name, tool),
on: () => undefined,
});
const updates: unknown[] = [];
const result = await tools.get('game_resource_generate')?.execute?.(
'stream-tool',
{},
new AbortController().signal,
(update: unknown) => updates.push(update),
);
expect(updates).toEqual([
{ content: [], details: details('generating') },
{ content: [], details: details('saving') },
]);
expect(result).toEqual({ content: [], details: details('saved') });
expect(invoke).toHaveBeenCalledWith(expect.objectContaining({
toolName: 'game_resource_generate',
onUpdate: expect.any(Function),
}));
} finally {
for (const [key, value] of Object.entries(previous)) {
const environmentKey = key === 'bridge' ? 'MAKELORE_PI_BRIDGE_URL'
: key === 'token' ? 'MAKELORE_PI_WORKER_TOKEN'
: key === 'context' ? 'MAKELORE_PI_CONTEXT_FILE'
: 'MAKELORE_PI_WORKER_ROLE';
if (value === undefined) delete process.env[environmentKey];
else process.env[environmentKey] = value;
}
}
});
it('executes versioned product tools through the authenticated real bundle', async () => {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-product-bundle-'));
roots.push(root);