export interface AgentOfflineExportFlowOptions { driverKeys: string[]; chooseDestination: () => Promise; exportPackage: (destination: string, driverKeys: string[]) => Promise; } export interface AgentOfflineExportFlowResult { destination: string; result: TResult; } export interface AgentOfflineExportActionOptions extends AgentOfflineExportFlowOptions { setBusy: (busy: boolean) => void; onSuccess: (exported: AgentOfflineExportFlowResult) => void | Promise; onError: (error: unknown) => void | Promise; } export type AgentOfflineExportActionResult = "empty" | "cancelled" | "exported" | "failed"; /** * Keep destination selection and package creation in one testable workflow. * The caller owns the busy state so it can cover the native save dialog too. */ export async function runAgentOfflineExportFlow({ driverKeys, chooseDestination, exportPackage }: AgentOfflineExportFlowOptions): Promise | null> { if (driverKeys.length === 0) return null; const destination = await chooseDestination(); if (!destination) return null; return { destination, result: await exportPackage(destination, [...driverKeys]), }; } /** * Own the complete UI action lifecycle, including the native save dialog. * This keeps busy-state release and success/error side effects testable as one * contract instead of splitting them between the component and export helper. */ export async function runAgentOfflineExportAction({ driverKeys, chooseDestination, exportPackage, setBusy, onSuccess, onError }: AgentOfflineExportActionOptions): Promise { if (driverKeys.length === 0) return "empty"; setBusy(true); try { const exported = await runAgentOfflineExportFlow({ driverKeys, chooseDestination, exportPackage }); if (!exported) return "cancelled"; await onSuccess(exported); return "exported"; } catch (error) { await onError(error); return "failed"; } finally { setBusy(false); } }