feat(coding): compose plugin host lifecycle
This commit is contained in:
@@ -35,6 +35,17 @@ import { createDataServiceOperations } from '../services/data-service-client';
|
||||
import { createPreviewDataSessionManager, type PreviewDataSessionManager } from '../services/preview-data-session';
|
||||
import { archivePiConversationSession } from '../coding-runtime/pi/resource-loader';
|
||||
import { resolveLegacyProjectModel } from '../coding-projects/legacy-v1';
|
||||
import { createProjectPluginService } from '../coding-plugins/project-service';
|
||||
import {
|
||||
createCodingCapabilityRegistry,
|
||||
type CodingCapabilityRegistry,
|
||||
} from '../coding-plugins/registry';
|
||||
import { createDataServicePluginAdapter } from '../coding-plugins/adapters/data-service';
|
||||
import { PluginPolicyClient } from '../services/plugin-policy-client';
|
||||
import {
|
||||
createCodingProjectPluginService,
|
||||
type CodingProjectPluginService,
|
||||
} from './coding-product-services';
|
||||
|
||||
export interface CodingCompositionPaths {
|
||||
executablePath: string;
|
||||
@@ -106,6 +117,85 @@ export function createCodingComposition(
|
||||
accounts: await getProviderService().listAccounts(),
|
||||
modelSummaries: [],
|
||||
});
|
||||
let runtime: PiConversationRuntime | undefined;
|
||||
let plugins: CodingProjectPluginService | undefined;
|
||||
let previewDataSession: PreviewDataSessionManager | undefined;
|
||||
const projects = new CodingProjectService(projectStore, {
|
||||
migration: {
|
||||
resolveLegacyModel: async ({ legacyModel }) => resolveLegacyProjectModel(
|
||||
legacyModel,
|
||||
await getProviderService().listAccounts(),
|
||||
),
|
||||
},
|
||||
createConversationStore: conversationStoreForProject,
|
||||
onResourcesChanged: async (project) => {
|
||||
runtime?.markResourcesStale();
|
||||
const conversations = await conversationStoreForProject(project.path).read()
|
||||
.then((file) => file.conversations)
|
||||
.catch(() => []);
|
||||
for (const conversation of conversations) registry.forget(conversation.id);
|
||||
},
|
||||
onProjectIdentityChanging: async (project) => {
|
||||
previewDataSession?.invalidate('project_identity_changed');
|
||||
await plugins?.deactivate(project.path);
|
||||
await options.browser.close(project.path);
|
||||
},
|
||||
onProjectDeactivated: async (project, reason) => {
|
||||
previewDataSession?.invalidate('project_deactivated');
|
||||
const conversations = await conversationStoreForProject(project.path).read()
|
||||
.then((file) => file.conversations)
|
||||
.catch(() => []);
|
||||
await Promise.allSettled([
|
||||
plugins?.deactivate(project.path),
|
||||
options.browser.close(project.path),
|
||||
...conversations.map(({ id }) => runtime?.dispose(id, reason)),
|
||||
]);
|
||||
},
|
||||
});
|
||||
const dataService = createDataServiceOperations({ projects });
|
||||
const dataServiceAdapter = createDataServicePluginAdapter(dataService);
|
||||
const policyClient = new PluginPolicyClient();
|
||||
const projectPlugins = createProjectPluginService({
|
||||
onManagedInputsChanged: async ({ projectPath }) => {
|
||||
runtime?.markResourcesStale();
|
||||
const conversations = await conversationStoreForProject(projectPath).read()
|
||||
.then((file) => file.conversations)
|
||||
.catch(() => []);
|
||||
for (const conversation of conversations) registry.forget(conversation.id);
|
||||
},
|
||||
onAdapterDeactivated: async ({ projectPath, pluginId }) => {
|
||||
previewDataSession?.invalidate('project_deactivated');
|
||||
await plugins?.deactivate(projectPath, pluginId);
|
||||
},
|
||||
});
|
||||
const capabilityRegistry = createCodingCapabilityRegistry({
|
||||
policyClient,
|
||||
projectPlugins,
|
||||
adapters: [dataServiceAdapter],
|
||||
getDurableProjectId: async (projectPath, localProjectId) => {
|
||||
const active = await projects.requireActiveRealProjectWithIdentity(projectPath);
|
||||
if (active.project.id !== localProjectId) {
|
||||
throw new Error('The trusted coding project does not match the active project');
|
||||
}
|
||||
return active.projectId;
|
||||
},
|
||||
});
|
||||
const refreshingCapabilityRegistry: CodingCapabilityRegistry = {
|
||||
async resolveWorkerResources(input) {
|
||||
await policyClient.refresh();
|
||||
return await capabilityRegistry.resolveWorkerResources(input);
|
||||
},
|
||||
async invoke(input) {
|
||||
return await capabilityRegistry.invoke(input);
|
||||
},
|
||||
};
|
||||
productTools.configureCapabilityRegistry(refreshingCapabilityRegistry);
|
||||
plugins = createCodingProjectPluginService({
|
||||
projects,
|
||||
projectPlugins,
|
||||
policyClient,
|
||||
adapters: [dataServiceAdapter],
|
||||
});
|
||||
const workerPool = new PiWorkerPool({
|
||||
processBudget,
|
||||
revisionCoordinator: revisions,
|
||||
@@ -121,6 +211,7 @@ export function createCodingComposition(
|
||||
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
||||
: {}),
|
||||
extensionHost,
|
||||
capabilityRegistry: refreshingCapabilityRegistry,
|
||||
}),
|
||||
});
|
||||
const childOpener = createPiManagedSubagentChildOpener({
|
||||
@@ -136,13 +227,14 @@ export function createCodingComposition(
|
||||
...(getLocalProxyCredential
|
||||
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
||||
: {}),
|
||||
capabilityRegistry: refreshingCapabilityRegistry,
|
||||
});
|
||||
const subagents = new PiSubagentScheduler({
|
||||
openChild: childOpener,
|
||||
processBudget,
|
||||
reclaimProcessCapacity: (signal) => workerPool.reclaimIdleWorker(signal),
|
||||
});
|
||||
const runtime = new PiConversationRuntime({
|
||||
runtime = new PiConversationRuntime({
|
||||
pool: workerPool,
|
||||
registry,
|
||||
extensionHost,
|
||||
@@ -165,37 +257,6 @@ export function createCodingComposition(
|
||||
? { acquireBackgroundLease: options.acquireBackgroundLease }
|
||||
: {}),
|
||||
});
|
||||
let previewDataSession: PreviewDataSessionManager | undefined;
|
||||
const projects = new CodingProjectService(projectStore, {
|
||||
migration: {
|
||||
resolveLegacyModel: async ({ legacyModel }) => resolveLegacyProjectModel(
|
||||
legacyModel,
|
||||
await getProviderService().listAccounts(),
|
||||
),
|
||||
},
|
||||
createConversationStore: conversationStoreForProject,
|
||||
onResourcesChanged: async (project) => {
|
||||
runtime.markResourcesStale();
|
||||
const conversations = await conversationStoreForProject(project.path).read()
|
||||
.then((file) => file.conversations)
|
||||
.catch(() => []);
|
||||
for (const conversation of conversations) registry.forget(conversation.id);
|
||||
},
|
||||
onProjectIdentityChanging: async (project) => {
|
||||
previewDataSession?.invalidate('project_identity_changed');
|
||||
await options.browser.close(project.path);
|
||||
},
|
||||
onProjectDeactivated: async (project, reason) => {
|
||||
previewDataSession?.invalidate('project_deactivated');
|
||||
const conversations = await conversationStoreForProject(project.path).read()
|
||||
.then((file) => file.conversations)
|
||||
.catch(() => []);
|
||||
await Promise.allSettled([
|
||||
options.browser.close(project.path),
|
||||
...conversations.map(({ id }) => runtime.dispose(id, reason)),
|
||||
]);
|
||||
},
|
||||
});
|
||||
const conversations = new CodingConversationService(projects, runtime, {
|
||||
archiveSession: async ({ projectId, sessionKey }) => {
|
||||
await archivePiConversationSession({
|
||||
@@ -210,8 +271,6 @@ export function createCodingComposition(
|
||||
productTools,
|
||||
listPiCommands: (conversationId) => conversations.listLiveCommands(conversationId),
|
||||
});
|
||||
const dataService = createDataServiceOperations({ projects });
|
||||
productTools.configureDataService(dataService);
|
||||
previewDataSession = createPreviewDataSessionManager({ projects });
|
||||
if (typeof options.browser.configurePreviewDataSession === 'function') {
|
||||
options.browser.configurePreviewDataSession(previewDataSession);
|
||||
@@ -224,6 +283,7 @@ export function createCodingComposition(
|
||||
return {
|
||||
attachments,
|
||||
dataService,
|
||||
plugins,
|
||||
previewDataSession,
|
||||
productTools,
|
||||
projects,
|
||||
@@ -232,6 +292,10 @@ export function createCodingComposition(
|
||||
host,
|
||||
async sleep(reason) {
|
||||
if (reason === 'background_sleep' && runtime.hasActiveWork()) return;
|
||||
if (reason === 'auth_cleanup') {
|
||||
const active = await projects.getActiveProject();
|
||||
if (active) await plugins?.deactivate(active.path);
|
||||
}
|
||||
const conversationIds = runtime.getDiagnostics().workers.map((worker) => worker.conversationId);
|
||||
await Promise.allSettled(conversationIds.map((conversationId) => (
|
||||
runtime.dispose(conversationId, reason)
|
||||
@@ -243,6 +307,8 @@ export function createCodingComposition(
|
||||
options.browser.configurePreviewDataSession(undefined);
|
||||
}
|
||||
unsubscribeBrowserLifecycle();
|
||||
const active = await projects.getActiveProject();
|
||||
if (active) await plugins?.deactivate(active.path);
|
||||
await subagents.close();
|
||||
await runtime.shutdown();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user