581 lines
23 KiB
TypeScript
581 lines
23 KiB
TypeScript
import { accessSync, constants } from 'node:fs';
|
|
import path from 'node:path';
|
|
import type { AgentBrowserModule } from '../agent-browser';
|
|
import type { AgentBrowserSnapshot } from '../../shared/agent-browser';
|
|
import { CodingAttachmentStore } from '../coding-projects/attachment-store';
|
|
import { createCodingConversationStore } from '../coding-projects/conversation-store';
|
|
import { CodingProjectService } from '../coding-projects/project-service';
|
|
import {
|
|
createCodingProjectStore,
|
|
type CodingProjectStore,
|
|
type CodingProjectStorage,
|
|
} from '../coding-projects/project-store';
|
|
import { CodingConversationService } from '../coding-runtime/conversation-service';
|
|
import { PiManagedExtensionHost } from '../coding-runtime/pi/extension-host';
|
|
import { PiProjectWriteLeaseCoordinator } from '../coding-runtime/pi/write-lease';
|
|
import { PiAgentServerProcess } from '../coding-runtime/pi/agent-server-process';
|
|
import { PiManagedInputRevisionCoordinator } from '../coding-runtime/pi/managed-input-revision';
|
|
import { PiProductTools } from '../coding-runtime/pi/product-tools';
|
|
import {
|
|
buildPiProviderCatalog,
|
|
resolvePiProviderCredentialFromSecretStore,
|
|
selectPiProviderModel,
|
|
} from '../coding-runtime/pi/provider-config';
|
|
import {
|
|
createPiManagedWorkerOpener,
|
|
PiConversationRuntime,
|
|
} from '../coding-runtime/pi/runtime';
|
|
import { PiSessionRegistry } from '../coding-runtime/pi/session-registry';
|
|
import { createPiManagedSubagentChildOpener } from '../coding-runtime/pi/subagent-child';
|
|
import { PiSubagentScheduler } from '../coding-runtime/pi/subagent';
|
|
import { PiProcessBudget, PiWorkerPool } from '../coding-runtime/pi/worker-pool';
|
|
import { getProviderService } from '../services/providers/provider-service';
|
|
import {
|
|
isCodingProviderAuthenticationError,
|
|
refreshCodingProviderCredential,
|
|
} from './coding-provider-auth';
|
|
import { createCodingProductHost, type CodingProductComposition } from './coding-product-services';
|
|
import { createDataServiceOperations } from '../services/data-service-client';
|
|
import { createPreviewDataSessionManager, type PreviewDataSessionManager } from '../services/preview-data-session';
|
|
import {
|
|
archivePiConversationSession,
|
|
getPiManagedPaths,
|
|
} from '../coding-runtime/pi/resource-loader';
|
|
import { createProjectPluginService } from '../coding-plugins/project-service';
|
|
import {
|
|
createCodingCapabilityRegistry,
|
|
} from '../coding-plugins/registry';
|
|
import { createDataServicePluginAdapter } from '../coding-plugins/adapters/data-service';
|
|
import { createGameResourcePluginAdapter } from '../coding-plugins/adapters/game-resource';
|
|
import { GameResourceClient } from '../services/game-resource-client';
|
|
import {
|
|
GameResourceDeliveryCoordinator,
|
|
GameResourceDeliveryReceiptStore,
|
|
} from '../services/game-resource-delivery';
|
|
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,
|
|
resolveBundledCodingPluginRootPaths,
|
|
} from '../coding-plugins/manifest';
|
|
import {
|
|
createCodingProjectPluginService,
|
|
createCodingPluginMarketplaceService,
|
|
type CodingProjectPluginService,
|
|
type CodingPluginMarketplaceService,
|
|
} from './coding-product-services';
|
|
import { CODE_OWNED_OPTIONAL_BUNDLED_RELEASES } from '../../shared/coding-plugins';
|
|
import { ModelToolRegistry } from '../coding-runtime/pi/model-tools/model-tool-registry';
|
|
import { DevicePackageManager } from '../coding-packages/device-package-manager';
|
|
import { DevicePackageTools } from '../coding-packages/device-package-tools';
|
|
|
|
export interface CodingCompositionPaths {
|
|
executablePath: string;
|
|
cliPath: string;
|
|
serverPath: string;
|
|
npmCliPath?: string;
|
|
userDataDir: string;
|
|
bundledSkillsDir: string;
|
|
}
|
|
|
|
export interface CreateCodingCompositionOptions {
|
|
storage: CodingProjectStorage;
|
|
projectStore?: CodingProjectStore;
|
|
browser: AgentBrowserModule;
|
|
paths: CodingCompositionPaths;
|
|
getLocalProxyCredential?(): string | undefined;
|
|
acquireBackgroundLease?(lease: { id: string; kind: 'coding-run' }): () => void;
|
|
marketplaceClient?: MarketplaceClient;
|
|
packageStore?: PluginPackageStoreType;
|
|
accountCache?: AccountPluginCache;
|
|
clientVersion?: string;
|
|
policyClient?: PluginPolicyClient;
|
|
requestAgentBrowserPresentation?(snapshot: AgentBrowserSnapshot): void;
|
|
publishAgentBrowserState?(snapshot: AgentBrowserSnapshot): void;
|
|
}
|
|
|
|
type PiWorkerExecutableProbe = (candidate: string) => boolean;
|
|
|
|
function canExecute(candidate: string): boolean {
|
|
try {
|
|
accessSync(candidate, constants.X_OK);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function resolvePiWorkerExecutablePath(
|
|
executablePath: string,
|
|
options: {
|
|
platform?: NodeJS.Platform;
|
|
canExecute?: PiWorkerExecutableProbe;
|
|
} = {},
|
|
): string {
|
|
if ((options.platform ?? process.platform) !== 'darwin') return executablePath;
|
|
|
|
// The product binary remains a Foreground LaunchServices app even in Node mode.
|
|
// Electron's generic Helper is LSUIElement=true, so workers stay out of the Dock.
|
|
const executableName = path.basename(executablePath);
|
|
const helperExecutablePath = path.resolve(
|
|
path.dirname(executablePath),
|
|
'..',
|
|
'Frameworks',
|
|
`${executableName} Helper.app`,
|
|
'Contents',
|
|
'MacOS',
|
|
`${executableName} Helper`,
|
|
);
|
|
return (options.canExecute ?? canExecute)(helperExecutablePath)
|
|
? helperExecutablePath
|
|
: executablePath;
|
|
}
|
|
|
|
export function resolveCodingPiRuntimePaths(input: {
|
|
isPackaged: boolean;
|
|
resourcesPath: string;
|
|
appPath: string;
|
|
executablePath: string;
|
|
}): Pick<CodingCompositionPaths, 'executablePath' | 'cliPath' | 'serverPath' | 'npmCliPath'> {
|
|
return {
|
|
executablePath: resolvePiWorkerExecutablePath(input.executablePath),
|
|
cliPath: input.isPackaged
|
|
? path.join(input.resourcesPath, 'pi-runtime', 'dist', 'cli.js')
|
|
: path.join(
|
|
input.appPath,
|
|
'node_modules',
|
|
'@earendil-works',
|
|
'pi-coding-agent',
|
|
'dist',
|
|
'cli.js',
|
|
),
|
|
serverPath: input.isPackaged
|
|
? path.join(input.resourcesPath, 'resources', 'pi-agent-server.mjs')
|
|
: path.join(input.appPath, 'resources', 'pi-agent-server.mjs'),
|
|
npmCliPath: input.isPackaged
|
|
? path.join(input.resourcesPath, 'publish-runtime', 'bin', 'npm-cli.js')
|
|
: path.join(input.appPath, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
};
|
|
}
|
|
|
|
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 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[]): (() => Promise<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 async () => {
|
|
if (released) return;
|
|
released = true;
|
|
const cleanup: Array<Promise<void>> = [];
|
|
for (const releaseId of uniqueReleaseIds) {
|
|
const count = activePluginReleaseCounts.get(releaseId) ?? 0;
|
|
if (count <= 1) {
|
|
activePluginReleaseCounts.delete(releaseId);
|
|
cleanup.push(packageStore.releaseActiveWorker(releaseId));
|
|
} else {
|
|
activePluginReleaseCounts.set(releaseId, count - 1);
|
|
}
|
|
}
|
|
await Promise.all(cleanup);
|
|
};
|
|
};
|
|
let effectiveResolver: EffectivePluginResolver | undefined;
|
|
let invalidateDeviceResources = async (): Promise<void> => undefined;
|
|
const projectStore = options.projectStore ?? createCodingProjectStore(options.storage);
|
|
const attachments = new CodingAttachmentStore(
|
|
path.join(options.paths.userDataDir, 'coding-runtime', 'attachments'),
|
|
);
|
|
const modelToolRegistry = new ModelToolRegistry();
|
|
const devicePackageManager = new DevicePackageManager({
|
|
rootDir: path.join(options.paths.userDataDir, 'coding-runtime', 'device-packages'),
|
|
executablePath: options.paths.executablePath,
|
|
cliPath: options.paths.cliPath,
|
|
...(options.paths.npmCliPath ? { npmCliPath: options.paths.npmCliPath } : {}),
|
|
onGenerationChanged: async () => await invalidateDeviceResources(),
|
|
});
|
|
const devicePackageTools = new DevicePackageTools(devicePackageManager);
|
|
const projectWriteLeases = new PiProjectWriteLeaseCoordinator();
|
|
const productTools = new PiProductTools({
|
|
browser: options.browser,
|
|
attachments,
|
|
bundledSkillsDir: options.paths.bundledSkillsDir,
|
|
modelToolRegistry,
|
|
devicePackageTools,
|
|
requestAgentBrowserPresentation: options.requestAgentBrowserPresentation,
|
|
publishAgentBrowserState: options.publishAgentBrowserState,
|
|
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(projectWriteLeases);
|
|
extensionHost.configureProductTools(productTools);
|
|
const conversationStores = new Map<string, ReturnType<typeof createCodingConversationStore>>();
|
|
const conversationStoreForProject = (projectPath: string) => {
|
|
const existing = conversationStores.get(projectPath);
|
|
if (existing) return existing;
|
|
const created = createCodingConversationStore(projectPath);
|
|
conversationStores.set(projectPath, created);
|
|
return created;
|
|
};
|
|
const registry = new PiSessionRegistry({
|
|
projectStore,
|
|
createConversationStore: conversationStoreForProject,
|
|
});
|
|
const revisions = new PiManagedInputRevisionCoordinator();
|
|
const processBudget = new PiProcessBudget();
|
|
const agentServer = new PiAgentServerProcess({
|
|
executablePath: options.paths.executablePath,
|
|
serverPath: options.paths.serverPath,
|
|
runtimeRoot: path.dirname(path.dirname(options.paths.cliPath)),
|
|
configDir: getPiManagedPaths(options.paths.userDataDir).configDir,
|
|
});
|
|
const loadProviderInput = async () => ({
|
|
accounts: await getProviderService().listAccounts(),
|
|
modelSummaries: [],
|
|
});
|
|
let runtime: PiConversationRuntime | undefined;
|
|
let plugins: CodingProjectPluginService | undefined;
|
|
let previewDataSession: PreviewDataSessionManager | undefined;
|
|
const projects = new CodingProjectService(projectStore, {
|
|
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 gameResourceClient = new GameResourceClient();
|
|
const gameResourceDelivery = new GameResourceDeliveryCoordinator({
|
|
client: gameResourceClient,
|
|
receipts: new GameResourceDeliveryReceiptStore(path.join(
|
|
options.paths.userDataDir,
|
|
'coding-runtime',
|
|
'game-resource',
|
|
'receipts.json',
|
|
)),
|
|
leases: projectWriteLeases,
|
|
recordTouchedPaths: async (conversationId, runId, paths) => {
|
|
await productTools.recordTouchedPaths(conversationId, runId, paths);
|
|
},
|
|
...(options.acquireBackgroundLease
|
|
? { acquireBackgroundLease: options.acquireBackgroundLease }
|
|
: {}),
|
|
});
|
|
const gameResourceAdapter = createGameResourcePluginAdapter({
|
|
client: gameResourceClient,
|
|
delivery: gameResourceDelivery,
|
|
marketplace: marketplaceClient,
|
|
packageStore,
|
|
makeloreVersion: options.clientVersion ?? '2.0.0',
|
|
bundledReleases: CODE_OWNED_OPTIONAL_BUNDLED_RELEASES,
|
|
});
|
|
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: () => [...knownPluginIds],
|
|
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);
|
|
},
|
|
});
|
|
effectiveResolver = createEffectivePluginResolver({
|
|
definitions: pluginDefinitions,
|
|
packageStore,
|
|
marketplace: marketplaceClient,
|
|
accountCache,
|
|
getAccountBinding: () => marketplaceClient.getCurrentAccountBinding(),
|
|
getEnabledPluginIds: (projectPath) => projectPlugins.getEnabledPluginIds(projectPath),
|
|
policyClient,
|
|
});
|
|
const capabilityRegistry = createCodingCapabilityRegistry({
|
|
policyClient,
|
|
projectPlugins,
|
|
adapters: [dataServiceAdapter, gameResourceAdapter],
|
|
definitions: pluginDefinitions,
|
|
effectiveResolver,
|
|
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;
|
|
},
|
|
});
|
|
productTools.configureCapabilityRegistry(capabilityRegistry);
|
|
plugins = createCodingProjectPluginService({
|
|
projects,
|
|
projectPlugins,
|
|
policyClient,
|
|
adapters: [dataServiceAdapter, gameResourceAdapter],
|
|
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,
|
|
processMode: 'shared',
|
|
maxIdle: 8,
|
|
revisionCoordinator: revisions,
|
|
openWorker: createPiManagedWorkerOpener({
|
|
registry,
|
|
executablePath: options.paths.executablePath,
|
|
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,
|
|
createProcess: (processOptions) => agentServer.createWorker(processOptions),
|
|
...(getLocalProxyCredential
|
|
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
|
: {}),
|
|
extensionHost,
|
|
capabilityRegistry,
|
|
modelToolRegistry,
|
|
devicePackageManager,
|
|
devicePackageTools: devicePackageTools.tools,
|
|
}),
|
|
});
|
|
const childOpener = createPiManagedSubagentChildOpener({
|
|
projectStore,
|
|
executablePath: options.paths.executablePath,
|
|
cliPath: options.paths.cliPath,
|
|
userDataDir: options.paths.userDataDir,
|
|
bundledSkillsDir: options.paths.bundledSkillsDir,
|
|
extensionHost,
|
|
loadProviderInput,
|
|
resolveCredential: resolvePiProviderCredentialFromSecretStore,
|
|
getRevision: () => revisions.current,
|
|
...(getLocalProxyCredential
|
|
? { getLocalProxyCredential: async () => getLocalProxyCredential() }
|
|
: {}),
|
|
capabilityRegistry,
|
|
getSkillRoots: async () => (effectiveResolver
|
|
? (await effectiveResolver.getSkillSources()).map(({ packageRoot }) => packageRoot)
|
|
: []),
|
|
});
|
|
const subagents = new PiSubagentScheduler({
|
|
openChild: childOpener,
|
|
processBudget,
|
|
});
|
|
runtime = new PiConversationRuntime({
|
|
pool: workerPool,
|
|
registry,
|
|
extensionHost,
|
|
subagentScheduler: subagents,
|
|
resolveModel: async (model) => selectPiProviderModel(
|
|
buildPiProviderCatalog(await loadProviderInput()),
|
|
model,
|
|
),
|
|
isAuthenticationError: isCodingProviderAuthenticationError,
|
|
refreshCredential: refreshCodingProviderCredential,
|
|
projectImage: async ({ data, mime }) => await attachments.put(Buffer.from(data, 'base64'), mime),
|
|
resolveImages: async (refs) => await Promise.all(refs.map(async ({ attachmentId }) => {
|
|
const record = await attachments.read(attachmentId);
|
|
return {
|
|
type: 'image',
|
|
data: record.data.toString('base64'),
|
|
mimeType: record.mime,
|
|
};
|
|
})),
|
|
...(options.acquireBackgroundLease
|
|
? { acquireBackgroundLease: options.acquireBackgroundLease }
|
|
: {}),
|
|
});
|
|
const conversations = new CodingConversationService(projects, runtime, {
|
|
archiveSession: async ({ projectId, sessionKey }) => {
|
|
await archivePiConversationSession({
|
|
userDataDir: options.paths.userDataDir,
|
|
projectId,
|
|
sessionKey,
|
|
});
|
|
},
|
|
});
|
|
const invalidateManagedResources = async (): Promise<void> => {
|
|
await runtime?.refreshResources();
|
|
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);
|
|
}
|
|
};
|
|
invalidateDeviceResources = invalidateManagedResources;
|
|
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();
|
|
},
|
|
});
|
|
void gameResourceDelivery.resumePending().catch(() => undefined);
|
|
const unsubscribeMarketplaceSession = subscribeWorksSquareSession(() => {
|
|
void invalidateManagedResources();
|
|
void gameResourceDelivery.resumePending().catch(() => undefined);
|
|
});
|
|
const host = createCodingProductHost({
|
|
projects,
|
|
productTools,
|
|
getEnabledPluginIds: (projectPath) => projectPlugins.getEnabledPluginIds(projectPath),
|
|
effectiveResolver,
|
|
listPiCommands: (conversationId) => conversations.listLiveCommands(conversationId),
|
|
});
|
|
previewDataSession = createPreviewDataSessionManager({ projects });
|
|
if (typeof options.browser.configurePreviewDataSession === 'function') {
|
|
options.browser.configurePreviewDataSession(previewDataSession);
|
|
}
|
|
const unsubscribeBrowserLifecycle = typeof options.browser.subscribeLifecycle === 'function'
|
|
? options.browser.subscribeLifecycle((event) => {
|
|
previewDataSession?.handleAgentBrowserLifecycle(event);
|
|
})
|
|
: () => undefined;
|
|
return {
|
|
attachments,
|
|
dataService,
|
|
devicePackages: devicePackageManager,
|
|
plugins,
|
|
pluginMarketplace,
|
|
marketplace: pluginMarketplace,
|
|
previewDataSession,
|
|
productTools,
|
|
projects,
|
|
conversations,
|
|
runtime,
|
|
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)
|
|
)));
|
|
if (reason === 'background_sleep' && runtime.hasActiveWork()) return;
|
|
if (reason === 'background_sleep') {
|
|
await options.browser.close().catch(() => undefined);
|
|
}
|
|
await agentServer.stop();
|
|
},
|
|
async shutdown() {
|
|
gameResourceDelivery.dispose();
|
|
previewDataSession?.dispose();
|
|
if (typeof options.browser.configurePreviewDataSession === 'function') {
|
|
options.browser.configurePreviewDataSession(undefined);
|
|
}
|
|
unsubscribeBrowserLifecycle();
|
|
const active = await projects.getActiveProject();
|
|
if (active) await plugins?.deactivate(active.path);
|
|
try {
|
|
await subagents.close();
|
|
await runtime.shutdown();
|
|
} finally {
|
|
try {
|
|
await agentServer.stop();
|
|
} finally {
|
|
unsubscribeMarketplaceSession();
|
|
packageStore.dispose();
|
|
marketplaceClient.dispose();
|
|
}
|
|
}
|
|
},
|
|
};
|
|
}
|