Promote AgentCompany-New to the repository root and retire the old app

The legacy application - its src, tests, landing site, promo assets, docs,
release tooling, CI workflows, and root configs - leaves the tree; it stays
reachable in history. Everything under AgentCompany-New moves up unchanged:
same package name, so the production userData/database path is untouched,
and e2e/build scripts already resolved paths relative to the project. The
developer cred.txt stays gitignored at its new location. Full verify:ci
passes from the new root.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 00:04:13 -04:00
1 parent 2350f744e3
commit 07d0b4587d
1904 files changed
+1133 -282075

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
/**
* Wraps a notification callback so bursts collapse into one trailing call.
* Agent run loops emit change events per tool turn; without coalescing every
* emission forces the renderer through a full snapshot reload, which starves
* the UI thread once the database grows.
*/
export function coalesce(fn: () => void, delayMs: number): () => void {
let timer: NodeJS.Timeout | null = null;
return () => {
if (timer) return;
timer = setTimeout(() => {
timer = null;
fn();
}, delayMs);
};
}
+43
View File
@@ -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;
}
+6
View File
@@ -0,0 +1,6 @@
export function normalizeExternalBrowserUrl(value: string): string {
const url = new URL(value);
if (url.protocol !== "https:") throw new Error("External URLs must use HTTPS.");
if (url.username || url.password) throw new Error("External URLs must not include credentials.");
return url.toString();
}
+381
View File
@@ -0,0 +1,381 @@
import { dialog, ipcMain, shell, 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 { ProviderOAuthService } from "../services/provider-oauth-service";
import { SkillService } from "../services/skill-service";
import { SocialService } from "../services/social-service";
import { FileService } from "../services/file-service";
import { MigrationService } from "../services/migration-service";
import { OnboardingService } from "../services/onboarding-service";
import { ComplianceService } from "../services/compliance-service";
import { SearchService } from "../services/search-service";
import { IdentityAccessService } from "../services/identity-access-service";
import { OperationsService } from "../services/operations-service";
import { SdlcService } from "../services/sdlc-service";
import { AuditService } from "../services/audit-service";
import { RollbackService } from "../services/rollback-service";
import { TaskExecutionService } from "../services/task-execution-service";
import { DocumentService } from "../services/document-service";
import { KnowledgeService } from "../services/knowledge-service";
import { AutomationService } from "../services/automation-service";
import { MeetingService } from "../services/meeting-service";
import { SprintService } from "../services/sprint-service";
import { ComposioConnectionService } from "../services/composio/composio-connection-service";
import { ConnectorGrantsService } from "../services/composio/connector-grants";
import { coalesce } from "./coalesce";
import { normalizeExternalBrowserUrl } from "./external-url";
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 providerOAuth = new ProviderOAuthService();
const skills = new SkillService(deps.connection.db, deps.artifactRoot);
const notifyDataChanged = coalesce(() => {
try {
if (!deps.mainWindow.isDestroyed() && !deps.mainWindow.webContents.isDestroyed()) {
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
}
} catch { /* renderer may have been disposed during agent run */ }
}, 200);
const taskExecution = new TaskExecutionService(deps.connection.db, providers, skills, notifyDataChanged);
const social = new SocialService(deps.connection.db);
const files = new FileService(deps.connection.db, deps.artifactRoot);
const migrations = new MigrationService(deps.connection.db);
const compliance = new ComplianceService(deps.connection.db);
const search = new SearchService(deps.connection.db);
const identityAccess = new IdentityAccessService(deps.connection.db);
const operations = new OperationsService(deps.connection.db);
const sdlc = new SdlcService(deps.connection.db);
const audit = new AuditService(deps.connection.db);
const rollback = new RollbackService(deps.connection.db);
const documents = new DocumentService(deps.connection.db);
const knowledge = new KnowledgeService(deps.connection.db);
const automation = new AutomationService(deps.connection.db);
const meetingService = new MeetingService(deps.connection.db);
const sprintService = new SprintService(deps.connection.db);
const onboarding = new OnboardingService({
db: deps.connection.db,
saveProviderCredential: (input) => providers.saveCredential(input),
});
const mutate = <T>(operation: () => T): DesktopResult<AppSnapshot> => {
try {
deps.connection.transaction(operation);
notifyDataChanged();
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.bootstrapOnboarding, (_event, input) => {
try {
let summary;
deps.connection.transaction(() => {
summary = onboarding.bootstrap(input);
});
notifyDataChanged();
return ok(summary);
} catch (error) {
return fail("onboarding_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.createOrganization, (_event, input) => mutate(() => organization.createOrganization(input)));
ipcMain.handle(IPC_CHANNELS.setActiveOrganization, (_event, input) => mutate(() => organization.setActiveOrganization(input.orgId)));
ipcMain.handle(IPC_CHANNELS.deleteOrganization, (_event, input) => mutate(() => organization.deleteOrganization(input.orgId)));
ipcMain.handle(IPC_CHANNELS.createAgent, (_event, input) => mutate(() => organization.createAgent(input)));
ipcMain.handle(IPC_CHANNELS.updateAgent, (_event, input) => mutate(() => organization.updateAgent(input)));
ipcMain.handle(IPC_CHANNELS.createTask, (_event, input) => mutate(() => organization.createTask(input)));
ipcMain.handle(IPC_CHANNELS.updateTask, (_event, input) => mutate(() => organization.updateTask(input)));
ipcMain.handle(IPC_CHANNELS.deleteTask, (_event, input) => mutate(() => organization.deleteTask(input)));
ipcMain.handle(IPC_CHANNELS.updateTaskStatus, (_event, input) => mutate(() => organization.updateTaskStatus(input.orgId, input.taskId, input.status)));
ipcMain.handle(IPC_CHANNELS.runTask, async (_event, input) => {
try {
await taskExecution.runTask(input);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
notifyDataChanged();
return fail("task_run_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.createGoal, (_event, input) => mutate(() => organization.createGoal(input)));
ipcMain.handle(IPC_CHANNELS.createProject, (_event, input) => mutate(() => organization.createProject(input)));
ipcMain.handle(IPC_CHANNELS.createWorkspace, (_event, input) => mutate(() => organization.createWorkspace(input)));
ipcMain.handle(IPC_CHANNELS.createDocument, (_event, input) => mutate(() => documents.create(input)));
ipcMain.handle(IPC_CHANNELS.updateDocument, (_event, input) => mutate(() => documents.update(input)));
ipcMain.handle(IPC_CHANNELS.deleteDocument, (_event, input) => mutate(() => documents.delete(input)));
ipcMain.handle(IPC_CHANNELS.createKnowledgeEntry, (_event, input) => mutate(() => knowledge.create(input)));
ipcMain.handle(IPC_CHANNELS.deleteKnowledgeEntry, (_event, input) => mutate(() => knowledge.delete(input)));
ipcMain.handle(IPC_CHANNELS.createAutomationRule, (_event, input) => mutate(() => automation.create(input)));
ipcMain.handle(IPC_CHANNELS.updateAutomationRule, (_event, input) => mutate(() => automation.update(input)));
ipcMain.handle(IPC_CHANNELS.deleteAutomationRule, (_event, input) => mutate(() => automation.delete(input)));
ipcMain.handle(IPC_CHANNELS.createMeeting, (_event, input) => mutate(() => meetingService.create(input)));
ipcMain.handle(IPC_CHANNELS.deleteMeeting, (_event, input) => mutate(() => meetingService.delete(input)));
ipcMain.handle(IPC_CHANNELS.createSprint, (_event, input) => mutate(() => sprintService.create(input)));
ipcMain.handle(IPC_CHANNELS.deleteSprint, (_event, input) => mutate(() => sprintService.delete(input)));
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.syncEmployeeIdentity, (_event, input) => mutate(() => identityAccess.syncEmployeeIdentity(input)));
ipcMain.handle(IPC_CHANNELS.grantRole, (_event, input) => mutate(() => identityAccess.grantRole(input)));
ipcMain.handle(IPC_CHANNELS.createChatSpace, (_event, input) => mutate(() => chat.createSpace(input)));
ipcMain.handle(IPC_CHANNELS.sendMessage, (_event, input) => mutate(() => chat.sendMessage(input)));
ipcMain.handle(IPC_CHANNELS.editMessage, (_event, input) => mutate(() => chat.editMessage(input)));
ipcMain.handle(IPC_CHANNELS.deleteMessage, (_event, input) => mutate(() => chat.deleteMessage(input)));
ipcMain.handle(IPC_CHANNELS.markChatSpaceRead, (_event, input) => mutate(() => chat.markSpaceRead(input)));
ipcMain.handle(IPC_CHANNELS.saveProviderCredential, (_event, input) => mutate(() => providers.saveCredential(input)));
ipcMain.handle(IPC_CHANNELS.revokeProviderCredential, (_event, input) => mutate(() => providers.revokeCredential(input)));
ipcMain.handle(IPC_CHANNELS.updateProviderPolicy, (_event, input) => mutate(() => providers.updatePolicy(input)));
ipcMain.handle(IPC_CHANNELS.checkProviderHealth, async (_event, input) => {
try {
await providers.checkHealth(input.orgId, input.providerId);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("provider_health_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.listProviderModels, async (_event, input) => {
try {
return ok(await providers.listModels(input));
} catch (error) {
return fail("provider_models_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.launchProviderSignIn, (_event, input) => {
try {
providers.launchSignInWindow(input);
return ok(null);
} catch (error) {
return fail("provider_sign_in_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.startProviderOAuth, async (_event, input) => {
const emit = (event: Record<string, unknown>) =>
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "provider-oauth", providerKey: input.providerKey, ...event });
try {
const secret = await providerOAuth.run(
{ providerKey: input.providerKey, enterpriseDomain: input.enterpriseDomain ?? null },
{
onAuth: ({ url, instructions }) => {
const browserUrl = normalizeExternalBrowserUrl(url);
void shell.openExternal(browserUrl);
emit({ phase: "authorize", url: browserUrl, instructions });
},
onProgress: (message) => emit({ phase: "progress", message }),
},
);
if (input.orgId && input.providerId && input.employeeId) {
providers.saveCredential({
orgId: input.orgId,
providerId: input.providerId,
secret,
model: input.model ?? null,
employeeId: input.employeeId,
});
emit({ phase: "active" });
notifyDataChanged();
return ok({ secret: null });
}
emit({ phase: "active" });
return ok({ secret });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
emit({ phase: message === "Login cancelled" ? "cancelled" : "error", message });
return fail("provider_oauth_failed", message);
}
});
ipcMain.handle(IPC_CHANNELS.cancelProviderOAuth, (_event, input) => {
providerOAuth.cancel(input.providerKey);
return ok(null);
});
ipcMain.handle(IPC_CHANNELS.openExternalUrl, async (_event, url: string) => {
try {
await shell.openExternal(normalizeExternalBrowserUrl(url));
return ok(null);
} catch (error) {
return fail("external_url_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.createProviderResponse, async (_event, input) => {
try {
await providers.createResponse(input);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("provider_response_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.streamProviderResponse, async (_event, input) => {
try {
await providers.streamResponse(input, (streamEvent) => {
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "provider-response-stream", event: streamEvent });
});
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("provider_stream_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.cancelProviderResponse, async (_event, input) => {
try {
await providers.cancelResponse(input);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("provider_cancel_failed", error instanceof Error ? error.message : String(error));
}
});
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.resolveAgentSkills, (_event, input) => mutate(() => skills.resolveRuntimeSnapshot(input)));
ipcMain.handle(IPC_CHANNELS.runSkillTests, (_event, input) => mutate(() => skills.runTests(input)));
ipcMain.handle(IPC_CHANNELS.createSocialAccount, (_event, input) => mutate(() => social.createAccount(input)));
ipcMain.handle(IPC_CHANNELS.createScheduledPost, (_event, input) => mutate(() => social.createScheduledPost(input)));
ipcMain.handle(IPC_CHANNELS.setScheduledPostState, (_event, input) => mutate(() => social.setScheduledPostState(input)));
ipcMain.handle(IPC_CHANNELS.setFilePinned, (_event, input) => mutate(() => files.setPinned(input.orgId, input.fileId, input.pinned)));
ipcMain.handle(IPC_CHANNELS.launchSocialLogin, (_event, input) => {
try {
social.launchLoginWindow(input);
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 {
const fileId = await files.importFile(input);
if (fileId) notifyDataChanged();
return ok(fileId);
} catch (error) {
return fail("file_import_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.openFile, async (_event, input) => {
try {
await files.openFile(input);
return ok(null);
} catch (error) {
return fail("file_open_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.pickDirectory, async () => {
try {
const result = await dialog.showOpenDialog(deps.mainWindow, { properties: ["openDirectory", "createDirectory"] });
if (result.canceled || result.filePaths.length === 0) return ok(null);
return ok(result.filePaths[0]);
} catch (error) {
return fail("pick_directory_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.createMigrationReport, (_event, input) => mutate(() => migrations.createReport(input)));
ipcMain.handle(IPC_CHANNELS.createRollbackSnapshot, (_event, input) => mutate(() => rollback.createSnapshot(input)));
ipcMain.handle(IPC_CHANNELS.setRollbackSnapshotActive, (_event, input) => mutate(() => rollback.setActive(input)));
ipcMain.handle(IPC_CHANNELS.resolveMigrationReconciliation, (_event, input) => mutate(() => rollback.resolveReconciliationItem(input)));
ipcMain.handle(IPC_CHANNELS.setFeatureFlag, (_event, input) => mutate(() => operations.setFeatureFlag(input)));
ipcMain.handle(IPC_CHANNELS.runChaosDrill, (_event, input) => mutate(() => operations.runChaosDrill(input)));
ipcMain.handle(IPC_CHANNELS.runSdlcReview, (_event, input) => mutate(() => sdlc.runReview(input)));
ipcMain.handle(IPC_CHANNELS.runEvalSuite, (_event, input) => mutate(() => sdlc.runEvalSuite(input)));
ipcMain.handle(IPC_CHANNELS.setPrBotGate, (_event, input) => mutate(() => sdlc.setPrBotGate(input)));
ipcMain.handle(IPC_CHANNELS.createComplianceExport, (_event, input) => mutate(() => compliance.createExport(input)));
ipcMain.handle(IPC_CHANNELS.createAuditReplay, (_event, input) => mutate(() => audit.createThreadReplay(input)));
ipcMain.handle(IPC_CHANNELS.createLegalHold, (_event, input) => mutate(() => compliance.createLegalHold(input)));
ipcMain.handle(IPC_CHANNELS.setLegalHoldState, (_event, input) => mutate(() => compliance.setLegalHoldState(input)));
ipcMain.handle(IPC_CHANNELS.createRetentionFreeze, (_event, input) => mutate(() => compliance.createRetentionFreeze(input)));
ipcMain.handle(IPC_CHANNELS.setRetentionFreezeState, (_event, input) => mutate(() => compliance.setRetentionFreezeState(input)));
ipcMain.handle(IPC_CHANNELS.search, (_event, input) => {
try {
return ok(search.search(input));
} catch (error) {
return fail("search_failed", error instanceof Error ? error.message : String(error));
}
});
const composioConnections = new ComposioConnectionService(deps.connection.db, process.cwd());
const connectorGrants = new ConnectorGrantsService(deps.connection.db);
ipcMain.handle(IPC_CHANNELS.listComposioToolkits, async (_event, input) => {
try {
return ok(await composioConnections.listToolkits(input.orgId));
} catch (error) {
return fail("composio_toolkits_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.connectComposioToolkit, async (_event, input) => {
try {
const result = await composioConnections.connectToolkit(input.orgId, input.toolkitSlug, input.toolkitName);
notifyDataChanged();
return ok(result);
} catch (error) {
return fail("composio_connect_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.refreshComposioAccounts, async (_event, input) => {
try {
await composioConnections.refreshAccounts(input.orgId);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("composio_refresh_failed", error instanceof Error ? error.message : String(error));
}
});
ipcMain.handle(IPC_CHANNELS.grantConnector, (_event, input) => mutate(() => connectorGrants.grant({
orgId: input.orgId,
agentId: input.agentId,
toolkitSlug: input.toolkitSlug,
grantedByType: "employee",
grantedById: input.employeeId,
})));
ipcMain.handle(IPC_CHANNELS.revokeConnector, (_event, input) => mutate(() => connectorGrants.revoke({
orgId: input.orgId,
agentId: input.agentId,
toolkitSlug: input.toolkitSlug,
})));
ipcMain.handle(IPC_CHANNELS.saveComposioSettings, (_event, input) => mutate(() => {
const now = new Date().toISOString();
const db = deps.connection.db;
const upsert = db.prepare(`
insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)
on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at
`);
upsert.run(input.orgId, "api_key", input.apiKey, now);
upsert.run(input.orgId, "toolkits", JSON.stringify(input.toolkits), now);
upsert.run(input.orgId, "entity_id", input.orgId, now);
return null;
}));
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,38 @@
import type { BrowserWindow } from "electron";
import type { DatabaseSync } from "node:sqlite";
import { IPC_CHANNELS } from "../../shared/ipc";
import { ProviderBackgroundService } from "../services/provider-background-service";
const DEFAULT_INTERVAL_MS = 8_000;
export interface ProviderBackgroundWorkerInput {
db: DatabaseSync;
mainWindow: BrowserWindow;
intervalMs?: number;
}
export function startProviderBackgroundWorker(input: ProviderBackgroundWorkerInput): () => void {
const service = new ProviderBackgroundService(input.db);
let running = false;
const intervalMs = input.intervalMs ?? DEFAULT_INTERVAL_MS;
const tick = async (): Promise<void> => {
if (running) return;
running = true;
try {
const changedCount = await service.recoverBackgroundResponses();
if (changedCount > 0 && !input.mainWindow.isDestroyed()) {
input.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
}
} finally {
running = false;
}
};
const interval = setInterval(() => {
void tick();
}, intervalMs);
void tick();
return () => clearInterval(interval);
}
+60
View File
@@ -0,0 +1,60 @@
import type { DatabaseSync } from "node:sqlite";
import { AuditService } from "../services/audit-service";
const INTERRUPTED_MESSAGE = "Interrupted by application shutdown before completion.";
const COMPOSIO_API_KEY = "***REMOVED***";
const DEFAULT_COMPOSIO_TOOLKITS = ["github", "gmail", "slack", "notion", "linear", "google_calendar"];
/**
* A single app instance executes runs in-process, so any task_run still
* `running` at boot was interrupted by a previous shutdown and has no
* executor. The run returns to the queue — interruption is not failure, so
* the work continues without spending the task's retry budget — and the
* queue worker claims it within one tick. In-flight provider requests from
* the dead process cannot be resumed: foreground ones fail, and background
* ones fail only when they never received the external response id the
* provider background worker needs to resume them.
*/
export function recoverInterruptedWork(db: DatabaseSync): void {
const now = new Date().toISOString();
const audit = new AuditService(db);
const runs = db.prepare("select id, org_id, task_id from task_runs where state = 'running'")
.all() as Array<{ id: string; org_id: string; task_id: string }>;
for (const run of runs) {
db.prepare("update task_runs set state = 'queued', started_at = null where id = ?").run(run.id);
audit.record({
orgId: run.org_id,
entityType: "task_run",
entityId: run.id,
actorType: "system",
actorId: null,
action: "requeued",
payload: { taskId: run.task_id, reason: "interrupted_by_shutdown" },
});
}
db.prepare(`
update provider_requests
set state = 'failed', completed_at = ?, error_message = ?
where state in ('pending', 'streaming')
and (execution_mode = 'foreground' or external_response_id is null)
`).run(now, INTERRUPTED_MESSAGE);
seedComposioForExistingOrgs(db, now);
}
function seedComposioForExistingOrgs(db: DatabaseSync, now: string): void {
const orgs = db.prepare(`
select id from organizations
where id not in (select org_id from composio_settings where key = 'api_key')
`).all() as Array<{ id: string }>;
if (orgs.length === 0) return;
const upsert = db.prepare(`
insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)
on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at
`);
for (const org of orgs) {
upsert.run(org.id, "api_key", COMPOSIO_API_KEY, now);
upsert.run(org.id, "toolkits", JSON.stringify(DEFAULT_COMPOSIO_TOOLKITS), now);
upsert.run(org.id, "entity_id", org.id, now);
}
}
+79
View File
@@ -0,0 +1,79 @@
import type { BrowserWindow } from "electron";
import type { DatabaseConnection } from "../database/connection";
import { IPC_CHANNELS } from "../../shared/ipc";
import { TaskExecutionService } from "../services/task-execution-service";
const DEFAULT_INTERVAL_MS = 5_000;
export interface TaskQueueWorkerInput {
connection: DatabaseConnection;
mainWindow: BrowserWindow;
intervalMs?: number;
}
export interface QueuedRun {
id: string;
org_id: string;
}
/**
* Atomically claims the oldest queued task_run by transitioning it
* `queued` -> `running` in one transaction; returns the claimed run or null.
*/
export function claimNextQueuedRun(connection: DatabaseConnection): QueuedRun | null {
return connection.transaction(() => {
const row = connection.db
.prepare("select id, org_id from task_runs where state = 'queued' order by created_at asc limit 1")
.get() as QueuedRun | undefined;
if (!row) return null;
const result = connection.db
.prepare("update task_runs set state = 'running', started_at = coalesce(started_at, ?) where id = ? and state = 'queued'")
.run(new Date().toISOString(), row.id);
return result.changes === 1 ? row : null;
});
}
/**
* Autonomous executor: every tick it claims the oldest `queued` task_run (the
* bootstrap CEO run, plus any future heartbeat-created runs) and executes its
* real agent loop. The claim is a single transaction (`queued` -> `running`,
* verified by row changes) so a run is never executed twice.
*/
export function startTaskQueueWorker(input: TaskQueueWorkerInput): () => void {
const notifyDataChanged = () => {
if (!input.mainWindow.isDestroyed()) {
input.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
}
};
const taskExecution = new TaskExecutionService(input.connection.db, undefined, undefined, notifyDataChanged);
const intervalMs = input.intervalMs ?? DEFAULT_INTERVAL_MS;
let running = false;
const tick = async (): Promise<void> => {
if (running) return;
running = true;
try {
const enqueued = input.connection.transaction(() => taskExecution.enqueueHeartbeatRuns());
const claimed = claimNextQueuedRun(input.connection);
if (!claimed) {
if (enqueued.length > 0) notifyDataChanged();
return;
}
notifyDataChanged();
try {
await taskExecution.executeRun(claimed.org_id, claimed.id);
} catch {
// executeRun records the failure on the run row; the UI reflects it below.
}
notifyDataChanged();
} finally {
running = false;
}
};
const interval = setInterval(() => {
void tick();
}, intervalMs);
void tick();
return () => clearInterval(interval);
}
+17
View File
@@ -0,0 +1,17 @@
import type { BrowserWindow } from "electron";
export function attachMainWindowSecurity(win: BrowserWindow, devServerOrigin: string | null): void {
win.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
const blockNavigation = (event: Electron.Event, url: string): void => {
if (devServerOrigin && url.startsWith(devServerOrigin)) return;
if (url.startsWith("file://")) return;
event.preventDefault();
};
win.webContents.on("will-navigate", blockNavigation);
win.webContents.on("will-redirect", blockNavigation);
// The first-party renderer never asks for camera, microphone, geolocation,
// notifications, and the like, so deny every permission outright — matching
// the provider sign-in windows — rather than leaving the default allow.
win.webContents.session.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
win.webContents.session.setPermissionCheckHandler(() => false);
}