fix(04): wire pending approval notifications in IPC and API server paths

This commit is contained in:
ZacharyZhang-NY committed 2026-03-22 17:12:40 -04:00
1 parent 9d01a7acc4
commit a2a110f0c5
5 files changed
+26 -2

No files matched your search

+11 -1
View File
@@ -9,7 +9,7 @@ import { resolveApprovalDecision, shouldAutoApproveApproval } from "./approval-p
import type { AppDatabase } from "./database";
import { inferTaskType } from "./connector-matching";
import { dispatchAgentMessage } from "./message-dispatch";
import type { DesktopEvent } from "@shared/types";
import type { DesktopEvent, SectionId } from "@shared/types";
/**
* Decode an HTTP request body to a UTF-8 string, handling the common Windows
@@ -78,6 +78,7 @@ export interface ApiServerEventCallbacks {
onDocumentCreated: (companyId: string, authorAgentId: string | null, projectId: string | null) => void;
onAutoAssignTask: (taskId: string, companyId: string) => string | null;
emitEvent: (event: DesktopEvent) => void;
notify: (options: { title: string; body: string; urgency: "critical" | "informational"; navigation?: { section: SectionId; entityId?: string }; batchKey?: string }) => void;
}
export class AgentApiServer {
@@ -114,6 +115,7 @@ export class AgentApiServer {
onDocumentCreated: callbacks?.onDocumentCreated ?? (() => {}),
onAutoAssignTask: callbacks?.onAutoAssignTask ?? (() => null),
emitEvent: callbacks?.emitEvent ?? (() => {}),
notify: callbacks?.notify ?? (() => {}),
};
this.registerRoutes();
}
@@ -1009,6 +1011,14 @@ export class AgentApiServer {
detail: "Auto-approved by company policy",
});
log.info(`[auto-approve] Auto-approved approval ${id}: ${input.payloadSummary}`);
} else {
this.callbacks.notify({
title: "Approval Required",
body: input.payloadSummary ?? `${input.type} approval pending`,
urgency: "critical",
navigation: { section: "approvals", entityId: id },
batchKey: "pending_approval",
});
}
this.onDomainChanged();
+3 -1
View File
@@ -8,7 +8,7 @@ import { createServiceGraph } from "./service-graph";
import { createIpcSupport } from "./ipc-support";
import { createNotificationService } from "./notification-service";
import type { NotificationOptions } from "./notification-service";
import type { DesktopEvent } from "@shared/types";
import type { DesktopEvent, SectionId } from "@shared/types";
interface BootstrapApplicationDependencies {
hasSingleInstanceLock: boolean;
@@ -33,6 +33,7 @@ interface BootstrapApplicationDependencies {
onDocumentCreated: (companyId: string, authorAgentId: string | null, projectId: string | null) => void;
onAutoAssignTask: (taskId: string, companyId: string) => string | null;
emitEvent: (event: DesktopEvent) => void;
notify: (options: { title: string; body: string; urgency: "critical" | "informational"; navigation?: { section: SectionId; entityId?: string }; batchKey?: string }) => void;
}, allowedOrigins: string[]) => AgentApiServer;
createRunToken: (agentId: string, companyId: string, runId: string) => string;
seedDefaultAutomationRules: (companyId: string) => void;
@@ -168,6 +169,7 @@ export async function bootstrapApplication({
},
onAutoAssignTask: (taskId, companyId) => db.autoAssignTask(taskId, companyId),
emitEvent,
notify,
}, [isDev && devServerUrl ? new URL(devServerUrl).origin : null, "app://-"].filter((origin): origin is string => Boolean(origin)));
apiServerRef.current = apiServer;
+2
View File
@@ -1,5 +1,6 @@
import type { IpcMainInvokeEvent } from "electron";
import type { DesktopResult } from "@shared/contracts";
import type { SectionId } from "@shared/types";
import type { AppDatabase } from "./database";
export type CompanyScopedTable = "workspaces" | "agents" | "goals" | "projects" | "tasks" | "approvals" | "runs" | "secrets";
@@ -30,6 +31,7 @@ export interface RegisterCoreHandlersDependencies {
resetAfterRestore: () => void;
getApiPort: () => number;
listConnectorModels: (connectorId: string) => Promise<Array<{ id: string; label: string }>>;
notify: (options: { title: string; body: string; urgency: "critical" | "informational"; navigation?: { section: SectionId; entityId?: string }; batchKey?: string }) => void;
}
export function ok<T>(data: T): DesktopResult<T> {
+9
View File
@@ -25,6 +25,7 @@ export function registerCoreOperationHandlers({
dispatchAutomation,
queueTaskRun,
getWorkerProcess,
notify,
}: Pick<
RegisterCoreHandlersDependencies,
| "registerHandle"
@@ -37,6 +38,7 @@ export function registerCoreOperationHandlers({
| "dispatchAutomation"
| "queueTaskRun"
| "getWorkerProcess"
| "notify"
>) {
registerHandle(IPC_CHANNELS.requestApproval, async (_event, payload) => {
const parsed = approvalInputSchema.parse(payload);
@@ -81,6 +83,13 @@ export function registerCoreOperationHandlers({
agentId: parsed.requestedByAgentId ?? undefined,
taskId: parsed.relatedTaskId ?? undefined,
});
notify({
title: "Approval Required",
body: parsed.payloadSummary ?? `${parsed.type} approval pending`,
urgency: "critical",
navigation: { section: "approvals", entityId: id },
batchKey: "pending_approval",
});
publishDomainChanged();
return ok(id);
});
+1
View File
@@ -145,6 +145,7 @@ export function createMainRuntimeSupport(state: MainRuntimeState) {
requireBrowserActionService().resetBrowserManager();
},
getApiPort: () => state.apiServer?.getPort() ?? 0,
notify: (options) => requireIpcSupport().notify(options),
listConnectorModels: async (connectorId) => {
const definition = getConnectorDefinition(connectorId as Parameters<typeof getConnectorDefinition>[0]);
return definition.listModels ? definition.listModels() : [];