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

@@ -13,13 +13,20 @@ import {
parsePiSubagentDispatchRequest,
type PiSubagentScheduler,
} from './subagent';
import {
isPiProductToolName,
type PiProductToolName,
type PiProductTools,
} from './product-tools';
import type { PiProductTools } from './product-tools';
import type { CodingPluginToolDefinition } from '../../../shared/coding-plugins';
import type { PiSkillEntry } from './resource-loader';
const MAX_REQUEST_BYTES = 64 * 1024;
const PRODUCT_TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,63}$/u;
const CORE_PRODUCT_TOOL_NAMES = new Set([
'agent_browser',
'game_asset_browser',
'game_asset_review',
'task_state',
'changed_file',
'runtime_context',
]);
interface WorkerRegistrationRecord {
token: string;
@@ -28,6 +35,10 @@ interface WorkerRegistrationRecord {
projectId: string;
projectPath: string | null;
skillIds: string[];
catalogRevision?: number;
allowedToolNames: string[];
tools: CodingPluginToolDefinition[];
projectWriteLeaseToolNames: string[];
role: 'parent' | 'child';
contextFile: string;
runId: string | null;
@@ -39,6 +50,7 @@ export interface PiExtensionWorkerRegistration {
extensionPath: string;
env: NodeJS.ProcessEnv;
sensitiveValues: string[];
allowedToolNames: readonly string[];
dispose(): Promise<void>;
}
@@ -47,7 +59,9 @@ export interface RegisterPiExtensionWorkerInput {
generation: number;
projectId: string;
projectPath?: string;
skillIds?: readonly string[];
skillEntries?: readonly PiSkillEntry[];
catalogRevision?: number;
tools?: readonly CodingPluginToolDefinition[];
extensionsDir: string;
role?: 'parent' | 'child';
runId?: string;
@@ -77,7 +91,7 @@ interface ProductToolBridgeRequest {
workerGeneration: number;
runId: string;
resourceId: string;
toolName: PiProductToolName;
toolName: string;
input: unknown;
}
@@ -122,7 +136,9 @@ function bridgeRequest(value: unknown): value is BridgeRequest {
if (!common) return false;
if (value.action === 'subagent.dispatch') return 'request' in value;
if (value.action === 'product.invoke') {
return isPiProductToolName(value.toolName) && 'input' in value;
return typeof value.toolName === 'string'
&& PRODUCT_TOOL_NAME_PATTERN.test(value.toolName)
&& 'input' in value;
}
if (value.action === 'changes.bash') return true;
if (value.action === 'changes.touched') {
@@ -201,6 +217,23 @@ export class PiManagedExtensionHost {
if (this.productTools && !input.projectPath?.trim()) {
throw new Error('Product tools require a worker project path');
}
const skillEntries = [...new Map(
(input.skillEntries ?? [])
.filter((entry) => entry.id.trim() && entry.entryPath.trim())
.map((entry) => [entry.id.trim(), {
id: entry.id.trim(),
entryPath: entry.entryPath.trim().replaceAll('\\', '/'),
}]),
).values()];
const tools = role === 'child'
? []
: [...new Map(
(input.tools ?? []).map((tool) => [tool.name, structuredClone(tool)]),
).values()];
const allowedToolNames = tools.map(({ name }) => name);
const projectWriteLeaseToolNames = tools
.filter(({ projectWriteLease }) => projectWriteLease)
.map(({ name }) => name);
const token = randomBytes(32).toString('base64url');
const contextFile = path.join(input.extensionsDir, `worker-${randomUUID()}.json`);
const record: WorkerRegistrationRecord = {
@@ -209,7 +242,11 @@ export class PiManagedExtensionHost {
generation: input.generation,
projectId: input.projectId,
projectPath: input.projectPath?.trim() || null,
skillIds: [...new Set((input.skillIds ?? []).map((id) => id.trim()).filter(Boolean))],
skillIds: skillEntries.map(({ id }) => id),
...(input.catalogRevision === undefined ? {} : { catalogRevision: input.catalogRevision }),
allowedToolNames,
tools,
projectWriteLeaseToolNames,
role,
contextFile,
runId: role === 'child'
@@ -230,6 +267,7 @@ export class PiManagedExtensionHost {
MAKELORE_PI_WORKER_ROLE: role,
},
sensitiveValues: [token],
allowedToolNames: [...allowedToolNames],
dispose: async () => {
if (disposed) return;
disposed = true;
@@ -370,6 +408,11 @@ export class PiManagedExtensionHost {
this.respond(response, 403, { error: 'Child workers cannot invoke parent product tools' });
return;
}
if (!CORE_PRODUCT_TOOL_NAMES.has(value.toolName)
&& !record.allowedToolNames.includes(value.toolName)) {
this.respond(response, 403, { error: 'Product tool is not enabled for this worker' });
return;
}
if (!this.productTools || !record.projectPath) {
this.respond(response, 503, { error: 'Product tools are unavailable' });
return;
@@ -544,6 +587,11 @@ export class PiManagedExtensionHost {
conversationId: record.conversationId,
workerGeneration: record.generation,
role: record.role,
skillIds: record.skillIds,
...(record.catalogRevision === undefined ? {} : { catalogRevision: record.catalogRevision }),
allowedToolNames: record.allowedToolNames,
tools: record.tools,
projectWriteLeaseToolNames: record.projectWriteLeaseToolNames,
...(record.runId ? { runId: record.runId } : {}),
});
}