maybeObserveWorkflowParity compares the selected workflow graph's routing
against the legacy authoritative run for the same task and records
workflow:parity-observed / -drift audit events (aggregated by
getWorkflowParitySummary). The shadow walks the graph via WorkflowGraphTaskRunner
with no-side-effect seams whose outcomes mirror the legacy task's reality
(execute reached review?, review verdict, merged?), then maps visited seam nodes
to execute/review/merge stages. Hooked in handoffTaskToReview, gated by
workflowInterpreterDualObserve (off by default), wrapped so it never affects the
authoritative handoff. Scope: execute->review->merge routing parity; full
execution-fidelity shadow is future graduation work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the two needs-human findings from PR #1363 review, plus surface the flag.
Merge seam (project-engine.ts):
- manualMergeResolvers is now a per-task LIST of waiters. Both the dashboard
"merge now" path and the interpreter merge seam call onMerge, so a single
resolver per task let the second caller overwrite (and strand) the first.
All resolve/reject/requeue/late-resolver/shutdown sites drain the whole list.
- New requestInterpreterMerge() honors auto-merge eligibility: when autoMerge
is off (or the task isn't merge-ready) it returns merged:false instead of
forcing the merge, so a graph merge node can't override an autoMerge-off
project — it parks the task in review for a human. setMergeRequester now wires
the interpreter to this gate rather than the human bypass.
Selection race (store.ts):
- selectTaskWorkflow/clearTaskWorkflowSelection now hold one withTaskLock across
their whole mutate sequence. Extracted updateTaskUnlocked() (the per-task lock
is non-reentrant, so they couldn't wrap the public updateTask without
deadlocking) and call that inside the lock.
Settings:
- Add "Workflow Graph Engine (run custom workflows)" to the Experimental
Features list so the workflowGraphExecutor flag is a labeled toggle in
Settings → Experimental, not just a raw key.
Tests: interpreter-merge-seam.test.ts (multi-waiter resolve/reject + autoMerge
eligibility gate); existing merge lifecycle/bypass/selection suites still pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile + CodeRabbit findings across core/engine/dashboard. Stale findings
(written against earlier commits) verified and skipped; valid ones fixed.
Engine:
- await-input: do not clear pausedReason in the /input route (the node's
marker must survive unpause); the node clears it after consuming input.
Embed a colon-free epoch watermark in the marker so only post-pause steering
comments count as the reply (ISO timestamps collided with the colon
separator and the dashboard question parser).
- gate nodes without a registered runner now fail closed (throw) instead of
silently passing.
- a thrown interpreter error in maybeExecuteWorkflowGraph now falls back to the
legacy pipeline instead of stranding the task in-progress.
- approved-CLI path clears the stale awaiting-cli-approval status/marker.
Core:
- persist+cascade workflow selection: purge task_workflow_selection rows and
compiled workflow_steps on physical task deletes; migration 105 cleans
already-orphaned rows; catch-cleanup for materialized steps when the owner
write fails; WF-id allocation now in a BEGIN IMMEDIATE transaction.
- compiler validates the canonical execute->review->merge seam order (rejects
duplicate/misordered seams).
- disk-backed reopen round-trip + tightened updatedAt/list assertions.
Dashboard:
- WorkflowSelector clears stale default/options across project changes and on
fetch failure; InlineCreateCard/NewTaskModal reset the workflow on all
clear/discard paths and include it in dirty-state.
- WorkflowNodeEditor: config-key deletion now persists; removed an invalid
eslint-disable that was itself a hard lint error.
- TaskCard: single status badge for awaiting-input (no duplicate).
- WorkflowResultsTab: reset paused-action UI between pauses; surface
resume/approve failures inline.
- TaskDetailModal: treat awaiting-user-input/awaiting-cli-approval/paused as
not-in-progress for the live-log subscription.
- workflow-flow-mapping: don't write synthetic node names back into IR.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Main independently landed the same pausedReason persistence fix (column in
the task INSERT/upsert, TaskRow type, rowToTask mapping) with the column
ordered between paused and userPaused; adopt main's ordering and drop this
branch's duplicate entries so the values array stays aligned. Union the
dashboard package.json deps (main's compound-engineering plugin alongside
this branch's sorted pi-coding-agent/cli-printing-press/cursor-runtime).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Agent-native parity: users can list workflows and select one for a task in
the dashboard, so agents should be able to as well. Adds two task-session
tools — fn_workflow_list (read: built-ins + user definitions) and
fn_workflow_select (assign a workflow to a task, defaulting to the current
one) — wired into the executor's customTools and exported from the engine
index. Covered by unit tests against mock stores.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The node "Auto-approve requests" toggle was captured but unused. The only
human-approval pause reachable from a custom node is the CLI first-run
trust-on-first-use gate (review-style nodes run as ephemeral readonly agents
with no permission gate), so autoApprove now bypasses that pause — a superset
of the CLI-specific cliSkipApproval flag. The inspector explains the effect
when the toggle is on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #1363 review findings:
- core: pausedReason was written in-memory and read by SELECT but never
persisted by the task upsert (missing column/value) nor mapped back in
rowToTask — so it was lost on every reload. Add it to both. This is the
root cause behind the workflow CLI-approval / await-input pause cycle and
also fixes token-budget / worktrunk pause reasons silently vanishing.
- dashboard: approve-cli now derives the approved command exclusively from
the task's pausedReason; a caller-supplied body.command is ignored, closing
a trust-on-first-use bypass.
- engine: await-input nodes resume only when THIS node paused the task (its
marker on pausedReason), not on any pre-existing steering comment.
- engine: write-capable custom nodes (coding/script/CLI) are refused until a
task worktree exists, so they never mutate the shared repo root before the
execute seam.
- engine: document cliSkipApproval as an intentional workflow-author-only
escape hatch; scriptName is now const (ESLint).
- tests: pausedReason round-trip coverage in store-persistence; approve-cli
body-command-ignored + no-pending-command coverage; built-in-aware list
assertion in workflow-routes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Users can now watch everything the agent does while a CE stage works, steer
it mid-stage, and read the whole conversation as a proper chat surface.
Live output:
- New host capability: CreateInteractiveAiSessionOptions.onProgress — the
engine adapter streams thinking/text deltas + tool start/end markers from
the pi agent hooks (any plugin can use this).
- Orchestrator buffers per-session live activity (merged deltas, discrete
tool lines, capped), emits throttled progress events over SSE, and
GET /sessions/:id attaches it as liveActivity for the polling fallback.
- Routes detach turn execution: start/answer/resume return immediately
(status active) and clients converge via push/poll — the turn is watchable
instead of hidden inside a blocking POST.
- Turn timeout is now INACTIVITY-based: an actively-working long turn is
never killed; a quiet one interrupts with its working trace preserved.
- On settle the trace persists into history as a condensed record.
Steering:
- Stage protocol: responses may be a direct answer, {value, comment}
(answer + guidance), or {feedback} (guidance without answering); the
system prompt instructs agents to treat steering as first-class input.
- CeFlow: guidance textarea alongside selectable questions — attach to the
clicked answer, or "Send guidance" on its own.
Q&A UI:
- Transcript no longer hides control records: past questions/answers render
as chat bubbles (option ids → labels), steering turns marked, working
traces as collapsible "Agent work" blocks, completion marker.
- Live working pane (pulse + streaming thinking/tool lines) while a turn runs.
Tests: 130 plugin tests green (14 new: live buffer/flush ordering, inactivity
watchdog survives active work, detached convergence, steering payload shapes,
transcript rendering, live pane). Engine seam tests green; plugin/core/
engine/dashboard tsc clean. Core full suite OOMs locally (known orchestrator-
shell issue) — covered by CI shards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A CLI node can set cliSkipApproval to bypass the trust-on-first-use pause and
run its command immediately. Exposed as a checkbox in the node inspector.
- runFeatureValidation now lazy-ensures a linked assertion (FN-5902) instead
of the removed zero-assertion auto-pass, for both task-completion and the
stranded-feature recovery path
- CONCEPTS.md: union of main's Merge-lifecycle cluster and this branch's
Missions clusters; Contract Assertion entry updated for FN-5902 semantics
- AGENTS.md: take main's docs/solutions + CONCEPTS.md pointer wording
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CLI prompt nodes now accept a raw cliCommand (any command + args), not just
named scripts. A raw command must be explicitly approved by the user before it
runs: an unapproved command pauses the task (status awaiting-cli-approval) with
the command shown; the user approves via POST /tasks/:id/workflow/approve-cli,
which records the exact command string in settings.approvedWorkflowCliCommands
and resumes. Named scripts (settings.scripts) still never require approval.
Adds POST /tasks/:id/workflow/input to answer await-input nodes (records a
steering comment + resumes).
Prompt nodes now support an executor kind: model (provider/model override),
agent (adopts a named agent's model + custom instructions), skill (prompt
becomes a skill invocation), and cli (named project script with the prompt in
FUSION_NODE_PROMPT — raw commands still never accepted). Per-node maxRetries
overrides the executor-wide default (capped at 10). Await-input nodes pause the
task with status 'awaiting-user-input' and the question as pausedReason; on
unpause the newest steering comment is consumed as the answer and exposed in
graph context.
- liveness: graph-routed tasks count as executing in getExecutingTaskIds/
isTaskActive and are skipped by recoverCompletedTask + the completed-task
resume fast-path — recovery can no longer drive a parallel lifecycle (P0)
- handleGraphFailure: sets status 'failed' (self-healing revival exemption,
prevents FN-5704-style re-run loop) and leaves paused tasks untouched
- execute seam distinguishes pause/abort from implementation failure
- merge seam: 30-minute timeout so a wedged queue cannot strand the run
- awaitAbortInFlightTaskWork: defensive interceptor/routing cleanup
- deleteWorkflowDefinition: cascades to selections, materialized steps,
affected tasks' enabledWorkflowSteps, and the project default
- Add behavior-level tests for the shared merge-enqueue funnel
(enqueueEligibleInReviewTasks) with a Surface Enumeration of all
in-review entry surfaces, per review
- Seed real stale in-review fixtures in the FN-5147 no-mutation
regression block so sweeps enumerate candidates and the assertions
are non-vacuous
- Keep per-task auto-merge gating uniform across reclaim/contamination
candidate columns: the suggested in-review-only scoping broke the
FN-5704 regression contract (reclaim short-circuits when autoMerge
is off); documented the tension in code comments and the learning doc
- Drop hardcoded commit hash from the learning doc
Real engine seams: execute delegates to the legacy implementation phase via a
completion interceptor that stops execute() at the implementation-complete
boundary (no double review/merge); review performs the in-review handoff; merge
resolves through ProjectEngine.onMerge over the same serialized merge queue
(wired via a late-bound setMergeRequester, mirroring setMergeEnqueuer). Custom
graph nodes run on the proven WorkflowStep machinery (readonly tool policy,
verdict parsing). Adds a 'planning' seam to the vocabulary (no-op for
pre-specified tasks; custom planning is a prompt node today).
Entry point: execute() routes graph-selected tasks through the runner when
experimentalFeatures.workflowGraphExecutor is on, with process-wide routing
claims (FN-4811 posture), duplicate-dispatch dropping, pre-run errors falling
back to legacy, and mid-run errors parking the task in review (never re-running
the implementation, never stranding the task).
Flag off by default: all 587 executor tests pass unchanged.
Loads a task's selected workflow, runs the graph with injected legacy seams
(execute/review/merge) and a custom-node runner, and maps the terminal outcome
to completed/failed/fell-back. Any interpreter-level error falls back so the
caller can run the legacy pipeline — a task is never stranded. Covered with
fake seams: lifecycle ordering, failure routing, gate blocking, fallback
reasons, diagnostics isolation. Includes the interpreter-cutover plan doc.
Non-seam prompt/script nodes now dispatch to an injected WorkflowCustomNodeRunner
instead of throwing; gate nodes support an executable (prompt/script-backed) form
alongside the original context-gate contract. WorkflowGraphExecutor accepts the
runner via deps.
Tasks with autoMerge explicitly enabled never auto-merged when the
project-level setting was disabled: the merge enqueue gate
(allowInReviewMergeProcessing) and all 19 in-review self-healing sweeps
checked only settings.autoMerge, and the board stall-signal hydration
passed the raw global into the diagnostic gates.
Introduce allowsAutoMergeProcessing(task, settings) in core — additive
relative to the global setting so configs with global auto-merge ON are
unchanged (explicit autoMerge:false tasks still flow to the merger's
manual-required parking) — and use it at the enqueue gate, every
self-healing sweep, and the store's stall/stalled signal contexts.
A mission feature could be left status="done" while its loopState never
advanced past "implementing" and it had no linked board task, so it was
never validated. The slice-completion gate (computeSliceStatus) correctly
refuses to count an assertion-linked done feature until its validator
passes, but nothing re-drove a task-less feature — so the slice, milestone,
and whole mission could never auto-progress.
Active-mission recovery now detects these stranded done features and re-runs
assertion validation directly (read-only judge, no board task): on pass the
feature becomes legitimately complete, on fail the normal fix-feature flow
takes over. Extracted the feature-validation path into a shared
runFeatureValidation helper used by both task-completion and recovery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The FN-5930 squash merge combined two wait-for-exit strategies,
leaving a shadowed 'const exited' and double await. Collapse to
a single register-before-kill pattern.
Fusion-Task-Id: FN-5930
Reduce flakiness in the real-git verification spawn supervision test.\n\n- write the child PID to stdout with an awaited newline flush before the parent exits\n- collapse the scenario branching so the SIGTERM path is mutually exclusive with crash handling\n- await parent process exit during cleanup and document coverage across normal, signal, and crash teardown paths\n\nFiles changed:\n .../verification-spawn-supervision.real-git.test.ts | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-5930
Fusion-Task-Lineage: 20c3fe73-a640-4c34-8a07-edf5bdda4b25
Closes the U2/U5 skill-discovery carry-forward so the plugin's interactive ce-*
sessions actually load the stage's bundled skill in a live agent (not just in
scripted-fake tests).
Root cause: createFnAgent built its DefaultResourceLoader without forwarding any
skill-discovery path, and the interactive seam options couldn't carry one. The
loader's skillsOverride only *filters* skills already discovered from cwd's
standard roots, so the plugin-local .fusion-ce-skills/<id>/SKILL.md was never
discoverable.
Fix (end-to-end):
- AgentOptions.additionalSkillPaths forwarded into DefaultResourceLoader
- CreateInteractiveAiSessionOptions gains requestedSkillNames + additionalSkillPaths
- the interactive engine adapter forwards them to createFnAgent (skills +
additionalSkillPaths)
- the orchestrator runs the session with cwd on the real project root and hands
it [stage.skillId] + the install root
Proven: a real DefaultResourceLoader with additionalSkillPaths discovers ce-plan
and filters out ce-work; the orchestrator passes the right id/path/cwd. Plugin 96,
engine 136, core 99 tests green.
Keep planning summary actions responsive with operation-specific loading indicators.
- pass separate single-task and breakdown loading flags into the planning summary view
- show the Creating spinner only on Create Single Task and the Breaking down spinner only on Break into Tasks while keeping the sibling action disabled
- add regression coverage for both pending-action paths and normalize the restart integration test temp worktree root under /private/tmp
Files changed:
.../dashboard/app/components/PlanningModeModal.tsx | 14 ++-
.../PlanningModeModal.planning-flow.test.tsx | 138 +++++++++++++++++++++
.../src/__tests__/restart.integration.test.ts | 7 +-
3 files changed, 151 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-5912
Fusion-Task-Lineage: b93da566-0c2b-4ff8-83ea-b6e009dfd650
Add unread mailbox status to the desktop header toggle while keeping pending approvals prioritized.
- show an unread status dot on the desktop header mailbox toggle when unread mail exists without pending approvals
- keep pending-approval indicators taking precedence and hide mailbox indicators while the mailbox view is active
- extend Header coverage for unread-only, pending-only, combined, zero-count, and active-mailbox states
- update restart integration coverage to reuse an existing worktree during orphaned resume concurrency
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/Header.tsx | 9 +++++--
.../app/components/__tests__/Header.test.tsx | 26 ++++++++++++++++---
.../src/__tests__/restart.integration.test.ts | 29 ++++++++++++++++++++--
4 files changed, 58 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-5915
Fusion-Task-Lineage: 3a207e2c-ca58-402f-94cf-3a9e514fe263
Bundle pinned copies of 7 CE pipeline-stage skills (strategy, ideate,
brainstorm, plan, work, code-review, compound) under src/skills/ and declare
them via PluginSkillContribution. Empirical finding: the skills contribution
alone does not make a SKILL.md resolvable in a session -- the engine ingests it
as a name only. So onLoad runs an idempotent, isolation-guarded physical install
into a plugin-local .fusion-ce-skills/ dir (never a global ~/.claude/skills),
which the engine skill-resolver can then discover. Proven against the real
loadSkills + resolveSessionSkills pipeline.
Expose createInteractiveAiSession on route/loader PluginContext alongside the
one-shot createAiSession. The prompt->parse->retry->pause->resume loop is
reimplemented in an engine seam modeled on planning.ts (no engine await-input
primitive exists). Reuses PlanningQuestion/PlanningResponse; stays generic with
no plugin-specific concepts. Injected on route contexts only; tool/runtime
contexts omit it (parity with createAiSession).
Ensure agent-created ntfy notifications fall back to the task description when the title is still empty.
- pass task descriptions through task-created notification payloads
- cover empty-title task creation in notification service tests
- verify ntfy task-created messages render the id+description fallback
- add a patch changeset for the published CLI package
Files changed:
.changeset/fn-5890-task-created-title.md | 5 +++++
.../engine/src/__tests__/notification-service.test.ts | 12 +++++++++++-
packages/engine/src/__tests__/ntfy-provider.test.ts | 16 ++++++++++++++++
packages/engine/src/notification/notification-service.ts | 1 +
4 files changed, 33 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5890
Fusion-Task-Lineage: 4a67a4d0-c3a8-4923-a506-7a67a8836127
Retry incomplete executor work with a fresh session after non-continuable session errors.
- add executor recovery handling that clears session state and requeues incomplete tasks to todo while retry budget remains
- preserve terminal failure behavior once the fresh-session retry budget is exhausted
- extend reliability coverage and AGENTS.md backstop notes for the new retry path
Files changed:
AGENTS.md | 1 +
.../post-done-continuation-no-wedge.test.ts | 48 +++++++++++++++++++++-
packages/engine/src/executor.ts | 40 ++++++++++++++++++
3 files changed, 87 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5888
Fusion-Task-Lineage: 4ebce260-8923-4599-aced-541533b94543
Persist merge metadata for AI fast-forward landings and done-task recovery.
- capture landed files and shortstat metadata from the single landed commit in the AI merge finalizer
- persist mergeDetails and modifiedFiles for landed squash commits, and record commit associations without setting no-op attribution flags
- extend done-task self-healing to backfill merge metadata when baseCommitSha exists but mergeDetails is empty
- add reliability and self-healing coverage for landed-file persistence, empty AI merges, and recovery behavior
Files changed:
AGENTS.md | 1 +
.../ai-merge-ff-landed-files.test.ts | 150 +++++++++++++++++++++
packages/engine/src/__tests__/self-healing.test.ts | 76 +++++++++++
packages/engine/src/merger-ai.ts | 40 +++++-
packages/engine/src/merger.ts | 30 ++++-
packages/engine/src/self-healing.ts | 11 +-
6 files changed, 303 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-5874
Fusion-Task-Lineage: a407910c-9ce8-4049-86ef-e80f045c981a
Tighten the branch-conflicts recovery test to use an isolated secondary worktree root.
- create a unique temporary parent directory for the detached secondary worktree
- track the temporary root for cleanup instead of only the nested worktree path
- keep the reanchor regression focused on detached worktree recovery behavior
Files changed:
packages/engine/src/__tests__/branch-conflicts-recovery.test.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5863
Fusion-Task-Lineage: 6cfb02c0-e9fd-456d-a78b-451e3e0707b2
Update Fusion to the latest pi coding agent and pi AI dependency release.
- bump @earendil-works/pi-coding-agent from ^0.77.0 to ^0.78.0 in the CLI, dashboard, and engine packages
- bump @earendil-works/pi-ai from ^0.77.0 to ^0.78.0 where it is consumed
- add a patch changeset documenting the published @runfusion/fusion update and refresh the lockfile
Files changed:
.changeset/fn-5862-pi-coding-agent-0.78.md | 5 +
packages/cli/package.json | 4 +-
packages/dashboard/package.json | 2 +-
packages/engine/package.json | 4 +-
pnpm-lock.yaml | 193 +++++++++++++++++++----------
5 files changed, 138 insertions(+), 70 deletions(-)
Fusion-Task-Id: FN-5862
Fusion-Task-Lineage: abdfa987-7ba5-45f7-8bea-3db3329a760c