Rebuild AgentCompany Electron app

This commit is contained in:
ZacharyZhang-NY committed 2026-05-20 23:27:00 -04:00
1 parent bc5daa6eb8
commit fcf6d90731
56 files changed
+8667

No files matched your search

@@ -0,0 +1,43 @@
import { BrowserWindow } from "electron";
import { join } from "node:path";
import { attachMainWindowSecurity } from "./window-security";
export interface CreateWindowOptions {
preloadPath: string;
rendererDist: string;
devServerUrl?: string;
}
export function createMainWindow(options: CreateWindowOptions): BrowserWindow {
const win = new BrowserWindow({
width: 1280,
height: 860,
minWidth: 980,
minHeight: 700,
title: "AgentCompany",
show: false,
titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "hidden",
webPreferences: {
preload: options.preloadPath,
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
webSecurity: true,
allowRunningInsecureContent: false,
},
});
attachMainWindowSecurity(
win,
options.devServerUrl ? new URL(options.devServerUrl).origin : null,
);
win.once("ready-to-show", () => win.show());
if (options.devServerUrl) {
win.loadURL(options.devServerUrl);
} else {
win.loadFile(join(options.rendererDist, "index.html"));
}
return win;
}
@@ -0,0 +1,83 @@
import { ipcMain, type BrowserWindow } from "electron";
import type { DatabaseConnection } from "../database/connection";
import { IPC_CHANNELS } from "../../shared/ipc";
import { fail, ok, type DesktopResult } from "../../shared/result";
import type { AppSnapshot } from "../../shared/domain";
import { SnapshotService } from "../services/snapshot-service";
import { OrganizationService } from "../services/organization-service";
import { ApprovalService } from "../services/approval-service";
import { ChatService } from "../services/chat-service";
import { ProviderService } from "../services/provider-service";
import { SkillService } from "../services/skill-service";
import { SocialService } from "../services/social-service";
import { FileService } from "../services/file-service";
export interface IpcDeps {
connection: DatabaseConnection;
mainWindow: BrowserWindow;
artifactRoot: string;
}
export function registerIpcHandlers(deps: IpcDeps): void {
const snapshot = new SnapshotService(deps.connection.db);
const organization = new OrganizationService(deps.connection.db);
const approvals = new ApprovalService(deps.connection.db);
const chat = new ChatService(deps.connection.db);
const providers = new ProviderService(deps.connection.db);
const skills = new SkillService(deps.connection.db);
const social = new SocialService(deps.connection.db);
const files = new FileService(deps.connection.db, deps.artifactRoot);
const mutate = <T>(operation: () => T): DesktopResult<AppSnapshot> => {
try {
deps.connection.transaction(operation);
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
return ok(snapshot.load());
} catch (error) {
return fail("operation_failed", error instanceof Error ? error.message : String(error));
}
};
ipcMain.handle(IPC_CHANNELS.loadSnapshot, () => {
try {
return ok(snapshot.load());
} catch (error) {
return fail("snapshot_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.createOrganization, (_event, input) => mutate(() => organization.createOrganization(input)));
ipcMain.handle(IPC_CHANNELS.createAgent, (_event, input) => mutate(() => organization.createAgent(input)));
ipcMain.handle(IPC_CHANNELS.createTask, (_event, input) => mutate(() => organization.createTask(input)));
ipcMain.handle(IPC_CHANNELS.updateTaskStatus, (_event, input) => mutate(() => organization.updateTaskStatus(input.orgId, input.taskId, input.status)));
ipcMain.handle(IPC_CHANNELS.createApproval, (_event, input) => mutate(() => approvals.create(input)));
ipcMain.handle(IPC_CHANNELS.decideApproval, (_event, input) => mutate(() => approvals.decide(input)));
ipcMain.handle(IPC_CHANNELS.sendMessage, (_event, input) => mutate(() => chat.sendMessage(input)));
ipcMain.handle(IPC_CHANNELS.saveProviderCredential, (_event, input) => mutate(() => providers.saveCredential(input)));
ipcMain.handle(IPC_CHANNELS.importSkillDirectory, (_event, input) => mutate(() => skills.importDirectory(input)));
ipcMain.handle(IPC_CHANNELS.bindSkill, (_event, input) => mutate(() => skills.bind(input)));
ipcMain.handle(IPC_CHANNELS.createSocialAccount, (_event, input) => mutate(() => social.createAccount(input)));
ipcMain.handle(IPC_CHANNELS.launchSocialLogin, (_event, input) => {
try {
social.launchLoginWindow(input.socialAccountId);
return ok(null);
} catch (error) {
return fail("social_login_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.importFile, async (_event, input) => {
try {
await files.importFile(input);
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
return ok(snapshot.load());
} catch (error) {
return fail("file_import_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.windowMinimize, () => deps.mainWindow.minimize());
ipcMain.handle(IPC_CHANNELS.windowMaximize, () => deps.mainWindow.isMaximized() ? deps.mainWindow.unmaximize() : deps.mainWindow.maximize());
ipcMain.handle(IPC_CHANNELS.windowClose, () => deps.mainWindow.close());
}
@@ -0,0 +1,10 @@
import type { BrowserWindow } from "electron";
export function attachMainWindowSecurity(win: BrowserWindow, devServerOrigin: string | null): void {
win.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
win.webContents.on("will-navigate", (event, url) => {
if (devServerOrigin && url.startsWith(devServerOrigin)) return;
if (url.startsWith("file://")) return;
event.preventDefault();
});
}