- Keep active streaming responses alive when resuming an existing quick chat session instead of resetting stream state
- Update useQuickChat session-resume logic to only restart waiting indicators when appropriate
- Adjust QuickChatFAB waiting indicator behavior so it reflects real in-flight assistant activity
- Add focused hook and component tests covering same-session resume and waiting-indicator regressions
Fusion-Task-Id: FN-2904
- Add missing Claude model entries and extend provider metadata handling for model extras
- Improve subprocess diagnostics in pi-claude-cli process management for clearer failure visibility
- Add targeted tests for provider model extras and process-manager diagnostic behavior
- Update Settings modal copy for project default model guidance and record changes in pi-claude-cli changelog
Fusion-Task-Id: FN-2898
- Strip private @fusion/* workspace devDependencies from the published CLI manifest via prepare-publish-manifest and package metadata updates
- Replace cross-spawn usage and add staged bundle layout assertions to verify resolver output in dist packaging
- Add per-task/project model override resolution across core, dashboard settings/task modals, and route coverage with new regression tests
- Strengthen engine merge/recovery handling for paused/interrupted/squash paths and surface merger timeline activity with additional self-healing and merger tests
- Add changesets for npm bundle dependency fixes, project model override stabilization, and FTS5 corruption recovery
Fusion-Task-Id: FN-2897
Follow-up to 502fddf20: when the merge was empty (no commit made),
filesChanged/insertions/deletions were still being captured from
git show --shortstat HEAD — which describes pre-merge HEAD's commit,
unrelated to this task. Consumers (dashboard, audit log) would render
those numbers next to "no commit landed", which is misleading.
Clear stats to 0 alongside the omitted commitSha. Also drop the stats
line from the agent-log summary when mergeWasEmpty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Users couldn't see the merger's per-task activity from the dashboard
agent-log view — only the executor's session output was visible. When a
merge took an unexpected path (rebase ran twice, attempt 2 auto-resolved
3 lockfiles, attempt 3 fell back, etc.) the only record was in process
logs, which most users don't have access to.
Add appendAgentLog calls at the high-signal merge events:
- Pre-merge rebase: when each stage (remote → remoteRef, local-base
→ local HEAD) completes successfully
- Each merge attempt start, with attempt number + strategy summary
- Final merge outcome: strategy, attempt count, commit sha, file stats,
and edge cases (empty merge, deferred sha)
Source attribution uses "merger" so the dashboard can style/filter
these distinct from the executor's per-step messages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
recoverInterruptedMergingTasks searched for landed commits by grepping
commit subjects for the task ID. Users with includeTaskIdInCommit=false
have commit subjects like `feat: ...` (no task ID), so if the merger
crashed after committing but before storing mergeDetails, recovery would
silently fail to find the commit and incorrectly retry the merge.
Three layered defenses:
1. Emit a Fusion-Task-Id: <id> trailer in every Fusion-managed merge
commit body. The 4 fallback commit invocations now include
`-m "Fusion-Task-Id: ..."`. After the AI agent commits, an
idempotent ensureTaskIdTrailerOnHead() amends the trailer in via
`git interpret-trailers` (no-op if already present).
2. findLandedTaskCommit now tries three sources in order:
a. task.mergeDetails.commitSha (if reachable from HEAD)
b. Fusion-Task-Id trailer grep (anchored regex)
c. Subject grep (legacy commits)
3. Trailer grep uses an anchored regex `^Fusion-Task-Id: <id>$` so
it doesn't false-match task IDs appearing as substrings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two cases where mergeDetails.commitSha was wrong:
1. Empty-squash success paths (mergeAttempt + attemptWithSideStrategy
return true when nothing was staged) recorded pre-merge HEAD as the
task's commitSha. That commit had nothing to do with this task —
misleading the dashboard, audit log, and recovery scans.
2. pushAfterMerge can trigger an internal pull --rebase that rewrites
HEAD; mergeDetails was captured before push, so the stored sha
referenced a now-orphaned commit.
Fix:
- Track an empty-merge flag on AiInvocationTracker, set at the three
squashIsEmpty/staged===0 sites. Metadata block omits commitSha when
the flag is set.
- After successful pushAfterMerge, recapture HEAD and update
mergeDetails.commitSha if it changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pre-merge rebase had the local-base rebase nested inside the
remote-rebase success path, so when no remote resolved (or worktreePath
was missing), the entire block exited without running local-base. That
left smart-prefer-main exposed: with no rebase, the -X ours fallback
would silently re-introduce code main had recently deleted, which is
exactly the case the strategy is meant to prevent.
Restructure so remote rebase (Stage 1) and local-base rebase (Stage 2)
run as independent gates. Local-base rebase still picks up sibling-task
merges that landed locally even when the remote stage was skipped or
disabled, so prefer-main always gets at least one defense.
Also relax the semantic-incompatibility guard: prefer-main now requires
EITHER stage to remain enabled (was: required worktreeRebaseBeforeMerge).
Extracted runLocalBaseRebase() helper to remove duplication between the
two entry points (after Stage 1 vs. standalone) and centralize the
ancestor-check + abort handling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit of task.status assignments across packages/core and packages/engine
surfaced five additional transient/in-flight statuses that should block
auto-merge but weren't in BLOCKING_TASK_STATUSES:
- awaiting-approval — triage spec awaiting user approval
- needs-replan — scheduler/executor/triage signaled re-plan needed
- mission-validation — mission-level validation in flight
- queued — scheduler-side transient state
- stuck-killed — defensive guard; task killed by stuck detector
Each represents a state where finalizing the merge violates user intent
or invariants. Group the set with comments explaining the intent of each
group so future additions can be slotted appropriately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two recovery scans operated on in-review tasks without checking
!task.paused:
- recoverMergedReviewTasks would move a paused task whose merge was
already confirmed to done, against user intent.
- recoverMisclassifiedFailures would clear the error on a paused failed
task, defeating the user's intent to investigate manually.
Add the paused guard to both, matching the pattern used by all other
recovery scans. Completes the pause-vs-stuck audit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Attempt 2 of the merge cascade caught any git merge --squash failure into
mergeExitedWithConflicts=true. If the failure was non-conflict (pre-commit
hook rejection, IO error, locked repo) and produced no U files, the code
fell into the "all conflicts auto-resolved" branch with empty classified
arrays, ran deterministic verification on pre-merge HEAD, and returned
true — recording merge metadata for a merge that never happened.
Distinguish "exit code 1 with U files" (recoverable) from "any other
failure" (real). When a real failure surfaces with no conflicts, raise a
sentinel MergeNonConflictError that the outer mergeAttempt catch propagates
without retrying — retrying just re-runs the same broken command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
recoverInterruptedMergingTasks was the one self-healing scan that didn't
guard !task.paused. A user who paused a task mid-merge (status=merging,
column=in-review) would still see the recovery scan finalize or unblock
the merge once the stuck timeout elapsed, violating their pause intent.
The other 12 self-healing recovery scans either explicitly skip paused
tasks or operate on terminal/metadata-only state where pause is moot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A task in column "in-review" but with status "planning" (or the legacy
"specifying" alias) was passing the merge eligibility check, so the
auto-merger would finalize a branch the user had just moved back to
re-plan. The hardcoded BLOCKING_TASK_STATUSES set didn't include either
status — only failed / awaiting-* / merging-* were checked.
Add both to the set so getTaskMergeBlocker reports them as blockers,
matching the expected user mental model of "moved to planning = won't
auto-merge".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The smart-prefer-main strategy depends on a successful pre-merge rebase
to honor main's deletions. Previously, three failure modes silently fell
through to the -X ours merge, which would re-introduce code main had
just removed (because branch additions vs main deletions don't textually
conflict and -X ours only resolves content conflicts, not modify/delete).
- Hard-fail when prefer-main is paired with worktreeRebaseBeforeMerge=false
(semantically incoherent combination)
- Hard-fail when the pre-merge rebase starts and aborts (any of the three
rebase paths: remote, nested local-base, or fallback local-only)
- Warn (not throw) on environmental silent skips — no remote resolvable
or no worktreePath — so the gap is observable in logs without breaking
common test/setup environments
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add Database APIs to rebuild the tasks_fts index and detect FTS5 corruption signatures
- Retry task upserts once after rebuilding FTS5 when corruption errors are encountered
- Extend TaskStore health checks to include FTS5 integrity verification
- Add regression tests for rebuild/integrity behavior and upsert recovery, plus a patch changeset for @runfusion/fusion
- Render merge strategy descriptions behind expandable disclosure sections in Settings modal
- Add dedicated disclosure styling and update task form expectations for the new copy structure
- Extend Settings modal tests to cover disclosure toggling and merge-description visibility
- Improve model onboarding API key action layout on mobile by stretching form width and aligning button placement
Previously this transition cleared only transient execution state
(status/error/worktree/blockedBy/workflowStepResults), but kept the
prior branch, baseBranch, baseCommitSha, summary, and recovery
counters. That meant retrying a reviewed task resumed on the old
branch with a stale summary instead of starting fresh. Now those
fields are also cleared on in-review → todo. Other reopen paths
(in-progress/done → todo/triage) keep their existing behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FN-2869 merge resolution claimed to keep this block removed but
the diff actually re-added it, so it started showing up on the task
details "definition" tab again.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Keep the GitHub import modal open after successful issue or PR import instead of auto-closing
- Reset selected issue/PR after import so the Import action disables until a new selection is made
- On mobile, return from preview to list view after a successful import for faster repeated imports
- Expand GitHubImportModal tests to cover stay-open behavior, selection reset, mobile view reset, and stabilized auto-load button assertions
Surface the Default Model selector directly after the authenticated
provider list so users see it without scrolling past the disclosures.
Hydration of the previously-saved default on reopen was already in
place — this just relocates the existing block.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the stale infusion-link changeset entry that conflicts with the FN-2837 correction already on main.
- Preserve the current runfusion.ai update-notice baseline instead of reintroducing reverted release-note metadata.
- Add a patch changeset entry for @runfusion/fusion describing the update-notice learn-more link adjustment.
- Record release-note metadata in .changeset/fn-2837-add-infusion-link.md for the upcoming publish.
The single "smart" strategy is now two flavors with the new default flipped
to prefer-main. Both share a pre-cascade `git fetch origin <currentBranch>`
+ best-effort fast-forward so a freshly-pushed sibling commit doesn't get
clobbered when the fallback resolves a conflict against a stale base.
- "smart-prefer-main" (new default): -X ours fallback. Protects just-merged
sibling work from being regressed by a concurrent task branch.
- "smart-prefer-branch": -X theirs fallback. Equivalent to legacy "smart".
Legacy "smart" / "prefer-main" enum values are accepted and normalized via
`normalizeMergeConflictStrategy()` so existing settings.json files migrate
seamlessly. The fast-forward step gracefully degrades on fetch failure or
divergent local main (logs and continues).
Updates settings UI dropdown, test helpers, and adds 5 fetch+ff regression
tests + 7 normalize-helper tests. Lint cleanup of two empty catch blocks
in scripts/release.mjs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The engine's merger imports MergeConflictStrategy from @fusion/core, but it
was defined in core/src/types.ts without being re-exported from index.ts,
breaking dashboard typecheck against a clean checkout (no engine dist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dry-run gate was at the bottom of the script, after version bump,
lockfile update, CHANGELOG sync, build, and the chore(release) commit
had already run — so a dry-run left the local repo with a stray version
commit that had to be reset. Move the gate to right after the version
selection so dry-run shows the full preview (changesets, proposed
version, override prompt) and exits before mutating anything. Drop the
now-dead late check and the redundant DRY_RUN short-circuit in confirm().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dry-run was previously bundled with --yes for the version prompt, so
it silently accepted the proposed version and skipped the interactive
flow it's meant to exercise. Only --yes should auto-accept.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inadvertent infusion.ai links slipped into the dashboard update-available
banner and settings modal (plus matching tests and changeset). Point them
at the actual marketing domain, runfusion.ai.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs(FN-2869): pluggable notification providers reference + architecture
- chore: changeset for pluggable notification providers
- ui(merge): clarify mergeConflictStrategy option labels — both "smart" and "prefer-main" run the full smart cascade; differ only in the final fallback (-X theirs vs -X ours)
- TaskDetailModal: keep Node Routing summary block removed
Conflicts in executor.ts, core/index.ts, and RoutingTab.* resolved by
keeping HEAD — fn-2869 branched from a stale main and was dragging in
older versions of files that have since been updated by sibling tasks
(including the FN-2887 rebase fix on executor.ts itself).
Show pending changeset summaries, compute the proposed semver via
`pnpm changeset status --output`, and prompt for an override before
applying. If the user picks a different version, post-process every
fixed-group package's package.json + CHANGELOG.md heading so the
commit, npm publish, and tag all use the chosen version. --yes and
--dry-run skip the prompt and accept the proposed version.
Adds a changeset for the recent SetupWizard scroll + model onboarding
handoff fixes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace notifications UI with reusable provider cards for ntfy and webhook settings
- Add event option metadata rendering and provider-specific test notification actions
- Align notification card gutters with existing settings layout and mobile behavior
- Expand SettingsModal and mobile tests to cover provider card visibility and interactions
On a fresh install useAuthOnboarding's effect ran at mount before the
setup wizard's 500ms auto-open timer fired. The one-shot ref locked,
and the resolved fetch could either stack model onboarding on top of
the wizard or never re-trigger after the wizard closed.
- Gate the trigger on projectId being set so the wizard owns the
bootstrap phase; the auth check only fires once a project exists.
- Re-check setupWizardOpen via a ref when the auth fetch resolves to
avoid stacking onboarding on top of a wizard opened mid-fetch.
- Release the one-shot in that suppressed branch so the effect retries
when the wizard closes.
Adds two regression tests: fresh-install handoff and mid-fetch wizard
suppression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On a fresh install useAuthOnboarding's effect ran at mount before the
setup wizard's 500ms auto-open timer fired. The one-shot ref locked,
and the resolved fetch could either stack model onboarding on top of
the wizard or never re-trigger after the wizard closed.
- Gate the trigger on projectId being set so the wizard owns the
bootstrap phase; the auth check only fires once a project exists.
- Re-check setupWizardOpen via a ref when the auth fetch resolves to
avoid stacking onboarding on top of a wizard opened mid-fetch.
- Release the one-shot in that suppressed branch so the effect retries
when the wizard closes.
Adds two regression tests: fresh-install handoff and mid-fetch wizard
suppression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modal could clip its footer on older laptops / browsers without
dvh support: the height calc fell back to invalid, max-height was
dropped, and the parent overlay had no overflow rule. Added a vh
fallback before the dvh line and made .setup-wizard-overlay
scrollable as a safety net.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related executor fixes:
1. Spawned child agents previously bypassed the executor model lane hierarchy
and used settings.defaultProvider/defaultModelId directly, ignoring
project-level executionProvider/executionModelId from .fusion/config.json.
Resolve via resolveExecutorModelPair() so children honor the same
precedence as the parent executor.
2. Pre-merge workflow step AI calls now have a wall-clock timeout
(settings.workflowStepTimeoutMs, default 6 min) and fall back to the
configured validatorFallback / fallback model on timeout. The 20-min
stuck-detector kill loop was the only escape hatch when a provider's
streaming API hung mid-response, and the kill triggered a same-provider
retry — guaranteeing repeat hangs. The runner now races the prompt against
a timeout; on timeout it disposes the session, logs a clear entry, and
re-runs the step once with a distinct fallback provider/model. If neither
completes (or no fallback is configured), the step returns a normal
failure that flows into the existing handleWorkflowStepFailure retry path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add CLI support for task node routing with create --node, task set-node, and task clear-node commands
- Extend settings command validation/output for defaultNodeId and unavailableNodePolicy and document new keys
- Surface task routing details in CLI task show plus dashboard Routing tab status/binding reason indicators
- Enhance Settings modal node routing UX with selected-node health status and add/refresh tests and changeset
- Add list view node status indicator styles for bulk edit controls
- Remove duplicated memory dream trigger test blocks introduced during conflict resolution
- Keep dashboard test suite deterministic by retaining a single canonical memory dream trigger describe
- Replace split move control with a single move button that supports primary action plus arrow-zone menu toggling
- Add keyboard support for move options via ArrowDown to open and Escape to close and refocus the trigger
- Update task detail styling for the unified move button and arrow affordance states
- Refresh TaskDetailModal and TaskForm tests to cover the new move control behavior and preset selection query
- Extend batch task update APIs and route handlers to accept nodeId overrides alongside model settings
- Update ListView bulk edit UX to load nodes and apply node overrides through batch updates
- Add node routing visibility improvements in task detail/settings UI and align related labels/styles
- Expand dashboard/API test coverage for node override batch updates and bulk edit behavior
The prior commit re-enqueued tasks after stale-merge recovery but didn't
account for the engine's in-memory `mergeActive` set, which still held the
wedged task. `internalEnqueueMerge` silently no-ops when the entry is
present, so the re-enqueue had no effect.
The recovery callback now also aborts the active merge's signal and
disposes its session if the wedged attempt was the currently-active one.
This is what unsticks tasks where an AI provider call is hung mid-await
and the surrounding `try/finally` never gets a chance to run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stale-merge recovery now calls back into ProjectEngine's auto-merge queue
directly instead of waiting on the 15s polling sweep — wired via a new
InProcessRuntime.setMergeEnqueuer hook so SelfHealingManager can re-enqueue
without leaking engine internals.
createWorktree mirrors the merge-time rebase: when worktreeRebaseBeforeMerge
is enabled, the new task branch is rebased onto <remote>/<defaultBranch>
right after creation, so executors start from origin's tip with local main
replayed on top. Best-effort — fetch/rebase failures abort cleanly and
leave the merge-time rebase as the backstop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Redesign the task Routing tab with effective-node summary rows, unhealthy node signaling, and direct per-task override selection
- Add robust override update handling (loading/saving states, stale-task guards, clear override action, and in-progress lock messaging)
- Extend TaskCard execution time indicator behavior to in-review cards and add focused regression coverage
- Refresh dashboard tests and styling for routing and node-status presentation, and remove an unused remote settings API import
- Add a reusable ProjectNodeSelector component and hook RoutingTab to it for per-task node overrides
- Move default node and unavailable-node policy controls into the dedicated Node Routing settings section
- Add descriptive routing guidance text and project-level note in SettingsModal
- Expand dashboard tests for routing tab behavior and node routing settings UX