feat(FN-1511): apply skill selection to reviewer, merger, and heartbeat sessions

This commit is contained in:
gsxdsm
2026-04-14 08:24:18 -07:00
parent 4e217e493d
commit 383995a434
5 changed files with 65 additions and 0 deletions

View File

@@ -905,6 +905,7 @@ export class HeartbeatMonitor {
// Lazy-load createKbAgent and promptWithFallback
const { createKbAgent, promptWithFallback } = await import("./pi.js");
const { buildSessionSkillContextSync } = await import("./session-skill-context.js");
// Build tools with task creation tracking and run context for mutation correlation
const heartbeatTools = this.createHeartbeatTools(agentId, taskStore, taskId, runContext, audit);
@@ -916,6 +917,9 @@ export class HeartbeatMonitor {
agent: agent.role as AgentRole,
});
// Build skill selection context for heartbeat session (uses waking agent's skills, no role fallback)
const skillContext = buildSessionSkillContextSync(agent, "heartbeat", rootDir);
// Create agent session
const { session } = await createKbAgent({
cwd: rootDir,
@@ -939,6 +943,8 @@ export class HeartbeatMonitor {
toolCallCount++;
agentLogger?.onToolEnd(name, isError, result);
},
// Skill selection: use waking agent's skills (heartbeat has no role fallback)
...(skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
});
// Track for monitoring

View File

@@ -2339,6 +2339,7 @@ export class TaskExecutor {
validatorFallbackModelId: settings.validatorFallbackModelId,
store,
taskId,
task: detail,
agentPrompts: settings.agentPrompts,
agentStore: this.options.agentStore,
rootDir: this.rootDir,

View File

@@ -7,6 +7,7 @@ import { join } from "node:path";
import { getTaskMergeBlocker, type TaskStore, type MergeResult, type MergeDetails, type WorkflowStep, type WorkflowStepResult, type Settings, type AgentPromptsConfig } from "@fusion/core";
import { resolveAgentPrompt } from "@fusion/core";
import { createKbAgent, describeModel, promptWithFallback, compactSessionContext } from "./pi.js";
import { buildSessionSkillContext } from "./session-skill-context.js";
import type { WorktreePool } from "./worktree-pool.js";
import { AgentLogger } from "./agent-logger.js";
import { mergerLog } from "./logger.js";
@@ -2018,6 +2019,22 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
mergerInstructions,
);
// Build skill selection context (assigned agent skills take precedence over role fallback)
let skillContext = undefined;
if (options.agentStore) {
try {
const task = await store.getTask(taskId);
skillContext = await buildSessionSkillContext({
agentStore: options.agentStore,
task,
sessionPurpose: "merger",
projectRootDir: rootDir,
});
} catch {
// Graceful fallback - no skill selection
}
}
const { session } = await createKbAgent({
cwd: rootDir,
systemPrompt: mergerSystemPrompt,
@@ -2030,6 +2047,8 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
defaultProvider: settings.defaultProvider,
defaultModelId: settings.defaultModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
});
options.onSession?.(session);
@@ -2472,6 +2491,22 @@ If issues are found that need attention, describe them clearly.`;
}
const postMergeSystemPrompt = buildSystemPromptWithInstructions(systemPrompt, postMergeInstructions);
// Build skill selection context for post-merge session
let postMergeSkillContext = undefined;
if (mergeOptions.agentStore) {
try {
const task = await store.getTask(taskId);
postMergeSkillContext = await buildSessionSkillContext({
agentStore: mergeOptions.agentStore,
task,
sessionPurpose: "merger",
projectRootDir: rootDir,
});
} catch {
// Graceful fallback - no skill selection
}
}
const { session } = await createKbAgent({
cwd: rootDir,
systemPrompt: postMergeSystemPrompt,
@@ -2481,6 +2516,8 @@ If issues are found that need attention, describe them clearly.`;
fallbackProvider: settings.fallbackProvider,
fallbackModelId: settings.fallbackModelId,
defaultThinkingLevel: settings.defaultThinkingLevel,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {}),
});
mergerLog.log(`${taskId}: [post-merge] workflow step '${workflowStep.name}' using model ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);

View File

@@ -11,6 +11,7 @@
import type { TaskStore, TaskComment, AgentPromptsConfig } from "@fusion/core";
import { resolveAgentPrompt } from "@fusion/core";
import { createKbAgent, describeModel, promptWithFallback } from "./pi.js";
import { buildSessionSkillContext } from "./session-skill-context.js";
import { AgentLogger } from "./agent-logger.js";
import { reviewerLog } from "./logger.js";
import { checkSessionError } from "./usage-limit-detector.js";
@@ -208,6 +209,8 @@ export interface ReviewOptions {
store?: TaskStore;
/** Task ID for agent log persistence. Required alongside `store`. */
taskId?: string;
/** Task with optional assignedAgentId for skill selection. */
task?: { assignedAgentId?: string | null };
/** User comments on the task (author === "user"). For spec reviews, the reviewer explicitly checks that every comment is addressed. */
userComments?: TaskComment[];
/** Agent prompt configuration for resolving custom reviewer prompts. */
@@ -283,6 +286,21 @@ export async function reviewStep(
reviewerInstructions,
);
// Build skill selection context (assigned agent skills take precedence over role fallback)
let skillContext = undefined;
if (options.agentStore && options.rootDir) {
try {
skillContext = await buildSessionSkillContext({
agentStore: options.agentStore,
task: options.task ?? {},
sessionPurpose: "reviewer",
projectRootDir: options.rootDir,
});
} catch {
// Graceful fallback - no skill selection
}
}
// Spawn a reviewer agent with read-only tools
const { session } = await createKbAgent({
cwd,
@@ -297,6 +315,8 @@ export async function reviewStep(
fallbackProvider: validatorFallbackProvider,
fallbackModelId: validatorFallbackModelId,
defaultThinkingLevel: options.defaultThinkingLevel,
// Skill selection: use assigned agent skills if available, otherwise role fallback
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
});
reviewerLog.log(`${taskId}: reviewer using model ${describeModel(session)}`);

View File

@@ -1203,6 +1203,7 @@ export class TriageProcessor {
defaultThinkingLevel: currentSettings.defaultThinkingLevel,
store,
taskId,
task: currentDetail,
userComments: currentUserComments.length > 0 ? currentUserComments : undefined,
agentStore: this.options.agentStore,
rootDir,