feat(FN-2014): merge fusion/fn-2014
This commit is contained in:
@@ -6,7 +6,7 @@ import { isAbsolute, join } from "node:path";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import type { TaskStore, Task, TaskDetail, StepStatus, Settings, WorkflowStep, MissionStore, Slice, AgentState, AgentCapability, RunMutationContext } from "@fusion/core";
|
||||
import { buildExecutionMemoryInstructions, getTaskMergeBlocker, resolveAgentPrompt, runCommandAsync, type RunCommandResult } from "@fusion/core";
|
||||
import { buildExecutionMemoryInstructions, getTaskMergeBlocker, resolveAgentPrompt, type RunCommandResult } from "@fusion/core";
|
||||
import { findWorktreeUser } from "./merger.js";
|
||||
import { generateWorktreeName, slugify } from "./worktree-names.js";
|
||||
import { Type, type Static } from "@mariozechner/pi-ai";
|
||||
@@ -96,11 +96,43 @@ async function runConfiguredCommand(
|
||||
cwd: string,
|
||||
timeoutMs: number,
|
||||
): Promise<RunCommandResult> {
|
||||
return runCommandAsync(command, {
|
||||
cwd,
|
||||
timeoutMs,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
});
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(command, {
|
||||
cwd,
|
||||
timeout: timeoutMs,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
return {
|
||||
stdout: stdout?.toString?.() ?? "",
|
||||
stderr: stderr?.toString?.() ?? "",
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
bufferExceeded: false,
|
||||
timedOut: false,
|
||||
};
|
||||
} catch (error: any) {
|
||||
const code = error?.code;
|
||||
const status = typeof error?.status === "number" ? error.status : null;
|
||||
const exitCode = typeof code === "number" ? code : status;
|
||||
const message = String(error?.message ?? "");
|
||||
|
||||
return {
|
||||
stdout: error?.stdout?.toString?.() ?? "",
|
||||
stderr: error?.stderr?.toString?.() ?? "",
|
||||
exitCode,
|
||||
signal: (error?.signal as NodeJS.Signals | null | undefined) ?? null,
|
||||
bufferExceeded:
|
||||
code === "ENOBUFS"
|
||||
|| code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
|
||||
|| message.includes("maxBuffer"),
|
||||
timedOut:
|
||||
code === "ETIMEDOUT"
|
||||
|| (error?.killed === true && (error?.signal === "SIGTERM" || message.includes("timed out"))),
|
||||
spawnError: code === "ENOENT" || code === "EACCES" ? error : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tool parameter schemas (module-level for reuse in ToolDefinition generics) ──
|
||||
|
||||
@@ -5,7 +5,7 @@ import { promisify } from "node:util";
|
||||
const execAsync = promisify(exec);
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { getTaskMergeBlocker, runCommandAsync, type TaskStore, type MergeResult, type MergeDetails, type WorkflowStep, type WorkflowStepResult, type Settings, type AgentPromptsConfig } from "@fusion/core";
|
||||
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";
|
||||
@@ -570,43 +570,31 @@ async function runVerificationCommand(
|
||||
};
|
||||
|
||||
try {
|
||||
const commandResult = await runCommandAsync(command, {
|
||||
const { stdout, stderr } = await execAsync(command, {
|
||||
cwd: rootDir,
|
||||
timeoutMs: 300_000,
|
||||
encoding: "utf-8",
|
||||
timeout: 300_000,
|
||||
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
|
||||
});
|
||||
result.stdout = commandResult.stdout;
|
||||
result.stderr = commandResult.stderr;
|
||||
result.exitCode = commandResult.exitCode;
|
||||
result.success = !commandResult.spawnError
|
||||
&& !commandResult.timedOut
|
||||
&& commandResult.exitCode === 0;
|
||||
|
||||
if (result.success) {
|
||||
const bufferNote = commandResult.bufferExceeded ? ", output exceeded buffer" : "";
|
||||
mergerLog.log(`${taskId}: ${type} command succeeded`);
|
||||
await store.logEntry(taskId, `[verification] ${type} command succeeded (exit 0${bufferNote})`);
|
||||
return result;
|
||||
}
|
||||
result.stdout = stdout?.toString?.() || "";
|
||||
result.stderr = stderr?.toString?.() || "";
|
||||
result.exitCode = 0;
|
||||
result.success = true;
|
||||
|
||||
const failureText = commandResult.spawnError?.message
|
||||
|| (commandResult.timedOut ? "Command timed out" : "")
|
||||
|| commandResult.stderr
|
||||
|| commandResult.stdout
|
||||
|| `Command exited with ${commandResult.exitCode ?? commandResult.signal ?? "unknown status"}`;
|
||||
throw Object.assign(new Error(failureText), {
|
||||
stdout: commandResult.stdout,
|
||||
stderr: commandResult.stderr,
|
||||
status: commandResult.exitCode,
|
||||
code: commandResult.timedOut ? "ETIMEDOUT" : undefined,
|
||||
});
|
||||
mergerLog.log(`${taskId}: ${type} command succeeded`);
|
||||
await store.logEntry(taskId, `[verification] ${type} command succeeded (exit 0)`);
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
result.stdout = error.stdout?.toString() || "";
|
||||
result.stderr = error.stderr?.toString() || "";
|
||||
result.exitCode = error.status ?? null;
|
||||
const maxBufferExceeded = error.code === "ENOBUFS"
|
||||
|| error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
|
||||
|| error.message?.includes("maxBuffer");
|
||||
result.stdout = error?.stdout?.toString?.() || "";
|
||||
result.stderr = error?.stderr?.toString?.() || "";
|
||||
result.exitCode = typeof error?.status === "number"
|
||||
? error.status
|
||||
: (typeof error?.code === "number" ? error.code : null);
|
||||
|
||||
const maxBufferExceeded = error?.code === "ENOBUFS"
|
||||
|| error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
|
||||
|| String(error?.message ?? "").includes("maxBuffer");
|
||||
result.success = maxBufferExceeded && result.exitCode === 0;
|
||||
|
||||
if (result.success) {
|
||||
@@ -620,7 +608,7 @@ async function runVerificationCommand(
|
||||
|
||||
// Keep command output out of process logs. The bounded excerpt is stored on
|
||||
// the task for diagnostics without dumping test output to the engine stdout.
|
||||
const output = result.stderr || result.stdout || error.message || "Unknown error";
|
||||
const output = result.stderr || result.stdout || error?.message || "Unknown error";
|
||||
const summary = summarizeVerificationOutput(output, type);
|
||||
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}); output captured in task log`);
|
||||
await store.logEntry(
|
||||
|
||||
@@ -332,9 +332,15 @@ export async function reviewStep(
|
||||
}
|
||||
|
||||
// Spawn a reviewer agent with read-only tools
|
||||
const memoryAgent = options.rootDir && options.agentStore && options.task?.assignedAgentId
|
||||
? await options.agentStore.getAgent(options.task.assignedAgentId).catch(() => null)
|
||||
: null;
|
||||
const assignedAgentId = options.task?.assignedAgentId ?? null;
|
||||
const agentStore = options.agentStore;
|
||||
const memoryAgent =
|
||||
options.rootDir
|
||||
&& agentStore
|
||||
&& assignedAgentId
|
||||
&& typeof (agentStore as { getAgent?: unknown }).getAgent === "function"
|
||||
? await agentStore.getAgent(assignedAgentId).catch(() => null)
|
||||
: null;
|
||||
const memoryTools = options.rootDir && options.settings?.memoryEnabled !== false
|
||||
? [
|
||||
createMemorySearchTool(options.rootDir, options.settings, memoryAgent ? {
|
||||
|
||||
Reference in New Issue
Block a user