feat: integrate marketplace plugins with coding runtime
This commit is contained in:
@@ -40,6 +40,20 @@ import {
|
||||
createCodingCapabilityRegistry,
|
||||
} from '../coding-plugins/registry';
|
||||
import { createDataServicePluginAdapter } from '../coding-plugins/adapters/data-service';
|
||||
import { AccountPluginCache } from '../coding-plugins/account-plugin-cache';
|
||||
import {
|
||||
createMarketplaceClient,
|
||||
type MarketplaceClient,
|
||||
} from '../coding-plugins/marketplace-client';
|
||||
import {
|
||||
PluginPackageStore,
|
||||
type PluginPackageStore as PluginPackageStoreType,
|
||||
} from '../coding-plugins/package-store';
|
||||
import {
|
||||
createEffectivePluginResolver,
|
||||
type EffectivePluginResolver,
|
||||
} from '../coding-plugins/effective-resolver';
|
||||
import { subscribeWorksSquareSession } from '../services/works-square-session';
|
||||
import { PluginPolicyClient } from '../services/plugin-policy-client';
|
||||
import {
|
||||
loadBundledCodingPluginDefinitionsSync,
|
||||
@@ -47,7 +61,9 @@ import {
|
||||
} from '../coding-plugins/manifest';
|
||||
import {
|
||||
createCodingProjectPluginService,
|
||||
createCodingPluginMarketplaceService,
|
||||
type CodingProjectPluginService,
|
||||
type CodingPluginMarketplaceService,
|
||||
} from './coding-product-services';
|
||||
|
||||
export interface CodingCompositionPaths {
|
||||
@@ -64,6 +80,11 @@ export interface CreateCodingCompositionOptions {
|
||||
paths: CodingCompositionPaths;
|
||||
getLocalProxyCredential?(): string | undefined;
|
||||
acquireBackgroundLease?(lease: { id: string; kind: 'coding-run' }): () => void;
|
||||
marketplaceClient?: MarketplaceClient;
|
||||
packageStore?: PluginPackageStoreType;
|
||||
accountCache?: AccountPluginCache;
|
||||
clientVersion?: string;
|
||||
policyClient?: PluginPolicyClient;
|
||||
}
|
||||
|
||||
export function resolveCodingPiRuntimePaths(input: {
|
||||
@@ -105,6 +126,38 @@ export function createCodingComposition(
|
||||
entryPath: path.basename(skill.entryPath),
|
||||
}))
|
||||
));
|
||||
const accountCache = options.accountCache ?? new AccountPluginCache();
|
||||
const marketplaceClient = options.marketplaceClient ?? createMarketplaceClient({ accountCache });
|
||||
const packageStore = options.packageStore ?? new PluginPackageStore({
|
||||
rootDir: path.join(options.paths.userDataDir, 'coding-plugins'),
|
||||
marketplace: marketplaceClient,
|
||||
accountCache,
|
||||
clientVersion: options.clientVersion ?? '2.0.0',
|
||||
});
|
||||
const activePluginReleaseCounts = new Map<string, number>();
|
||||
const registerActivePluginReleases = (releaseIds: readonly string[]): (() => void) => {
|
||||
const uniqueReleaseIds = [...new Set(releaseIds)];
|
||||
for (const releaseId of uniqueReleaseIds) {
|
||||
const count = activePluginReleaseCounts.get(releaseId) ?? 0;
|
||||
if (count === 0) packageStore.registerActiveWorker(releaseId);
|
||||
activePluginReleaseCounts.set(releaseId, count + 1);
|
||||
}
|
||||
let released = false;
|
||||
return () => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
for (const releaseId of uniqueReleaseIds) {
|
||||
const count = activePluginReleaseCounts.get(releaseId) ?? 0;
|
||||
if (count <= 1) {
|
||||
activePluginReleaseCounts.delete(releaseId);
|
||||
packageStore.releaseActiveWorker(releaseId);
|
||||
} else {
|
||||
activePluginReleaseCounts.set(releaseId, count - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
let effectiveResolver: EffectivePluginResolver | undefined;
|
||||
const projectStore = options.projectStore ?? createCodingProjectStore(options.storage);
|
||||
const attachments = new CodingAttachmentStore(
|
||||
path.join(options.paths.userDataDir, 'coding-runtime', 'attachments'),
|
||||
@@ -114,6 +167,14 @@ export function createCodingComposition(
|
||||
attachments,
|
||||
bundledSkillsDir: options.paths.bundledSkillsDir,
|
||||
pluginSkillSources,
|
||||
getPluginSkillSources: async () => effectiveResolver
|
||||
? (await effectiveResolver.getSkillSources()).map((source) => ({
|
||||
id: source.id,
|
||||
pluginId: source.pluginId,
|
||||
directory: source.directory,
|
||||
entryPath: source.entryPath,
|
||||
}))
|
||||
: [],
|
||||
});
|
||||
const extensionHost = new PiManagedExtensionHost();
|
||||
extensionHost.configureProductTools(productTools);
|
||||
@@ -172,9 +233,17 @@ export function createCodingComposition(
|
||||
});
|
||||
const dataService = createDataServiceOperations({ projects });
|
||||
const dataServiceAdapter = createDataServicePluginAdapter(dataService);
|
||||
const policyClient = new PluginPolicyClient();
|
||||
const policyClient = options.policyClient ?? new PluginPolicyClient();
|
||||
const knownPluginIds = new Set(pluginDefinitions.map(({ id }) => id));
|
||||
// Existing user Releases are discovered from the device index at startup;
|
||||
// unlike the bundled catalog they cannot be enumerated synchronously. Keep
|
||||
// the ProjectPluginService's known-ID gate accurate for a reopened app while
|
||||
// preserving unknown IDs in existing project files.
|
||||
void packageStore.readInstalledIndex()
|
||||
.then((records) => records.forEach(({ pluginId }) => knownPluginIds.add(pluginId)))
|
||||
.catch(() => undefined);
|
||||
const projectPlugins = createProjectPluginService({
|
||||
knownPluginIds: pluginDefinitions.map(({ id }) => id),
|
||||
knownPluginIds: () => [...knownPluginIds],
|
||||
onManagedInputsChanged: async ({ projectPath }) => {
|
||||
runtime?.markResourcesStale();
|
||||
const conversations = await conversationStoreForProject(projectPath).read()
|
||||
@@ -187,11 +256,21 @@ export function createCodingComposition(
|
||||
await plugins?.deactivate(projectPath, pluginId);
|
||||
},
|
||||
});
|
||||
effectiveResolver = createEffectivePluginResolver({
|
||||
definitions: pluginDefinitions,
|
||||
packageStore,
|
||||
marketplace: marketplaceClient,
|
||||
accountCache,
|
||||
getAccountBinding: () => marketplaceClient.getCurrentAccountBinding(),
|
||||
getEnabledPluginIds: (projectPath) => projectPlugins.getEnabledPluginIds(projectPath),
|
||||
policyClient,
|
||||
});
|
||||
const capabilityRegistry = createCodingCapabilityRegistry({
|
||||
policyClient,
|
||||
projectPlugins,
|
||||
adapters: [dataServiceAdapter],
|
||||
definitions: pluginDefinitions,
|
||||
effectiveResolver,
|
||||
getDurableProjectId: async (projectPath, localProjectId) => {
|
||||
const active = await projects.requireActiveRealProjectWithIdentity(projectPath);
|
||||
if (active.project.id !== localProjectId) {
|
||||
@@ -207,6 +286,15 @@ export function createCodingComposition(
|
||||
policyClient,
|
||||
adapters: [dataServiceAdapter],
|
||||
definitions: pluginDefinitions,
|
||||
effectiveResolver,
|
||||
getDefinitions: async () => {
|
||||
const installed = await packageStore.readInstalledIndex();
|
||||
const definitions = await Promise.all(installed.map(async ({ pluginId }) => {
|
||||
const release = await packageStore.getInstalled(pluginId);
|
||||
return release?.definition ?? null;
|
||||
}));
|
||||
return definitions.flatMap((definition) => definition ? [definition] : []);
|
||||
},
|
||||
});
|
||||
const workerPool = new PiWorkerPool({
|
||||
processBudget,
|
||||
@@ -217,6 +305,10 @@ export function createCodingComposition(
|
||||
cliPath: options.paths.cliPath,
|
||||
userDataDir: options.paths.userDataDir,
|
||||
bundledSkillsDir: options.paths.bundledSkillsDir,
|
||||
getSkillRoots: async () => (effectiveResolver
|
||||
? (await effectiveResolver.getSkillSources()).map(({ packageRoot }) => packageRoot)
|
||||
: []),
|
||||
registerActivePluginReleases,
|
||||
loadProviderInput,
|
||||
resolveCredential: resolvePiProviderCredentialFromSecretStore,
|
||||
...(getLocalProxyCredential
|
||||
@@ -240,6 +332,9 @@ export function createCodingComposition(
|
||||
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
||||
: {}),
|
||||
capabilityRegistry,
|
||||
getSkillRoots: async () => (effectiveResolver
|
||||
? (await effectiveResolver.getSkillSources()).map(({ packageRoot }) => packageRoot)
|
||||
: []),
|
||||
});
|
||||
const subagents = new PiSubagentScheduler({
|
||||
openChild: childOpener,
|
||||
@@ -278,10 +373,33 @@ export function createCodingComposition(
|
||||
});
|
||||
},
|
||||
});
|
||||
const invalidateManagedResources = async (): Promise<void> => {
|
||||
runtime?.markResourcesStale();
|
||||
const projectPaths = [...conversationStores.entries()];
|
||||
for (const [, store] of projectPaths) {
|
||||
const conversationsInProject = await store.read()
|
||||
.then((file) => file.conversations)
|
||||
.catch(() => []);
|
||||
for (const conversation of conversationsInProject) registry.forget(conversation.id);
|
||||
}
|
||||
};
|
||||
const pluginMarketplace: CodingPluginMarketplaceService = createCodingPluginMarketplaceService({
|
||||
marketplace: marketplaceClient,
|
||||
packageStore,
|
||||
clientVersion: options.clientVersion ?? '2.0.0',
|
||||
onChanged: async ({ pluginId, kind }) => {
|
||||
if (kind === 'install' || kind === 'update') knownPluginIds.add(pluginId);
|
||||
await invalidateManagedResources();
|
||||
},
|
||||
});
|
||||
const unsubscribeMarketplaceSession = subscribeWorksSquareSession(() => {
|
||||
void invalidateManagedResources();
|
||||
});
|
||||
const host = createCodingProductHost({
|
||||
projects,
|
||||
productTools,
|
||||
getEnabledPluginIds: (projectPath) => projectPlugins.getEnabledPluginIds(projectPath),
|
||||
effectiveResolver,
|
||||
listPiCommands: (conversationId) => conversations.listLiveCommands(conversationId),
|
||||
});
|
||||
previewDataSession = createPreviewDataSessionManager({ projects });
|
||||
@@ -297,6 +415,8 @@ export function createCodingComposition(
|
||||
attachments,
|
||||
dataService,
|
||||
plugins,
|
||||
pluginMarketplace,
|
||||
marketplace: pluginMarketplace,
|
||||
previewDataSession,
|
||||
productTools,
|
||||
projects,
|
||||
@@ -322,8 +442,14 @@ export function createCodingComposition(
|
||||
unsubscribeBrowserLifecycle();
|
||||
const active = await projects.getActiveProject();
|
||||
if (active) await plugins?.deactivate(active.path);
|
||||
await subagents.close();
|
||||
await runtime.shutdown();
|
||||
try {
|
||||
await subagents.close();
|
||||
await runtime.shutdown();
|
||||
} finally {
|
||||
unsubscribeMarketplaceSession();
|
||||
packageStore.dispose();
|
||||
marketplaceClient.dispose();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user