feat(FN-5443): merge fusion/fn-5443

This commit is contained in:
gsxdsm
2026-05-21 13:36:39 -07:00
parent 252ae24434
commit 9bc5b5f4e9
9 changed files with 89 additions and 13 deletions

View File

@@ -2349,7 +2349,13 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
});
await store.moveTask(task.id, "in-progress");
await store.moveTask(task.id, "in-review");
await store.updateTask(task.id, { status: "failed", error: "429 rate limited" });
await store.updateTask(task.id, {
status: "failed",
error: "429 rate limited",
taskDoneRetryCount: 2,
workflowStepRetries: 3,
stuckKillCount: 4,
});
const retryTool = api.tools.get("fn_task_retry")!;
const result = await retryTool.execute("retry-exec", { id: task.id }, undefined, undefined, makeCtx(tmpDir));
@@ -2362,6 +2368,9 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
expect(updated?.status).toBeFalsy();
expect(updated?.error).toBeFalsy();
expect(updated?.steps[1].status).toBe("in-progress");
expect(updated?.taskDoneRetryCount).toBe(0);
expect(updated?.workflowStepRetries).toBe(0);
expect(updated?.stuckKillCount).toBe(0);
});
it("moves zero-step execution-failed in-review task to todo and clears failure state", async () => {
@@ -2410,7 +2419,14 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
});
await store.moveTask(task.id, "in-progress");
await store.moveTask(task.id, "in-review");
await store.updateTask(task.id, { status: "failed", error: "merge conflict", mergeRetries: 3 });
await store.updateTask(task.id, {
status: "failed",
error: "merge conflict",
mergeRetries: 3,
taskDoneRetryCount: 5,
workflowStepRetries: 4,
stuckKillCount: 7,
});
const retryTool = api.tools.get("fn_task_retry")!;
const result = await retryTool.execute("retry-merge", { id: task.id }, undefined, undefined, makeCtx(tmpDir));
@@ -2423,6 +2439,9 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
expect(updated?.status).toBeFalsy();
expect(updated?.error).toBeFalsy();
expect(updated?.mergeRetries).toBe(0);
expect(updated?.taskDoneRetryCount).toBe(0);
expect(updated?.workflowStepRetries).toBe(0);
expect(updated?.stuckKillCount).toBe(0);
});
it("keeps zero-step merge-failed in-review task with prior merge attempts in-review and resets merge state", async () => {

View File

@@ -2415,6 +2415,9 @@ describe("runTaskRetry", () => {
baseCommitSha: null,
recoveryRetryCount: null,
nextRecoveryAt: null,
taskDoneRetryCount: 0,
workflowStepRetries: 0,
stuckKillCount: 0,
});
expect(mockMoveTask).toHaveBeenCalledWith("FN-001", "todo");
expect(mockLogEntry).toHaveBeenCalledWith("FN-001", "Retry requested from CLI", "Task reset to todo for retry");
@@ -2475,6 +2478,9 @@ describe("runTaskRetry", () => {
baseCommitSha: null,
recoveryRetryCount: null,
nextRecoveryAt: null,
taskDoneRetryCount: 0,
workflowStepRetries: 0,
stuckKillCount: 0,
});
expect(mockMoveTask).toHaveBeenCalledWith("FN-001", "todo");
expect(mockLogEntry).toHaveBeenCalledWith("FN-001", "Retry requested from CLI", "Task reset to todo for retry");

View File

@@ -1,4 +1,4 @@
import { TaskStore, COLUMNS, COLUMN_LABELS, CentralCore, extractIntentSignature, findNearDuplicates, getTaskDuplicateLineage, reconcileDeterministicDuplicate, runDeterministicDuplicateGuard, type Settings, type Column, type StepStatus, type AgentLogType, type AgentLogEntry, type IntentSignature, type NearDuplicateCandidate, type NearDuplicateMatch } from "@fusion/core";
import { TaskStore, COLUMNS, COLUMN_LABELS, CentralCore, buildManualRetryResetPatch, extractIntentSignature, findNearDuplicates, getTaskDuplicateLineage, reconcileDeterministicDuplicate, runDeterministicDuplicateGuard, type Settings, type Column, type StepStatus, type AgentLogType, type AgentLogEntry, type IntentSignature, type NearDuplicateCandidate, type NearDuplicateMatch } from "@fusion/core";
import { aiMergeTask } from "@fusion/engine";
import { createInterface } from "node:readline/promises";
import type { PlanningQuestion, PlanningSummary } from "@fusion/core";
@@ -1011,6 +1011,7 @@ export async function runTaskRetry(id: string, projectName?: string) {
baseCommitSha: null,
recoveryRetryCount: null,
nextRecoveryAt: null,
...buildManualRetryResetPatch(),
});
// Move to todo column

View File

@@ -5,6 +5,7 @@ import {
TaskStore,
COLUMNS,
COLUMN_LABELS,
buildManualRetryResetPatch,
validateNodeOverrideChange,
type Task,
type InsightCategory,
@@ -999,7 +1000,11 @@ export default function kbExtension(pi: ExtensionAPI) {
hasIncompleteSteps || (task.steps.length === 0 && (task.mergeRetries ?? 0) === 0);
if (isExecutionFailureInReview) {
await store.updateTask(params.id, { status: null, error: null, stuckKillCount: 0 });
await store.updateTask(params.id, {
status: null,
error: null,
...buildManualRetryResetPatch(),
});
await store.logEntry(params.id, "Retry requested via Fusion extension (execution failure in-review → todo, preserving progress)");
await store.moveTask(params.id, "todo", { preserveProgress: true });
return {
@@ -1008,7 +1013,12 @@ export default function kbExtension(pi: ExtensionAPI) {
};
}
await store.updateTask(params.id, { status: null, error: null, stuckKillCount: 0, mergeRetries: 0 });
await store.updateTask(params.id, {
status: null,
error: null,
...buildManualRetryResetPatch(),
mergeRetries: 0,
});
await store.logEntry(params.id, "Retry requested via Fusion extension (in-review merge retry, mergeRetries reset)");
return {
content: [{ type: "text", text: `Retried ${params.id} → in-review (merge retry state cleared)` }],
@@ -1017,7 +1027,11 @@ export default function kbExtension(pi: ExtensionAPI) {
}
// Clear failure state and move to todo for other columns
await store.updateTask(params.id, { status: null, error: null });
await store.updateTask(params.id, {
status: null,
error: null,
...buildManualRetryResetPatch(),
});
// Move to todo column
await store.moveTask(params.id, 'todo');