fix(coding): remediate ML-07 plugin authority
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
BUNDLED_CODING_PLUGIN_SETTINGS_SURFACES,
|
||||
CODE_OWNED_PLUGIN_PERMISSION_IDS,
|
||||
DATA_SERVICE_CAPABILITY_IDS,
|
||||
DATA_SERVICE_OPERATION_DEFINITIONS,
|
||||
DATA_SERVICE_PLUGIN_ID,
|
||||
DATA_SERVICE_TOOL_NAMES,
|
||||
type AgentPluginsRootManifest,
|
||||
@@ -563,6 +565,9 @@ export function parseCodingPluginManifest(
|
||||
requiresBackend,
|
||||
skills,
|
||||
tools,
|
||||
operations: pluginId === DATA_SERVICE_PLUGIN_ID
|
||||
? DATA_SERVICE_OPERATION_DEFINITIONS
|
||||
: tools.map(({ capabilityId, operation, name }) => ({ capabilityId, operation, toolName: name })),
|
||||
surfaces: normalizedSurfaces,
|
||||
});
|
||||
validateDataServiceDefinition(definition, capabilityManifestPath);
|
||||
@@ -613,6 +618,42 @@ export async function loadCodingPluginDefinition(packageRoot: string): Promise<C
|
||||
return definition;
|
||||
}
|
||||
|
||||
export function loadCodingPluginDefinitionSync(packageRoot: string): CodingPluginDefinition {
|
||||
const root = path.resolve(packageRoot);
|
||||
const pluginManifestPath = path.join(root, PACKAGE_MANIFEST_FILE);
|
||||
const rootValue = readJson(readFileSync(pluginManifestPath, 'utf8'), pluginManifestPath);
|
||||
const rootManifest = parseAgentPluginsRootManifest(rootValue, pluginManifestPath);
|
||||
const capabilityManifest = normalizeCandidatePath(
|
||||
root,
|
||||
root,
|
||||
rootManifest.extensions['com.makelore'].capabilityManifest,
|
||||
pluginManifestPath,
|
||||
'extensions.com.makelore.capabilityManifest',
|
||||
);
|
||||
if (capabilityManifest !== CAPABILITY_MANIFEST_RELATIVE_PATH) {
|
||||
fail(pluginManifestPath, 'extensions.com.makelore.capabilityManifest', `must point to ${CAPABILITY_MANIFEST_RELATIVE_PATH}`);
|
||||
}
|
||||
const capabilityManifestPath = path.join(root, capabilityManifest);
|
||||
const capabilityValue = readJson(readFileSync(capabilityManifestPath, 'utf8'), capabilityManifestPath);
|
||||
const definition = parseCodingPluginManifest(rootValue, capabilityValue, {
|
||||
packageRoot: root,
|
||||
rootManifestPath: pluginManifestPath,
|
||||
capabilityManifestPath,
|
||||
});
|
||||
for (const skill of definition.skills) {
|
||||
try {
|
||||
readFileSync(path.join(root, skill.entryPath), 'utf8');
|
||||
} catch (error) {
|
||||
fail(
|
||||
capabilityManifestPath,
|
||||
`skills.${skill.id}.entry`,
|
||||
`Skill entry could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
export function resolveBundledCodingPluginRootPaths(resourcesRoot: string): string[] {
|
||||
const root = path.resolve(resourcesRoot);
|
||||
return BUNDLED_CODING_PLUGIN_ROOTS.map((relativeRoot) => path.join(root, relativeRoot));
|
||||
@@ -620,22 +661,7 @@ export function resolveBundledCodingPluginRootPaths(resourcesRoot: string): stri
|
||||
|
||||
export const resolveBundledPluginRoots = resolveBundledCodingPluginRootPaths;
|
||||
|
||||
export async function loadBundledCodingPluginDefinitions(
|
||||
resourcesRoot: string,
|
||||
): Promise<CodingPluginDefinition[]> {
|
||||
const roots = resolveBundledCodingPluginRootPaths(resourcesRoot);
|
||||
const definitions = await Promise.all(roots.map(async (root, index) => {
|
||||
const definition = await loadCodingPluginDefinition(root);
|
||||
const expectedRoot = BUNDLED_CODING_PLUGIN_ROOTS[index];
|
||||
if (definition.id !== `makelore.${expectedRoot}`) {
|
||||
throw new CodingPluginManifestError(
|
||||
path.join(root, PACKAGE_MANIFEST_FILE),
|
||||
'name',
|
||||
`fixed bundle root ${expectedRoot} contains ${definition.id}`,
|
||||
);
|
||||
}
|
||||
return definition;
|
||||
}));
|
||||
function validateBundledDefinitions(definitions: readonly CodingPluginDefinition[]): void {
|
||||
const pluginIds = new Set<string>();
|
||||
const capabilityIds = new Set<string>();
|
||||
const skillIds = new Set<string>();
|
||||
@@ -657,7 +683,46 @@ export async function loadBundledCodingPluginDefinitions(
|
||||
capabilityIds.add(tool.capabilityId);
|
||||
}
|
||||
}
|
||||
return definitions;
|
||||
}
|
||||
|
||||
export async function loadBundledCodingPluginDefinitions(
|
||||
resourcesRoot: string,
|
||||
): Promise<readonly CodingPluginDefinition[]> {
|
||||
const roots = resolveBundledCodingPluginRootPaths(resourcesRoot);
|
||||
const definitions = await Promise.all(roots.map(async (root, index) => {
|
||||
const definition = await loadCodingPluginDefinition(root);
|
||||
const expectedRoot = BUNDLED_CODING_PLUGIN_ROOTS[index];
|
||||
if (definition.id !== `makelore.${expectedRoot}`) {
|
||||
throw new CodingPluginManifestError(
|
||||
path.join(root, PACKAGE_MANIFEST_FILE),
|
||||
'name',
|
||||
`fixed bundle root ${expectedRoot} contains ${definition.id}`,
|
||||
);
|
||||
}
|
||||
return definition;
|
||||
}));
|
||||
validateBundledDefinitions(definitions);
|
||||
return Object.freeze(definitions);
|
||||
}
|
||||
|
||||
export function loadBundledCodingPluginDefinitionsSync(
|
||||
resourcesRoot: string,
|
||||
): readonly CodingPluginDefinition[] {
|
||||
const roots = resolveBundledCodingPluginRootPaths(resourcesRoot);
|
||||
const definitions = roots.map((root, index) => {
|
||||
const definition = loadCodingPluginDefinitionSync(root);
|
||||
const expectedRoot = BUNDLED_CODING_PLUGIN_ROOTS[index];
|
||||
if (definition.id !== `makelore.${expectedRoot}`) {
|
||||
throw new CodingPluginManifestError(
|
||||
path.join(root, PACKAGE_MANIFEST_FILE),
|
||||
'name',
|
||||
`fixed bundle root ${expectedRoot} contains ${definition.id}`,
|
||||
);
|
||||
}
|
||||
return definition;
|
||||
});
|
||||
validateBundledDefinitions(definitions);
|
||||
return Object.freeze(definitions);
|
||||
}
|
||||
|
||||
export const parseBundledCodingPlugins = loadBundledCodingPluginDefinitions;
|
||||
|
||||
@@ -2,7 +2,6 @@ import path from 'node:path';
|
||||
import { atomicWriteJson, readJsonFile, type JsonFileWriter } from '../coding-projects/atomic-json';
|
||||
import { readCodingProjectConfigV2 } from '../coding-projects/project-config';
|
||||
import {
|
||||
BUNDLED_CODING_PLUGIN_DEFINITIONS,
|
||||
DATA_SERVICE_PLUGIN_ID,
|
||||
} from '../../shared/coding-plugins';
|
||||
|
||||
@@ -98,7 +97,7 @@ function knownPluginIdSet(
|
||||
): Set<string> {
|
||||
const source = typeof value === 'function'
|
||||
? value()
|
||||
: value ?? BUNDLED_CODING_PLUGIN_DEFINITIONS.map(({ id }) => id);
|
||||
: value ?? [DATA_SERVICE_PLUGIN_ID];
|
||||
return new Set(source.map((id) => id.trim()).filter(Boolean));
|
||||
}
|
||||
|
||||
@@ -193,6 +192,7 @@ interface ReadSelectionResult {
|
||||
export class ProjectPluginService {
|
||||
private readonly mutationTails = new Map<string, Promise<unknown>>();
|
||||
private readonly managedInputRevisions = new Map<string, number>();
|
||||
private readonly legacySelections = new Map<string, readonly string[]>();
|
||||
|
||||
constructor(private readonly options: ProjectPluginServiceOptions = {}) {}
|
||||
|
||||
@@ -211,6 +211,7 @@ export class ProjectPluginService {
|
||||
const result = await this.readFile(filePath);
|
||||
const knownIds = knownPluginIdSet(this.options.knownPluginIds);
|
||||
if (result.file) {
|
||||
this.legacySelections.delete(project);
|
||||
const enabledPluginIds = result.file.enabledPluginIds;
|
||||
return {
|
||||
projectPath: project,
|
||||
@@ -225,7 +226,11 @@ export class ProjectPluginService {
|
||||
};
|
||||
}
|
||||
|
||||
const legacyProjectedPluginIds = await this.legacyProjectedPluginIds(project);
|
||||
let legacyProjectedPluginIds = this.legacySelections.get(project);
|
||||
if (!legacyProjectedPluginIds) {
|
||||
legacyProjectedPluginIds = Object.freeze(await this.legacyProjectedPluginIds(project));
|
||||
this.legacySelections.set(project, legacyProjectedPluginIds);
|
||||
}
|
||||
const enabledPluginIds = [...legacyProjectedPluginIds];
|
||||
return {
|
||||
projectPath: project,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
BUNDLED_CODING_PLUGIN_DEFINITIONS,
|
||||
type CodingPluginDefinition,
|
||||
type CodingPluginToolDefinition,
|
||||
type PluginBillingMode,
|
||||
@@ -120,7 +119,7 @@ export interface CodingCapabilityRegistryOptions {
|
||||
getEnabledPluginIds(projectPath: string): Promise<readonly string[]>;
|
||||
};
|
||||
adapters: readonly CodingPluginAdapter[];
|
||||
definitions?: readonly CodingPluginDefinition[];
|
||||
definitions: readonly CodingPluginDefinition[];
|
||||
getDurableProjectId?: (projectPath: string, localProjectId: string) => Promise<string> | string;
|
||||
}
|
||||
|
||||
@@ -340,7 +339,7 @@ export class CodingCapabilityRegistryImpl implements CodingCapabilityRegistryPor
|
||||
private readonly adaptersByPluginId: ReadonlyMap<string, CodingPluginAdapter>;
|
||||
|
||||
constructor(private readonly options: CodingCapabilityRegistryOptions) {
|
||||
const definitions = (options.definitions ?? BUNDLED_CODING_PLUGIN_DEFINITIONS).filter(definitionValid);
|
||||
const definitions = options.definitions.filter(definitionValid);
|
||||
this.definitions = Object.freeze([...definitions]);
|
||||
const tools = new Map<string, { definition: CodingPluginDefinition; tool: CodingPluginToolDefinition }>();
|
||||
for (const definition of this.definitions) {
|
||||
|
||||
Reference in New Issue
Block a user