feat(FN-3939): audit and patch PR #59 residual defects, add retry parity an

Completes FN-3939 audit work for PR #59 residual defects by refactoring the mission execution loop in the engine (halving its complexity), adding self-healing test coverage, and wiring in task workflow route improvements. Retry and validation behavior updates are documented in the changeset and skil

Fusion-Task-Id: FN-3939
This commit is contained in:
Fusion
2026-05-10 16:19:04 -07:00
committed by gsxdsm
parent 60d16aa267
commit 76c00fbadd
15 changed files with 308 additions and 94 deletions

View File

@@ -75,7 +75,7 @@ Unpause a task — resumes automated agent and scheduler interaction.
### fn_task_retry
Retry a failed task — clears the error state. Tasks in other columns move to todo; tasks in in-review stay in-place for auto-merge retry.
Retry a failed task — clears the error state. Non-review failures move to todo; in-review execution failures move to todo preserving progress; in-review merge failures stay in-place for auto-merge retry.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|

View File

@@ -19,7 +19,7 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
| `fn_task_attach` | Attach a file to a task. Supports images (png, jpg, gif, webp) and text files (txt, log, json, yaml, yml, toml, csv, xml). |
| `fn_task_pause` | Pause a task — stops all automated agent and scheduler interaction for this task. |
| `fn_task_unpause` | Unpause a task — resumes automated agent and scheduler interaction. |
| `fn_task_retry` | Retry a failed task — clears the error state. Tasks in other columns move to todo; tasks in in-review stay in-place for auto-merge retry. |
| `fn_task_retry` | Retry a failed task — clears the error state. Non-review failures move to todo; in-review execution failures move to todo preserving progress; in-review merge failures stay in-place for auto-merge retry. |
| `fn_task_duplicate` | Duplicate an existing task, creating a fresh copy in planning. Copies the title and description but resets all execution state. The AI planning agent will replan the new task. |
| `fn_task_refine` | Request a refinement of a completed or in-review task. Creates a new follow-up task in planning that references the original task as a dependency. Use this when a done or in-review task needs additional work, improvements, or follow-up changes. |
| `fn_task_archive` | Archive a done task (move from done → archived). Archived tasks are preserved for historical reference but moved out of the main board view. |

View File

@@ -1693,6 +1693,73 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
});
});
describe("fn_task_retry", () => {
it("moves execution-failed in-review task (incomplete steps) to todo preserving progress", async () => {
const store = new TaskStore(tmpDir);
await store.init();
const task = await store.createTask({
title: "execution-failed task",
description: "test",
column: "todo",
});
await store.updateTask(task.id, {
steps: [
{ name: "Step 0", status: "done" },
{ name: "Step 1", status: "in-progress" },
{ name: "Step 2", status: "pending" },
],
});
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" });
const retryTool = api.tools.get("fn_task_retry")!;
const result = await retryTool.execute("retry-exec", { id: task.id }, undefined, undefined, makeCtx(tmpDir));
expect(result.isError).toBeFalsy();
expect(result.details.newColumn).toBe("todo");
const updated = await store.getTask(task.id);
expect(updated?.column).toBe("todo");
expect(updated?.status).toBeFalsy();
expect(updated?.error).toBeFalsy();
expect(updated?.steps[1].status).toBe("in-progress");
});
it("keeps merge-failed in-review task (all steps done) in in-review and resets merge state", async () => {
const store = new TaskStore(tmpDir);
await store.init();
const task = await store.createTask({
title: "merge-failed task",
description: "test",
column: "todo",
});
await store.updateTask(task.id, {
steps: [
{ name: "Step 0", status: "done" },
{ name: "Step 1", status: "done" },
],
});
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 });
const retryTool = api.tools.get("fn_task_retry")!;
const result = await retryTool.execute("retry-merge", { id: task.id }, undefined, undefined, makeCtx(tmpDir));
expect(result.isError).toBeFalsy();
expect(result.details.newColumn).toBe("in-review");
const updated = await store.getTask(task.id);
expect(updated?.column).toBe("in-review");
expect(updated?.status).toBeFalsy();
expect(updated?.error).toBeFalsy();
expect(updated?.mergeRetries).toBe(0);
});
});
describe("fn_list_agents", () => {
it("returns agent list", async () => {
await seedAgent(tmpDir, { name: "alpha-agent" });

View File

@@ -826,12 +826,13 @@ export default function kbExtension(pi: ExtensionAPI) {
name: "fn_task_retry",
label: "fn: Retry Task",
description:
"Retry a failed task — clears the error state. Tasks in other columns move to todo; tasks in in-review stay in-place for auto-merge retry.",
"Retry a failed task — clears the error state. Non-review failures move to todo; in-review execution failures move to todo preserving progress; in-review merge failures stay in-place for auto-merge retry.",
promptSnippet: "Retry a failed Fusion task (clears error, moves to todo or stays in in-review)",
promptGuidelines: [
"Use when a task has failed and needs to be retried",
"Only tasks in 'failed' or 'stuck-killed' state can be retried",
"Tasks in 'in-review' stay in in-review — only the error/retry state is cleared, and the auto-merge system re-attempts",
"In-review tasks with incomplete steps (pending/in-progress) move to todo with preserveProgress so execution can resume",
"In-review tasks with all steps done stay in in-review and reset merge retry state for auto-merge re-attempt",
"Tasks in other columns are moved to the todo column with error state cleared",
],
parameters: Type.Object({
@@ -862,12 +863,26 @@ export default function kbExtension(pi: ExtensionAPI) {
};
}
// In-review retry: keep the task in in-review, clear only error/retry state
// In-review retry: distinguish between execution failures and merge failures.
if (task.column === 'in-review') {
const hasIncompleteSteps =
task.steps.length > 0 &&
task.steps.some((s: { status: string }) => s.status === "pending" || s.status === "in-progress");
if (hasIncompleteSteps) {
await store.updateTask(params.id, { status: null, error: null, stuckKillCount: 0 });
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 {
content: [{ type: "text", text: `Retried ${params.id} → todo (execution failure, preserving step progress)` }],
details: { taskId: params.id, newColumn: 'todo' },
};
}
await store.updateTask(params.id, { status: null, error: null, stuckKillCount: 0, mergeRetries: 0 });
await store.logEntry(params.id, "Retry requested via Fusion extension (in-review retry, mergeRetries reset)");
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, task stays in in-review)` }],
content: [{ type: "text", text: `Retried ${params.id} → in-review (merge retry state cleared)` }],
details: { taskId: params.id, newColumn: 'in-review' },
};
}