fix(coding): remediate ML-07 plugin authority

This commit is contained in:
2026-08-27 20:40:58 +08:00
parent 9407c67df2
commit cf13aa7eba
29 changed files with 1008 additions and 214 deletions

View File

@@ -42,6 +42,10 @@ import {
} from '../coding-plugins/registry';
import { createDataServicePluginAdapter } from '../coding-plugins/adapters/data-service';
import { PluginPolicyClient } from '../services/plugin-policy-client';
import {
loadBundledCodingPluginDefinitionsSync,
resolveBundledCodingPluginRootPaths,
} from '../coding-plugins/manifest';
import {
createCodingProjectPluginService,
type CodingProjectPluginService,
@@ -88,6 +92,20 @@ export function createCodingComposition(
options: CreateCodingCompositionOptions,
): CodingProductComposition {
const getLocalProxyCredential = options.getLocalProxyCredential;
const bundledPluginsDir = path.join(
path.dirname(path.resolve(options.paths.bundledSkillsDir)),
'coding-plugins',
);
const pluginDefinitions = loadBundledCodingPluginDefinitionsSync(bundledPluginsDir);
const pluginRoots = resolveBundledCodingPluginRootPaths(bundledPluginsDir);
const pluginSkillSources = pluginDefinitions.flatMap((definition, index) => (
definition.skills.map((skill) => ({
id: skill.id,
pluginId: definition.id,
directory: path.join(pluginRoots[index] as string, path.dirname(skill.entryPath)),
entryPath: path.basename(skill.entryPath),
}))
));
const projectStore = options.projectStore ?? createCodingProjectStore(options.storage);
const attachments = new CodingAttachmentStore(
path.join(options.paths.userDataDir, 'coding-runtime', 'attachments'),
@@ -96,6 +114,7 @@ export function createCodingComposition(
browser: options.browser,
attachments,
bundledSkillsDir: options.paths.bundledSkillsDir,
pluginSkillSources,
});
const extensionHost = new PiManagedExtensionHost();
extensionHost.configureProductTools(productTools);
@@ -156,6 +175,7 @@ export function createCodingComposition(
const dataServiceAdapter = createDataServicePluginAdapter(dataService);
const policyClient = new PluginPolicyClient();
const projectPlugins = createProjectPluginService({
knownPluginIds: pluginDefinitions.map(({ id }) => id),
onManagedInputsChanged: async ({ projectPath }) => {
runtime?.markResourcesStale();
const conversations = await conversationStoreForProject(projectPath).read()
@@ -172,6 +192,7 @@ export function createCodingComposition(
policyClient,
projectPlugins,
adapters: [dataServiceAdapter],
definitions: pluginDefinitions,
getDurableProjectId: async (projectPath, localProjectId) => {
const active = await projects.requireActiveRealProjectWithIdentity(projectPath);
if (active.project.id !== localProjectId) {
@@ -195,6 +216,7 @@ export function createCodingComposition(
projectPlugins,
policyClient,
adapters: [dataServiceAdapter],
definitions: pluginDefinitions,
});
const workerPool = new PiWorkerPool({
processBudget,
@@ -269,6 +291,7 @@ export function createCodingComposition(
const host = createCodingProductHost({
projects,
productTools,
getEnabledPluginIds: (projectPath) => projectPlugins.getEnabledPluginIds(projectPath),
listPiCommands: (conversationId) => conversations.listLiveCommands(conversationId),
});
previewDataSession = createPreviewDataSessionManager({ projects });

View File

@@ -20,7 +20,6 @@ import type { PiProductTools } from '../coding-runtime/pi/product-tools';
import type { DataServiceOperations } from '../services/data-service-client';
import type { PreviewDataSessionManager } from '../services/preview-data-session';
import {
BUNDLED_CODING_PLUGIN_DEFINITIONS,
type CodingPluginDefinition,
type PluginBillingMode,
} from '../../shared/coding-plugins';
@@ -97,6 +96,8 @@ export interface CodingPluginProjectProjection {
items: Array<{
id: string;
version: string;
contractVersion: number;
requiresBackend: boolean;
displayName: string;
description: string;
enabled: boolean;
@@ -105,7 +106,17 @@ export interface CodingPluginProjectProjection {
skills: Array<{ id: string; assignedAgentIds: string[] }>;
capabilities: Array<{
id: string;
operations: Array<{ id: string; billing: PublicPluginBilling }>;
operations: Array<{
id: string;
billing: PublicPluginBilling;
tool: {
name: string;
label: string;
description: string;
mutation: 'read' | 'write' | 'destructive';
permissions: string[];
} | null;
}>;
}>;
settingsSurface: string | null;
}>;
@@ -137,7 +148,7 @@ export interface CreateCodingProjectPluginServiceOptions {
projectPlugins: Pick<ProjectPluginService, 'getEnabledPluginIds' | 'setEnabled'>;
policyClient: Pick<PluginPolicyClient, 'refresh' | 'getState'>;
adapters: readonly CodingPluginAdapter[];
definitions?: readonly CodingPluginDefinition[];
definitions: readonly CodingPluginDefinition[];
}
function policyOperation(
@@ -245,7 +256,7 @@ function effectiveState(input: {
export function createCodingProjectPluginService(
options: CreateCodingProjectPluginServiceOptions,
): CodingProjectPluginService {
const definitions = options.definitions ?? BUNDLED_CODING_PLUGIN_DEFINITIONS;
const definitions = options.definitions;
const adapters = new Map(options.adapters.map((adapter) => [adapter.pluginId, adapter]));
async function project(localProjectId: string) {
@@ -278,13 +289,27 @@ export function createCodingProjectPluginService(
const policyAvailable = Boolean(
pluginPolicy?.supported_contract_versions.includes(definition.contractVersion),
);
const capabilityIds = [...new Set(definition.tools.map(({ capabilityId }) => capabilityId))];
const capabilityIds = [...new Set(definition.operations.map(({ capabilityId }) => capabilityId))];
const capabilities = capabilityIds.flatMap((capabilityId) => {
const operations = definition.tools
.filter((tool) => tool.capabilityId === capabilityId)
.flatMap((tool) => {
const joined = policyOperation(policy, definition, capabilityId, tool.operation);
return joined ? [{ id: tool.operation, billing: publicBilling(joined.billing, policy) }] : [];
const operations = definition.operations
.filter((candidate) => candidate.capabilityId === capabilityId)
.flatMap((candidate) => {
const joined = policyOperation(policy, definition, capabilityId, candidate.operation);
if (!joined) return [];
const tool = candidate.toolName
? definition.tools.find(({ name }) => name === candidate.toolName)
: undefined;
return [{
id: candidate.operation,
billing: publicBilling(joined.billing, policy),
tool: tool ? {
name: tool.name,
label: tool.label,
description: tool.description,
mutation: tool.mutation,
permissions: [...tool.permissions],
} : null,
}];
});
return operations.length > 0 ? [{ id: capabilityId, operations }] : [];
});
@@ -292,6 +317,8 @@ export function createCodingProjectPluginService(
return {
id: definition.id,
version: definition.version,
contractVersion: definition.contractVersion,
requiresBackend: definition.requiresBackend,
displayName: definition.displayName,
description: definition.description,
enabled,
@@ -349,6 +376,7 @@ export interface CodingProductHostOptions {
projects: Pick<CodingProjectService, 'getActiveProject' | 'findActiveConversation'>;
productTools: PiProductTools;
files?: CodingProjectFileService;
getEnabledPluginIds?(projectPath: string): Promise<readonly string[]>;
listPiCommands?(conversationId: string): Promise<unknown>;
}
@@ -455,16 +483,23 @@ export function createCodingProductHost(options: CodingProductHostOptions): Codi
},
async listSkills(agentId) {
const project = await activeProject();
const enabledPluginIds = options.getEnabledPluginIds
? await options.getEnabledPluginIds(project.path)
: [];
return await options.productTools.listSkills(
await selectedSkillIds(project.path, agentId),
enabledPluginIds,
);
},
async listCommands(conversationId) {
const context = await conversationContext(conversationId);
const enabledPluginIds = options.getEnabledPluginIds
? await options.getEnabledPluginIds(context.project.path)
: [];
const piCommands = options.listPiCommands
? normalizePiCommands(await options.listPiCommands(conversationId))
: [];
return await options.productTools.listCommands(context.skillIds, piCommands);
return await options.productTools.listCommands(context.skillIds, piCommands, enabledPluginIds);
},
async getChanges(conversationId) {
await conversationContext(conversationId);