feat(FN-3025): add agent onboarding flow with launch gating
- Add backend onboarding session service and legacy API routes for start, state, and import/export generation - Introduce experimental AgentOnboardingModal UI with stream-state handling, model selector wiring, and onboarding launch flow in AgentsView - Add onboarding gating via prompt overrides/store updates and include search behavior fix for partial-match filtering - Expand dashboard/core tests for onboarding API, modal behavior, agents/settings integration, and prompt override handling Fusion-Task-Id: FN-3025
This commit is contained in:
@@ -2456,6 +2456,30 @@ export type PlanningStreamEvent =
|
||||
| { type: "error"; data: string }
|
||||
| { type: "complete"; data: Record<string, never> };
|
||||
|
||||
export interface AgentOnboardingSummary {
|
||||
name: string;
|
||||
role: AgentCapability | "custom";
|
||||
instructionsText: string;
|
||||
thinkingLevel: "off" | "minimal" | "low" | "medium" | "high";
|
||||
maxTurns: number;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
reportsTo?: string;
|
||||
soul?: string;
|
||||
memory?: string;
|
||||
skills?: string[];
|
||||
templateId?: string;
|
||||
patternAgentId?: string;
|
||||
rationale?: string;
|
||||
}
|
||||
|
||||
export type AgentOnboardingStreamEvent =
|
||||
| { type: "thinking"; data: string }
|
||||
| { type: "question"; data: PlanningQuestion }
|
||||
| { type: "summary"; data: AgentOnboardingSummary }
|
||||
| { type: "error"; data: string }
|
||||
| { type: "complete"; data: Record<string, never> };
|
||||
|
||||
/** Start a new planning session with an initial plan */
|
||||
export function startPlanning(initialPlan: string, projectId?: string): Promise<PlanningSession> {
|
||||
return api<PlanningSession>(withProjectId("/planning/start", projectId), {
|
||||
@@ -2531,6 +2555,56 @@ export function cancelPlanning(sessionId: string, projectId?: string, tabId?: st
|
||||
});
|
||||
}
|
||||
|
||||
export function startAgentOnboardingStreaming(
|
||||
intent: string,
|
||||
context: {
|
||||
existingAgents: Array<{ id: string; name: string; role: string }>;
|
||||
templates: Array<{ id: string; label: string; description?: string }>;
|
||||
},
|
||||
projectId?: string,
|
||||
modelOverride?: { planningModelProvider?: string; planningModelId?: string },
|
||||
): Promise<{ sessionId: string }> {
|
||||
return api<{ sessionId: string }>(withProjectId("/agents/onboarding/start-streaming", projectId), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
intent,
|
||||
context,
|
||||
planningModelProvider: modelOverride?.planningModelProvider,
|
||||
planningModelId: modelOverride?.planningModelId,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function respondToAgentOnboarding(
|
||||
sessionId: string,
|
||||
responses: Record<string, unknown>,
|
||||
projectId?: string,
|
||||
): Promise<{ type: "question" | "complete"; data: PlanningQuestion | AgentOnboardingSummary }> {
|
||||
return api(withProjectId("/agents/onboarding/respond", projectId), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ sessionId, responses }),
|
||||
});
|
||||
}
|
||||
|
||||
export function retryAgentOnboardingSession(sessionId: string, projectId?: string): Promise<{ success: boolean; sessionId: string }> {
|
||||
return api(withProjectId(`/agents/onboarding/${encodeURIComponent(sessionId)}/retry`, projectId), {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export function stopAgentOnboardingGeneration(sessionId: string, projectId?: string): Promise<{ success: boolean }> {
|
||||
return api(withProjectId(`/agents/onboarding/${encodeURIComponent(sessionId)}/stop`, projectId), {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export function cancelAgentOnboarding(sessionId: string, projectId?: string): Promise<void> {
|
||||
return api(withProjectId("/agents/onboarding/cancel", projectId), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ sessionId }),
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a task from a completed planning session */
|
||||
export function createTaskFromPlanning(
|
||||
sessionId: string,
|
||||
@@ -3613,6 +3687,63 @@ export function getPlanningStreamUrl(sessionId: string, projectId?: string): str
|
||||
return buildApiUrl(withProjectId(`/planning/${encodeURIComponent(sessionId)}/stream`, projectId));
|
||||
}
|
||||
|
||||
export function getAgentOnboardingStreamUrl(sessionId: string, projectId?: string): string {
|
||||
return buildApiUrl(withProjectId(`/agents/onboarding/${encodeURIComponent(sessionId)}/stream`, projectId));
|
||||
}
|
||||
|
||||
export function connectAgentOnboardingStream(
|
||||
sessionId: string,
|
||||
projectId: string | undefined,
|
||||
handlers: {
|
||||
onThinking?: (data: string) => void;
|
||||
onQuestion?: (data: PlanningQuestion) => void;
|
||||
onSummary?: (data: AgentOnboardingSummary) => void;
|
||||
onError?: (data: string) => void;
|
||||
onComplete?: () => void;
|
||||
onConnectionStateChange?: (state: StreamConnectionState) => void;
|
||||
},
|
||||
options?: { maxReconnectAttempts?: number },
|
||||
): { close: () => void; isConnected: () => boolean } {
|
||||
const url = getAgentOnboardingStreamUrl(sessionId, projectId);
|
||||
const resilient = createResilientEventSource(
|
||||
url,
|
||||
{
|
||||
events: {
|
||||
thinking: (event) => {
|
||||
try { handlers.onThinking?.(JSON.parse(event.data)); } catch { handlers.onThinking?.(event.data); }
|
||||
},
|
||||
question: (event) => {
|
||||
try { handlers.onQuestion?.(JSON.parse(event.data) as PlanningQuestion); } catch {}
|
||||
},
|
||||
summary: (event) => {
|
||||
try { handlers.onSummary?.(JSON.parse(event.data) as AgentOnboardingSummary); } catch {}
|
||||
},
|
||||
error: (event) => {
|
||||
try {
|
||||
const parsed = JSON.parse(event.data);
|
||||
handlers.onError?.(parsed.message || parsed);
|
||||
} catch {
|
||||
handlers.onError?.(event.data || "Stream error");
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
handlers.onComplete?.();
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
maxReconnectAttempts: options?.maxReconnectAttempts,
|
||||
onConnectionStateChange: handlers.onConnectionStateChange,
|
||||
onFatalError: (message) => handlers.onError?.(message),
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
close: resilient.close,
|
||||
isConnected: resilient.isConnected,
|
||||
};
|
||||
}
|
||||
|
||||
/** Connect to planning session SSE stream and handle events
|
||||
*
|
||||
* Returns an object with:
|
||||
|
||||
Reference in New Issue
Block a user