feat(FN-4429): document user-cancel move-to-todo contract
Fusion-Task-Id: FN-4429 Fusion-Task-Lineage: b010437b-0e58-43f6-81b3-40dcebbf0873
This commit is contained in:
5
.changeset/fn-4429-user-cancel-move.md
Normal file
5
.changeset/fn-4429-user-cancel-move.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
User-initiated drag/move of an in-progress task back to todo now hard-cancels the active executor session, aborts running task work before disposal, and parks the task with `userPaused: true` so scheduler dispatch does not immediately restart it.
|
||||||
@@ -221,6 +221,8 @@ const { stdout, stderr } = await execAsync(command, {
|
|||||||
|
|
||||||
`execSync` is only acceptable for short, deterministic git plumbing (`git rev-parse`, `git branch -d`, `git worktree remove`, etc.). When in doubt, use async.
|
`execSync` is only acceptable for short, deterministic git plumbing (`git rev-parse`, `git branch -d`, `git worktree remove`, etc.). When in doubt, use async.
|
||||||
|
|
||||||
|
User-initiated `moveTask(in-progress → todo)` is a hard cancel contract: executor listeners must abort active sessions before dispose, stop step/workflow subprocesses, and leave the task parked in `todo` with `userPaused` semantics intact. Engine-initiated rebounds (pause, stuck recovery, workflow rerun, self-healing) must continue to use default `moveSource: "engine"` plus the appropriate `preserve*` flags (`preserveResumeState`, `preserveProgress`, `preserveWorktree`) and must not set `userPaused`.
|
||||||
|
|
||||||
## Git Conventions
|
## Git Conventions
|
||||||
|
|
||||||
- Commit messages: `feat(FN-XXX):`, `fix(FN-XXX):`, `test(FN-XXX):`
|
- Commit messages: `feat(FN-XXX):`, `fix(FN-XXX):`, `test(FN-XXX):`
|
||||||
|
|||||||
@@ -1100,6 +1100,13 @@ Task steps use statuses: `pending`, `in-progress`, `done`, `skipped`.
|
|||||||
- **Pre-merge** steps run in executor (`runWorkflowSteps()`) — bypassed in fast mode
|
- **Pre-merge** steps run in executor (`runWorkflowSteps()`) — bypassed in fast mode
|
||||||
- **Post-merge** steps run in merger (`runPostMergeWorkflowSteps()`)
|
- **Post-merge** steps run in merger (`runPostMergeWorkflowSteps()`)
|
||||||
|
|
||||||
|
### User cancel via move-to-todo
|
||||||
|
- `TaskStore.moveTask()` accepts `moveSource: "user" | "engine"` (default `"engine"`) and emits `task:moved` with `source` so listeners can distinguish manual moves from engine rebounds.
|
||||||
|
- Manual `in-progress → todo` moves (dashboard route `/tasks/:id/move` with `moveSource: "user"`) atomically set `task.userPaused = true`; engine/default rebounds do not.
|
||||||
|
- Any move to `in-progress` clears `task.userPaused` in the same store write so explicit redispatch resumes normally.
|
||||||
|
- `TaskExecutor` treats manual `in-progress → todo` as hard cancel: it marks the task as user-canceled, aborts active session types before dispose/termination, and suppresses preserve-resume auto-bounces while logging `Execution canceled by user — leaving task in todo`.
|
||||||
|
- Scheduler dispatch loop skips `todo` tasks with `userPaused === true` (queues with a user-paused reason) until a user explicitly moves the task back to `in-progress`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Stalled review detection
|
### Stalled review detection
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ fn task unarchive FN-001
|
|||||||
|
|
||||||
**Paused-state normalization on reopen:** When a task is moved from `in-progress`, `in-review`, or `done` back to `todo` or `triage` (retry/requeue), Fusion clears `task.paused` and `task.pausedByAgentId` to prevent contradictory `todo + paused` or `in-progress + paused` states. A paused task in `todo` is excluded from scheduler dispatch.
|
**Paused-state normalization on reopen:** When a task is moved from `in-progress`, `in-review`, or `done` back to `todo` or `triage` (retry/requeue), Fusion clears `task.paused` and `task.pausedByAgentId` to prevent contradictory `todo + paused` or `in-progress + paused` states. A paused task in `todo` is excluded from scheduler dispatch.
|
||||||
|
|
||||||
|
**Manual cancel park (`userPaused`):** A user-initiated move from `in-progress` to `todo` (`moveSource: "user"`) sets `task.userPaused = true` and hard-cancels active executor work. Scheduler treats `userPaused` tasks as intentionally parked and will not auto-dispatch them until the task is explicitly moved back to `in-progress` (which clears `userPaused`). Engine/internal requeues (`moveSource: "engine"`) do not set this flag.
|
||||||
|
|
||||||
**Paused-state normalization on explicit completion:** When an agent calls `fn_task_done` on a paused task, Fusion clears `task.paused` and `task.pausedByAgentId` regardless of the task's column (`in-progress` or `todo`). `task.paused` prevents new work from starting, but does not block an agent from completing in-flight work and transitioning the task to `done`. The scheduler respects `globalPause` independently.
|
**Paused-state normalization on explicit completion:** When an agent calls `fn_task_done` on a paused task, Fusion clears `task.paused` and `task.pausedByAgentId` regardless of the task's column (`in-progress` or `todo`). `task.paused` prevents new work from starting, but does not block an agent from completing in-flight work and transitioning the task to `done`. The scheduler respects `globalPause` independently.
|
||||||
|
|
||||||
**Global pause vs task pause:** `settings.globalPause` gates new scheduler dispatches and is checked by the `fn_task_done` handoff logic. Task-level `task.paused` is a per-task gate that blocks execution start. They are independent — a task can be paused individually even when `globalPause` is `false`, and clearing `task.paused` does not affect `globalPause`.
|
**Global pause vs task pause:** `settings.globalPause` gates new scheduler dispatches and is checked by the `fn_task_done` handoff logic. Task-level `task.paused` is a per-task gate that blocks execution start. They are independent — a task can be paused individually even when `globalPause` is `false`, and clearing `task.paused` does not affect `globalPause`.
|
||||||
|
|||||||
Reference in New Issue
Block a user