feat(pi): materialize effective plugin worker tools

This commit is contained in:
2026-08-27 17:24:16 +08:00
parent c4dd8923a0
commit fd891ff3bb
12 changed files with 617 additions and 212 deletions

View File

@@ -1,6 +1,6 @@
// @vitest-environment node
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
@@ -11,6 +11,10 @@ import { PiProcessBudget } from '../../electron/coding-runtime/pi/worker-pool';
import type { AgentBrowserModule } from '../../electron/agent-browser';
import { CodingAttachmentStore } from '../../electron/coding-projects/attachment-store';
import { PiProductTools } from '../../electron/coding-runtime/pi/product-tools';
import {
DATA_SERVICE_PLUGIN_DEFINITION,
type CodingPluginToolDefinition,
} from '../../shared/coding-plugins';
type ExtensionHandler = (...arguments_: unknown[]) => Promise<unknown> | unknown;
type ExtensionTool = {
@@ -42,6 +46,83 @@ afterEach(async () => {
});
describe('Makelore Pi extension bundle', () => {
it('materializes only the frozen plugin declarations and lease metadata', async () => {
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-dynamic-bundle-'));
roots.push(root);
const host = new PiManagedExtensionHost();
hosts.push(host);
const pluginTool: CodingPluginToolDefinition = {
name: 'data_service_inspect',
label: 'Data Service inspect',
description: 'Inspect the active project Data Service instance.',
capabilityId: 'data-service.control',
operation: 'inspect',
roles: ['parent'],
mutation: 'read',
projectWriteLease: true,
permissions: ['project.data.read'],
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
};
const registration = await host.registerWorker({
conversationId: 'dynamic-conversation',
generation: 1,
projectId: 'dynamic-project',
projectPath: root,
extensionsDir: root,
skillEntries: [{ id: 'data-service', entryPath: 'skills/data-service/SKILL.md' }],
catalogRevision: 19,
tools: [pluginTool],
});
await host.bindRun('dynamic-conversation', 1, 'dynamic-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}?dynamic=${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,
});
await new Promise<void>((resolve) => setImmediate(resolve));
expect(tools.has('data_service_inspect')).toBe(true);
expect(tools.has('data_service_put_document')).toBe(false);
expect(tools.get('data_service_inspect')?.parameters).toEqual(pluginTool.inputSchema);
const context = JSON.parse(await readFile(registration.env.MAKELORE_PI_CONTEXT_FILE as string, 'utf8'));
expect(context).toMatchObject({
catalogRevision: 19,
allowedToolNames: ['data_service_inspect'],
projectWriteLeaseToolNames: ['data_service_inspect'],
});
const denied = await post(registration, {
action: 'product.invoke', conversationId: 'dynamic-conversation',
workerGeneration: 1, runId: 'dynamic-run', resourceId: 'denied-tool',
toolName: 'data_service_put_document', input: {},
});
expect(denied.status).toBe(403);
} 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);
@@ -76,7 +157,10 @@ describe('Makelore Pi extension bundle', () => {
hosts.push(host);
const worker = await host.registerWorker({
conversationId: 'conversation-tools', generation: 1, projectId: 'project-a',
projectPath: root, skillIds: ['agent-browser'], extensionsDir: root,
projectPath: root,
skillEntries: [{ id: 'agent-browser', entryPath: 'agent-browser/SKILL.md' }],
catalogRevision: 3,
extensionsDir: root,
});
await host.bindRun('conversation-tools', 1, 'run-tools');
const previous = {
@@ -97,7 +181,7 @@ describe('Makelore Pi extension bundle', () => {
};
const tools = new Map<string, ExtensionTool>();
const handlers = new Map<string, ExtensionHandler>();
module.default({
await module.default({
registerTool: (tool) => tools.set(tool.name, tool),
on: (event, handler) => handlers.set(event, handler),
});
@@ -192,6 +276,8 @@ describe('Makelore Pi extension bundle', () => {
hosts.push(host);
const extensionWorker = await host.registerWorker({
conversationId: 'conversation-a1', generation: 1, projectId: 'project-a', extensionsDir: root,
tools: [...DATA_SERVICE_PLUGIN_DEFINITION.tools],
catalogRevision: 4,
});
const waitingWorker = await host.registerWorker({
conversationId: 'conversation-a2', generation: 1, projectId: 'project-a', extensionsDir: root,
@@ -217,7 +303,7 @@ describe('Makelore Pi extension bundle', () => {
};
const handlers = new Map<string, ExtensionHandler>();
const tools = new Map<string, ExtensionTool>();
module.default({
await module.default({
registerTool: (tool) => tools.set(tool.name, tool),
on: (event, handler) => handlers.set(event, handler),
});
@@ -237,21 +323,9 @@ describe('Makelore Pi extension bundle', () => {
];
for (const name of dataServiceTools) {
const tool = tools.get(name);
expect(tool?.parameters).toMatchObject({
type: 'object', additionalProperties: false,
});
expect(Object.keys(tool?.parameters?.properties ?? {})).not.toEqual(
expect.arrayContaining(['owner', 'project', 'path', 'token', 'endpoint', 'url', 'handle']),
expect(tool?.parameters).toEqual(
DATA_SERVICE_PLUGIN_DEFINITION.tools.find(({ name: candidate }) => candidate === name)?.inputSchema,
);
const properties = tool?.parameters?.properties as Record<string, Record<string, unknown>>;
if (name === 'data_service_get_document'
|| name === 'data_service_put_document'
|| name === 'data_service_delete_document') {
expect(properties.document_id?.not).toEqual({ enum: ['.', '..'] });
}
if (name === 'data_service_put_document' || name === 'data_service_delete_document') {
expect(properties.if_revision?.maximum).toBe(Number.MAX_SAFE_INTEGER);
}
}
const updates: unknown[] = [];
@@ -355,7 +429,7 @@ describe('Makelore Pi extension bundle', () => {
};
const tools: string[] = [];
const handlers = new Map<string, ExtensionHandler>();
module.default({
await module.default({
registerTool: (tool) => tools.push(tool.name),
on: (event, handler) => handlers.set(event, handler),
});