merge: wave 3a executor (02-03 IPC) into main
This commit is contained in:
commit
b949187356
1093 files changed
+446427
-2
No files matched your search
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -168,6 +168,7 @@ export function createMainRuntimeSupport(state: MainRuntimeState) {
|
||||
bootstrapOnboarding: (input) => requireOnboardingService().bootstrapOnboarding(input),
|
||||
getBrowserActions: () => requireServiceGraph().getBrowserActionService(),
|
||||
randomFallbackCompanyId: () => requireConnectorHealthService().randomFallbackCompanyId(),
|
||||
skillService: requireServiceGraph().getOrCreateSkillService(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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) =>
|
||||
|
||||
+51
-1
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import type { AgentMessageRecord, AgentMetrics, BrowserActionRecord, CommentRecord, CompanyMetrics, DesktopEvent, InboxItem, OnboardingBootstrapResult, ProfileSnapshot, RunLogChunk, StandupReport } from "./types";
|
||||
import type { AgentMessageRecord, AgentMetrics, BrowserActionRecord, CommentRecord, CompanyMetrics, DesktopEvent, InboxItem, OnboardingBootstrapResult, ProfileSnapshot, RunLogChunk, SkillImportResult, SkillRecord, SkillUpdateStatus, StandupReport } from "./types";
|
||||
|
||||
export const IPC_CHANNELS = {
|
||||
loadSnapshot: "agent-company:load-snapshot",
|
||||
@@ -73,6 +73,13 @@ export const IPC_CHANNELS = {
|
||||
runWorkflow: "agent-company:run-workflow",
|
||||
autoAssignTask: "agent-company:auto-assign-task",
|
||||
searchMessages: "agent-company:search-messages",
|
||||
importSkill: "agent-company:import-skill",
|
||||
listSkills: "agent-company:list-skills",
|
||||
getSkillDetail: "agent-company:get-skill-detail",
|
||||
deleteSkill: "agent-company:delete-skill",
|
||||
assignSkillToAgent: "agent-company:assign-skill-to-agent",
|
||||
removeSkillFromAgent: "agent-company:remove-skill-from-agent",
|
||||
checkSkillUpdate: "agent-company:check-skill-update",
|
||||
} as const;
|
||||
|
||||
const optionalId = z.preprocess(
|
||||
@@ -514,6 +521,42 @@ export const autoAssignSchema = z.object({
|
||||
companyId: z.string().uuid(),
|
||||
});
|
||||
|
||||
export const importSkillSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
source: z.string().min(1),
|
||||
});
|
||||
|
||||
export const listSkillsSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const getSkillDetailSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
skillId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const deleteSkillSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
skillId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const assignSkillToAgentSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
agentId: z.string().min(1),
|
||||
skillId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const removeSkillFromAgentSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
agentId: z.string().min(1),
|
||||
skillId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const checkSkillUpdateSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
skillId: z.string().min(1),
|
||||
});
|
||||
|
||||
export type DesktopResult<T> =
|
||||
| { ok: true; data: T }
|
||||
| { ok: false; error: { code: string; message: string } };
|
||||
@@ -589,6 +632,13 @@ export type DesktopApi = {
|
||||
runWorkflow: (input: z.infer<typeof runWorkflowSchema>) => Promise<DesktopResult<boolean>>;
|
||||
autoAssignTask: (input: z.infer<typeof autoAssignSchema>) => Promise<DesktopResult<string>>;
|
||||
searchMessages: (input: z.infer<typeof searchMessagesSchema>) => Promise<DesktopResult<AgentMessageRecord[]>>;
|
||||
importSkill: (companyId: string, source: string) => Promise<DesktopResult<SkillImportResult[]>>;
|
||||
listSkills: (companyId: string) => Promise<DesktopResult<SkillRecord[]>>;
|
||||
getSkillDetail: (companyId: string, skillId: string) => Promise<DesktopResult<SkillRecord>>;
|
||||
deleteSkill: (companyId: string, skillId: string) => Promise<DesktopResult<void>>;
|
||||
assignSkillToAgent: (companyId: string, agentId: string, skillId: string) => Promise<DesktopResult<void>>;
|
||||
removeSkillFromAgent: (companyId: string, agentId: string, skillId: string) => Promise<DesktopResult<void>>;
|
||||
checkSkillUpdate: (companyId: string, skillId: string) => Promise<DesktopResult<SkillUpdateStatus>>;
|
||||
recoveryRetry: (taskId: string, agentId: string, companyId: string) => Promise<DesktopResult<null>>;
|
||||
recoveryDismiss: (taskId: string) => Promise<DesktopResult<null>>;
|
||||
recoveryFail: (taskId: string) => Promise<DesktopResult<null>>;
|
||||
|
||||
@@ -453,6 +453,52 @@ export interface OnboardingBootstrapResult {
|
||||
runId: string | null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Skill system types (mirrored from main/skill-types.ts for IPC boundary)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type SkillSourceType = "github" | "local_path" | "url" | "skills_sh" | "managed";
|
||||
export type TrustLevel = "markdown_only" | "assets" | "scripts_executables";
|
||||
|
||||
export interface FileInventoryEntry {
|
||||
path: string;
|
||||
kind: "skill" | "reference" | "script" | "asset" | "markdown" | "other";
|
||||
}
|
||||
|
||||
export interface SkillRecord {
|
||||
id: string;
|
||||
companyId: string;
|
||||
key: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
markdown: string;
|
||||
sourceType: SkillSourceType;
|
||||
sourceLocator: string | null;
|
||||
sourceRef: string | null;
|
||||
trustLevel: TrustLevel;
|
||||
compatibility: string;
|
||||
fileInventory: FileInventoryEntry[];
|
||||
metadata: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface SkillImportResult {
|
||||
skillId: string;
|
||||
key: string;
|
||||
name: string;
|
||||
isUpdate: boolean;
|
||||
}
|
||||
|
||||
export interface SkillUpdateStatus {
|
||||
skillId: string;
|
||||
key: string;
|
||||
currentRef: string | null;
|
||||
latestRef: string | null;
|
||||
hasUpdate: boolean;
|
||||
}
|
||||
|
||||
export type SocialPlatform = "twitter" | "linkedin" | "instagram" | "facebook" | "reddit" | "youtube" | "tiktok" | "bluesky" | "other";
|
||||
export type SocialAccountStatus = "active" | "logged_in" | "login_required" | "suspended" | "paused";
|
||||
export type BrowserActionType = "post" | "reply" | "like" | "follow" | "browse_feed" | "screenshot" | "navigate" | "search";
|
||||
|
||||
Reference in new issue
Block a user