perf: optimize app startup and background lifecycles

This commit is contained in:
inman
2026-08-19 00:56:15 +08:00
parent 31332f6d76
commit 927e13349b
121 changed files with 4359 additions and 1400 deletions

View File

@@ -11,6 +11,7 @@ type AgentBrowserBody = {
url?: unknown;
action?: unknown;
visible?: unknown;
enabled?: unknown;
bounds?: unknown;
method?: unknown;
params?: unknown;
@@ -237,6 +238,20 @@ export async function handleAgentBrowserRoutes(
bounds: parseBounds(body.bounds),
});
await ensureProjectStillActive(ctx, project);
emitState(ctx, 'agent-browser:state', browser);
sendJson(res, 200, { success: true, browser });
return true;
}
if (url.pathname === '/api/agent-browser/diagnostics' && req.method === 'POST') {
requireRendererPresentation(req);
const body = await readBody(req);
const project = await resolveActiveProject(ctx, body.project_path);
const browser = await service.setDiagnostics({
projectPath: project.path,
enabled: body.enabled === true,
});
await ensureProjectStillActive(ctx, project);
sendJson(res, 200, { success: true, browser });
return true;
}

View File

@@ -1089,6 +1089,10 @@ async function handlePublishProjectSource(
}
let prepared: Awaited<ReturnType<typeof prepareProjectRelease>> | null = null;
const releaseLease = ctx.lifecycle?.acquireLease({
id: `release:${projectId}:${randomUUID()}`,
kind: 'release-build',
});
try {
if (!ctx.agentBrowser) throw new ProjectReleaseBuildError('LOCAL_BUILD_RUNTIME_UNAVAILABLE');
prepared = await prepareProjectRelease({ projectPath: localProject.path, clientVersion: app.getVersion() });
@@ -1242,10 +1246,77 @@ async function handlePublishProjectSource(
...(publishedMetadataPreserved ? { metadata_disposition: PUBLISHED_METADATA_PRESERVED } : {}),
});
} finally {
releaseLease?.();
await prepared?.dispose().catch(() => undefined);
}
}
async function handleStartReleaseJob(
req: IncomingMessage,
res: ServerResponse,
ctx: HostApiContext,
): Promise<void> {
if (!ctx.releaseJobs) {
sendJson(res, 503, { success: false, code: 'RELEASE_JOBS_UNAVAILABLE', error: '发布构建暂不可用。' });
return;
}
const body = await parseJsonBody<{ projectId?: unknown }>(req);
const projectId = readOptionalString(body.projectId);
if (!projectId) {
sendJson(res, 400, { success: false, code: 'PROJECT_ID_REQUIRED', error: '缺少项目标识。' });
return;
}
const project = (await ctx.opencodeProjectStore.listProjects()).find((candidate) => candidate.id === projectId);
if (!project) {
sendJson(res, 404, { success: false, code: 'PROJECT_NOT_FOUND', error: '本地项目不存在。' });
return;
}
try {
const job = ctx.releaseJobs.start({
projectId,
projectPath: project.path,
clientVersion: app.getVersion(),
});
sendJson(res, 202, { success: true, job });
} catch (error) {
if (error instanceof ProjectReleaseBuildError) {
sendJson(res, error.code === 'LOCAL_BUILD_BUSY' ? 409 : 400, {
success: false,
code: error.code,
error: error.message,
});
return;
}
throw error;
}
}
function handleReleaseJobStatus(
res: ServerResponse,
ctx: HostApiContext,
jobId: string,
): void {
const job = ctx.releaseJobs?.get(jobId);
if (!job) {
sendJson(res, 404, { success: false, code: 'RELEASE_JOB_NOT_FOUND', error: '发布任务不存在。' });
return;
}
sendJson(res, 200, { success: true, job });
}
function handleCancelReleaseJob(
res: ServerResponse,
ctx: HostApiContext,
jobId: string,
): void {
const job = ctx.releaseJobs?.cancel(jobId);
if (!job) {
sendJson(res, 404, { success: false, code: 'RELEASE_JOB_NOT_FOUND', error: '发布任务不存在。' });
return;
}
sendJson(res, 200, { success: true, job });
}
function createSpeechTranscriptionFormData(body: SpeechTranscriptionInput): FormData {
const audioBase64 = readRequiredString(body.audioBase64, 'audioBase64');
const fileName = readOptionalString(body.fileName) ?? 'voice.wav';
@@ -1404,6 +1475,39 @@ export async function handleWorksRoutes(
return true;
}
if (url.pathname === '/api/works/projects/release-jobs' && req.method === 'POST') {
if (!hasRendererCapability(req)) {
sendJson(res, 403, {
success: false,
status: 403,
code: 'RENDERER_CAPABILITY_REQUIRED',
error: 'Renderer capability required',
});
return true;
}
await handleStartReleaseJob(req, res, ctx);
return true;
}
const releaseJobMatch = url.pathname.match(/^\/api\/works\/projects\/release-jobs\/([^/]+)(?:\/(cancel))?$/);
if (releaseJobMatch && req.method === 'GET' && !releaseJobMatch[2]) {
handleReleaseJobStatus(res, ctx, decodeURIComponent(releaseJobMatch[1]));
return true;
}
if (releaseJobMatch && req.method === 'POST' && (releaseJobMatch[2] === 'cancel' || url.searchParams.get('action') === 'cancel')) {
if (!hasRendererCapability(req)) {
sendJson(res, 403, {
success: false,
status: 403,
code: 'RENDERER_CAPABILITY_REQUIRED',
error: 'Renderer capability required',
});
return true;
}
handleCancelReleaseJob(res, ctx, decodeURIComponent(releaseJobMatch[1]));
return true;
}
if (url.pathname === '/api/works/projects/mine' && req.method === 'GET') {
await handleListMyProjects(req, res, url);
return true;