refactor(FN-2161): standardize on createFnAgent naming
- Rename engine export and call sites to use createFnAgent consistently across runtime flows - Update core lazy engine loader and dashboard agent-generation/planning/chat paths to reference createFnAgent - Refresh affected unit and integration tests, including renaming pi-create-kb-agent.test.ts to pi-create-fn-agent.test.ts - Update AGENTS.md documentation references to match the new createFnAgent name
This commit is contained in:
@@ -30,16 +30,16 @@ vi.mock("./logger.js", () => {
|
||||
|
||||
// Mock pi.ts for executeHeartbeat tests
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
promptWithFallback: vi.fn(async (session: any, prompt: string) => {
|
||||
await session.prompt(prompt);
|
||||
}),
|
||||
}));
|
||||
|
||||
// Import the mocked functions for test control
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { createFnAgent } from "./pi.js";
|
||||
import { heartbeatLog } from "./logger.js";
|
||||
const mockedCreateKbAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateKbAgent = vi.mocked(createFnAgent);
|
||||
|
||||
// Mock store factory
|
||||
function createMockStore(overrides: Partial<AgentStore> = {}): AgentStore {
|
||||
@@ -1198,7 +1198,7 @@ describe("HeartbeatMonitor", () => {
|
||||
let mockTaskStore: TaskStore;
|
||||
let mockAgent: Agent;
|
||||
|
||||
// Helper: create a mock session returned by createKbAgent
|
||||
// Helper: create a mock session returned by createFnAgent
|
||||
function createMockAgentSession() {
|
||||
return {
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -2722,7 +2722,7 @@ describe("HeartbeatMonitor", () => {
|
||||
expect(taskLogTool.name).toBe("task_log");
|
||||
});
|
||||
|
||||
it("passes model config from agent runtimeConfig to createKbAgent", async () => {
|
||||
it("passes model config from agent runtimeConfig to createFnAgent", async () => {
|
||||
const store = createStoreWithAgentForExec({
|
||||
runtimeConfig: { modelProvider: "openai", modelId: "gpt-4o" },
|
||||
});
|
||||
@@ -2898,7 +2898,7 @@ describe("HeartbeatMonitor", () => {
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("completes run as failed when createKbAgent throws", async () => {
|
||||
it("completes run as failed when createFnAgent throws", async () => {
|
||||
const store = createStoreWithAgentForExec();
|
||||
mockedCreateKbAgent.mockRejectedValue(new Error("Model unavailable"));
|
||||
|
||||
@@ -4561,10 +4561,10 @@ describe("HeartbeatTriggerScheduler", () => {
|
||||
describe("executeHeartbeat — skill selection resolver contract (FN-1510/FN-1511)", () => {
|
||||
// We need to test the skill selection contract without affecting other tests.
|
||||
// Since buildSessionSkillContextSync is called via dynamic import inside executeHeartbeat,
|
||||
// we need to test the integration at a higher level - verifying that createKbAgent
|
||||
// we need to test the integration at a higher level - verifying that createFnAgent
|
||||
// receives the skillSelection option when agent has skills.
|
||||
|
||||
// Helper: create a mock session returned by createKbAgent
|
||||
// Helper: create a mock session returned by createFnAgent
|
||||
function createMockAgentSession() {
|
||||
return {
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -4690,11 +4690,11 @@ describe("executeHeartbeat — skill selection resolver contract (FN-1510/FN-151
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
// These tests verify the skill selection contract at the createKbAgent level.
|
||||
// These tests verify the skill selection contract at the createFnAgent level.
|
||||
// Since we can't easily mock dynamic imports, we verify that when an agent has
|
||||
// skills in metadata, the createKbAgent is called and the result includes skill info.
|
||||
// skills in metadata, the createFnAgent is called and the result includes skill info.
|
||||
|
||||
it("createKbAgent is called with agent session for heartbeat with skills", async () => {
|
||||
it("createFnAgent is called with agent session for heartbeat with skills", async () => {
|
||||
mockedCreateKbAgent.mockResolvedValue({
|
||||
session: createMockAgentSession(),
|
||||
} as any);
|
||||
@@ -4711,7 +4711,7 @@ describe("executeHeartbeat — skill selection resolver contract (FN-1510/FN-151
|
||||
expect(result.status).toBe("completed");
|
||||
});
|
||||
|
||||
it("createKbAgent is called with correct cwd for skill resolution", async () => {
|
||||
it("createFnAgent is called with correct cwd for skill resolution", async () => {
|
||||
mockedCreateKbAgent.mockResolvedValue({
|
||||
session: createMockAgentSession(),
|
||||
} as any);
|
||||
@@ -4751,7 +4751,7 @@ describe("executeHeartbeat — skill selection resolver contract (FN-1510/FN-151
|
||||
});
|
||||
|
||||
describe("executeHeartbeat — skill selection non-fatal (FN-1510/FN-1511)", () => {
|
||||
// Helper: create a mock session returned by createKbAgent
|
||||
// Helper: create a mock session returned by createFnAgent
|
||||
function createMockAgentSession() {
|
||||
return {
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
|
||||
@@ -1058,8 +1058,8 @@ export class HeartbeatMonitor {
|
||||
},
|
||||
};
|
||||
|
||||
// Lazy-load createKbAgent and promptWithFallback
|
||||
const { createKbAgent, promptWithFallback } = await import("./pi.js");
|
||||
// Lazy-load createFnAgent and promptWithFallback
|
||||
const { createFnAgent, 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
|
||||
@@ -1135,7 +1135,7 @@ export class HeartbeatMonitor {
|
||||
}
|
||||
|
||||
// Create agent session
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt,
|
||||
tools: "readonly",
|
||||
|
||||
@@ -59,12 +59,12 @@ export interface AgentLoggerOptions {
|
||||
* detailed argument summaries via {@link summarizeToolArgs}.
|
||||
*
|
||||
* Produces `onText` and `onToolStart` callbacks compatible with
|
||||
* `createKbAgent`'s `AgentOptions` interface.
|
||||
* `createFnAgent`'s `AgentOptions` interface.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const logger = new AgentLogger({ store, taskId, onAgentText, onAgentTool });
|
||||
* const { session } = await createKbAgent({
|
||||
* const { session } = await createFnAgent({
|
||||
* cwd: worktreePath,
|
||||
* onText: logger.onText,
|
||||
* onToolStart: logger.onToolStart,
|
||||
|
||||
@@ -12,15 +12,15 @@ import type {
|
||||
} from "@fusion/core";
|
||||
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
promptWithFallback: vi.fn(),
|
||||
}));
|
||||
|
||||
import { createKbAgent, promptWithFallback } from "./pi.js";
|
||||
import { createFnAgent, promptWithFallback } from "./pi.js";
|
||||
import { AgentReflectionService } from "./agent-reflection.js";
|
||||
import { createReflectOnPerformanceTool, reflectOnPerformanceParams } from "./agent-tools.js";
|
||||
|
||||
const mockedCreateKbAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateKbAgent = vi.mocked(createFnAgent);
|
||||
const mockedPromptWithFallback = vi.mocked(promptWithFallback);
|
||||
|
||||
function makeAgent(overrides: Partial<Agent> = {}): Agent {
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
TaskStore,
|
||||
} from "@fusion/core";
|
||||
import { createLogger } from "./logger.js";
|
||||
import { createKbAgent, promptWithFallback } from "./pi.js";
|
||||
import { createFnAgent, promptWithFallback } from "./pi.js";
|
||||
|
||||
const reflectionLog = createLogger("reflection");
|
||||
|
||||
@@ -97,7 +97,7 @@ export class AgentReflectionService {
|
||||
}
|
||||
|
||||
let responseText = "";
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: REFLECTION_SYSTEM_PROMPT,
|
||||
tools: "readonly",
|
||||
|
||||
@@ -11,7 +11,7 @@ const cronLoggerSpies = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
const piModuleMocks = vi.hoisted(() => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
promptWithFallback: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -24,7 +24,7 @@ vi.mock("./logger.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: piModuleMocks.createKbAgent,
|
||||
createFnAgent: piModuleMocks.createFnAgent,
|
||||
promptWithFallback: piModuleMocks.promptWithFallback,
|
||||
}));
|
||||
|
||||
@@ -99,7 +99,7 @@ describe("CronRunner", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
piModuleMocks.promptWithFallback.mockResolvedValue(undefined);
|
||||
piModuleMocks.createKbAgent.mockResolvedValue({
|
||||
piModuleMocks.createFnAgent.mockResolvedValue({
|
||||
session: {
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
@@ -145,7 +145,7 @@ describe("CronRunner", () => {
|
||||
|
||||
describe("createAiPromptExecutor", () => {
|
||||
it("returns response text even when session disposal throws", async () => {
|
||||
piModuleMocks.createKbAgent.mockImplementation(async (options: { onText?: (delta: string) => void }) => {
|
||||
piModuleMocks.createFnAgent.mockImplementation(async (options: { onText?: (delta: string) => void }) => {
|
||||
options.onText?.("hello ");
|
||||
options.onText?.("world");
|
||||
return {
|
||||
|
||||
@@ -618,7 +618,7 @@ const AI_AUTOMATION_SYSTEM_PROMPT = [
|
||||
].join("\n");
|
||||
|
||||
/**
|
||||
* Create an AiPromptExecutor that uses createKbAgent for real AI execution.
|
||||
* Create an AiPromptExecutor that uses createFnAgent for real AI execution.
|
||||
*
|
||||
* Each call creates a fresh agent session, runs the prompt, collects the
|
||||
* text response, and disposes the session.
|
||||
@@ -629,13 +629,13 @@ const AI_AUTOMATION_SYSTEM_PROMPT = [
|
||||
export async function createAiPromptExecutor(cwd: string): Promise<AiPromptExecutor> {
|
||||
// We import lazily to keep the factory self-contained and to avoid
|
||||
// pulling pi.ts into the module graph when AI execution isn't used.
|
||||
const { createKbAgent, promptWithFallback } = await import("./pi.js");
|
||||
const { createFnAgent, promptWithFallback } = await import("./pi.js");
|
||||
const disposeLog = createLogger("cron-runner");
|
||||
|
||||
return async (prompt: string, modelProvider?: string, modelId?: string): Promise<string> => {
|
||||
let responseText = "";
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd,
|
||||
systemPrompt: AI_AUTOMATION_SYSTEM_PROMPT,
|
||||
tools: "readonly",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { detectReviewHandoffIntent } from "./executor.js";
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
describeModel: vi.fn().mockReturnValue("mock-provider/mock-model"),
|
||||
compactSessionContext: vi.fn(async (session, instructions) => {
|
||||
// Delegate to session.compact if available (supports loop recovery tests)
|
||||
@@ -165,7 +165,7 @@ vi.mock("@mariozechner/pi-coding-agent", () => {
|
||||
});
|
||||
|
||||
import { TaskExecutor, buildExecutionPrompt } from "./executor.js";
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { createFnAgent } from "./pi.js";
|
||||
import { reviewStep as mockedReviewStepFn } from "./reviewer.js";
|
||||
import { execSync } from "node:child_process";
|
||||
import { findWorktreeUser, aiMergeTask } from "./merger.js";
|
||||
@@ -177,7 +177,7 @@ import { StepSessionExecutor } from "./step-session-executor.js";
|
||||
import { executorLog } from "./logger.js";
|
||||
import { withRateLimitRetry } from "./rate-limit-retry.js";
|
||||
|
||||
const mockedCreateHaiAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateHaiAgent = vi.mocked(createFnAgent);
|
||||
const mockedSessionManager = vi.mocked(SessionManager);
|
||||
const mockedGenerateWorktreeName = vi.mocked(generateWorktreeName);
|
||||
const mockedFindWorktreeUser = vi.mocked(findWorktreeUser);
|
||||
@@ -3151,7 +3151,7 @@ describe("TaskExecutor pause behavior", () => {
|
||||
// Should use SessionManager.open for the initial resumed execution
|
||||
expect(mockedSessionManager.open).toHaveBeenCalledWith(sessionFilePath);
|
||||
|
||||
// The first createKbAgent call should use the opened session manager
|
||||
// The first createFnAgent call should use the opened session manager
|
||||
const firstCall = mockedCreateHaiAgent.mock.calls[0][0] as any;
|
||||
expect(firstCall.sessionManager).toBeDefined();
|
||||
|
||||
@@ -4032,7 +4032,7 @@ describe("TaskExecutor enginePaused soft pause (no agent termination)", () => {
|
||||
const mockedReviewStep = vi.mocked(mockedReviewStepFn);
|
||||
|
||||
/**
|
||||
* Helper: executes a task and captures the custom tools passed to createKbAgent.
|
||||
* Helper: executes a task and captures the custom tools passed to createFnAgent.
|
||||
* Returns a map of tool name → tool execute function for direct testing.
|
||||
*/
|
||||
async function captureTools(): Promise<Record<string, (id: string, params: any) => Promise<any>>> {
|
||||
@@ -4262,7 +4262,7 @@ describe("Code review verdict enforcement - task_update blocking", () => {
|
||||
});
|
||||
|
||||
it("EXECUTOR_SYSTEM_PROMPT contains code review enforcement language", async () => {
|
||||
// Capture the system prompt passed to createKbAgent
|
||||
// Capture the system prompt passed to createFnAgent
|
||||
let capturedSystemPrompt = "";
|
||||
mockedCreateHaiAgent.mockImplementation(async (opts: any) => {
|
||||
capturedSystemPrompt = opts.systemPrompt || "";
|
||||
@@ -4340,7 +4340,7 @@ describe("RETHINK verdict handling", () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: run executor and capture custom tools from createKbAgent mock.
|
||||
* Helper: run executor and capture custom tools from createFnAgent mock.
|
||||
* Returns the tools map keyed by tool name.
|
||||
*/
|
||||
async function captureRethinkTools(store: any, options?: any) {
|
||||
@@ -6985,7 +6985,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// createKbAgent called twice: main agent + workflow step agent
|
||||
// createFnAgent called twice: main agent + workflow step agent
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Second call should be the workflow step with readonly tools
|
||||
@@ -7207,7 +7207,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Should only call createKbAgent once (main execution), skip workflow step
|
||||
// Should only call createFnAgent once (main execution), skip workflow step
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Should log that it was skipped
|
||||
@@ -7340,7 +7340,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// createKbAgent called twice: main agent + workflow step agent
|
||||
// createFnAgent called twice: main agent + workflow step agent
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Second call should use the workflow step's model override
|
||||
@@ -7510,7 +7510,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Should only call createKbAgent once (main execution — no agent for script mode)
|
||||
// Should only call createFnAgent once (main execution — no agent for script mode)
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Should log script execution
|
||||
@@ -7868,7 +7868,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Should only call createKbAgent once (main execution)
|
||||
// Should only call createFnAgent once (main execution)
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Should log that it was skipped
|
||||
@@ -7957,7 +7957,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// createKbAgent called twice: main agent + workflow step agent (prompt mode)
|
||||
// createFnAgent called twice: main agent + workflow step agent (prompt mode)
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Second call should use prompt mode (readonly tools, agent-based)
|
||||
@@ -8064,7 +8064,7 @@ describe("Workflow Steps Execution", () => {
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// createKbAgent called twice: main agent + 1 pre-merge step (post-merge skipped)
|
||||
// createFnAgent called twice: main agent + 1 pre-merge step (post-merge skipped)
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Verify the workflow step results only contain pre-merge
|
||||
@@ -9302,7 +9302,7 @@ describe("Agent Spawning", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("creates child agent session via createKbAgent", async () => {
|
||||
it("creates child agent session via createFnAgent", async () => {
|
||||
const agentStore = createMockAgentStore();
|
||||
const { tools } = await captureToolsWithAgentStore(agentStore);
|
||||
|
||||
@@ -9312,7 +9312,7 @@ describe("Agent Spawning", () => {
|
||||
task: "Do some work",
|
||||
});
|
||||
|
||||
// createKbAgent is called at least twice: once for parent, once for child
|
||||
// createFnAgent is called at least twice: once for parent, once for child
|
||||
expect(mockedCreateHaiAgent.mock.calls.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Find the child session call
|
||||
@@ -10039,7 +10039,7 @@ describe("TaskExecutor agent execution flow (FN-978)", () => {
|
||||
let capturedOnText: ((delta: string) => void) | undefined;
|
||||
|
||||
mockedCreateHaiAgent.mockImplementation(async (opts: any) => {
|
||||
// Capture the onText callback that's passed to createKbAgent
|
||||
// Capture the onText callback that's passed to createFnAgent
|
||||
capturedOnText = opts.onText;
|
||||
return {
|
||||
session: {
|
||||
@@ -10169,7 +10169,7 @@ describe("TaskExecutor agent execution flow (FN-978)", () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// The executing guard prevents duplicate execution from the event handler.
|
||||
// Note: createKbAgent may be called a second time if the agent finishes
|
||||
// Note: createFnAgent may be called a second time if the agent finishes
|
||||
// without calling task_done (retry path), but the initial trigger should
|
||||
// only cause one execution, not two.
|
||||
// Verify that store.on was called with task:moved (listener registered)
|
||||
@@ -10286,7 +10286,7 @@ describe("StepSessionExecutor integration", () => {
|
||||
expect(mockedStepSessionExecutor).toHaveBeenCalled();
|
||||
// executeAll should have been called
|
||||
expect(mockExecuteAll).toHaveBeenCalledOnce();
|
||||
// createKbAgent should NOT have been called for step-session path
|
||||
// createFnAgent should NOT have been called for step-session path
|
||||
expect(mockedCreateHaiAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -10945,7 +10945,7 @@ describe("TaskExecutor skillSelection regression (FN-1511)", () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper: execute a task and capture createKbAgent call arguments.
|
||||
* Helper: execute a task and capture createFnAgent call arguments.
|
||||
*/
|
||||
async function captureCreateKbAgentArgs(options?: {
|
||||
assignedAgentId?: string;
|
||||
@@ -11019,7 +11019,7 @@ describe("TaskExecutor skillSelection regression (FN-1511)", () => {
|
||||
}
|
||||
|
||||
describe("single-session mode (runStepsInNewSessions: false)", () => {
|
||||
it("passes skillSelection to createKbAgent when assigned agent has skills", async () => {
|
||||
it("passes skillSelection to createFnAgent when assigned agent has skills", async () => {
|
||||
const args = await captureCreateKbAgentArgs({
|
||||
assignedAgentId: "agent-001",
|
||||
assignedAgentSkills: ["triage", "executor"],
|
||||
@@ -11085,7 +11085,7 @@ describe("TaskExecutor skillSelection regression (FN-1511)", () => {
|
||||
// Note: These tests verify that skillSelection flows from executor to
|
||||
// StepSessionExecutor. The full integration is complex due to mock setup,
|
||||
// so we verify the contract indirectly through the step-session-executor tests.
|
||||
// The executor tests focus on verifying skillSelection is present in createKbAgent calls.
|
||||
// The executor tests focus on verifying skillSelection is present in createFnAgent calls.
|
||||
// See StepSessionExecutor skillSelection tests in step-session-executor.test.ts.
|
||||
|
||||
// Skipped: Integration tests for step-session skill selection are covered
|
||||
@@ -11114,7 +11114,7 @@ describe("TaskExecutor messaging tools", () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper: execute a task and capture the customTools array passed to createKbAgent.
|
||||
* Helper: execute a task and capture the customTools array passed to createFnAgent.
|
||||
*/
|
||||
async function captureCustomTools(options?: {
|
||||
messageStore?: unknown;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { buildExecutionMemoryInstructions, getTaskMergeBlocker, resolveAgentProm
|
||||
import { findWorktreeUser } from "./merger.js";
|
||||
import { generateWorktreeName, slugify } from "./worktree-names.js";
|
||||
import { Type, type Static } from "@mariozechner/pi-ai";
|
||||
import { createKbAgent, describeModel, promptWithFallback, compactSessionContext } from "./pi.js";
|
||||
import { createFnAgent, describeModel, promptWithFallback, compactSessionContext } from "./pi.js";
|
||||
import { buildSessionSkillContext } from "./session-skill-context.js";
|
||||
import { reviewStep, type ReviewVerdict } from "./reviewer.js";
|
||||
import { ModelRegistry, SessionManager, type ToolDefinition, type AgentSession } from "@mariozechner/pi-coding-agent";
|
||||
@@ -1562,7 +1562,7 @@ export class TaskExecutor {
|
||||
const codeReviewVerdicts = new Map<number, ReviewVerdict>();
|
||||
|
||||
let wasPaused = false;
|
||||
// Mutable ref — populated after createKbAgent, tools access lazily via closure
|
||||
// Mutable ref — populated after createFnAgent, tools access lazily via closure
|
||||
const sessionRef: { current: AgentSession | null } = { current: null };
|
||||
const stepCheckpoints = new Map<number, string>();
|
||||
|
||||
@@ -1665,7 +1665,7 @@ export class TaskExecutor {
|
||||
|
||||
// sessionFile must be let because it's destructured alongside session which is reassigned
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { session, sessionFile } = await createKbAgent({
|
||||
let { session, sessionFile } = await createFnAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: executorSystemPrompt,
|
||||
tools: "coding",
|
||||
@@ -1895,7 +1895,7 @@ export class TaskExecutor {
|
||||
this.activeSessions.delete(task.id);
|
||||
session.dispose();
|
||||
|
||||
const { session: retrySession, sessionFile: retrySessionFile } = await createKbAgent({
|
||||
const { session: retrySession, sessionFile: retrySessionFile } = await createFnAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: executorSystemPrompt,
|
||||
tools: "coding",
|
||||
@@ -3413,7 +3413,7 @@ and show an appropriate message to the user.\`
|
||||
projectRootDir: this.rootDir,
|
||||
});
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: stepSystemPrompt,
|
||||
tools: toolMode,
|
||||
@@ -4311,7 +4311,7 @@ and show an appropriate message to the user.\`
|
||||
});
|
||||
|
||||
// Create child agent session
|
||||
const { session: childSession } = await createKbAgent({
|
||||
const { session: childSession } = await createFnAgent({
|
||||
cwd: childWorktreePath,
|
||||
systemPrompt: childSystemPrompt,
|
||||
tools: "coding",
|
||||
|
||||
@@ -17,7 +17,7 @@ export { MissionAutopilot, type MissionAutopilotOptions } from "./mission-autopi
|
||||
export { MissionExecutionLoop, type MissionExecutionLoopOptions, type ValidationResult, loopLog } from "./mission-execution-loop.js";
|
||||
export { aiMergeTask, type MergerOptions } from "./merger.js";
|
||||
export { reviewStep, type ReviewType, type ReviewVerdict, type ReviewResult, type ReviewOptions } from "./reviewer.js";
|
||||
export { createKbAgent, promptWithFallback, type AgentOptions, type AgentResult } from "./pi.js";
|
||||
export { createFnAgent, promptWithFallback, type AgentOptions, type AgentResult } from "./pi.js";
|
||||
export {
|
||||
resolveSessionSkills,
|
||||
createSkillsOverrideFromSelection,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
promptWithFallback: vi.fn(async (session, prompt, options) => {
|
||||
if (options === undefined) {
|
||||
await session.prompt(prompt);
|
||||
@@ -113,11 +113,11 @@ import {
|
||||
type ConflictCategory,
|
||||
} from "./merger.js";
|
||||
import { mergerLog } from "./logger.js";
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { createFnAgent } from "./pi.js";
|
||||
import { execSync } from "node:child_process";
|
||||
import { type TaskStore, type Task, type MergeResult, DEFAULT_SETTINGS } from "@fusion/core";
|
||||
|
||||
const mockedCreateHaiAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateHaiAgent = vi.mocked(createFnAgent);
|
||||
const mockedExecSync = vi.mocked(execSync);
|
||||
const { existsSync: mockedExistsSyncRaw, readFileSync: mockedReadFileSyncRaw } = await import("node:fs");
|
||||
const mockedExistsSync = vi.mocked(mockedExistsSyncRaw);
|
||||
@@ -925,7 +925,7 @@ describe("aiMergeTask — model settings threading", () => {
|
||||
} as any);
|
||||
});
|
||||
|
||||
it("passes defaultProvider and defaultModelId from settings to createKbAgent", async () => {
|
||||
it("passes defaultProvider and defaultModelId from settings to createFnAgent", async () => {
|
||||
const store = createMockStore(
|
||||
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||
@@ -3771,7 +3771,7 @@ describe("aiMergeTask — post-merge workflow steps", () => {
|
||||
await aiMergeTask(store, "/tmp/root", "FN-050");
|
||||
|
||||
// getWorkflowStep may be called but pre-merge steps should not trigger agent creation
|
||||
// beyond the merge agent itself. We verify createKbAgent was called only once (merge agent)
|
||||
// beyond the merge agent itself. We verify createFnAgent was called only once (merge agent)
|
||||
// since pre-merge steps are skipped in the merger
|
||||
const mergeAgentCalls = mockedCreateHaiAgent.mock.calls.filter(
|
||||
(c: any) => c[0]?.systemPrompt?.includes("You are a merge agent")
|
||||
@@ -4169,7 +4169,7 @@ describe("aiMergeTask — fresh session and compaction recovery", () => {
|
||||
});
|
||||
}
|
||||
|
||||
it("creates a fresh session for merge agent via createKbAgent", async () => {
|
||||
it("creates a fresh session for merge agent via createFnAgent", async () => {
|
||||
setupFreshSessionExecSync();
|
||||
|
||||
const sessionInstances: any[] = [];
|
||||
@@ -5026,7 +5026,7 @@ describe("aiMergeTask — skill selection resolver contract (FN-1510/FN-1511)",
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("passes skillSelection to createKbAgent when agentStore is provided", async () => {
|
||||
it("passes skillSelection to createFnAgent when agentStore is provided", async () => {
|
||||
const { buildSessionSkillContext } = await import("./session-skill-context.js");
|
||||
vi.mocked(buildSessionSkillContext).mockResolvedValue({
|
||||
skillSelectionContext: {
|
||||
@@ -5064,7 +5064,7 @@ describe("aiMergeTask — skill selection resolver contract (FN-1510/FN-1511)",
|
||||
});
|
||||
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalled();
|
||||
// Find the first createKbAgent call (main merger agent)
|
||||
// Find the first createFnAgent call (main merger agent)
|
||||
const firstCall = mockedCreateHaiAgent.mock.calls[0];
|
||||
const opts = firstCall[0];
|
||||
expect(opts.skillSelection).toBeDefined();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { existsSync } from "node:fs";
|
||||
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 { createFnAgent, 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";
|
||||
@@ -662,7 +662,7 @@ async function attemptInMergeVerificationFix(
|
||||
}
|
||||
|
||||
// Create the fix agent session
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: rootDir, // Runs on the main branch in the project root
|
||||
systemPrompt: `You are a verification fix agent running during a merge on the main branch.
|
||||
|
||||
@@ -1395,7 +1395,7 @@ You are assisting with a paused \`git pull --rebase\`.
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt,
|
||||
tools: "coding",
|
||||
@@ -2563,7 +2563,7 @@ interface AiAgentParams {
|
||||
/**
|
||||
* Run the AI agent to resolve conflicts and/or write commit message.
|
||||
*
|
||||
* Each invocation creates a **fresh session** via `createKbAgent` to ensure
|
||||
* Each invocation creates a **fresh session** via `createFnAgent` to ensure
|
||||
* no stale conversation state from previous merge attempts or unrelated sessions
|
||||
* pollutes the merge context. The session is disposed in the `finally` block
|
||||
* regardless of success or failure.
|
||||
@@ -2673,7 +2673,7 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
||||
}
|
||||
}
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt: mergerSystemPrompt,
|
||||
tools: "coding",
|
||||
@@ -3123,7 +3123,7 @@ If issues are found that need attention, describe them clearly.`;
|
||||
}
|
||||
}
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: rootDir,
|
||||
systemPrompt: postMergeSystemPrompt,
|
||||
tools: toolMode,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* MissionExecutionLoop unit tests.
|
||||
*
|
||||
* Tests the validation cycle orchestration class with mocked TaskStore, MissionStore,
|
||||
* and AI agent (createKbAgent/promptWithFallback).
|
||||
* and AI agent (createFnAgent/promptWithFallback).
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
@@ -31,9 +31,9 @@ const mockSessionHolder: {
|
||||
|
||||
// Mock the pi module before MissionExecutionLoop is imported
|
||||
vi.mock("./pi.js", () => {
|
||||
const createKbAgent = vi.fn(() => Promise.resolve({ session: mockSessionHolder.session }));
|
||||
const createFnAgent = vi.fn(() => Promise.resolve({ session: mockSessionHolder.session }));
|
||||
const promptWithFallback = vi.fn().mockResolvedValue(undefined);
|
||||
return { createKbAgent, promptWithFallback };
|
||||
return { createFnAgent, promptWithFallback };
|
||||
});
|
||||
|
||||
vi.mock("./logger.js", () => ({
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
MissionFeature,
|
||||
MissionValidatorRun,
|
||||
} from "@fusion/core";
|
||||
import { createKbAgent, promptWithFallback, type AgentResult } from "./pi.js";
|
||||
import { createFnAgent, promptWithFallback, type AgentResult } from "./pi.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
|
||||
/** Logger for the mission execution loop subsystem. */
|
||||
@@ -325,7 +325,7 @@ export class MissionExecutionLoop extends EventEmitter {
|
||||
|
||||
try {
|
||||
// Create validation agent session
|
||||
session = await createKbAgent({
|
||||
session = await createFnAgent({
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: this.buildValidationSystemPrompt(feature, assertions, taskContext),
|
||||
tools: "readonly",
|
||||
@@ -383,7 +383,7 @@ export class MissionExecutionLoop extends EventEmitter {
|
||||
* Run the actual validation session with the AI agent.
|
||||
*/
|
||||
private async runValidationSession(
|
||||
agentSession: Awaited<ReturnType<typeof createKbAgent>>["session"],
|
||||
agentSession: Awaited<ReturnType<typeof createFnAgent>>["session"],
|
||||
prompt: string,
|
||||
): Promise<void> {
|
||||
// Use promptWithFallback for resilience - if the primary model fails,
|
||||
@@ -401,7 +401,7 @@ export class MissionExecutionLoop extends EventEmitter {
|
||||
* We extract the text from the AI's messages and parse the JSON response.
|
||||
*/
|
||||
private async parseValidationResult(
|
||||
agentSession: Awaited<ReturnType<typeof createKbAgent>>["session"],
|
||||
agentSession: Awaited<ReturnType<typeof createFnAgent>>["session"],
|
||||
assertions: MissionContractAssertion[],
|
||||
): Promise<ValidationResult> {
|
||||
try {
|
||||
@@ -467,7 +467,7 @@ export class MissionExecutionLoop extends EventEmitter {
|
||||
* Looks for the last assistant message with text content.
|
||||
*/
|
||||
private extractResponseTextFromSession(
|
||||
agentSession: Awaited<ReturnType<typeof createKbAgent>>["session"],
|
||||
agentSession: Awaited<ReturnType<typeof createFnAgent>>["session"],
|
||||
): string | undefined {
|
||||
try {
|
||||
// Access the session state to get messages
|
||||
|
||||
@@ -130,7 +130,7 @@ describe("worktree path boundary helpers", () => {
|
||||
|
||||
const tools = [mockReadTool as any];
|
||||
|
||||
// Simulate wrapping (normally done inside createKbAgent)
|
||||
// Simulate wrapping (normally done inside createFnAgent)
|
||||
const { wrapToolsWithBoundary } = await import("./pi.js");
|
||||
const wrapped = wrapToolsWithBoundary(
|
||||
tools,
|
||||
@@ -365,7 +365,7 @@ describe("worktree path boundary helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createKbAgent", () => {
|
||||
describe("createFnAgent", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
execSyncMock.mockReturnValue("");
|
||||
@@ -395,9 +395,9 @@ describe("createKbAgent", () => {
|
||||
return "worktree /project\nHEAD abc123\nbranch refs/heads/main\n";
|
||||
});
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await expect(createKbAgent({
|
||||
await expect(createFnAgent({
|
||||
cwd: "/project/.worktrees/fn-001",
|
||||
systemPrompt: "test",
|
||||
tools: "coding",
|
||||
@@ -422,9 +422,9 @@ describe("createKbAgent", () => {
|
||||
"worktree /project/.worktrees/fn-001\nHEAD def456\nbranch refs/heads/fusion/fn-001\n";
|
||||
});
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/project/.worktrees/fn-001",
|
||||
systemPrompt: "test",
|
||||
tools: "coding",
|
||||
@@ -452,9 +452,9 @@ describe("createKbAgent", () => {
|
||||
errors: [],
|
||||
});
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -474,9 +474,9 @@ describe("createKbAgent", () => {
|
||||
});
|
||||
|
||||
it("avoids lock-based SettingsManager.create when loading extension providers", async () => {
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -495,9 +495,9 @@ describe("createKbAgent", () => {
|
||||
provider === "zai" && modelId === "glm-5.1" ? undefined : { provider, id: modelId }
|
||||
));
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await expect(createKbAgent({
|
||||
await expect(createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -513,9 +513,9 @@ describe("createKbAgent", () => {
|
||||
provider === "openai-codex" && modelId === "missing-model" ? undefined : { provider, id: modelId }
|
||||
));
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await expect(createKbAgent({
|
||||
await expect(createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "coding",
|
||||
@@ -529,9 +529,9 @@ describe("createKbAgent", () => {
|
||||
});
|
||||
|
||||
it("creates a session when configured models resolve successfully", async () => {
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -570,9 +570,9 @@ describe("createKbAgent", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -620,9 +620,9 @@ describe("createKbAgent", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -643,9 +643,9 @@ describe("createKbAgent", () => {
|
||||
});
|
||||
|
||||
it("enables auto-compaction to prevent context-window overflow", async () => {
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "coding",
|
||||
@@ -660,9 +660,9 @@ describe("createKbAgent", () => {
|
||||
});
|
||||
|
||||
it("passes compaction enabled alongside retry settings", async () => {
|
||||
const { createKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent } = await import("./pi.js");
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/tmp",
|
||||
systemPrompt: "test",
|
||||
tools: "readonly",
|
||||
@@ -734,7 +734,7 @@ describe("createKbAgent", () => {
|
||||
},
|
||||
}));
|
||||
|
||||
const { createKbAgent: freshCreateKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent: freshCreateKbAgent } = await import("./pi.js");
|
||||
|
||||
await freshCreateKbAgent({
|
||||
cwd: "/tmp",
|
||||
@@ -809,7 +809,7 @@ describe("createKbAgent", () => {
|
||||
},
|
||||
}));
|
||||
|
||||
const { createKbAgent: freshCreateKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent: freshCreateKbAgent } = await import("./pi.js");
|
||||
|
||||
await freshCreateKbAgent({
|
||||
cwd: "/tmp",
|
||||
@@ -881,7 +881,7 @@ describe("createKbAgent", () => {
|
||||
},
|
||||
}));
|
||||
|
||||
const { createKbAgent: freshCreateKbAgent } = await import("./pi.js");
|
||||
const { createFnAgent: freshCreateKbAgent } = await import("./pi.js");
|
||||
|
||||
await freshCreateKbAgent({
|
||||
cwd: "/tmp",
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { describeModel, compactSessionContext, COMPACTION_FALLBACK_INSTRUCTIONS, createKbAgent, promptWithFallback, type AgentOptions } from "./pi.js";
|
||||
import { describeModel, compactSessionContext, COMPACTION_FALLBACK_INSTRUCTIONS, createFnAgent, promptWithFallback, type AgentOptions } from "./pi.js";
|
||||
import { createAgentSession, type AgentSession } from "@mariozechner/pi-coding-agent";
|
||||
import { piLog } from "./logger.js";
|
||||
|
||||
@@ -263,7 +263,7 @@ describe("promptWithFallback context recovery", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createKbAgent skills parameter", () => {
|
||||
describe("createFnAgent skills parameter", () => {
|
||||
let piLogSpy: ReturnType<typeof vi.spyOn>;
|
||||
let piWarnSpy: ReturnType<typeof vi.spyOn>;
|
||||
let piErrorSpy: ReturnType<typeof vi.spyOn>;
|
||||
@@ -306,7 +306,7 @@ describe("createKbAgent skills parameter", () => {
|
||||
skills: ["review", "fusion"],
|
||||
};
|
||||
|
||||
await createKbAgent(options);
|
||||
await createFnAgent(options);
|
||||
|
||||
// Verify resolveSessionSkills was called with auto-derived context
|
||||
expect(mockResolveSessionSkills).toHaveBeenCalledTimes(1);
|
||||
@@ -328,7 +328,7 @@ describe("createKbAgent skills parameter", () => {
|
||||
},
|
||||
};
|
||||
|
||||
await createKbAgent(options);
|
||||
await createFnAgent(options);
|
||||
|
||||
// Verify resolveSessionSkills was called with explicit skillSelection (not auto-derived)
|
||||
expect(mockResolveSessionSkills).toHaveBeenCalledTimes(1);
|
||||
@@ -350,7 +350,7 @@ describe("createKbAgent skills parameter", () => {
|
||||
skills: [],
|
||||
};
|
||||
|
||||
await createKbAgent(options);
|
||||
await createFnAgent(options);
|
||||
|
||||
// Verify no skill resolution occurred
|
||||
expect(mockResolveSessionSkills).not.toHaveBeenCalled();
|
||||
@@ -364,7 +364,7 @@ describe("createKbAgent skills parameter", () => {
|
||||
skills: ["review", "fusion"],
|
||||
};
|
||||
|
||||
await createKbAgent(options);
|
||||
await createFnAgent(options);
|
||||
|
||||
// Verify the log message includes the skill names
|
||||
expect(piLogSpy).toHaveBeenCalledWith(
|
||||
@@ -389,7 +389,7 @@ describe("createKbAgent skills parameter", () => {
|
||||
skills: ["nonexistent-skill"],
|
||||
};
|
||||
|
||||
await createKbAgent(options);
|
||||
await createFnAgent(options);
|
||||
|
||||
// The diagnostics should be logged
|
||||
expect(mockResolveSessionSkills).toHaveBeenCalled();
|
||||
@@ -601,7 +601,7 @@ describe("session failure diagnostics", () => {
|
||||
.mockResolvedValueOnce({ session: primarySession } as any)
|
||||
.mockResolvedValueOnce({ session: fallbackSession } as any);
|
||||
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd: "/test/project",
|
||||
systemPrompt: "Test fallback swap",
|
||||
defaultProvider: "test",
|
||||
@@ -639,7 +639,7 @@ describe("piLog structured diagnostics", () => {
|
||||
});
|
||||
|
||||
it("logs session creation with model info", async () => {
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/test/project",
|
||||
systemPrompt: "Test",
|
||||
defaultProvider: "test",
|
||||
@@ -668,7 +668,7 @@ describe("piLog structured diagnostics", () => {
|
||||
},
|
||||
} as any);
|
||||
|
||||
await createKbAgent({
|
||||
await createFnAgent({
|
||||
cwd: "/test/project",
|
||||
systemPrompt: "Test",
|
||||
defaultProvider: "test",
|
||||
@@ -688,7 +688,7 @@ describe("piLog structured diagnostics", () => {
|
||||
createAgentSessionMock.mockReset();
|
||||
createAgentSessionMock.mockRejectedValueOnce(new Error("fatal model failure"));
|
||||
|
||||
await expect(createKbAgent({
|
||||
await expect(createFnAgent({
|
||||
cwd: "/test/project",
|
||||
systemPrompt: "Test",
|
||||
defaultProvider: "test",
|
||||
|
||||
@@ -671,8 +671,8 @@ export function wrapToolsWithBoundary(
|
||||
* Create a pi agent session configured for fn.
|
||||
* Reuses the user's existing pi auth and model configuration.
|
||||
*/
|
||||
export async function createKbAgent(options: AgentOptions): Promise<AgentResult> {
|
||||
piLog.log(`createKbAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
|
||||
export async function createFnAgent(options: AgentOptions): Promise<AgentResult> {
|
||||
piLog.log(`createFnAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
|
||||
const authStorage = createFusionAuthStorage();
|
||||
const modelRegistry = new ModelRegistry(authStorage, getModelRegistryModelsPath());
|
||||
await registerExtensionProviders(options.cwd, modelRegistry);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { AgentSemaphore } from "./concurrency.js";
|
||||
// ── Module-level mocks (matching existing test patterns) ──────────────────
|
||||
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
describeModel: vi.fn().mockReturnValue("mock-provider/mock-model"),
|
||||
promptWithFallback: vi.fn(async (session, prompt, options) => {
|
||||
if (options === undefined) {
|
||||
@@ -96,12 +96,12 @@ import { TriageProcessor } from "./triage.js";
|
||||
import { Scheduler } from "./scheduler.js";
|
||||
import { aiMergeTask } from "./merger.js";
|
||||
import { WorktreePool, scanIdleWorktrees, cleanupOrphanedWorktrees } from "./worktree-pool.js";
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { createFnAgent } from "./pi.js";
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, readdirSync } from "node:fs";
|
||||
import type { Task, TaskDetail, TaskStep, Column, Settings, StepStatus } from "@fusion/core";
|
||||
|
||||
const mockedCreateHaiAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateHaiAgent = vi.mocked(createFnAgent);
|
||||
const mockedExecSync = vi.mocked(execSync);
|
||||
const mockedExistsSync = vi.mocked(existsSync);
|
||||
const mockedReaddirSync = vi.mocked(readdirSync);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
describeModel: vi.fn().mockReturnValue("mock-provider/mock-model"),
|
||||
promptWithFallback: vi.fn(async (session, prompt, options) => {
|
||||
if (options === undefined) {
|
||||
@@ -13,9 +13,9 @@ vi.mock("./pi.js", () => ({
|
||||
}));
|
||||
|
||||
import { reviewStep, REVIEWER_SYSTEM_PROMPT } from "./reviewer.js";
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { createFnAgent } from "./pi.js";
|
||||
|
||||
const mockedCreateHaiAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateHaiAgent = vi.mocked(createFnAgent);
|
||||
|
||||
function createMockSession(reviewText: string) {
|
||||
return {
|
||||
@@ -38,7 +38,7 @@ describe("reviewStep — model settings threading", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("passes defaultProvider and defaultModelId to createKbAgent when provided", async () => {
|
||||
it("passes defaultProvider and defaultModelId to createFnAgent when provided", async () => {
|
||||
mockedCreateHaiAgent.mockResolvedValue(
|
||||
createMockSession("### Verdict: APPROVE\n### Summary\nLooks good."),
|
||||
);
|
||||
@@ -130,7 +130,7 @@ describe("reviewStep — spec review type", () => {
|
||||
expect(result.verdict).toBe("RETHINK");
|
||||
});
|
||||
|
||||
it("calls createKbAgent with readonly tools and correct system prompt", async () => {
|
||||
it("calls createFnAgent with readonly tools and correct system prompt", async () => {
|
||||
mockedCreateHaiAgent.mockResolvedValue(
|
||||
createMockSession("### Verdict: APPROVE\n### Summary\nGood spec."),
|
||||
);
|
||||
@@ -504,7 +504,7 @@ describe("reviewStep — user comments in spec review", () => {
|
||||
sessionManager: { getLeafId: vi.fn() },
|
||||
},
|
||||
} as any);
|
||||
vi.mocked(createKbAgent).mockImplementation(mockedCreateHaiAgent);
|
||||
vi.mocked(createFnAgent).mockImplementation(mockedCreateHaiAgent);
|
||||
});
|
||||
|
||||
it("includes user comments in spec review request", async () => {
|
||||
@@ -647,7 +647,7 @@ describe("reviewStep — skill selection resolver contract (FN-1510/FN-1511)", (
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("passes skillSelection to createKbAgent when agentStore and rootDir are provided", async () => {
|
||||
it("passes skillSelection to createFnAgent when agentStore and rootDir are provided", async () => {
|
||||
const { buildSessionSkillContext } = await import("./session-skill-context.js");
|
||||
vi.mocked(buildSessionSkillContext).mockResolvedValue({
|
||||
skillSelectionContext: {
|
||||
@@ -828,7 +828,7 @@ describe("reviewStep — skill selection resolver contract (FN-1510/FN-1511)", (
|
||||
},
|
||||
);
|
||||
|
||||
// Verify the resolved names are passed to createKbAgent
|
||||
// Verify the resolved names are passed to createFnAgent
|
||||
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
|
||||
const opts = mockedCreateHaiAgent.mock.calls[0][0];
|
||||
expect(opts.skillSelection?.requestedSkillNames).toEqual(resolvedNames);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import type { TaskStore, TaskComment, AgentPromptsConfig, Settings } from "@fusion/core";
|
||||
import { buildReviewerMemoryInstructions, resolveAgentPrompt } from "@fusion/core";
|
||||
import { createKbAgent, describeModel, promptWithFallback } from "./pi.js";
|
||||
import { createFnAgent, describeModel, promptWithFallback } from "./pi.js";
|
||||
import { buildSessionSkillContext } from "./session-skill-context.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import { reviewerLog } from "./logger.js";
|
||||
@@ -359,7 +359,7 @@ export async function reviewStep(
|
||||
} : undefined),
|
||||
]
|
||||
: undefined;
|
||||
const { session } = await createKbAgent({
|
||||
const { session } = await createFnAgent({
|
||||
cwd,
|
||||
systemPrompt: reviewerSystemPrompt,
|
||||
tools: "readonly",
|
||||
|
||||
@@ -56,10 +56,10 @@ export interface SessionSkillContextInput {
|
||||
|
||||
/**
|
||||
* Result of building session skill context.
|
||||
* Contains the SkillSelectionContext for createKbAgent and any diagnostics.
|
||||
* Contains the SkillSelectionContext for createFnAgent and any diagnostics.
|
||||
*/
|
||||
export interface SessionSkillContextResult {
|
||||
/** Context to pass to createKbAgent's skillSelection option */
|
||||
/** Context to pass to createFnAgent's skillSelection option */
|
||||
skillSelectionContext: SkillSelectionContext | undefined;
|
||||
/** Normalized skill names that were resolved (for logging/debugging) */
|
||||
resolvedSkillNames: string[];
|
||||
@@ -159,7 +159,7 @@ export const SKILL_DIAGNOSTIC_MESSAGES = {
|
||||
// ── Main Builder ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build session skill context for createKbAgent.
|
||||
* Build session skill context for createFnAgent.
|
||||
*
|
||||
* Applies precedence rules:
|
||||
* 1. Use assigned agent skills if available
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 2. Optional caller-requested skill names (for per-task overrides)
|
||||
*
|
||||
* The resolver reads project settings files directly (read-only) and produces
|
||||
* a filter set used by createKbAgent's DefaultResourceLoader.skillsOverride.
|
||||
* a filter set used by createFnAgent's DefaultResourceLoader.skillsOverride.
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
|
||||
@@ -538,7 +538,7 @@ Some freeform text without checkboxes.`;
|
||||
|
||||
// Mock pi.js for StepSessionExecutor tests
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: vi.fn(),
|
||||
createFnAgent: vi.fn(),
|
||||
promptWithFallback: vi.fn(async (session: any, prompt: string) => {
|
||||
await session.prompt(prompt);
|
||||
}),
|
||||
@@ -639,13 +639,13 @@ vi.mock("node:fs", () => ({
|
||||
existsSync: vi.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
import { createKbAgent } from "./pi.js";
|
||||
import { createFnAgent } from "./pi.js";
|
||||
import { generateWorktreeName } from "./worktree-names.js";
|
||||
import { execSync } from "node:child_process";
|
||||
import { AgentSemaphore } from "./concurrency.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
|
||||
const mockedCreateKbAgent = vi.mocked(createKbAgent);
|
||||
const mockedCreateKbAgent = vi.mocked(createFnAgent);
|
||||
const mockedExecSync = vi.mocked(execSync);
|
||||
const mockedGenerateWorktreeName = vi.mocked(generateWorktreeName);
|
||||
const mockedCreateLogger = vi.mocked(createLogger);
|
||||
@@ -2023,7 +2023,7 @@ describe("StepSessionExecutor", () => {
|
||||
// ── Skill Selection Regression Tests (FN-1514) ──────────────────────────
|
||||
//
|
||||
// Note: These tests verify that skillSelection is passed through the
|
||||
// StepSessionExecutor to createKbAgent calls. The actual skill resolution
|
||||
// StepSessionExecutor to createFnAgent calls. The actual skill resolution
|
||||
// logic is tested in session-skill-context.test.ts.
|
||||
// The full integration with executeAll is tested indirectly through
|
||||
// the executor tests which create StepSessionExecutor with skillSelection.
|
||||
@@ -2074,7 +2074,7 @@ describe("StepSessionExecutor skillSelection regression (FN-1511)", () => {
|
||||
describe("StepSessionExecutor tool availability", () => {
|
||||
/**
|
||||
* These tests verify tool configuration by capturing the customTools
|
||||
* passed to createKbAgent during executeStep execution. Each test
|
||||
* passed to createFnAgent during executeStep execution. Each test
|
||||
* uses fake timers and advances time to resolve any pending sleep()s.
|
||||
*/
|
||||
async function captureCustomTools(options?: {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { join } from "node:path";
|
||||
import type { AgentSession } from "@mariozechner/pi-coding-agent";
|
||||
import type { AgentStore, MessageStore, TaskDetail, Settings, TaskStore } from "@fusion/core";
|
||||
|
||||
import { createKbAgent, promptWithFallback, describeModel } from "./pi.js";
|
||||
import { createFnAgent, promptWithFallback, describeModel } from "./pi.js";
|
||||
import type { SkillSelectionContext } from "./skill-resolver.js";
|
||||
import { generateWorktreeName } from "./worktree-names.js";
|
||||
import { AgentSemaphore } from "./concurrency.js";
|
||||
@@ -848,7 +848,7 @@ export class StepSessionExecutor {
|
||||
? settings.executionGlobalModelId
|
||||
: settings.defaultModelId));
|
||||
|
||||
const createResult = await createKbAgent({
|
||||
const createResult = await createFnAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: `You are an AI agent executing step ${stepIndex} of task ${taskDetail.id}. Follow instructions precisely.`,
|
||||
defaultProvider: executorProvider,
|
||||
|
||||
@@ -23,7 +23,7 @@ vi.mock("./reviewer.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./pi.js", () => ({
|
||||
createKbAgent: mockCreateKbAgent,
|
||||
createFnAgent: mockCreateKbAgent,
|
||||
describeModel: vi.fn().mockReturnValue("mock-model"),
|
||||
promptWithFallback: vi.fn().mockReturnValue("mock-prompt"),
|
||||
}));
|
||||
@@ -1277,7 +1277,7 @@ describe("taskCreate tool model inheritance", () => {
|
||||
|
||||
it("closes parent after proactive split even when breakIntoSubtasks is undefined", async () => {
|
||||
// Test that the post-session closure path doesn't gate on breakIntoSubtasks.
|
||||
// Strategy: capture the customTools from createKbAgent, then have
|
||||
// Strategy: capture the customTools from createFnAgent, then have
|
||||
// promptWithFallback invoke the task_create tool to simulate the agent
|
||||
// proactively splitting an oversized task.
|
||||
const task: Task = {
|
||||
@@ -1329,7 +1329,7 @@ describe("taskCreate tool model inheritance", () => {
|
||||
.mockResolvedValueOnce(childTask2),
|
||||
});
|
||||
|
||||
// Capture customTools from createKbAgent call
|
||||
// Capture customTools from createFnAgent call
|
||||
let capturedCustomTools: any[] = [];
|
||||
const mockDispose = vi.fn();
|
||||
mockCreateKbAgent.mockImplementation(async (opts: any) => {
|
||||
@@ -1457,7 +1457,7 @@ describe("taskCreate tool model inheritance", () => {
|
||||
pollIntervalMs: 100_000,
|
||||
});
|
||||
|
||||
// Mock createKbAgent to throw a transient error
|
||||
// Mock createFnAgent to throw a transient error
|
||||
mockCreateKbAgent.mockRejectedValue(new Error("upstream connect error"));
|
||||
|
||||
await processor.specifyTask(task);
|
||||
@@ -1633,7 +1633,7 @@ describe("taskCreate tool model inheritance", () => {
|
||||
getTask: vi.fn().mockResolvedValue({ ...task, attachments: [] }),
|
||||
});
|
||||
|
||||
// Set up createKbAgent to return a session that immediately throws
|
||||
// Set up createFnAgent to return a session that immediately throws
|
||||
// after the model log line, so we can verify the appendAgentLog call.
|
||||
// The session will be created, model logged, then promptWithFallback
|
||||
// throws — but the model log has already been written.
|
||||
@@ -2447,10 +2447,10 @@ describe("tool callback behavior (FN-1500)", () => {
|
||||
const processor = new TriageProcessor(store, "/tmp/root", { stuckTaskDetector: mockDetector });
|
||||
|
||||
// Access the agentLogger via internal agentWork closure
|
||||
// by running specifyTask and intercepting the createKbAgent call
|
||||
// by running specifyTask and intercepting the createFnAgent call
|
||||
let capturedOnAgentTool: ((id: string, name: string) => void) | undefined;
|
||||
mockCreateKbAgent.mockImplementation(async (opts: any) => {
|
||||
// Capture the onToolStart callback that was passed to createKbAgent
|
||||
// Capture the onToolStart callback that was passed to createFnAgent
|
||||
// This is the onAgentTool from agentLogger
|
||||
if (opts.onToolStart) {
|
||||
capturedOnAgentTool = opts.onToolStart;
|
||||
@@ -2586,7 +2586,7 @@ describe("TriageProcessor skillSelection regression (FN-1511)", () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper: execute triage on a task and capture createKbAgent call arguments.
|
||||
* Helper: execute triage on a task and capture createFnAgent call arguments.
|
||||
*/
|
||||
async function captureCreateKbAgentArgs(options?: {
|
||||
assignedAgentId?: string;
|
||||
@@ -2661,7 +2661,7 @@ describe("TriageProcessor skillSelection regression (FN-1511)", () => {
|
||||
}
|
||||
|
||||
describe("skillSelection context propagation", () => {
|
||||
it("passes skillSelection to createKbAgent with correct projectRootDir", async () => {
|
||||
it("passes skillSelection to createFnAgent with correct projectRootDir", async () => {
|
||||
const args = await captureCreateKbAgentArgs({
|
||||
assignedAgentId: "agent-001",
|
||||
assignedAgentSkills: ["triage"],
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
ToolDefinition,
|
||||
AgentSession,
|
||||
} from "@mariozechner/pi-coding-agent";
|
||||
import { createKbAgent, describeModel, promptWithFallback } from "./pi.js";
|
||||
import { createFnAgent, describeModel, promptWithFallback } from "./pi.js";
|
||||
import { reviewStep, type ReviewVerdict } from "./reviewer.js";
|
||||
import { buildSessionSkillContext } from "./session-skill-context.js";
|
||||
import { PRIORITY_SPECIFY, type AgentSemaphore } from "./concurrency.js";
|
||||
@@ -635,7 +635,7 @@ export class TriageProcessor {
|
||||
},
|
||||
});
|
||||
|
||||
// Mutable ref — populated after createKbAgent, tools access lazily via closure
|
||||
// Mutable ref — populated after createFnAgent, tools access lazily via closure
|
||||
const sessionRef: { current: AgentSession | null } = { current: null };
|
||||
// Checkpoint for RETHINK rewind — captured lazily on first review_spec call
|
||||
const checkpointRef: { current: string | null } = { current: null };
|
||||
@@ -704,7 +704,7 @@ export class TriageProcessor {
|
||||
projectRootDir: this.rootDir,
|
||||
});
|
||||
|
||||
let { session } = await createKbAgent({
|
||||
let { session } = await createFnAgent({
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: triageSystemPrompt,
|
||||
tools: "coding",
|
||||
@@ -868,7 +868,7 @@ export class TriageProcessor {
|
||||
specReviewVerdictRef.current = null;
|
||||
approvedCommentFingerprintRef.current = "";
|
||||
|
||||
const fallbackResult = await createKbAgent({
|
||||
const fallbackResult = await createFnAgent({
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: triageSystemPrompt,
|
||||
tools: "coding",
|
||||
|
||||
Reference in New Issue
Block a user