feat: remove legacy OpenCode runtime

Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
This commit is contained in:
2026-08-24 12:17:43 +08:00
parent 61fede2b4d
commit 5a275b93a7
199 changed files with 1330 additions and 64725 deletions

View File

@@ -6,11 +6,16 @@ import {
type CodingConversationV2,
} from './conversation-store';
import {
acknowledgeLegacyConversationNotice,
normalizeCodingProjectConfigV2,
readCodingProjectConfigV2,
writeCodingProjectConfigV2,
type CodingProjectConfigV2,
} from './project-config';
import {
migrateCodingProjectToV2,
type CodingProjectMigrationDependencies,
} from './migration';
import {
createLocalCodingProject,
type CodingProject,
@@ -58,6 +63,7 @@ export interface CodingProjectServiceOptions {
onProjectDeactivated?(project: CodingProject): Promise<void> | void;
createConversationStore?: typeof createCodingConversationStore;
writeConfig?: typeof writeCodingProjectConfigV2;
migration?: CodingProjectMigrationDependencies;
}
function requiredAbsolutePath(value: string | undefined, label: string): string {
@@ -97,6 +103,7 @@ export class CodingProjectService {
ReturnType<typeof createCodingConversationStore>
>();
private activeTransitionTail = Promise.resolve();
private readonly migrationFlights = new Map<string, Promise<CodingProjectConfigV2>>();
constructor(
private readonly store: CodingProjectStore,
@@ -202,8 +209,8 @@ export class CodingProjectService {
async setActiveProject(projectId: string): Promise<CodingProject> {
const project = await this.getProject(projectId);
const config = await readCodingProjectConfigV2(project.path);
if (config.status !== 'valid') {
const config = await this.readCurrentConfig(project.path);
if (!config) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_CONFIG_INVALID',
@@ -222,8 +229,8 @@ export class CodingProjectService {
async getConfig(projectId?: string): Promise<CodingProjectConfigSnapshot> {
const project = projectId ? await this.getProject(projectId) : await this.requireActiveProject();
const result = await readCodingProjectConfigV2(project.path);
if (result.status !== 'valid') {
const config = await this.readCurrentConfig(project.path);
if (!config) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_CONFIG_INVALID',
@@ -232,7 +239,7 @@ export class CodingProjectService {
}
return {
project,
config: result.config,
config,
knowledgeFiles: await this.listKnowledgeFiles(project.path),
};
}
@@ -297,6 +304,16 @@ export class CodingProjectService {
return await this.listKnowledgeFiles(project.path);
}
async acknowledgeLegacyConversationNotice(projectId: string): Promise<CodingProjectConfigSnapshot> {
const project = await this.getProject(projectId);
const config = await acknowledgeLegacyConversationNotice(project.path);
return {
project,
config,
knowledgeFiles: await this.listKnowledgeFiles(project.path),
};
}
conversationStore(projectPath: string): ReturnType<typeof createCodingConversationStore> {
const existing = this.conversationStores.get(projectPath);
if (existing) return existing;
@@ -331,6 +348,23 @@ export class CodingProjectService {
}
}
private async readCurrentConfig(projectPath: string): Promise<CodingProjectConfigV2 | null> {
const current = await readCodingProjectConfigV2(projectPath);
if (current.status === 'valid') return current.config;
if (current.status !== 'invalid' || !this.options.migration) return null;
const existing = this.migrationFlights.get(projectPath);
if (existing) return await existing;
const flight = migrateCodingProjectToV2(projectPath, this.options.migration)
.then((result) => result.config)
.finally(() => {
if (this.migrationFlights.get(projectPath) === flight) {
this.migrationFlights.delete(projectPath);
}
});
this.migrationFlights.set(projectPath, flight);
return await flight;
}
private transitionActiveProject<T>(operation: () => Promise<{
project: CodingProject;
value: T;