merge: wave 3a executor (02-03 IPC) into main

This commit is contained in:
ZacharyZhang-NY committed 2026-03-26 00:51:00 -04:00
commit b949187356
1093 files changed
+446427 -2

No files matched your search

+2
View File
@@ -3,6 +3,7 @@ import { z } from "zod";
import type { DesktopResult } from "@shared/contracts";
import type { bootstrapOnboardingSchema } from "@shared/contracts";
import type { AppDatabase } from "./database";
import type { createSkillService } from "./skill-service";
export interface BrowserActionControl {
clearSocialAccountData: (socialAccountId: string, screenshotPaths?: string[]) => Promise<void>;
@@ -38,6 +39,7 @@ export interface RegisterDomainHandlersDependencies {
bootstrapOnboarding: (input: z.infer<typeof bootstrapOnboardingSchema>) => unknown;
getBrowserActions: () => BrowserActionControl | null;
randomFallbackCompanyId: () => string;
skillService: ReturnType<typeof createSkillService> | null;
}
export function ok<T>(data: T): DesktopResult<T> {
+7
View File
@@ -5,6 +5,7 @@ import { registerBrowserHandlers } from "./ipc-browser-handlers";
import { registerContentOperationsHandlers } from "./ipc-content-operations-handlers";
import { registerOnboardingHandlers } from "./ipc-onboarding-handlers";
import { registerRecoveryHandlers } from "./ipc-recovery-handlers";
import { registerSkillHandlers } from "./ipc-skill-handlers";
export function registerDomainHandlers(dependencies: RegisterDomainHandlersDependencies) {
registerOnboardingHandlers(dependencies);
@@ -13,4 +14,10 @@ export function registerDomainHandlers(dependencies: RegisterDomainHandlersDepen
registerAutomationOperationsHandlers(dependencies);
registerAgentCoordinationHandlers(dependencies);
registerRecoveryHandlers(dependencies);
registerSkillHandlers({
registerHandle: dependencies.registerHandle,
ensureCompanyExists: dependencies.ensureCompanyExists,
publishDomainChanged: dependencies.publishDomainChanged,
skillService: dependencies.skillService,
});
}
+119
View File
@@ -0,0 +1,119 @@
import {
IPC_CHANNELS,
importSkillSchema,
listSkillsSchema,
getSkillDetailSchema,
deleteSkillSchema,
assignSkillToAgentSchema,
removeSkillFromAgentSchema,
checkSkillUpdateSchema,
} from "@shared/contracts";
import { fail, ok, type RegisterDomainHandlersDependencies } from "./ipc-domain-common";
import type { createSkillService } from "./skill-service";
interface SkillHandlerDependencies extends Pick<
RegisterDomainHandlersDependencies,
"registerHandle" | "ensureCompanyExists" | "publishDomainChanged"
> {
skillService: ReturnType<typeof createSkillService> | null;
}
export function registerSkillHandlers({
registerHandle,
ensureCompanyExists,
publishDomainChanged,
skillService,
}: SkillHandlerDependencies) {
function requireSkillService() {
if (!skillService) {
return fail("SERVICE_NOT_READY", "Skill service is not initialized.");
}
return null;
}
// ---- Import skill from source (GitHub URL, local path, raw URL, skills.sh) ----
registerHandle(IPC_CHANNELS.importSkill, async (_event, payload) => {
const parsed = importSkillSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
const result = await skillService!.importFromSource(parsed.companyId, parsed.source);
if (result.ok) {
publishDomainChanged();
}
return result;
});
// ---- List all skills for a company ----
registerHandle(IPC_CHANNELS.listSkills, async (_event, payload) => {
const parsed = listSkillsSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
return ok(skillService!.listForCompany(parsed.companyId));
});
// ---- Get skill detail ----
registerHandle(IPC_CHANNELS.getSkillDetail, async (_event, payload) => {
const parsed = getSkillDetailSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
return skillService!.getDetail(parsed.companyId, parsed.skillId);
});
// ---- Delete a skill ----
registerHandle(IPC_CHANNELS.deleteSkill, async (_event, payload) => {
const parsed = deleteSkillSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
const result = await skillService!.deleteSkill(parsed.companyId, parsed.skillId);
if (result.ok) {
publishDomainChanged();
}
return result;
});
// ---- Assign skill to agent ----
registerHandle(IPC_CHANNELS.assignSkillToAgent, async (_event, payload) => {
const parsed = assignSkillToAgentSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
const result = skillService!.assignToAgent(parsed.companyId, parsed.agentId, parsed.skillId);
if (result.ok) {
publishDomainChanged();
}
return result;
});
// ---- Remove skill from agent ----
registerHandle(IPC_CHANNELS.removeSkillFromAgent, async (_event, payload) => {
const parsed = removeSkillFromAgentSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
const result = skillService!.removeFromAgent(parsed.companyId, parsed.agentId, parsed.skillId);
if (result.ok) {
publishDomainChanged();
}
return result;
});
// ---- Check if skill has update available ----
registerHandle(IPC_CHANNELS.checkSkillUpdate, async (_event, payload) => {
const parsed = checkSkillUpdateSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const serviceError = requireSkillService();
if (serviceError) return serviceError;
return skillService!.checkUpdateStatus(parsed.companyId, parsed.skillId);
});
}
+1
View File
@@ -168,6 +168,7 @@ export function createMainRuntimeSupport(state: MainRuntimeState) {
bootstrapOnboarding: (input) => requireOnboardingService().bootstrapOnboarding(input),
getBrowserActions: () => requireServiceGraph().getBrowserActionService(),
randomFallbackCompanyId: () => requireConnectorHealthService().randomFallbackCompanyId(),
skillService: requireServiceGraph().getOrCreateSkillService(),
});
}
+16 -1
View File
@@ -12,6 +12,7 @@ import { createSkillService } from "./skill-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";
@@ -68,8 +69,20 @@ export function createServiceGraph({
let workerService: ReturnType<typeof createWorkerService> | null = null;
let updaterService: ReturnType<typeof createUpdaterService> | null = null;
let appLifecycleService: ReturnType<typeof createAppLifecycleService> | null = null;
let skillService: ReturnType<typeof createSkillService> | 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.");
}
@@ -250,6 +263,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
@@ -269,5 +283,6 @@ export function createServiceGraph({
getOnboardingService: () => onboardingService,
getUpdaterService: () => updaterService,
getAppLifecycleService: () => appLifecycleService,
getOrCreateSkillService,
};
}