feat(FN-3549): add mailbox approvals inbox and decision flow

- Add dashboard approvals APIs and route handlers for listing requests, fetching detail, and submitting approve/deny decisions
- Emit approval SSE events and subscribe mailbox refresh logic to keep approval lists and badges live
- Extend Mailbox view with an Approvals tab, pending/history filters, request detail history, and approve/deny actions
- Update mailbox styles, approval route tests, and docs for the new approvals workflow

Fusion-Task-Id: FN-3549
This commit is contained in:
Fusion
2026-05-09 05:38:34 -07:00
committed by gsxdsm
parent 73a3a80117
commit 070421c0ab
9 changed files with 770 additions and 113 deletions

View File

@@ -69,6 +69,7 @@ import type {
DockerExtraCli,
DockerNodeStatus,
ProjectNodePathMapping,
ApprovalRequestStatus,
} from "@fusion/core";
import type { PlanningQuestion, PlanningSummary } from "@fusion/core";
import type { ScheduledTask, ScheduledTaskCreateInput, ScheduledTaskUpdateInput, AutomationRunResult, Routine, RoutineCreateInput, RoutineUpdateInput, RoutineExecutionResult } from "@fusion/core";
@@ -7598,6 +7599,55 @@ export interface SendMessageInput {
wakeImmediately?: boolean;
}
export interface ApprovalRequestSummary {
id: string;
status: ApprovalRequestStatus;
actionCategory: string;
actionSummary: string;
agentId: string;
taskId?: string;
createdAt: string;
updatedAt: string;
decidedAt?: string;
decidedBy?: string;
}
export interface ApprovalRequestDetail extends ApprovalRequestSummary {
requester: {
actorId: string;
actorType: "agent" | "user" | "system";
actorName: string;
};
runId?: string;
requestedAt: string;
completedAt?: string;
targetAction: {
category: string;
action: string;
summary: string;
resourceType: string;
resourceId: string;
context?: Record<string, unknown>;
};
history: Array<{
id: string;
eventType: string;
actor: {
actorId: string;
actorType: "agent" | "user" | "system";
actorName: string;
};
note?: string;
createdAt: string;
}>;
}
export interface ApprovalListResponse {
requests: ApprovalRequestSummary[];
total: number;
pendingCount: number;
}
/** Fetch inbox messages for the current user. */
export function fetchInbox(
options?: { limit?: number; offset?: number; unreadOnly?: boolean; type?: MessageType },
@@ -7681,6 +7731,34 @@ export function fetchAgentMailbox(agentId: string, projectId?: string): Promise<
return api<AgentMailboxResponse>(withProjectId(`/agents/${encodeURIComponent(agentId)}/mailbox`, projectId));
}
export function fetchApprovals(
options?: { status?: ApprovalRequestStatus; limit?: number; offset?: number },
projectId?: string,
): Promise<ApprovalListResponse> {
const params = new URLSearchParams();
if (options?.status) params.set("status", options.status);
if (options?.limit !== undefined) params.set("limit", String(options.limit));
if (options?.offset !== undefined) params.set("offset", String(options.offset));
if (projectId) params.set("projectId", projectId);
const query = params.size > 0 ? `?${params.toString()}` : "";
return api<ApprovalListResponse>(`/approvals${query}`);
}
export function fetchApprovalDetail(id: string, projectId?: string): Promise<ApprovalRequestDetail> {
return api<ApprovalRequestDetail>(withProjectId(`/approvals/${encodeURIComponent(id)}`, projectId));
}
export function decideApproval(
id: string,
input: { decision: "approve" | "deny"; comment?: string },
projectId?: string,
): Promise<ApprovalRequestDetail> {
return api<ApprovalRequestDetail>(withProjectId(`/approvals/${encodeURIComponent(id)}/decision`, projectId), {
method: "POST",
body: JSON.stringify(input),
});
}
/** Fetch reflection history for an agent. */
export function fetchAgentReflections(agentId: string, limit?: number, projectId?: string): Promise<AgentReflection[]> {
const params = new URLSearchParams();