From bbdabbe675ecf5f7e1b9118e1519cf3f1de8e231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 26 Mar 2026 00:46:34 -0400 Subject: [PATCH] feat(02-03): wire SkillService into ServiceGraph and preload bridge - Add lazy SkillService creation in ServiceGraph with getOrCreateSkillService getter - Pass skillService to registerDomainHandlers in main-runtime-support - Add 7 skill operations to preload bridge (importSkill, listSkills, getSkillDetail, deleteSkill, assignSkillToAgent, removeSkillFromAgent, checkSkillUpdate) - Add skillService cleanup in app lifecycle shutdown --- src/main/main-runtime-support.ts | 1 + src/main/service-graph.ts | 17 ++++++++++++++++- src/preload/index.ts | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/main-runtime-support.ts b/src/main/main-runtime-support.ts index d4354d04..5e932b5d 100644 --- a/src/main/main-runtime-support.ts +++ b/src/main/main-runtime-support.ts @@ -168,6 +168,7 @@ export function createMainRuntimeSupport(state: MainRuntimeState) { bootstrapOnboarding: (input) => requireOnboardingService().bootstrapOnboarding(input), getBrowserActions: () => requireServiceGraph().getBrowserActionService(), randomFallbackCompanyId: () => requireConnectorHealthService().randomFallbackCompanyId(), + skillService: requireServiceGraph().getOrCreateSkillService(), }); } diff --git a/src/main/service-graph.ts b/src/main/service-graph.ts index aae90abd..5ab02bf6 100644 --- a/src/main/service-graph.ts +++ b/src/main/service-graph.ts @@ -11,6 +11,7 @@ import { createOrchestrationService } from "./orchestration-service"; import { createUpdaterService } from "./updater-service"; import { createWorkerService } from "./worker-service"; import { createAppLifecycleService } from "./app-lifecycle-service"; +import { createSkillService } from "./skill-service"; import type { DesktopEvent, RunRecord } from "@shared/types"; import type { AutomationEventContext } from "./automation-service"; import type { NotificationOptions } from "./notification-service"; @@ -67,8 +68,20 @@ export function createServiceGraph({ let workerService: ReturnType | null = null; let updaterService: ReturnType | null = null; let appLifecycleService: ReturnType | null = null; + let skillService: ReturnType | null = null; - const wakeAgentIfPossible = (agentId: string | null | undefined, companyId: string, trigger: string) => { + function getOrCreateSkillService() { + if (!skillService) { + skillService = createSkillService({ + db, + profileDir: db.profileDir, + logger, + }); + } + return skillService; + } + + const wakeAgentIfPossible =(agentId: string | null | undefined, companyId: string, trigger: string) => { if (!orchestrationService) { throw new Error("Orchestration service is not initialized."); } @@ -236,6 +249,7 @@ export function createServiceGraph({ orchestrationService = null; updaterService = null; appLifecycleService = null; + skillService = null; db.close(); // Force exit after grace period — covers all platforms (macOS Dock, Windows tray, Linux) // Worker child processes (claude/codex CLI) may keep the event loop alive indefinitely @@ -255,5 +269,6 @@ export function createServiceGraph({ getOnboardingService: () => onboardingService, getUpdaterService: () => updaterService, getAppLifecycleService: () => appLifecycleService, + getOrCreateSkillService, }; } diff --git a/src/preload/index.ts b/src/preload/index.ts index d55e5e99..3f6d2610 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -76,6 +76,20 @@ const api: DesktopApi = { deleteWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteWorkflow, input), runWorkflow: (input) => ipcRenderer.invoke(IPC_CHANNELS.runWorkflow, input), autoAssignTask: (input) => ipcRenderer.invoke(IPC_CHANNELS.autoAssignTask, input), + importSkill: (companyId, source) => + ipcRenderer.invoke(IPC_CHANNELS.importSkill, { companyId, source }), + listSkills: (companyId) => + ipcRenderer.invoke(IPC_CHANNELS.listSkills, { companyId }), + getSkillDetail: (companyId, skillId) => + ipcRenderer.invoke(IPC_CHANNELS.getSkillDetail, { companyId, skillId }), + deleteSkill: (companyId, skillId) => + ipcRenderer.invoke(IPC_CHANNELS.deleteSkill, { companyId, skillId }), + assignSkillToAgent: (companyId, agentId, skillId) => + ipcRenderer.invoke(IPC_CHANNELS.assignSkillToAgent, { companyId, agentId, skillId }), + removeSkillFromAgent: (companyId, agentId, skillId) => + ipcRenderer.invoke(IPC_CHANNELS.removeSkillFromAgent, { companyId, agentId, skillId }), + checkSkillUpdate: (companyId, skillId) => + ipcRenderer.invoke(IPC_CHANNELS.checkSkillUpdate, { companyId, skillId }), recoveryRetry: (taskId: string, agentId: string, companyId: string) => ipcRenderer.invoke("recovery:retry", { taskId, agentId, companyId }), recoveryDismiss: (taskId: string) =>