FN-6622: wire dashboard interviews to session skills
Dashboard interview lanes now request the same role-fallback and enabled plugin skills as agent-acting sessions. - Add skillSelection to agent onboarding and milestone/slice interview agent creation. - Thread the plugin runner through mission interview and onboarding routes. - Cover enabled and disabled plugin skill selection behavior in dashboard tests. - Document the newly covered dashboard interview lanes and add a minor changeset. Files changed: .../fn-6622-session-skill-interview-lanes.md | 5 ++ docs/agents.md | 2 +- .../src/__tests__/agent-onboarding.test.ts | 68 ++++++++++++++++++++++ .../__tests__/milestone-slice-interview.test.ts | 66 +++++++++++++++++++++ packages/dashboard/src/agent-onboarding.ts | 10 +++- .../dashboard/src/milestone-slice-interview.ts | 39 ++++++++++--- packages/dashboard/src/mission-routes.ts | 10 ++-- ...gister-agent-import-export-generation-routes.ts | 3 +- 8 files changed, 187 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-6622 Fusion-Task-Lineage: 5e3dded1-1866-4b76-8302-253e5b3867fa
This commit is contained in:
5
.changeset/fn-6622-session-skill-interview-lanes.md
Normal file
5
.changeset/fn-6622-session-skill-interview-lanes.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
Load selected Fusion and enabled plugin skills in milestone/slice interview and agent-onboarding dashboard sessions.
|
||||
@@ -24,7 +24,7 @@ fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>]
|
||||
- Each message is stored as a `user-to-agent` MessageStore message from `cli` with `metadata.wakeRecipient=true`.
|
||||
- Agent replies are polled from your inbox and printed as they arrive.
|
||||
- Dashboard-created agent chat sessions request the target agent's declared `metadata.skills` plus enabled plugin-contributed skills, so skills such as `ce-debug` are available in chat when the contributing plugin is enabled. Model-only QuickChat sessions request enabled plugin skills, and room responder sessions request the responder agent's skills.
|
||||
- Agent-acting session lanes share the same skill-injection contract as executor sessions: executor, merger, triage, reviewer, heartbeat, step-session, dashboard chat/room responders, CLI agent execution, planning, mission interview, workflow design, memory dreams/insight extraction, and scheduled cron automation all request agent/fallback skills plus enabled plugin-contributed skills when a plugin runner is available. Utility-only lanes that only summarize/extract/generate JSON (title/PR summaries, memory compaction, subtask breakdown, text refinement, agent generation, PR metadata generation, evaluator/research synthesis, and similar one-shot helpers) intentionally stay exempt to avoid loading skills where no agent-style tool loop can use them.
|
||||
- Agent-acting session lanes share the same skill-injection contract as executor sessions: executor, merger, triage, reviewer, heartbeat, step-session, dashboard chat/room responders, CLI agent execution, planning, mission interview, milestone/slice interview, agent-onboarding interview, workflow design, memory dreams/insight extraction, and scheduled cron automation all request agent/fallback skills plus enabled plugin-contributed skills when a plugin runner is available. Utility-only lanes that only summarize/extract/generate JSON (title/PR summaries, memory compaction, subtask breakdown, text refinement, agent generation, PR metadata generation, evaluator/research synthesis, and similar one-shot helpers) intentionally stay exempt to avoid loading skills where no agent-style tool loop can use them.
|
||||
- In dashboard model-loop chat (main chat, QuickChat, and room responders), typing `/skill:{name}` requests that skill for the current AI session and strips the slash token from the prompt sent to the model. The requested skill is still subject to the normal enabled/disabled execution-skill filters; CLI-agent-backed PTY chat keeps raw terminal input semantics and does not interpret this command.
|
||||
|
||||
### Flags
|
||||
|
||||
@@ -6,6 +6,21 @@ const { mockCreateFnAgent } = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
listCliAdapterDescriptors: () => [],
|
||||
buildSessionSkillContextSync: (_agent: unknown, sessionPurpose: string, projectRootDir: string, pluginRunner?: { getPluginSkills?: () => Array<{ pluginId: string; skill: { name: string; enabled?: boolean } }> }) => {
|
||||
const requestedSkillNames = ["fusion"];
|
||||
for (const contribution of pluginRunner?.getPluginSkills?.() ?? []) {
|
||||
const name = contribution.skill.name.trim();
|
||||
if (contribution.skill.enabled === false || name.length === 0 || requestedSkillNames.includes(name)) {
|
||||
continue;
|
||||
}
|
||||
requestedSkillNames.push(name);
|
||||
}
|
||||
return {
|
||||
skillSelectionContext: { projectRootDir, requestedSkillNames, sessionPurpose },
|
||||
resolvedSkillNames: requestedSkillNames,
|
||||
skillSource: "role-fallback" as const,
|
||||
};
|
||||
},
|
||||
createFnAgent: mockCreateFnAgent,
|
||||
}));
|
||||
|
||||
@@ -47,6 +62,12 @@ async function waitFor(check: () => boolean, timeoutMs = 2000): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function createSkillPluginRunner(skills: Array<{ name: string; enabled?: boolean }>) {
|
||||
return {
|
||||
getPluginSkills: () => skills.map((skill) => ({ pluginId: "fusion-plugin-compound-engineering", skill })),
|
||||
};
|
||||
}
|
||||
|
||||
describe("agent-onboarding", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -246,6 +267,53 @@ describe("agent-onboarding", () => {
|
||||
expect(prompt).toContain("messageResponseMode: immediate");
|
||||
});
|
||||
|
||||
it("requests role-fallback and enabled plugin skills for model-only onboarding agents", async () => {
|
||||
mockCreateFnAgent.mockResolvedValueOnce(
|
||||
createMockAgent([
|
||||
JSON.stringify({
|
||||
type: "question",
|
||||
data: { id: "goal", type: "text", question: "What is the primary goal?" },
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await startAgentOnboardingSession(
|
||||
"127.0.0.1",
|
||||
{ intent: "skills", existingAgents: [], templates: [] },
|
||||
process.cwd(),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
createSkillPluginRunner([
|
||||
{ name: "ce-debug" },
|
||||
{ name: "disabled-skill", enabled: false },
|
||||
]),
|
||||
);
|
||||
|
||||
const options = mockCreateFnAgent.mock.calls.at(-1)?.[0] as { skillSelection?: { requestedSkillNames?: string[] } };
|
||||
expect(options.skillSelection?.requestedSkillNames).toEqual(["fusion", "ce-debug"]);
|
||||
});
|
||||
|
||||
it("requests role-fallback skills when onboarding plugin runner is unavailable", async () => {
|
||||
mockCreateFnAgent.mockResolvedValueOnce(
|
||||
createMockAgent([
|
||||
JSON.stringify({
|
||||
type: "question",
|
||||
data: { id: "goal", type: "text", question: "What is the primary goal?" },
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await startAgentOnboardingSession(
|
||||
"127.0.0.1",
|
||||
{ intent: "skills", existingAgents: [], templates: [] },
|
||||
process.cwd(),
|
||||
);
|
||||
|
||||
const options = mockCreateFnAgent.mock.calls.at(-1)?.[0] as { skillSelection?: { requestedSkillNames?: string[] } };
|
||||
expect(options.skillSelection?.requestedSkillNames).toEqual(["fusion"]);
|
||||
});
|
||||
|
||||
it("progresses through start -> question -> response -> final summary", async () => {
|
||||
mockCreateFnAgent.mockResolvedValueOnce(
|
||||
createMockAgent([
|
||||
|
||||
@@ -8,6 +8,21 @@ const { mockCreateFnAgent } = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
listCliAdapterDescriptors: () => [],
|
||||
buildSessionSkillContextSync: (_agent: unknown, sessionPurpose: string, projectRootDir: string, pluginRunner?: { getPluginSkills?: () => Array<{ pluginId: string; skill: { name: string; enabled?: boolean } }> }) => {
|
||||
const requestedSkillNames = ["fusion"];
|
||||
for (const contribution of pluginRunner?.getPluginSkills?.() ?? []) {
|
||||
const name = contribution.skill.name.trim();
|
||||
if (contribution.skill.enabled === false || name.length === 0 || requestedSkillNames.includes(name)) {
|
||||
continue;
|
||||
}
|
||||
requestedSkillNames.push(name);
|
||||
}
|
||||
return {
|
||||
skillSelectionContext: { projectRootDir, requestedSkillNames, sessionPurpose },
|
||||
resolvedSkillNames: requestedSkillNames,
|
||||
skillSource: "role-fallback" as const,
|
||||
};
|
||||
},
|
||||
createFnAgent: mockCreateFnAgent,
|
||||
}));
|
||||
|
||||
@@ -140,6 +155,23 @@ async function waitForCurrentQuestion(sessionId: string): Promise<void> {
|
||||
throw new Error("Timed out waiting for currentQuestion");
|
||||
}
|
||||
|
||||
async function waitForCreateFnAgentOptions(): Promise<{ skillSelection?: { requestedSkillNames?: string[] } }> {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const options = mockCreateFnAgent.mock.calls.at(-1)?.[0];
|
||||
if (options) {
|
||||
return options;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
}
|
||||
throw new Error("Timed out waiting for createFnAgent options");
|
||||
}
|
||||
|
||||
function createSkillPluginRunner(skills: Array<{ name: string; enabled?: boolean }>) {
|
||||
return {
|
||||
getPluginSkills: () => skills.map((skill) => ({ pluginId: "fusion-plugin-compound-engineering", skill })),
|
||||
};
|
||||
}
|
||||
|
||||
class MockAiSessionStore extends EventEmitter {
|
||||
rows = new Map<string, AiSessionRow>();
|
||||
|
||||
@@ -235,6 +267,40 @@ describe("milestone-slice-interview module", () => {
|
||||
});
|
||||
|
||||
describe("session lifecycle", () => {
|
||||
it("requests role-fallback and enabled plugin skills for model-only milestone/slice interview agents", async () => {
|
||||
await createTargetInterviewSession(
|
||||
"127.0.0.1",
|
||||
"milestone",
|
||||
"ms-skills",
|
||||
"Skillful Milestone",
|
||||
undefined,
|
||||
"/tmp/project",
|
||||
MOCK_TASK_STORE,
|
||||
createSkillPluginRunner([
|
||||
{ name: "ce-debug" },
|
||||
{ name: "disabled-skill", enabled: false },
|
||||
]),
|
||||
);
|
||||
|
||||
const options = await waitForCreateFnAgentOptions();
|
||||
expect(options.skillSelection?.requestedSkillNames).toEqual(["fusion", "ce-debug"]);
|
||||
});
|
||||
|
||||
it("requests role-fallback skills when milestone/slice plugin runner is unavailable", async () => {
|
||||
await createTargetInterviewSession(
|
||||
"127.0.0.1",
|
||||
"slice",
|
||||
"sl-skills",
|
||||
"Fallback Slice",
|
||||
undefined,
|
||||
"/tmp/project",
|
||||
MOCK_TASK_STORE,
|
||||
);
|
||||
|
||||
const options = await waitForCreateFnAgentOptions();
|
||||
expect(options.skillSelection?.requestedSkillNames).toEqual(["fusion"]);
|
||||
});
|
||||
|
||||
it("creates, retrieves, and cleans up a milestone session", async () => {
|
||||
const sessionId = await createTargetInterviewSession(
|
||||
"127.0.0.1",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { AgentCapability, PlanningQuestion } from "@fusion/core";
|
||||
import { resolvePrompt, type PromptOverrideMap } from "@fusion/core";
|
||||
import { createFnAgent as engineCreateFnAgent } from "@fusion/engine";
|
||||
import { buildSessionSkillContextSync, createFnAgent as engineCreateFnAgent } from "@fusion/engine";
|
||||
import { SessionEventBuffer, type SessionBufferedEvent } from "./sse-buffer.js";
|
||||
|
||||
export interface AgentOnboardingSummary {
|
||||
@@ -61,6 +61,7 @@ export type AgentOnboardingStreamEvent =
|
||||
export type AgentOnboardingStreamCallback = (event: AgentOnboardingStreamEvent, eventId?: number) => void;
|
||||
|
||||
const createFnAgent: typeof engineCreateFnAgent = engineCreateFnAgent;
|
||||
type SkillSelectionPluginRunner = Parameters<typeof buildSessionSkillContextSync>[3];
|
||||
const SESSION_TTL_MS = 30 * 60 * 1000;
|
||||
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
|
||||
const GENERATION_TIMEOUT_MS = 120_000;
|
||||
@@ -281,6 +282,7 @@ export async function startAgentOnboardingSession(
|
||||
modelProvider?: string,
|
||||
modelId?: string,
|
||||
promptOverrides?: PromptOverrideMap,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<string> {
|
||||
const id = randomUUID();
|
||||
const mode: OnboardingMode = initialContext.mode ?? "create";
|
||||
@@ -303,11 +305,17 @@ export async function startAgentOnboardingSession(
|
||||
sessions.set(id, session);
|
||||
|
||||
const systemPrompt = resolvePrompt("agent-onboarding-system", promptOverrides) || AGENT_ONBOARDING_SYSTEM_PROMPT;
|
||||
const skillContext = buildSessionSkillContextSync(null, "executor", rootDir, pluginRunner);
|
||||
session.agent = await createFnAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt,
|
||||
tools: "readonly",
|
||||
...(modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}),
|
||||
/*
|
||||
FNXC:InterviewSkills 2026-06-17-21:53:
|
||||
Agent onboarding is a model-only dashboard interview lane, so it must request executor role-fallback skills plus enabled plugin skills such as ce-debug like other agent-acting sessions.
|
||||
*/
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
onThinking: (delta: string) => {
|
||||
session.thinkingOutput += delta;
|
||||
agentOnboardingStreamManager.broadcast(session.id, { type: "thinking", data: delta });
|
||||
|
||||
@@ -96,11 +96,12 @@ function parseTargetInterviewResponseImpl(text: string): TargetInterviewResponse
|
||||
// Export the parse function for tests
|
||||
export { parseTargetInterviewResponseImpl as parseTargetInterviewResponse };
|
||||
|
||||
import { createFnAgent as engineCreateFnAgent } from "@fusion/engine";
|
||||
import { buildSessionSkillContextSync, createFnAgent as engineCreateFnAgent } from "@fusion/engine";
|
||||
import { createPlanningBoardTools } from "./planning-board-tools.js";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AgentResult = any;
|
||||
type SkillSelectionPluginRunner = Parameters<typeof buildSessionSkillContextSync>[3];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const createFnAgent: any = engineCreateFnAgent;
|
||||
|
||||
@@ -730,14 +731,21 @@ async function createTargetInterviewAgent(
|
||||
session: TargetInterviewSession,
|
||||
rootDir: string,
|
||||
store: TaskStore,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<AgentResult> {
|
||||
await ensureEngineReady();
|
||||
const skillContext = buildSessionSkillContextSync(null, "executor", rootDir, pluginRunner);
|
||||
|
||||
return createFnAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt: getSystemPrompt(session.targetType),
|
||||
tools: "readonly",
|
||||
customTools: [...createPlanningBoardTools(store)],
|
||||
/*
|
||||
FNXC:InterviewSkills 2026-06-17-21:42:
|
||||
Milestone and slice interview agents are model-only tool-loop sessions, so they must request executor role-fallback skills plus enabled plugin skills such as ce-debug instead of creating skill-less dashboard sessions.
|
||||
*/
|
||||
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||
onThinking: (delta: string) => {
|
||||
session.thinkingOutput += delta;
|
||||
persistThinking(session.id, session.thinkingOutput);
|
||||
@@ -787,6 +795,7 @@ async function ensureInterviewAgent(
|
||||
rootDir: string | undefined,
|
||||
store: TaskStore | undefined,
|
||||
historyForReplay: Array<{ question: PlanningQuestion; response: unknown }>,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<void> {
|
||||
if (session.agent) {
|
||||
return;
|
||||
@@ -804,7 +813,7 @@ async function ensureInterviewAgent(
|
||||
);
|
||||
}
|
||||
|
||||
session.agent = await createTargetInterviewAgent(session, rootDir, store);
|
||||
session.agent = await createTargetInterviewAgent(session, rootDir, store, pluginRunner);
|
||||
|
||||
if (historyForReplay.length === 0) {
|
||||
return;
|
||||
@@ -841,9 +850,14 @@ async function ensureInterviewAgent(
|
||||
/**
|
||||
* Initialize the AI agent for a session and start the first turn.
|
||||
*/
|
||||
async function initializeAgent(session: TargetInterviewSession, rootDir: string, store: TaskStore): Promise<void> {
|
||||
async function initializeAgent(
|
||||
session: TargetInterviewSession,
|
||||
rootDir: string,
|
||||
store: TaskStore,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<void> {
|
||||
try {
|
||||
session.agent = await createTargetInterviewAgent(session, rootDir, store);
|
||||
session.agent = await createTargetInterviewAgent(session, rootDir, store, pluginRunner);
|
||||
session.updatedAt = new Date();
|
||||
|
||||
// Send initial message to get first question
|
||||
@@ -1025,6 +1039,7 @@ export async function createTargetInterviewSession(
|
||||
missionContext: string | undefined,
|
||||
rootDir: string,
|
||||
store: TaskStore,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<string> {
|
||||
if (!checkRateLimit(ip)) {
|
||||
const resetTime = getRateLimitResetTime(ip);
|
||||
@@ -1054,7 +1069,7 @@ export async function createTargetInterviewSession(
|
||||
persistSession(session, "generating");
|
||||
|
||||
// Initialize AI agent in background
|
||||
initializeAgent(session, rootDir, store).catch((err) => {
|
||||
initializeAgent(session, rootDir, store, pluginRunner).catch((err) => {
|
||||
diagnostics.errorFromException("Failed to initialize agent for session", err, { sessionId, operation: "initialize-agent" });
|
||||
persistSession(session, "error", err.message || "Failed to initialize AI agent");
|
||||
milestoneSliceInterviewStreamManager.broadcast(sessionId, {
|
||||
@@ -1074,6 +1089,7 @@ export async function submitTargetInterviewResponse(
|
||||
responses: Record<string, unknown>,
|
||||
rootDir?: string,
|
||||
store?: TaskStore,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<TargetInterviewResponse> {
|
||||
const session = getTargetInterviewSession(sessionId);
|
||||
if (!session) {
|
||||
@@ -1095,7 +1111,7 @@ export async function submitTargetInterviewResponse(
|
||||
|
||||
if (!session.agent) {
|
||||
const replayHistory = session.history.slice(0, -1);
|
||||
await ensureInterviewAgent(session, rootDir, store, replayHistory);
|
||||
await ensureInterviewAgent(session, rootDir, store, replayHistory, pluginRunner);
|
||||
}
|
||||
|
||||
const message = formatResponseForAgent(session.currentQuestion, responses);
|
||||
@@ -1122,7 +1138,12 @@ export async function submitTargetInterviewResponse(
|
||||
/**
|
||||
* Retry a failed interview session.
|
||||
*/
|
||||
export async function retryTargetInterviewSession(sessionId: string, rootDir: string, store?: TaskStore): Promise<void> {
|
||||
export async function retryTargetInterviewSession(
|
||||
sessionId: string,
|
||||
rootDir: string,
|
||||
store?: TaskStore,
|
||||
pluginRunner?: SkillSelectionPluginRunner,
|
||||
): Promise<void> {
|
||||
const session = getTargetInterviewSession(sessionId);
|
||||
if (!session) {
|
||||
throw new TargetSessionNotFoundError(`Interview session ${sessionId} not found or expired`);
|
||||
@@ -1149,7 +1170,7 @@ export async function retryTargetInterviewSession(sessionId: string, rootDir: st
|
||||
persistSession(session, "generating");
|
||||
|
||||
if (session.history.length === 0) {
|
||||
await ensureInterviewAgent(session, rootDir, store, []);
|
||||
await ensureInterviewAgent(session, rootDir, store, [], pluginRunner);
|
||||
await continueAgentConversation(
|
||||
session,
|
||||
`I want to refine the scope for this ${session.targetType}: "${session.targetTitle}".` +
|
||||
@@ -1162,7 +1183,7 @@ export async function retryTargetInterviewSession(sessionId: string, rootDir: st
|
||||
const replayHistory = session.history.slice(0, -1);
|
||||
const lastEntry = session.history[session.history.length - 1];
|
||||
|
||||
await ensureInterviewAgent(session, rootDir, store, replayHistory);
|
||||
await ensureInterviewAgent(session, rootDir, store, replayHistory, pluginRunner);
|
||||
const replayMessage = formatResponseForAgent(
|
||||
lastEntry.question,
|
||||
coerceResponseRecord(lastEntry.question, lastEntry.response),
|
||||
|
||||
@@ -3294,6 +3294,7 @@ export function createMissionRouter(
|
||||
missionContext,
|
||||
rootDir,
|
||||
scopedStore,
|
||||
pluginRunner,
|
||||
);
|
||||
res.status(201).json({ sessionId });
|
||||
} catch (err: unknown) {
|
||||
@@ -3347,7 +3348,7 @@ export function createMissionRouter(
|
||||
|
||||
const { store: scopedStore } = await getProjectContext(req);
|
||||
const rootDir = scopedStore.getRootDir();
|
||||
const result = await submitTargetInterviewResponse(sessionId, responses, rootDir, scopedStore);
|
||||
const result = await submitTargetInterviewResponse(sessionId, responses, rootDir, scopedStore, pluginRunner);
|
||||
res.json(result);
|
||||
} catch (err: unknown) {
|
||||
const errName = err instanceof Error ? err.name : "";
|
||||
@@ -3511,7 +3512,7 @@ export function createMissionRouter(
|
||||
|
||||
const { store: scopedStore } = await getProjectContext(req);
|
||||
const rootDir = scopedStore.getRootDir();
|
||||
await retryTargetInterviewSession(sessionId, rootDir, scopedStore);
|
||||
await retryTargetInterviewSession(sessionId, rootDir, scopedStore, pluginRunner);
|
||||
res.json({ success: true, sessionId });
|
||||
} catch (err: unknown) {
|
||||
const errName = err instanceof Error ? err.name : "";
|
||||
@@ -3642,6 +3643,7 @@ export function createMissionRouter(
|
||||
missionContext,
|
||||
rootDir,
|
||||
scopedStore,
|
||||
pluginRunner,
|
||||
);
|
||||
res.status(201).json({ sessionId });
|
||||
} catch (err: unknown) {
|
||||
@@ -3695,7 +3697,7 @@ export function createMissionRouter(
|
||||
|
||||
const { store: scopedStore } = await getProjectContext(req);
|
||||
const rootDir = scopedStore.getRootDir();
|
||||
const result = await submitTargetInterviewResponse(sessionId, responses, rootDir, scopedStore);
|
||||
const result = await submitTargetInterviewResponse(sessionId, responses, rootDir, scopedStore, pluginRunner);
|
||||
res.json(result);
|
||||
} catch (err: unknown) {
|
||||
const errName = err instanceof Error ? err.name : "";
|
||||
@@ -3859,7 +3861,7 @@ export function createMissionRouter(
|
||||
|
||||
const { store: scopedStore } = await getProjectContext(req);
|
||||
const rootDir = scopedStore.getRootDir();
|
||||
await retryTargetInterviewSession(sessionId, rootDir, scopedStore);
|
||||
await retryTargetInterviewSession(sessionId, rootDir, scopedStore, pluginRunner);
|
||||
res.json({ success: true, sessionId });
|
||||
} catch (err: unknown) {
|
||||
const errName = err instanceof Error ? err.name : "";
|
||||
|
||||
@@ -875,7 +875,7 @@ async function persistImportedSkills(
|
||||
}
|
||||
|
||||
export function registerAgentGenerationRoutes(ctx: ApiRoutesContext): void {
|
||||
const { router, getProjectContext, rethrowAsApiError } = ctx;
|
||||
const { router, getProjectContext, rethrowAsApiError, options } = ctx;
|
||||
const agentGenerationDiagnostics = createSessionDiagnostics("agent-generation");
|
||||
|
||||
router.post("/agents/onboarding/start-streaming", async (req, res) => {
|
||||
@@ -922,6 +922,7 @@ export function registerAgentGenerationRoutes(ctx: ApiRoutesContext): void {
|
||||
planningModelProvider,
|
||||
planningModelId,
|
||||
settings.promptOverrides,
|
||||
options?.pluginRunner as Parameters<typeof import("@fusion/engine").buildSessionSkillContextSync>[3],
|
||||
);
|
||||
|
||||
res.status(201).json({ sessionId });
|
||||
|
||||
Reference in New Issue
Block a user