feat(FN-4636): complete Step 4 — wire command runners through sandbox backend

Fusion-Task-Id: FN-4636
Fusion-Task-Lineage: 38ff2f48-4cb1-42c2-8f64-eb3b8d3d7f2c
This commit is contained in:
Fusion
2026-05-15 10:09:55 -07:00
committed by gsxdsm
parent fcaa0d3b8f
commit 3c595ce223
3 changed files with 104 additions and 76 deletions

View File

@@ -2,6 +2,7 @@
import { execSync, exec, execFile } from "node:child_process";
import { promisify } from "node:util";
// Internal git plumbing intentionally bypasses sandbox backends.
const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);
import {
@@ -14,6 +15,8 @@ import {
type VerificationCommandResult,
type VerificationResult,
} from "./verification-utils.js";
import { resolveSandboxBackend } from "./sandbox/index.js";
import type { SandboxBackend } from "./sandbox/types.js";
// Re-export for backward compatibility (tests import from merger.ts)
export {
@@ -8935,6 +8938,13 @@ async function runPostMergeWorkflowSteps(
}
}
let postMergeScriptSandboxBackend: SandboxBackend | null = null;
function getPostMergeScriptSandboxBackend(): SandboxBackend {
postMergeScriptSandboxBackend ??= resolveSandboxBackend();
return postMergeScriptSandboxBackend;
}
/** Execute a script-mode post-merge workflow step in the provided execution directory. */
async function executePostMergeScriptStep(
store: TaskStore,
@@ -8951,25 +8961,40 @@ async function executePostMergeScriptStep(
return { success: false, error: `Script '${scriptName}' not found in project settings` };
}
try {
await execAsync(scriptCommand, {
cwd,
encoding: "utf-8",
timeout: 120_000,
maxBuffer: 10 * 1024 * 1024,
});
const backend = getPostMergeScriptSandboxBackend();
const result = await backend.run(scriptCommand, {
cwd,
encoding: "utf-8",
timeoutMs: 120_000,
maxBuffer: 10 * 1024 * 1024,
});
if (result.exitCode === 0 && !result.signal && !result.timedOut && !result.bufferExceeded && !result.spawnError) {
return { success: true, output: `Script '${scriptName}' completed successfully` };
} catch (err: any) {
const stderr = err.stderr?.toString()?.trim() || "";
const stdout = err.stdout?.toString()?.trim() || "";
const exitCode = err.code ?? err.status;
const parts: string[] = [];
if (exitCode !== undefined) parts.push(`Exit code: ${exitCode}`);
}
const stderr = result.stderr.trim();
const stdout = result.stdout.trim();
const parts: string[] = [];
if (result.spawnError) {
parts.push(result.spawnError.message);
} else {
if (result.exitCode !== null) parts.push(`Exit code: ${result.exitCode}`);
if (stdout) parts.push(`stdout: ${truncateWorkflowScriptOutput(stdout)}`);
if (stderr) parts.push(`stderr: ${truncateWorkflowScriptOutput(stderr)}`);
if (!parts.length) parts.push(err.message || "Unknown error");
return { success: false, error: parts.join("\n") };
}
if (!parts.length) parts.push("Unknown error");
return { success: false, error: parts.join("\n") };
}
export async function __executePostMergeScriptStepForTests(
store: TaskStore,
taskId: string,
workflowStep: WorkflowStep,
cwd: string,
settings: Settings,
): Promise<{ success: boolean; output?: string; error?: string }> {
return executePostMergeScriptStep(store, taskId, workflowStep, cwd, settings);
}
/** Execute a prompt-mode post-merge workflow step using an AI agent in the provided execution directory. */