feat(coding): add core host api composition

This commit is contained in:
2026-08-23 20:00:35 +08:00
parent 98bac20639
commit 22b4a9f9c4
23 changed files with 2258 additions and 59 deletions

View File

@@ -18,7 +18,6 @@ import {
resolveBundledCourseSkillsDir,
} from '../opencode/course-skills';
import {
createElectronProjectStorage,
createProjectStore,
type OpencodeProjectStore,
} from '../opencode/project-store';
@@ -95,10 +94,13 @@ import {
} from '../image-workspace/local-workspace';
import { WorksSquareDesignWorkspace } from '../image-workspace/works-square-workspace';
import type { DesignWorkspaceModule } from '../image-workspace/module';
import { CodingAttachmentStore } from '../coding-projects/attachment-store';
import { PiProductTools } from '../coding-runtime/pi/product-tools';
import { createElectronCodingProjectStorage } from '../coding-projects/project-store';
import { resolveBundledCodingSkillsDir } from '../coding-runtime/pi/resource-loader';
import { createCodingProductHost } from '../api/coding-product-services';
import type { CodingProductComposition } from '../api/coding-product-services';
import {
createCodingComposition,
resolveCodingPiRuntimePaths,
} from '../api/coding-composition';
// Diagnostic package: force Chromium networking onto HTTP/1.1 for transport A/B testing.
app.commandLine.appendSwitch('disable-http2');
@@ -206,6 +208,7 @@ let applicationInitialization: Promise<void> | null = null;
let imageWorkspaceModule: DesignWorkspaceModule | null = null;
let backgroundLifecycle!: BackgroundLifecycleController;
let releaseJobs: ReleaseJobManager | null = null;
let codingProducts: CodingProductComposition | null = null;
const mainWindowFocusState = createMainWindowFocusState();
const quitLifecycleState = createQuitLifecycleState();
const launchDeepLinkUrl = findNianCodeDeepLinkUrl(process.argv);
@@ -496,7 +499,8 @@ async function initialize(): Promise<void> {
// Only local storage and the lightweight workspace object are needed to
// construct the first window. Authentication and remote setup are restored
// after the renderer has had a chance to paint.
opencodeProjectStore = createProjectStore(await createElectronProjectStorage());
const codingProjectStorage = await createElectronCodingProjectStorage();
opencodeProjectStore = createProjectStore(codingProjectStorage);
worksSubmissionBinding = createWorksSubmissionBindingStore(opencodeProjectStore);
const localImageWorkspaceEnabled = isLocalImageWorkspaceDevelopmentEnabled({
@@ -553,29 +557,25 @@ async function initialize(): Promise<void> {
window.webContents.send('release-job:status', status);
}
});
const codingAttachments = new CodingAttachmentStore(
join(app.getPath('userData'), 'coding-runtime', 'attachments'),
);
const piProductTools = new PiProductTools({
codingProducts = createCodingComposition({
storage: codingProjectStorage,
browser: agentBrowser,
attachments: codingAttachments,
bundledSkillsDir: resolveBundledCodingSkillsDir({
isPackaged: app.isPackaged,
resourcesPath: process.resourcesPath,
appPath: app.getAppPath(),
}),
localProxyCredential: getHostApiToken() || undefined,
paths: {
...resolveCodingPiRuntimePaths({
isPackaged: app.isPackaged,
resourcesPath: process.resourcesPath,
appPath: app.getAppPath(),
executablePath: process.execPath,
}),
userDataDir: app.getPath('userData'),
bundledSkillsDir: resolveBundledCodingSkillsDir({
isPackaged: app.isPackaged,
resourcesPath: process.resourcesPath,
appPath: app.getAppPath(),
}),
},
});
const codingProducts = {
attachments: codingAttachments,
productTools: piProductTools,
host: createCodingProductHost({
productTools: piProductTools,
getActiveProject: async () => {
const project = await opencodeProjectStore.getActiveProject();
return project ? { id: project.id, path: project.path } : null;
},
}),
};
const hostApiContext: HostApiContext = {
opencodeManager,
opencodeProjectStore,
@@ -866,10 +866,14 @@ if (gotTheLock) {
const closeImageWorkspacePromise = imageWorkspaceModule?.closeEventSessions?.().catch((err) => {
logger.warn('imageWorkspace.closeEventSessions() error during quit:', err);
}) ?? Promise.resolve();
const stopCodingProductsPromise = codingProducts?.shutdown().catch((err) => {
logger.warn('codingProducts.shutdown() error during quit:', err);
}) ?? Promise.resolve();
const stopPromise = Promise.allSettled([
stopOpencodePromise,
stopAgentBrowserPromise,
closeImageWorkspacePromise,
stopCodingProductsPromise,
]);
const timeoutPromise = new Promise<'timeout'>((resolve) => {
setTimeout(() => resolve('timeout'), 5000);
@@ -900,6 +904,11 @@ if (gotTheLock) {
} catch {
// ignore — stop() may not be callable if state is corrupted
}
try {
void codingProducts?.shutdown().catch(() => { /* ignore */ });
} catch {
// ignore — shutdown() may not be callable if state is corrupted
}
// Give runtime stop a brief window, then force-exit.
setTimeout(() => {
process.exit(1);