feat(FN-4946): complete steps 5-8 implicit refusal docs and budget coverage

Fusion-Task-Id: FN-4946
Fusion-Task-Lineage: 9a48f72f-d2cc-4950-8a00-f69053dd1163
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 21:39:09 -07:00
committed by gsxdsm
parent c1a01fbb8a
commit 28595f57e9
3 changed files with 21 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Extend FN-4851 fn_task_done refusal guards (pending-code-review-revise, bulk-step-completion-without-review) to the implicit-completion path so agents cannot bypass them by drip-marking every step done via fn_task_update and exiting without calling fn_task_done. Implicit refusals share the existing requeue budget and escalate to in-review on exhaustion.

View File

@@ -367,6 +367,7 @@ The Ink-based TUI is part of `fn` (no separate `@fusion/tui` package). Implement
Structured logging via `createLogger()` from `packages/engine/src/logger.ts`. All lines prefixed with subsystem name. See [docs/diagnostics.md](./docs/diagnostics.md) for the full key-diagnostic-points catalog. Notable subsystems include `[executor]`, `[scheduler]`, `[stuck-detector]`, `[auto-claim-snapshot]`, `[prompt-size]`, `[wake-trigger-diagnostics]`, `[retry-burned]`, and `[room-ambiguity]`. Structured logging via `createLogger()` from `packages/engine/src/logger.ts`. All lines prefixed with subsystem name. See [docs/diagnostics.md](./docs/diagnostics.md) for the full key-diagnostic-points catalog. Notable subsystems include `[executor]`, `[scheduler]`, `[stuck-detector]`, `[auto-claim-snapshot]`, `[prompt-size]`, `[wake-trigger-diagnostics]`, `[retry-burned]`, and `[room-ambiguity]`.
`AgentSemaphore` (`packages/engine/src/concurrency.ts`) has defensive guards: `limit` getter returns minimum 1; `availableCount` returns 0 for invalid limits. `AgentSemaphore` (`packages/engine/src/concurrency.ts`) has defensive guards: `limit` getter returns minimum 1; `availableCount` returns 0 for invalid limits.
- `[executor] FN-XXX: fn_task_done refused (<class>) — <reason>` (explicit tool path) and `[executor] FN-XXX: fn_task_done refused (<class>) — <reason> (implicit completion)` (implicit all-steps-done path) now share refusal-class diagnostics for `summary-claims-incomplete` (explicit only), `bulk-step-completion-without-review`, and `pending-code-review-revise`; both paths consume the same `MAX_TASK_DONE_REQUEUE_RETRIES` budget and escalate to `in-review` with `status: "failed"` on exhaustion.
## Dashboard UI Styling Guide ## Dashboard UI Styling Guide

View File

@@ -77,10 +77,23 @@ describe("FN-4946 implicit refusal budget handling", () => {
const executor = new TaskExecutor(store as any, "/repo"); const executor = new TaskExecutor(store as any, "/repo");
await executor.execute(currentTask); await executor.execute(currentTask);
// Burn explicit-path refusal budget from 2 -> 3 (still todo), then implicit refusal should escalate immediately.
await doneTool.execute("d1", { summary: "I am not done yet." }); await doneTool.execute("d1", { summary: "I am not done yet." });
await (executor as any).handleImplicitTaskDoneRefusal({ ...currentTask, id: "FN-4946-B2" }, "/repo/.worktrees/swift-falcon", refusal()); expect(currentTask.taskDoneRetryCount).toBe(3);
expect(store.moveTask).toHaveBeenCalledWith("FN-4946-B2", "in-review"); await (executor as any).handleImplicitTaskDoneRefusal(
{ ...currentTask, id: "FN-4946-B2", column: "todo" },
"/repo/.worktrees/swift-falcon",
refusal(),
);
const inReviewMoves = store.moveTask.mock.calls.filter((call: any[]) => call[0] === "FN-4946-B2" && call[1] === "in-review");
expect(inReviewMoves.length).toBeGreaterThanOrEqual(1);
const implicitEscalationUpdate = store.updateTask.mock.calls.find(
([id, patch]: [string, Record<string, unknown>]) =>
id === "FN-4946-B2" && patch.status === "failed" && !("taskDoneRetryCount" in patch),
);
expect(implicitEscalationUpdate).toBeTruthy();
}); });
it("resets taskDoneRetryCount after later clean completion", async () => { it("resets taskDoneRetryCount after later clean completion", async () => {