# Fusion changelog User-facing release notes aggregated across all packages. This file is auto-synced from each `packages/*/CHANGELOG.md` by `scripts/release.mjs` — do not edit by hand. ## 0.38.1 ### @fusion/dashboard #### Patch Changes - bad8f52: Improve the mission manager mobile stacked layout so mission rows reflow cleanly: stacked mission list items switch to a column layout with stretched content, item actions become full-width and wrap instead of cramped inline controls, and run controls span the full width. - @fusion-plugin-examples/cli-printing-press@0.1.16 - @fusion-plugin-examples/dependency-graph@0.1.30 - @fusion-plugin-examples/roadmap@0.1.18 - @fusion/core@0.38.1 - @fusion/engine@0.38.1 - @fusion-plugin-examples/cursor-runtime@0.1.18 - @fusion-plugin-examples/droid-runtime@0.1.25 - @fusion-plugin-examples/hermes-runtime@0.2.49 - @fusion-plugin-examples/openclaw-runtime@0.2.49 - @fusion-plugin-examples/paperclip-runtime@0.2.49 ### @fusion/desktop #### Patch Changes - Updated dependencies [bad8f52] - @fusion/dashboard@0.38.1 - @fusion/core@0.38.1 ### @fusion/engine #### Patch Changes - @fusion/core@0.38.1 - @fusion/pi-claude-cli@0.38.1 ### @fusion/plugin-sdk #### Patch Changes - @fusion/core@0.38.1 ### @runfusion/fusion #### Patch Changes - bad8f52: Fix the Binary Release workflow so platform binaries publish to GitHub Releases again. The release job now tolerates a single failing build leg instead of being skipped (which previously suppressed all assets), the node_modules cache key includes CPU arch to stop arm64 runners restoring x64 native deps, the macOS CLI signing step is skipped gracefully when Apple certs are absent, and the dependency-graph plugin build uses a cross-platform copy step that no longer breaks the Windows desktop build. ### runfusion.ai #### Patch Changes - Updated dependencies [bad8f52] - @runfusion/fusion@0.38.1 ## 0.38.0 ### @fusion/dashboard #### Patch Changes - Updated dependencies [9112b7d] - @fusion/engine@0.38.0 - @fusion-plugin-examples/cli-printing-press@0.1.15 - @fusion-plugin-examples/dependency-graph@0.1.29 - @fusion-plugin-examples/roadmap@0.1.17 - @fusion/core@0.38.0 - @fusion-plugin-examples/cursor-runtime@0.1.17 - @fusion-plugin-examples/droid-runtime@0.1.24 - @fusion-plugin-examples/hermes-runtime@0.2.48 - @fusion-plugin-examples/openclaw-runtime@0.2.48 - @fusion-plugin-examples/paperclip-runtime@0.2.48 ### @fusion/desktop #### Patch Changes - @fusion/dashboard@0.38.0 - @fusion/core@0.38.0 ### @fusion/engine #### Patch Changes - 9112b7d: Fix scheduler overlap deferral starvation by considering only runnable queued todo tasks as higher-priority overlap competitors. Dependency-blocked queued tasks now keep their unmet-dependency queue state without reserving overlapping files from ready work, while active in-progress and eligible in-review tasks continue to hold explicit file-scope leases. Dispatch logs now distinguish unmet dependencies, active file-scope lease blocking, and higher-priority runnable queued-task deferral. - @fusion/core@0.38.0 - @fusion/pi-claude-cli@0.38.0 ### @fusion/plugin-sdk #### Patch Changes - @fusion/core@0.38.0 ### @runfusion/fusion #### Minor Changes - afc3b47: Adds goal-anchoring run-audit observability for Slice 2 hybrid anchoring with three `database` mutation types: `goal:injection-applied`, `goal:injection-skipped`, and `goal:retrieval-invoked`. Events carry count-only metadata contracts (`count`, plus `lane` for injection and `toolName` for retrieval, with optional `truncated`/`reason`/`notFound`) and avoid prompt bodies or goal title/description payloads. These events are available through the existing `GET /api/agents/:id/runs/:runId/audit` timeline route with standard date-range filtering via `startTime`/`endTime`. - 71e2aec: Add a goal-citation audit trail to support Slice 2 anchoring success-signal measurement. - Introduce a persisted `goal_citations` table (schema v93) with deduplication on `(goalId, surface, sourceRef)`. - Record citations from `agent_log` and `task_document` write seams. - Extract goal IDs using `GOAL_ID_PATTERN` (`/\bG-[0-9A-Z]+(?:-[0-9A-Z]+)*\b/g`) and store bounded snippets (max 200 chars). - Add `fn goals citations` with filters: `--goal`, `--agent`, `--surface`, `--since`, `--until`, `--limit`, and `--json`. - 4fee2c1: Add a branch-strategy dropdown to the New Task dialog with project-default, auto-new, existing, and custom-new modes. New tasks now submit `branchSelection`, and `auto-new` derives a persisted branch name using `fusion/{task-id}-{short-name}`. - 0605d13: Add mission-level branch strategy defaults so missions can persist whether triaged tasks should use project default branching, a shared existing/custom branch, or per-task derived branches. Mission create/edit flows now save both `baseBranch` and `branchStrategy`, and mission triage handlers apply that stored strategy by default (including autopilot triage when no explicit branch options are supplied). Also fix planning breakdown task creation to forward the selected branch options so multi-task planning respects the same branch selection used by single-task planning. - 7221413: Add per-mission/planning branch-group data-model foundations in `@fusion/core`. - Introduce durable `branch_groups` storage with source linkage (`mission`/`planning`), branch metadata, PR state, status, and auto-merge override. - Add `TaskStore` branch-group APIs: create/get/getBySource/list/update/setTaskBranchGroup. - Persist `Task.autoMerge` and `Mission.autoMerge` as optional overrides. - Reuse `Task.branchContext.groupId` for task↔group linkage (no separate `branchGroupId` column). - Bump project schema version to `94` with migration coverage and schema assertions. #### Patch Changes - 53d97e2: Clarify no-task heartbeat prompts when eligible Todo tasks exist but role policy filters them out of auto-claim candidates. - dbb0804: Fix per-task diff view incorrectly including a task's base commit when a done task lands as a no-op or its resolved merge SHA equals `baseCommitSha`. - 668e3a5: Mission creation now always returns a stopped mission. `POST /api/missions` and the mission store ignore create-time `autopilotEnabled` input, forcing new missions to `status: "planning"` with autopilot disabled and inactive. Autopilot remains a post-creation action via explicit mission start/update flows. - a014c6d: Auto-merge now treats transient provider/network failures during merge (for example "This operation was aborted", "socket hang up", and provider `server_error` payloads) as bounded retryable errors instead of immediate terminal failures. The engine re-enqueues affected in-review merges with exponential backoff for both direct and pull-request merge strategies, then parks the task as failed with explicit transient-retry exhaustion logs once the retry cap is reached. - d5b3336: Dashboard: OAuth re-login banner now clears a provider immediately after successful OAuth re-authentication, instead of waiting for the next auth-status polling interval. - 0044c23: Fix dashboard OAuth login for `github-copilot` when upstream auth storage invokes device-code callbacks. The `/api/auth/login` route now provides the expected callback wiring and preserves `deviceCode: { userCode, verificationUri }` in responses so Copilot login no longer crashes with `options.onDeviceCode is not a function`. - 4a60c2a: Backfill done-task "N files changed" chips when mergeDetails enrichment arrives after the initial done websocket snapshot. Task cards now pass a done-mode merge enrichment signature into diff-stats invalidation so `/api/tasks/:id/diff` is re-fetched and authoritative lineage stats render without requiring a manual refresh. ### runfusion.ai #### Patch Changes - Updated dependencies [53d97e2] - Updated dependencies [afc3b47] - Updated dependencies [71e2aec] - Updated dependencies [dbb0804] - Updated dependencies [4fee2c1] - Updated dependencies [0605d13] - Updated dependencies [668e3a5] - Updated dependencies [a014c6d] - Updated dependencies [d5b3336] - Updated dependencies [0044c23] - Updated dependencies [4a60c2a] - Updated dependencies [7221413] - @runfusion/fusion@0.38.0 ## 0.37.0 ### @fusion/dashboard #### Patch Changes - @fusion/core@0.37.0 - @fusion/engine@0.37.0 - @fusion-plugin-examples/cli-printing-press@0.1.14 - @fusion-plugin-examples/dependency-graph@0.1.28 - @fusion-plugin-examples/roadmap@0.1.16 - @fusion-plugin-examples/cursor-runtime@0.1.16 - @fusion-plugin-examples/droid-runtime@0.1.23 - @fusion-plugin-examples/hermes-runtime@0.2.47 - @fusion-plugin-examples/openclaw-runtime@0.2.47 - @fusion-plugin-examples/paperclip-runtime@0.2.47 ### @fusion/desktop #### Patch Changes - @fusion/core@0.37.0 - @fusion/dashboard@0.37.0 ### @fusion/engine #### Patch Changes - @fusion/core@0.37.0 - @fusion/pi-claude-cli@0.37.0 ### @fusion/plugin-sdk #### Patch Changes - @fusion/core@0.37.0 ### @runfusion/fusion #### Minor Changes - b335f3d: Add a new `fn_goal_show` tool for goal retrieval by ID, including structured JSON output via `details.goal` and a stable not-found contract (`GOAL_NOT_FOUND`). Also register `fn_goal_list` and `fn_goal_show` in the engine readonly tool allowlist so agent runtime sessions can use goal retrieval on the readonly path. #### Patch Changes - 230efa1: Update `useAiMergeCommitSummary` docs/JSDoc to match the intended default of `true`, including that merge commit summaries include a subject plus body summary (narrative + bullets + diff-stat). Also fixes AI merge-mode prompt guidance so AI-authored squash commits include a summarized body instead of subject-only commit messages. - b5f2f91: Do not mark executor sessions as failed when they are parked for pending code review. ### runfusion.ai #### Patch Changes - Updated dependencies [230efa1] - Updated dependencies [b335f3d] - Updated dependencies [b5f2f91] - @runfusion/fusion@0.37.0 ## 0.36.0 ### @fusion/dashboard #### Patch Changes - @fusion/core@0.36.0 - @fusion/engine@0.36.0 - @fusion-plugin-examples/cli-printing-press@0.1.13 - @fusion-plugin-examples/dependency-graph@0.1.27 - @fusion-plugin-examples/roadmap@0.1.15 - @fusion-plugin-examples/cursor-runtime@0.1.15 - @fusion-plugin-examples/droid-runtime@0.1.22 - @fusion-plugin-examples/hermes-runtime@0.2.46 - @fusion-plugin-examples/openclaw-runtime@0.2.46 - @fusion-plugin-examples/paperclip-runtime@0.2.46 ### @fusion/desktop #### Patch Changes - @fusion/core@0.36.0 - @fusion/dashboard@0.36.0 ### @fusion/engine #### Patch Changes - @fusion/core@0.36.0 - @fusion/pi-claude-cli@0.36.0 ### @fusion/plugin-sdk #### Patch Changes - @fusion/core@0.36.0 ### @runfusion/fusion #### Minor Changes - 2a35358: Add Goals REST API (`/api/goals`) with list/create/update/archive/unarchive endpoints. Creating a 6th active goal or unarchiving when already at 5 active now returns HTTP 409 with `ACTIVE_GOAL_LIMIT_EXCEEDED` details. - 009d569: Add `fn goals` CLI subcommand (`list` / `create` / `archive`) and pi extension tools (`fn_goal_list`, `fn_goal_create`, `fn_goal_archive`) for Slice 1 of the Goals primitive. Author-facing only — no agent anchoring yet. #### Patch Changes - f258a75: Fix ntfy JSON publish notifications to encode `priority` as the integer scale expected by ntfy so unicode mailbox/room notifications deliver successfully. - 2c4683a: Widen task detail modal on tablet viewports to use more of the 769px–1024px viewport. - e84673c: Close source-imported GitHub issues when their linked Fusion task is deleted, with parity to tracking-issue delete handling. Dashboard delete confirmation now prompts for `close`, `delete`, or `leave` on source-imported issues and forwards `githubIssueAction` through task deletion flows. For API callers that omit `githubIssueAction` (or send `auto`) on source-imported issue deletes, Fusion now defaults to `close`. - 200dda9: Suppress a misleading transient failure state when a worktree-local `.fusion/tasks//task.json` read briefly returns ENOENT during executor session startup. Fusion now treats this as recoverable, routes through existing auto-recovery, and avoids persisting `status: "failed"`/`error` so the red task-card error banner and failed notification are not shown for self-healed runs. - 6b27ab5: fix(FN-5627): default auto-prerebase to fire when branch is >=1 commit behind integration `decideAutoPrerebase()` previously defaulted `prerebaseDivergenceThreshold` to `0`, which meant the threshold path **never fired** unless the user explicitly set a positive value. Only hot-file matches could trigger prerebase. The result: tasks whose branch was started against an older main tip (because other tasks landed concurrently) would skip prerebase, build their squash commit against the stale base, and then fail at the `git update-ref` step because the squash commit didn't descend from current main. The merger correctly detected this as a non-fast-forward advance and threw `IntegrationBranchConcurrentAdvanceError` — with both "expected" and "observed" SHAs set to the current main tip (because `observedCurrentSha` was captured from the pre-update rev-parse). This produced the misleading "expected X, observed X" same-SHA error signature that stranded FN-5632 stuck at `mergeRetries=3`. New default: `prerebaseDivergenceThreshold = 1`. Any branch behind by at least 1 commit auto-rebases before squash. Users who want the legacy never-fire behavior can explicitly set `prerebaseDivergenceThreshold = 0`. Threshold comparison also changed from `>` to `>=` so an explicit threshold of N rebases at N+ commits behind instead of N+1+. The self-healing classifier comment for `spurious-concurrent-advance-same-sha` is updated to reflect that the signature can come from either the pre-FN-5627 misclassification OR the legitimate post-FN-5627 non-fast-forward path; the auto-recovery sweep is unchanged because both cases self-heal cleanly once prerebase fires on the retry. Tests: - Default threshold (undefined) fires at 1 commit behind - Explicit threshold = 0 stays as opt-out (never fire on commit-count) - Default threshold doesn't fire when branch is up-to-date (commitsBehind=0) - b2d547e: fix(FN-5627): close TOCTOU window between merger optimistic `mergeConfirmed: true` write and integration ref advance, add reachability gate on auto-merge fast-path The merger previously persisted `mergeConfirmed: true` + `commitSha` to the task row as soon as the local squash commit was built, **before** running `git update-ref refs/heads/` to actually advance the integration branch. If the ref-advance then failed for any reason (lock contention, hook rejection, packed-refs race, or a misclassified non-CAS error via the `merger-ref-update-advance.ts` string heuristic), the task row was poisoned: the auto-merge scheduler's `mergeConfirmed` fast-path would silently promote the never-landed work to `done` on the next tick, including emitting `task:merged` and closing the GitHub tracking issue. This affected at least 9 tasks across 2026-05-27/28 (FN-5596, FN-5597, FN-5599, FN-5612, FN-5613, FN-5614, FN-5616, FN-5623, FN-5625) — the merger silently dropped real work and marked the tasks complete. The fix has three layers: 1. **merger.ts** — In `reuseTaskWorktreeMerge` mode, persist `mergeConfirmed: false` initially. Promote to `true` only after `advanceIntegrationBranchRef` returns `advanced: true`. Other merge paths (legacy in-place merge, verified no-op fast-paths, owned-commit recovery) are unchanged because they advance the ref before this point. 2. **project-engine.ts** — Defense-in-depth reachability gate on the auto-merge "merge already confirmed" fast-path. Before `moveTask(taskId, "done")`, verify `git merge-base --is-ancestor ` succeeds. On failure, clear `mergeConfirmed`, mark task `status: "failed"`, leave in `in-review`, and emit `merger:fast-path-blocked-foreign-commit` run-audit event. Legitimate no-op merges (no `commitSha`) bypass the gate. 3. **merger-ref-update-advance.ts** — Replace the fragile string heuristic that classified update-ref failures as `concurrent-advance` (matching `"is at"` / `"expected"` / `"cannot lock ref"` in error text) with structured detection. After update-ref fails, re-read the ref: if observed equals expected, classify as `ref-update-refused` (no actual race occurred). Eliminates the misleading "expected X observed X" same-SHA log signature seen on FN-5625. - 694970b: fix(FN-5627): always rebase behind branches before squash regardless of user-configured prerebase threshold After the FN-5627 default-threshold fix landed (threshold=1 default), tasks were still getting stuck at `mergeRetries=3` with `Integration branch main advanced concurrently (expected X, observed X)` errors because user projects with explicit `prerebaseDivergenceThreshold` values higher than the branch's commits-behind count still skipped prerebase entirely. Example: a project with `prerebaseDivergenceThreshold: 50` for low-noise PR experience would skip prerebase on a task branched 4 commits behind main. The squash commit then doesn't descend from current main, and `git update-ref` correctly refuses the non-fast-forward advance — producing the misleading same-SHA error signature that stranded FN-5626, FN-5628, FN-5633. Root distinction missed in the earlier fix: the user-configurable `prerebaseDivergenceThreshold` controls the _user-visible severity reporting_ ("this branch is N commits behind"), while engine correctness requires a _safety invariant_ ("any branch behind main MUST be rebased before squash, or update-ref will fail"). These are independent concerns. New behavior: - After the hot-file and threshold checks, `decideAutoPrerebase()` now returns `fire: true` with `reason: "safety-fallback-any-divergence"` whenever `commitsBehind > 0`. - The threshold-based path still wins when tripped (so user-visible audit `reason` reflects the configured policy when applicable). - Full opt-out remains `prerebaseAutoEnabled: false` — that case skips the safety fallback too, and the user accepts that behind-branch merges will fail. - `prerebaseDivergenceThreshold: 0` is no longer a complete opt-out from the commit-count gate — it only suppresses the threshold-based reason label. Safety fallback still fires. Tests: - New `safety-fallback-any-divergence` reason added to `AutoPrerebaseDecision.reason` union. - 4 commits behind with threshold=50 → fires via safety fallback (was: skipped). - `prerebaseAutoEnabled=false` → no fire (full opt-out preserved). - Configured threshold tripping still wins the `reason` label. - Branch fully up-to-date (commitsBehind=0) → no-divergence (unchanged). - 5768d5e: feat(FN-5627): self-heal transient merge failures stuck at mergeRetries=3 After the FN-5627 merger fix landed, two in-review tasks (FN-5628, FN-5632) remained stuck at `mergeRetries=3` with `status='failed'` due to transient merge errors that the merger correctly identified but had no auto-recovery for: - `lease-handoff-failed: target-not-queued` — FN-5353 class race where the merge queue lease acquisition saw the task drop out of the queue between enqueue and handoff (typically due to a self-healing sweep cleaning stale `mergeQueue` rows mid-flight). - Legacy same-SHA spurious concurrent-advance errors persisted before FN-5627's `merger-ref-update-advance.ts` classifier fix landed. These tasks had no path forward except manual intervention. The `AUTO_MERGE_COOLDOWN_MS` cooldown reset takes hours and gives up too easily. This change adds `SelfHealingManager.recoverTransientMergeFailures()`, wired into both startup recovery and the periodic Batch 2 maintenance loop. For each in-review task with `mergeRetries >= MAX_AUTO_MERGE_RETRIES`, `status='failed'`, and an `error` matching `classifyTransientMergeError()`: 1. Reset `mergeRetries=0`, clear `status`/`error`. 2. Increment `mergeDetails.transientRecoveryCount` (new field on `MergeDetails`). 3. Re-enqueue via `requeueForAutoMerge`. 4. Emit `merger:transient-failure-auto-recovered` run-audit event. Bounded by `MAX_TRANSIENT_MERGE_RECOVERIES = 2` to avoid infinite loops on genuinely stuck tasks. Once exhausted, the task stays parked as failed and emits `merger:transient-failure-budget-exhausted` once with a `[transient-recovery-budget-exhausted]` marker on `error` for repeat-suppression. Non-transient failure classes (verification, build, real conflicts, etc.) are not eligible — only the pattern-matched transient classes auto-recover. No-op when `autoMerge=false`, no `requeueForAutoMerge` callback wired, or pause is active. Tests: - Lease-handoff transient recovery path - Same-SHA spurious-advance recovery (legacy pre-FN-5627) - Genuine concurrent-advance (different SHAs) NOT recovered - Non-transient failures (verification errors) NOT recovered - Budget exhaustion behavior - autoMerge=false no-op - e75c4da: fix(FN-5627): suppress ntfy notifications for transient merge failures the engine auto-recovers Even with the FN-5627 merger TOCTOU fix + transient-failure self-healing sweep + safety-fallback auto-prerebase landed, the merger can still hit transient failure classes (lease handoff races, brief same-SHA non-FF advances) for tasks whose branches are particularly out-of-sync. The self-healing sweep auto-recovers them within bounded budget — but each individual failure cycle was firing a ntfy alarm before the recovery cleared the failed state, producing user-facing alarm spam for tasks that were never actually stuck. Two layers of fix: 1. `NotificationService.handleTaskUpdated` now classifies `task.error` via the new shared `classifyTransientMergeError` helper before scheduling the deferred failure notification. Transient classes (`lease-handoff-target-not-queued`, `spurious-concurrent-advance-same-sha`) get logged as suppressed and never schedule a ntfy timer. 2. Defense-in-depth: `fireDeferredFailureNotification` re-classifies the error at dispatch time, so a failure scheduled before the suppression landed on a newer cycle still suppresses if the error matches a transient class. The classifier itself moved from `self-healing.ts` to a new logger-free `transient-merge-error-classifier.ts` module so consumers in `NotificationService` don't pull `createLogger` through the import chain and break test mocks of `../logger.js`. `self-healing.ts` re-exports the symbol for backward compatibility. Log prefix for the recovery actions also changed from `[FN-5627] Auto-recovering...` to `Auto-recovered:` so that `NotificationService.maybeSuppressTransientFailedNotification`'s existing `/^Auto-recovered:/` log-prefix check cancels any already-scheduled failure notification when the sweep runs mid-grace-window. Tests: - 3 new notification-service tests covering transient suppression for both error classes plus a control case ensuring genuine non-transient failures still notify. - Existing transient-recovery tests in self-healing.test.ts continue to pass against the relocated classifier. - b2dce7d: FN-5631 re-lands FN-5616 to add an opt-in `githubCloseSourceIssueOnDone` setting that closes source-imported GitHub issues when linked tasks are completed, including startup reconciliation for previously missed closes. - 1153b09: feat(FN-5637): update `fn init` to add `fusion.db`, `fusion.db-wal`, and `fusion.db-shm` to project `.gitignore` alongside `.fusion` and `.pi` so stray runtime SQLite files are not committed. - 5b5da2c: Fix bundled runtime plugin auto-install in globally installed CLI builds. Save/Save & Test for Paperclip, Hermes, OpenClaw, Cursor, and Droid runtime providers no longer fails with `unavailable in this build` when bundled plugins are present under `dist/plugins/`. - b96b0bc: Fix `fn update` npm EEXIST bin-link collisions by retrying once with `--force` and showing manual recovery guidance when the retry fails. - 2a35358: Add a new project-level `goals` table to the core schema and fresh database DDL. Bump `SCHEMA_VERSION` from 91 to 92 with an idempotent migration that creates `goals` and `idxGoalsStatus`. - 29ac58f: feat(FN-5633): standalone AI merge path (clean-room merge + AI reviewer) Adds a self-contained AI merge path (`merger.mode: "ai"`, the new default) that the engine dispatches to instead of the legacy `aiMergeTask` pipeline. It does not share the legacy scaffolding (prerebase / conflict-strategy ladder / post-merge audit / transient self-heal), which was buggy and error-prone. How it works: - **Clean room**: a throwaway detached worktree is created at the target branch's current tip, so the user's real checkout is never the merge surface — dirty files cannot be clobbered and the landing is a fast-forward by construction. - **AI merge**: an AI agent merges the task branch into the clean room and produces one squash commit, resolving conflicts in favor of the task's intent. - **AI reviewer with retries**: a fresh read-only reviewer audits the squash (completeness / collateral / conflict-soundness) and classifies any veto blocking vs advisory. It drives up to `merger.maxReviewPasses` corrective re-merges. After the budget, advisory concerns land with a logged warning; an unfixable BLOCKING (correctness) concern hard-fails (`AiMergeBlockedError`) rather than ship wrong code. Verdict parsing fails safe to blocking. - **Per-task target branch**: each task merges into its own target branch (or the default integration branch). The local checkout is only synced when it is on that target. - **Local checkout sync**: when the checkout is on the target branch, the ref + working tree advance together via `git merge --ff-only` (dirty state read accurately before the move); dirty edits are stashed, fast-forwarded, and restored — and if the restore conflicts the AI merger reconciles them (the original edits are also kept in a stash as a backup). A checkout on a different branch is advanced via `update-ref` and left untouched. Un-stashable dirty state advances the ref and leaves the working tree with a warning. Concurrent advances trigger a bounded rebuild on the new tip. - **Status + logs**: progress (merging / reviewing / corrective passes / landing / blocked / landed) is written to the task status pill and the task log stream. Settings: `merger.mode` (`ai` default / `deterministic` legacy), `merger.reviewerModel`, `merger.maxReviewPasses` (default 3), surfaced in Settings → Merge. When AI merge is on, the legacy merge-mechanics settings (integration worktree, conflict strategy, overlap guard, post-merge audit, direct-commit routing) are hidden since they do not apply. Commit message: the AI agent writes the squash commit subject as a concise summary of the actual changes (not just the task title), and every landed squash carries the board-association trailers — `Fusion-Task-Id: ` plus the canonical lineage trailer when the task has a `lineageId` — guaranteed via an idempotent amend even if the agent omits them, so the board associates the commit with the task. Verification: the merge agent is instructed to run the project's tests, type-check, and lint after resolving the merge and to fix any NEW failure the merge introduced (without being on the hook for pre-existing breakage) before committing. Editable prompt: the AI merge agent's base persona is the editable "merger" role prompt (Settings → Prompts); the non-negotiable clean-room / verification / commit-trailer rules are always appended so a custom prompt can't drop them. Reviewer model: the reviewer agent uses the project's reviewer/validator model lane (`resolveValidatorSettingsModel`: project validator → global validator → project default), not a merge-specific setting. No-branch guard: a missing task branch is a benign no-op only when the task was never executed or was already merged (branch cleaned up on re-process); if the task was executed (`baseCommitSha` recorded) and was never merged, the merge fails loudly rather than silently marking the task done. The legacy `aiMergeTask` pipeline is retained unchanged and used when `merger.mode: "deterministic"`. Tests: `merger-ai.test.ts` covers the verdict parser, clean merge, blocking hard-fail (no advance), advisory land, empty no-op, per-task target branch isolation, missing-target-branch error, and `landSquash` (clean ff, other-branch update-ref, dirty stash-restore, AI-resolved restore conflict). Engine merge-orchestration tests that assert the legacy path are pinned to `merger.mode: "deterministic"`. - cec191e: Migrate Fusion's pi dependencies from `@mariozechner/pi-coding-agent` / `@mariozechner/pi-ai` to the new `@earendil-works/*` scope and bump to `^0.77.0`. This follows the upstream project move to `https://github.com/earendil-works/pi` and updates transitive dependency resolution to the maintained package namespace. - aa7eccb: When `useAiMergeCommitSummary` is enabled, AI-authored merge commits now include a richer body: the short narrative headline plus an AI-generated bullet summary of changed modules/files, followed by a `Files changed` diff stat block. `mergeDetails.mergeCommitMessage` remains the short headline summary so dashboard UI consumers keep their existing concise display behavior. - d78fbcc: Fix GitHub PR modal/review fetches that call `gh api` through `runGhJsonAsync`. `runGhJson` and `runGhJsonAsync` now skip auto-appending `--json` for the `gh api` subcommand (which already returns JSON and rejects that flag), preventing runtime `unknown flag: --json` errors when loading PR comments/reviews. - 2df891f: ci: re-enable auto-trigger of binary release workflow on `v*` tags so GitHub Releases include CLI and desktop binaries ### runfusion.ai #### Patch Changes - Updated dependencies [f258a75] - Updated dependencies [2c4683a] - Updated dependencies [e84673c] - Updated dependencies [2a35358] - Updated dependencies [009d569] - Updated dependencies [200dda9] - Updated dependencies [6b27ab5] - Updated dependencies [b2d547e] - Updated dependencies [694970b] - Updated dependencies [5768d5e] - Updated dependencies [e75c4da] - Updated dependencies [b2dce7d] - Updated dependencies [1153b09] - Updated dependencies [5b5da2c] - Updated dependencies [b96b0bc] - Updated dependencies [2a35358] - Updated dependencies [29ac58f] - Updated dependencies [cec191e] - Updated dependencies [aa7eccb] - Updated dependencies [d78fbcc] - Updated dependencies [2df891f] - @runfusion/fusion@0.36.0 ## 0.35.0 ### @fusion/dashboard #### Patch Changes - Updated dependencies [1992049] - @fusion/engine@0.35.0 - @fusion-plugin-examples/cli-printing-press@0.1.12 - @fusion-plugin-examples/dependency-graph@0.1.26 - @fusion-plugin-examples/roadmap@0.1.14 - @fusion/core@0.35.0 - @fusion-plugin-examples/cursor-runtime@0.1.14 - @fusion-plugin-examples/droid-runtime@0.1.21 - @fusion-plugin-examples/hermes-runtime@0.2.45 - @fusion-plugin-examples/openclaw-runtime@0.2.45 - @fusion-plugin-examples/paperclip-runtime@0.2.45 ### @fusion/desktop #### Patch Changes - @fusion/dashboard@0.35.0 - @fusion/core@0.35.0 ### @fusion/engine #### Minor Changes - 1992049: Add opt-in RTK command rewriting for Pi bash tools via `FUSION_RTK_REWRITE`. #### Patch Changes - @fusion/core@0.35.0 - @fusion/pi-claude-cli@0.35.0 ### @fusion/plugin-sdk #### Patch Changes - @fusion/core@0.35.0 ### @runfusion/fusion #### Minor Changes - d767e2e: Add `openai-responses` as a supported custom provider `apiType` across CLI, engine, dashboard API validation, and dashboard forms. Custom providers configured with this apiType now route through pi-ai's built-in `openai-responses` transport while probe-model discovery continues to use the OpenAI-compatible `/v1/models` path. #### Patch Changes - da34bd0: Dashboard now shows a top-level "Re-login required" banner when a stored OAuth provider credential (Codex, Claude, etc.) has expired, and the engine logs the expired set on startup and once every 24 hours. - d76b6f9: TUI System panel now reliably shows the full auth token at all terminal widths so it can be selected and copied manually when the `[c]` shortcut is unavailable. - d767e2e: Fixed custom provider registration so provider keys are derived from the configured provider name (with deterministic collision suffixing) instead of internal UUID ids, ensuring model selector and logs show stable human-readable keys. Also fixed the OpenAI-compatible custom-provider registration path by validating end-to-end openai-completions round-trip behavior with a regression test. - 8a0fbf0: Fix the Bun-compiled `fn` executable so `--help` no longer crashes with a missing `react-devtools-core` module. The build now defines `process.env.DEV` as `false` during compile, allowing Ink's DEV-only devtools import path to be removed from the bundled binary. ### runfusion.ai #### Patch Changes - Updated dependencies [da34bd0] - Updated dependencies [d76b6f9] - Updated dependencies [d767e2e] - Updated dependencies [d767e2e] - Updated dependencies [8a0fbf0] - @runfusion/fusion@0.35.0 ## 0.34.0 ### @fusion/core #### Patch Changes - 6a6c6fd: Dashboard startup and request-storm fixes: - **Faster startup**: parallelized independent store inits, started CentralCore init early in background, and ran plugin loading concurrently with extension resolution. The duplicate-runtime root cause is also fixed — `shouldUseHybridExecutor` no longer auto-enables for local-only multi-project setups, where `ProjectEngineManager` already handles project lifecycle (set `FUSION_HYBRID_EXECUTOR=1` to force-enable). Eliminates ~7s of redundant self-healing pipeline work per cold start. - **Per-page request reduction**: added in-flight request dedupe (`packages/dashboard/app/api/dedupe.ts`) wrapped around the top API offenders. A single page load went from ~177 requests to ~101, with `/api/plugins/ui-slots` dropping from 17× to 1×. - **Stale-data-after-mutation hazard**: `forceFresh` option on the deduped fetchers now redirects ALL in-flight waiters to receive the fresh post-mutation response, not just the forcing caller. Generation counters in `useAgents` and `AgentListModal` provide a second layer of protection against slow polls overwriting fresh state. - **SSE refresh storm**: agent SSE event handler now debounces (250ms) with a trailing-edge guard, so multi-agent activity bursts coalesce to at most 2 refetches per burst instead of one per event. - **Live isolation-mode transition**: PATCH `/api/projects/:id` with an `isolationMode` change now returns a 503 with actionable guidance when HybridExecutor is unavailable (local-only single-node), instead of silently persisting a config that the live runtime won't honor. - **Error handling regression**: restored try/catch around `HybridExecutor.initialize` and `engineManager.ensureEngine` in the parallel engine setup so a paused or broken cwd project no longer aborts dashboard startup. - **TaskStore migration race**: sequenced the SQLite store inits (TaskStore → AutomationStore → PluginStore → AgentStore) since they all open the same `.fusion/fusion.db` and run `addColumnIfMissing` migrations with a TOCTOU `hasColumn` → `ALTER` pattern. - **`gh` CLI invocation storm**: `isGhAvailable()` and `isGhAuthenticated()` now memoize their results with a 60s TTL. `GitHubTrackingReconciler` was scanning up to 200 done tasks at startup and calling `hasGhAuth()` per task — each call shelled out to `gh --version` and `gh auth status` (which makes a network roundtrip), pinning the event loop for ~60s of synchronous `spawnSync` work. CPU-profile-confirmed: dropped from 71s (69% of cold-start CPU) to 2s. The cache benefits all 28+ call sites in `dashboard/src/github.ts`, the engine PR monitor, the research provider, and the API routes automatically. `resetGhAvailabilityCache()` is exported for login/logout flows that need to invalidate immediately. - **SQLite integrity check delay**: `PRAGMA integrity_check(100)` walks every page of the database file and was scheduled 3 seconds after init — landing right in the responsiveness-critical window for ~7s per database. Pushed the deferred-check timer to 60 seconds so the user is already interacting with the dashboard by the time it runs. The check itself is unchanged; corruption detection still works. - **Engine init event-loop yields**: `InProcessRuntime.start()` now awaits a `setImmediate`-based yield between major init phases (TaskStore → Plugins → WorktreePool → AgentStore → Scheduler → Executor → HeartbeatMonitor → SelfHealing) so HTTP requests can be processed between them instead of waiting on the entire stack. Same yield is now interleaved between each step of `SelfHealingManager.runStartupRecovery()` (34 steps per project) and its periodic maintenance batches. - **Deferred startup recovery**: `InProcessRuntime.start()` no longer awaits `resumeStartupRecoverySequence()` or `workerManager.reconcileOrphaned()` — both are correctness-preserving background operations and their git/SQLite work was blocking server-listen for several seconds. - **Deferred orphan-task AI agent resumption**: orphaned in-progress tasks resumed at engine restart now wait 30 seconds before spawning their AI agent session (worktree setup + pi-coding-agent session creation is heavy and saturates the event loop). Override via `FUSION_RESUME_ORPHAN_DELAY_MS=`; auto-zeroes under Vitest. - **Event-loop lag tracer**: opt-in debug aid for diagnosing cold-start regressions. Set `FUSION_TRACE_EL_LAG=/path/to/file.txt` to capture every block >150ms with a timestamp relative to process start. ### @fusion/dashboard #### Patch Changes - 6a6c6fd: Dashboard startup and request-storm fixes: - **Faster startup**: parallelized independent store inits, started CentralCore init early in background, and ran plugin loading concurrently with extension resolution. The duplicate-runtime root cause is also fixed — `shouldUseHybridExecutor` no longer auto-enables for local-only multi-project setups, where `ProjectEngineManager` already handles project lifecycle (set `FUSION_HYBRID_EXECUTOR=1` to force-enable). Eliminates ~7s of redundant self-healing pipeline work per cold start. - **Per-page request reduction**: added in-flight request dedupe (`packages/dashboard/app/api/dedupe.ts`) wrapped around the top API offenders. A single page load went from ~177 requests to ~101, with `/api/plugins/ui-slots` dropping from 17× to 1×. - **Stale-data-after-mutation hazard**: `forceFresh` option on the deduped fetchers now redirects ALL in-flight waiters to receive the fresh post-mutation response, not just the forcing caller. Generation counters in `useAgents` and `AgentListModal` provide a second layer of protection against slow polls overwriting fresh state. - **SSE refresh storm**: agent SSE event handler now debounces (250ms) with a trailing-edge guard, so multi-agent activity bursts coalesce to at most 2 refetches per burst instead of one per event. - **Live isolation-mode transition**: PATCH `/api/projects/:id` with an `isolationMode` change now returns a 503 with actionable guidance when HybridExecutor is unavailable (local-only single-node), instead of silently persisting a config that the live runtime won't honor. - **Error handling regression**: restored try/catch around `HybridExecutor.initialize` and `engineManager.ensureEngine` in the parallel engine setup so a paused or broken cwd project no longer aborts dashboard startup. - **TaskStore migration race**: sequenced the SQLite store inits (TaskStore → AutomationStore → PluginStore → AgentStore) since they all open the same `.fusion/fusion.db` and run `addColumnIfMissing` migrations with a TOCTOU `hasColumn` → `ALTER` pattern. - **`gh` CLI invocation storm**: `isGhAvailable()` and `isGhAuthenticated()` now memoize their results with a 60s TTL. `GitHubTrackingReconciler` was scanning up to 200 done tasks at startup and calling `hasGhAuth()` per task — each call shelled out to `gh --version` and `gh auth status` (which makes a network roundtrip), pinning the event loop for ~60s of synchronous `spawnSync` work. CPU-profile-confirmed: dropped from 71s (69% of cold-start CPU) to 2s. The cache benefits all 28+ call sites in `dashboard/src/github.ts`, the engine PR monitor, the research provider, and the API routes automatically. `resetGhAvailabilityCache()` is exported for login/logout flows that need to invalidate immediately. - **SQLite integrity check delay**: `PRAGMA integrity_check(100)` walks every page of the database file and was scheduled 3 seconds after init — landing right in the responsiveness-critical window for ~7s per database. Pushed the deferred-check timer to 60 seconds so the user is already interacting with the dashboard by the time it runs. The check itself is unchanged; corruption detection still works. - **Engine init event-loop yields**: `InProcessRuntime.start()` now awaits a `setImmediate`-based yield between major init phases (TaskStore → Plugins → WorktreePool → AgentStore → Scheduler → Executor → HeartbeatMonitor → SelfHealing) so HTTP requests can be processed between them instead of waiting on the entire stack. Same yield is now interleaved between each step of `SelfHealingManager.runStartupRecovery()` (34 steps per project) and its periodic maintenance batches. - **Deferred startup recovery**: `InProcessRuntime.start()` no longer awaits `resumeStartupRecoverySequence()` or `workerManager.reconcileOrphaned()` — both are correctness-preserving background operations and their git/SQLite work was blocking server-listen for several seconds. - **Deferred orphan-task AI agent resumption**: orphaned in-progress tasks resumed at engine restart now wait 30 seconds before spawning their AI agent session (worktree setup + pi-coding-agent session creation is heavy and saturates the event loop). Override via `FUSION_RESUME_ORPHAN_DELAY_MS=`; auto-zeroes under Vitest. - **Event-loop lag tracer**: opt-in debug aid for diagnosing cold-start regressions. Set `FUSION_TRACE_EL_LAG=/path/to/file.txt` to capture every block >150ms with a timestamp relative to process start. - Updated dependencies [6a6c6fd] - Updated dependencies [97f1143] - Updated dependencies [4e4830f] - @fusion/engine@0.34.0 - @fusion/core@0.34.0 - @fusion-plugin-examples/cli-printing-press@0.1.11 - @fusion-plugin-examples/dependency-graph@0.1.25 - @fusion-plugin-examples/roadmap@0.1.13 - @fusion-plugin-examples/cursor-runtime@0.1.13 - @fusion-plugin-examples/droid-runtime@0.1.20 - @fusion-plugin-examples/hermes-runtime@0.2.44 - @fusion-plugin-examples/openclaw-runtime@0.2.44 - @fusion-plugin-examples/paperclip-runtime@0.2.44 ### @fusion/desktop #### Patch Changes - Updated dependencies [6a6c6fd] - @fusion/dashboard@0.34.0 - @fusion/core@0.34.0 ### @fusion/engine #### Minor Changes - 97f1143: Add optional dependencies parameter to fn_task_update tool. Executors can now programmatically modify task dependency arrays during execution with `fn_task_update({ id: "FN-XXX", dependencies: ["FN-001", "FN-002"] })`. The parameter is optional and backward-compatible; omitting it preserves existing dependencies. Includes validation for self-dependency and non-existent task IDs. Eliminates the need for direct task.json editing workarounds. #### Patch Changes - 6a6c6fd: Dashboard startup and request-storm fixes: - **Faster startup**: parallelized independent store inits, started CentralCore init early in background, and ran plugin loading concurrently with extension resolution. The duplicate-runtime root cause is also fixed — `shouldUseHybridExecutor` no longer auto-enables for local-only multi-project setups, where `ProjectEngineManager` already handles project lifecycle (set `FUSION_HYBRID_EXECUTOR=1` to force-enable). Eliminates ~7s of redundant self-healing pipeline work per cold start. - **Per-page request reduction**: added in-flight request dedupe (`packages/dashboard/app/api/dedupe.ts`) wrapped around the top API offenders. A single page load went from ~177 requests to ~101, with `/api/plugins/ui-slots` dropping from 17× to 1×. - **Stale-data-after-mutation hazard**: `forceFresh` option on the deduped fetchers now redirects ALL in-flight waiters to receive the fresh post-mutation response, not just the forcing caller. Generation counters in `useAgents` and `AgentListModal` provide a second layer of protection against slow polls overwriting fresh state. - **SSE refresh storm**: agent SSE event handler now debounces (250ms) with a trailing-edge guard, so multi-agent activity bursts coalesce to at most 2 refetches per burst instead of one per event. - **Live isolation-mode transition**: PATCH `/api/projects/:id` with an `isolationMode` change now returns a 503 with actionable guidance when HybridExecutor is unavailable (local-only single-node), instead of silently persisting a config that the live runtime won't honor. - **Error handling regression**: restored try/catch around `HybridExecutor.initialize` and `engineManager.ensureEngine` in the parallel engine setup so a paused or broken cwd project no longer aborts dashboard startup. - **TaskStore migration race**: sequenced the SQLite store inits (TaskStore → AutomationStore → PluginStore → AgentStore) since they all open the same `.fusion/fusion.db` and run `addColumnIfMissing` migrations with a TOCTOU `hasColumn` → `ALTER` pattern. - **`gh` CLI invocation storm**: `isGhAvailable()` and `isGhAuthenticated()` now memoize their results with a 60s TTL. `GitHubTrackingReconciler` was scanning up to 200 done tasks at startup and calling `hasGhAuth()` per task — each call shelled out to `gh --version` and `gh auth status` (which makes a network roundtrip), pinning the event loop for ~60s of synchronous `spawnSync` work. CPU-profile-confirmed: dropped from 71s (69% of cold-start CPU) to 2s. The cache benefits all 28+ call sites in `dashboard/src/github.ts`, the engine PR monitor, the research provider, and the API routes automatically. `resetGhAvailabilityCache()` is exported for login/logout flows that need to invalidate immediately. - **SQLite integrity check delay**: `PRAGMA integrity_check(100)` walks every page of the database file and was scheduled 3 seconds after init — landing right in the responsiveness-critical window for ~7s per database. Pushed the deferred-check timer to 60 seconds so the user is already interacting with the dashboard by the time it runs. The check itself is unchanged; corruption detection still works. - **Engine init event-loop yields**: `InProcessRuntime.start()` now awaits a `setImmediate`-based yield between major init phases (TaskStore → Plugins → WorktreePool → AgentStore → Scheduler → Executor → HeartbeatMonitor → SelfHealing) so HTTP requests can be processed between them instead of waiting on the entire stack. Same yield is now interleaved between each step of `SelfHealingManager.runStartupRecovery()` (34 steps per project) and its periodic maintenance batches. - **Deferred startup recovery**: `InProcessRuntime.start()` no longer awaits `resumeStartupRecoverySequence()` or `workerManager.reconcileOrphaned()` — both are correctness-preserving background operations and their git/SQLite work was blocking server-listen for several seconds. - **Deferred orphan-task AI agent resumption**: orphaned in-progress tasks resumed at engine restart now wait 30 seconds before spawning their AI agent session (worktree setup + pi-coding-agent session creation is heavy and saturates the event loop). Override via `FUSION_RESUME_ORPHAN_DELAY_MS=`; auto-zeroes under Vitest. - **Event-loop lag tracer**: opt-in debug aid for diagnosing cold-start regressions. Set `FUSION_TRACE_EL_LAG=/path/to/file.txt` to capture every block >150ms with a timestamp relative to process start. - 4e4830f: Fix two bugs that compounded to produce bare `feat(FN-XXXX): merge fusion/fn-XXXX` merge commits in the dashboard: - **`Provided value cannot be bound to SQLite parameter 4` (TypeError) mid-merge**: the verification-fix finalize path called `upsertTaskCommitAssociation` with `commitSha` derived from a `git rev-parse HEAD` whose surrounding exec could reject under the parallel-attempt race, leaving `commitSha` undefined when bound to positional parameter 4. Extracted both duplicated callsites into a `recordCommitAssociationFromHead` helper that catches exec failures and validates each git output is non-empty before binding. The merge no longer fails over a denormalized lookup write when the commit itself landed cleanly. - **Bare-fallback subjects persisted into `mergeDetails.mergeCommitMessage`**: when `buildDeterministicMergeMessage`'s tier-3 fallback (`merge ${branch}`) made it onto a landed commit, the four `classification.commit.subject` / `landedCommit.subject` recovery sites in `self-healing.ts` and `aiMergeTask` copied that bare subject verbatim into `mergeDetails`. Added `regenerateBareMergeSubject` (in a new `merger-bare-subject.ts` module to keep self-healing's import graph narrow) which detects the bare pattern via `BARE_MERGE_SUBJECT_RE` and regenerates a descriptive subject from the landed commit's diff stat via the existing AI commit-subject summarizer. Cosmetic only — the git commit is never amended; the regenerated subject only populates the persisted `mergeDetails` and the in-process `MergeResult`. Gated by `settings.useAiMergeCommitSummary`. - Updated dependencies [6a6c6fd] - @fusion/core@0.34.0 - @fusion/pi-claude-cli@0.34.0 ### @fusion/plugin-sdk #### Patch Changes - Updated dependencies [6a6c6fd] - @fusion/core@0.34.0 ### @runfusion/fusion #### Minor Changes - 5eacd79: Add optional `baseBranch` support to mission creation and task planning flows. - `fn_mission_create` now accepts `baseBranch` to persist a mission-level default integration branch. - Mission feature/slice triage inherits mission `baseBranch` when no explicit triage base branch is supplied. - `fn_task_plan`/CLI planning paths now accept and forward `baseBranch` to created tasks. - 1fb905a: Planning Mode now lets you pick a branch strategy (project default, auto-named, existing, or custom new) and an optional base/merge-target branch when creating a task from a completed planning session. #### Patch Changes - 0a6da9f: Fix ntfy notification deep links: project-only links now switch projects, and task links to non-current projects resolve against the correct project before opening the modal. - 06a107d: Fix triage/executor not swapping to the configured planning fallback model when the primary provider's API key is missing (or returns 401/403/rate-limit). The top-level `promptWithFallback` now delegates to the rich session-attached path (which runs `isRetryableModelSelectionError` and `swapPromptSession`), with a WeakSet re-entry guard preserving the FN-4900 recursion fix. - 88c465c: Fix two engine reliability bugs surfaced by CI sharding repair: - Self-healing in-review branch rebind now dedups case-variant candidate refs by resolved SHA rather than lowercase name, so two distinct branches sharing a case-insensitive name on case-sensitive filesystems (Linux) are correctly flagged as ambiguous instead of one being silently picked. - CI test sharding: removed the `--` separator between `pnpm test` and `--shard`, which vitest's CLI parser was treating as end-of-flags and turning the shard selector into a positional file filter — silently disabling sharding so every shard ran the full suite. Test shards now run their actual slice. - CI test-shards jobs now check out with `fetch-depth: 0` so engine tests that depend on real git history (merge-base, ref resolution) behave the same on CI as locally. - PR Checks workflow now also runs on push to `main`, so post-merge regressions surface immediately instead of waiting for the next PR. - 6a6c6fd: Dashboard startup and request-storm fixes: - **Faster startup**: parallelized independent store inits, started CentralCore init early in background, and ran plugin loading concurrently with extension resolution. The duplicate-runtime root cause is also fixed — `shouldUseHybridExecutor` no longer auto-enables for local-only multi-project setups, where `ProjectEngineManager` already handles project lifecycle (set `FUSION_HYBRID_EXECUTOR=1` to force-enable). Eliminates ~7s of redundant self-healing pipeline work per cold start. - **Per-page request reduction**: added in-flight request dedupe (`packages/dashboard/app/api/dedupe.ts`) wrapped around the top API offenders. A single page load went from ~177 requests to ~101, with `/api/plugins/ui-slots` dropping from 17× to 1×. - **Stale-data-after-mutation hazard**: `forceFresh` option on the deduped fetchers now redirects ALL in-flight waiters to receive the fresh post-mutation response, not just the forcing caller. Generation counters in `useAgents` and `AgentListModal` provide a second layer of protection against slow polls overwriting fresh state. - **SSE refresh storm**: agent SSE event handler now debounces (250ms) with a trailing-edge guard, so multi-agent activity bursts coalesce to at most 2 refetches per burst instead of one per event. - **Live isolation-mode transition**: PATCH `/api/projects/:id` with an `isolationMode` change now returns a 503 with actionable guidance when HybridExecutor is unavailable (local-only single-node), instead of silently persisting a config that the live runtime won't honor. - **Error handling regression**: restored try/catch around `HybridExecutor.initialize` and `engineManager.ensureEngine` in the parallel engine setup so a paused or broken cwd project no longer aborts dashboard startup. - **TaskStore migration race**: sequenced the SQLite store inits (TaskStore → AutomationStore → PluginStore → AgentStore) since they all open the same `.fusion/fusion.db` and run `addColumnIfMissing` migrations with a TOCTOU `hasColumn` → `ALTER` pattern. - **`gh` CLI invocation storm**: `isGhAvailable()` and `isGhAuthenticated()` now memoize their results with a 60s TTL. `GitHubTrackingReconciler` was scanning up to 200 done tasks at startup and calling `hasGhAuth()` per task — each call shelled out to `gh --version` and `gh auth status` (which makes a network roundtrip), pinning the event loop for ~60s of synchronous `spawnSync` work. CPU-profile-confirmed: dropped from 71s (69% of cold-start CPU) to 2s. The cache benefits all 28+ call sites in `dashboard/src/github.ts`, the engine PR monitor, the research provider, and the API routes automatically. `resetGhAvailabilityCache()` is exported for login/logout flows that need to invalidate immediately. - **SQLite integrity check delay**: `PRAGMA integrity_check(100)` walks every page of the database file and was scheduled 3 seconds after init — landing right in the responsiveness-critical window for ~7s per database. Pushed the deferred-check timer to 60 seconds so the user is already interacting with the dashboard by the time it runs. The check itself is unchanged; corruption detection still works. - **Engine init event-loop yields**: `InProcessRuntime.start()` now awaits a `setImmediate`-based yield between major init phases (TaskStore → Plugins → WorktreePool → AgentStore → Scheduler → Executor → HeartbeatMonitor → SelfHealing) so HTTP requests can be processed between them instead of waiting on the entire stack. Same yield is now interleaved between each step of `SelfHealingManager.runStartupRecovery()` (34 steps per project) and its periodic maintenance batches. - **Deferred startup recovery**: `InProcessRuntime.start()` no longer awaits `resumeStartupRecoverySequence()` or `workerManager.reconcileOrphaned()` — both are correctness-preserving background operations and their git/SQLite work was blocking server-listen for several seconds. - **Deferred orphan-task AI agent resumption**: orphaned in-progress tasks resumed at engine restart now wait 30 seconds before spawning their AI agent session (worktree setup + pi-coding-agent session creation is heavy and saturates the event loop). Override via `FUSION_RESUME_ORPHAN_DELAY_MS=`; auto-zeroes under Vitest. - **Event-loop lag tracer**: opt-in debug aid for diagnosing cold-start regressions. Set `FUSION_TRACE_EL_LAG=/path/to/file.txt` to capture every block >150ms with a timestamp relative to process start. - bad6759: Enable editing the agent name during the review step of the New Agent dialog. - 7f01b53: Fix chat session API endpoints ignoring `projectId` in multi-project mode. `GET /chat/sessions`, `GET /chat/sessions/:id`, `GET /chat/sessions/:id/messages` and related mutation endpoints all used `options.chatStore` (the home-directory project's store) regardless of the `projectId` query parameter. In a multi-project daemon (e.g. running from `~/`) sessions belonging to secondary projects were invisible — list returned empty, fetching by ID returned 404. Root cause: `registerChatRoutes` accessed `options.chatStore` directly instead of routing through the per-project `resolveProjectChatContext` helper (already used correctly by `registerChatRoomRoutes` for the rooms API). Fix: introduce a `resolveScopedChatStore(projectId)` helper inside `registerChatRoutes` that delegates to `resolveProjectChatContext`, and replace all ten `options.chatStore` usages with calls to this helper. When `engineManager` is present and has an engine for the given `projectId`, the engine's own `ChatStore` is used; otherwise falls back to the default store (backward compatible). - 64056b3: Fix `useChat` truncating sessions longer than 50 messages on initial open. `loadMessages()` fetched `{ limit: 50 }` for the initial load. The `loadMoreMessages` callback was never called from `ChatView` (no scroll sentinel exists), so sessions beyond 50 messages were permanently cut off. Fix: introduce `fetchAllMessagesInChat()` that paginates through the API's 200-message cap and replace the initial load path. A stale-session guard (via `activeSessionRef`) prevents overwriting a switched session's messages. The forward-pagination path (`isPaginationRequest = true`) is preserved unchanged for backward compatibility. - 629aa29: Fix Windows compatibility in cloudflared install fallback by replacing `execFileAsync("mkdir", ["-p", ...])` with `fs.mkdir({ recursive: true })`. The shell-level `-p` flag is Unix-only and breaks installation on Windows cmd.exe with "A subdirectory or file -p already exists". The worktree-hooks fix from the original report was already landed independently. ### runfusion.ai #### Patch Changes - Updated dependencies [0a6da9f] - Updated dependencies [06a107d] - Updated dependencies [88c465c] - Updated dependencies [6a6c6fd] - Updated dependencies [bad6759] - Updated dependencies [7f01b53] - Updated dependencies [64056b3] - Updated dependencies [5eacd79] - Updated dependencies [1fb905a] - Updated dependencies [629aa29] - @runfusion/fusion@0.34.0 ## 0.33.0 ### @fusion/core #### Minor Changes - a201f56: feat(core): add `mergeAdvanceAutoSync` project setting (`"off" | "ff-only" | "stash-and-ff"`) Adds the schema for a new project setting that controls what happens in **other** worktrees still checked out on the integration branch when the merger advances the branch ref. Previously the merger only updated `refs/heads/` and left every other checkout's index and working tree pinned at the old tip, so `git status` in the user's project-root checkout reported the new commits as inverted "staged changes to be committed." Modes (default `"stash-and-ff"`): - `"off"` — preserve the legacy behavior; user must `git pull` or click the Merge Advance Notice banner Pull button. - `"ff-only"` — auto-fast-forward only clean worktrees; dirty worktrees stay untouched and the banner still surfaces. - `"stash-and-ff"` — run the Smart Pull pipeline (stash → fast-forward → pop). Pop conflicts emit `merge:auto-sync` audit events with `outcome: "stash-pop-conflict"` and surface through the existing dashboard stash-conflict modal. Schema-only in this changeset; the merger hook that consumes the setting lands in the follow-up engine change. - 51fc826: fix(engine,core): dedup heartbeat-spawned follow-ups by parent task Heartbeat agents create follow-up tasks via `fn_task_create`. Until now, the intake similarity guard scoped candidates by `sourceAgentId` only, so the same parent task could spawn many sibling tasks across heartbeats whenever triage rewrote their titles enough to dodge the title-fingerprint guard. The task-scoped heartbeat now stamps `sourceParentTaskId` (and `sourceRunId`) on every `fn_task_create`, and the intake duplicate matcher treats a candidate as a sibling when it shares either the caller's agent ID or the caller's parent task ID. Same-parent siblings with similar descriptions are auto-archived as before. Tool description and heartbeat prompts also now instruct agents to scan existing open tasks before creating, as a belt-and-suspenders layer above the deterministic dedup. #### Patch Changes - 408e20b: fix(merger): two root-cause fixes for tasks landing in Done with no commit on main **Bug 1: sibling fusion/fn-\* branch as merge target** — `resolveTaskMergeTarget` previously returned `task.baseBranch` unconditionally before falling back to the project default. When a task was dispatched as a sibling/dependent off another in-flight task's worktree, `baseBranch` ended up as the upstream's `fusion/fn-` branch. The merger then detached onto that sibling, squashed on top of it, and advanced `refs/heads/fusion/fn-` — never main. FN-5233's squash (`84563e549`) stranded on `fusion/fn-5339`; FN-5530's (`4140a3e0a`) stranded on `fusion/fn-5543`. The resolver now refuses any `fusion/fn-\*` candidate as a merge destination and falls through to the project default. The merger emits a new `merge:merge-target-rejected-fusion-sibling` audit event so the upstream `baseBranch`-propagation bug stays observable. **Bug 2: deadlock-recovery mis-attributed tasks to unrelated commits** — `findLandedTaskCommit` step (4) used `git log --grep=FN-XXXX` which matches the entire commit message (not just the subject) and blindly accepted the first hit. FN-5441 and FN-5446 were both marked done against `e3dbfaae` — an FN-5483 commit whose body merely _mentioned_ them by name in a paragraph about a refusal. The grep fallback now fetches each candidate's body and re-verifies ownership via a tightened `commitOwnedByTask`: trailers must be line-anchored (`(?:^|\n)Fusion-Task-Id: (?:\n|$)`), and the subject fallback must match a conventional-commit form (`():` or `:`), not a substring. Prose mentions can no longer claim a task. The historical recovery for FN-5233 has been cherry-picked to main as `2d2e5b809`. The other 11 affected tasks (FN-5441, FN-5446, FN-5472, FN-5484, FN-5487, FN-5490, FN-5515, FN-5517, FN-5526, FN-5539, FN-5540, FN-5542) remain in Done but need separate triage — 3 look like legitimate verification-only no-ops, the remaining 9 likely lost real work. - ec6643e: fix(test-utils): cancel subprocess tracking timer for every proc in afterEach The vitest subprocess guard registered a 60 s "command timed out" timer for each tracked child process and relied on `afterEach` to cancel it. Under concurrent load (`pnpm` recursive test runs) the timer could outlive the originating test and fire during a later test's `afterEach`, surfacing as spurious "Test subprocess guard detected unsafe child-process usage: Timed out after 60000ms" failures attributed to a different test name. The cleanup loop now scopes "Left running" failure reporting + SIGKILL to processes spawned by the current test, but unconditionally clears each tracked subprocess's timer so the 60 s timeout cannot fire after the afterEach completes. The grace period before declaring a process leaked is also raised from 200 ms to 1 s to absorb event-loop contention from slow git shells under recursive test load. - 4c31e88: feat(engine): merger auto-syncs project-root checkout after advancing integration-branch ref Wires `mergeAdvanceAutoSync` into the merger's post-ref-advance code path. After `advanceIntegrationBranchRef` ff-updates `refs/heads/`, the merger now enumerates other worktrees still on that branch (typically the user's project-root checkout) and reconciles each one's index + working tree to the new tip via `syncWorktreeToHead`. The reconciliation primitive is **not** a `git pull` — origin may still be at the previous tip (no `pushAfterMerge`), in which case `git pull --ff-only` is a no-op and a naive `stash → pull → pop` ends with the worktree restored to the old state. Instead `syncWorktreeToHead`: 1. Diffs the worktree against the _previous_ tip to isolate real user edits from the stale-index "phantom diff" that looks like inverted commits. 2. When the worktree is clean against the previous tip, runs `git reset --hard HEAD` to snap index + files forward. 3. In `stash-and-ff` mode with real edits, captures them as a binary patch against the previous tip, snaps to HEAD, then `git apply --3way` to restore. Untracked files are copied to a temp dir and restored after the snap. Patch conflicts surface as `synced-with-pop-conflict` with the patch left on disk for manual recovery. Each per-worktree attempt emits a `merge:auto-sync` audit event (new `GitMutationType`) with the outcome; the per-step `pull:fast-forward`, `stash:push`, `stash:pop`, and `stash:pop-conflict` events that pass through the auditor are tagged `metadata.autoSync = true` so downstream consumers can attribute them. The user-facing effect: with the default `mergeAdvanceAutoSync: "stash-and-ff"`, after a Fusion task merges the user's `git status` in the project-root checkout becomes clean and the working tree shows the new commits' content — no manual `git reset` or Pull-button click required. Set `mergeAdvanceAutoSync: "off"` to restore the legacy behavior (the Merge Advance Notice banner still surfaces and the user pulls by hand). Backstopped by `merger-auto-sync.slow.test.ts` covering: clean-sync snaps both index and files forward, ff-only with real edits is a no-op, stash-and-ff preserves untracked local files across the snap, task worktrees on `fusion/fn-*` branches are correctly skipped, and an empty branch map emits nothing. ### @fusion/dashboard #### Minor Changes - 6e7f1e5: feat(dashboard): explain "Recent integration-branch advances" and add a one-click "Sync working tree" fix Two additions to Git Manager → Status: **Info disclosure** — an `[i]` button next to the "Recent integration-branch advances (N need action)" header toggles an inline explainer. Covers what an "advance" is, what each `autoSyncOutcome` value means (`clean-sync`, `synced-with-edits-restored`, `off / not run`, `stash-failed`, `would-conflict`, …), and where to enable `mergeAdvanceAutoSync` for the permanent fix. **Sync working tree button** — when ≥1 advance shows `needsAction`, a button surfaces in the same header that calls the existing `POST /api/git/pull` (FN-5358 Smart Pull machinery: auto-stash dirty edits, fast-forward pull, restore stash, surface conflicts). On success the extended git status auto-refetches and the "need action" count drops; on conflict, the existing error toast fires. No new state machine — `handlePull`/`remoteLoading === "pull"` is the same plumbing the existing Pull button uses. - 85786e7: feat(dashboard): show extended integration-branch + working-tree state in Git Manager Repository Status panel now answers "what is the actual state of my project root vs the integration branch?" so operators can be sure of the picture even when the Merge Advance Notice banner has been dismissed. `GET /api/git/status` accepts a new `?extended=1` query and returns additional optional fields: - **integrationBranch** + **integrationBranchSource** — the canonical branch (resolved via `settings.integrationBranch` → legacy `baseBranch` → `origin/HEAD` → `main`) and where the value came from. - **integrationTipSha / originIntegrationTipSha** — SHAs at both ends, so operators can spot when local main has been advanced by the merger but origin/main hasn't caught up. - **aheadOfIntegration / behindIntegration** — HEAD vs local integration tip (useful when on a non-integration branch). - **aheadOfOriginIntegration / behindOriginIntegration** — local integration tip vs `origin/`. - **dirtyDetails** — staged/modified/untracked/conflicted counts + a 12-line porcelain sample. - **indexStaleVsHead** — true when the index reflects a previous tip and the worktree is clean against the index but not against HEAD. Surfaces the exact "phantom staged changes" scenario that `mergeAdvanceAutoSync` exists to fix. - **stashCount** — for at-a-glance recovery awareness. - **recentMergeAdvances** — up to 5 recent `merge:integration-ref-advance` audit events for the project root, joined with their `merge:auto-sync` outcomes; entries whose auto-sync didn't successfully bring this worktree forward are flagged `needsAction: true`. `GitManagerModal` now renders all of this: - The existing Branch / Commit / Working Tree / Remote Sync cards gain sub-text — Working Tree shows staged/modified/untracked/conflicted breakdown; Branch shows whether you're on the integration branch. - A second row of cards adds Integration branch (with resolution source + tip SHA), HEAD-vs-integration ahead/behind, local-integration-vs-origin ahead/behind, and stash count. - A yellow warning panel appears when `indexStaleVsHead` is true, telling the operator to enable `mergeAdvanceAutoSync` or run `git reset --hard HEAD`. - A "Recent integration-branch advances" list shows the last few merger advances with their per-advance auto-sync outcome, color-coded by whether they still need action. All `fetchGitStatus(projectId)` calls inside `GitManagerModal` now pass `{ extended: true }`. Other callers in the app are unaffected — the extra fields are optional and the un-extended response shape is unchanged. #### Patch Changes - 60a0012: fix(dashboard): stop main-chat and quick-chat composers from instantly dismissing the Android soft keyboard Two layered Android-specific fixes for the chat composers: 1. The body scroll-lock applied while the keyboard is open in main chat was an iOS-specific workaround for visualViewport drift. On Android Chrome it does the opposite of what we want — mutating `body { position: fixed; ... }` while the keyboard is opening causes Chrome to treat it as a focus-target relayout and immediately dismisses the keyboard. `useMobileScrollLock` is now gated to iOS UAs. 2. ChatView and QuickChatFAB both had an iOS-specific `onTouchStart` on the textarea that called `event.preventDefault()` and then programmatically refocused the input (to suppress iOS's visualViewport auto-scroll on re-focus). On Android, `preventDefault` on a textarea touchstart prevents the soft keyboard from opening — programmatic `focus()` alone does not raise the Android keyboard. Result: tapping the composer focused the input but the keyboard never appeared, looking like an instant dismiss. The touchstart workaround is now gated to iOS UAs via `isIOS()`. - a10fc56: fix(dashboard): keep Android keyboard open in main chat; disable kanban pinch-zoom Two Android-specific fixes: 1. **Keyboard dismissing in main chat.** `mobileKeyboardOpen` in `App.tsx` (derived from `useMobileKeyboard`) gates `project-content--with-mobile-nav` / `--with-footer` className assignment and MobileNavBar rendering. When the soft keyboard opened, those classes were removed and the nav unmounted, shrinking padding-bottom by ~80px in a single render. Android Chrome treats the resulting jump of the focused chat input as the focus target moving and instantly dismisses the keyboard. With `interactive-widget=resizes-content` set on Android, the layout viewport itself shrinks with the keyboard, so the hide-nav-on-keyboard behavior was redundant on Android (and harmful). The whole pattern is now gated to iOS via `isIOS()`. iOS path is unchanged. 2. **Pinch-zoom on kanban.** Android Chrome ignores `user-scalable=no` for accessibility, and the kanban board's `overflow-x: auto` columns combined with the inflated ICB produce a broken visual when the user zooms out. Adds `touch-action: pan-x pan-y` to `html, body` inside the mobile media query, which keeps scroll panning but disables pinch-zoom (Chat and MissionManager were unaffected because they don't expose a wide horizontal scrollable region). - de67c51: fix(dashboard): pull syncs the worktree to local integration tip, not just to origin The integration-mode `POST /api/git/pull` (used by the merge-advance-notice banner) only ran `git merge --ff-only origin/` after fetching. When the merger had advanced local `refs/heads/` via `update-ref` but the user hadn't pushed yet, the worktree's HEAD already resolved to the new sha (symbolic ref follow) but the working tree and index were still at the old state. The fast-forward step short-circuited (`already up to date with origin`) and the user saw "Pull completed" with `fromSha === toSha` while their files visibly stayed behind. Pull now explicitly resets the worktree to `refs/heads/` after the origin fast-forward step. The autostash above protects user edits, so the reset is safe regardless of whether the origin FF ran. - 5d35b64: fix(dashboard): remove duplicate integration-advances UI; Sync working tree is now pure-local (no origin fetch) **Removed duplicate UI** — Git Manager → Status had two overlapping sections rendering the same data: a `Sync local tip` button + a `Recent integration advances` list, sitting above the highlighted `Recent integration-branch advances` block (the one with the lost-work warnings). Deleted the duplicate (`gm-integration-actions` + `gm-recent-advances`) along with the dead `mergeAdvanceEvents` state, fetcher, and SSE subscription that only fed it. **Sync working tree is now pure-local** — for the "N need action" case the merger has already advanced `refs/heads/` locally and the worktree just needs to follow. Previously the button called the integration-mode pull which ran `tryFastForwardFromOrigin` first, silently pulling in unrelated remote commits. New `skipOriginFetch` option on `PullGitBranchOptions.integration` (and the matching `POST /api/git/pull` body field) skips the origin step entirely. The Sync button passes `skipOriginFetch: true`, so the sequence is: auto-stash → `git reset --hard refs/heads/` → restore stash. Origin is not touched. Help disclosure updated to match the new behavior. - 4f38ed1: fix(dashboard): clear `needs action` on recent integration-branch advances after manual sync The Git Manager's "Recent integration-branch advances" list derived `needsAction` purely from the original `merge:auto-sync` audit-event outcome. When the operator clicked "Sync working tree" — or fixed up the worktree by hand — the worktree caught up to the integration tip, but the list kept showing "(N need action)" because the historical audit events still recorded the original failure/disabled state. `collectRecentMergeAdvances` now also checks whether each advance's `toSha` is reachable from the current HEAD. If it is, the worktree already contains that advance and `needsAction` is false regardless of what the audit trail recorded. - ef12df4: fix(dashboard): close 8 review findings on extended Git Manager status + Integration branch setting **Settings persistence (data-loss)** — the project-settings patch builder now applies null-as-delete to all non-model keys, matching the global-settings branch. Previously, clearing the Integration branch field (picking `(auto-detect)` or clicking `Use dropdown`) set `integrationBranch: undefined`, which `JSON.stringify` silently dropped — the server retained the stale explicit value and the operator could not un-pin the branch from the UI. **`isIndexStale` was wrong both directions** — the heuristic (`diff --cached --name-only` non-empty AND `diff --name-only` empty) fired false-positive on benign `git add` and false-negative whenever the worktree had any unrelated edit. Replaced with a reflog-anchored check: stale iff `refs/heads/@{1}` exists, HEAD is a descendant of it, and `git diff-index --cached ` is empty (i.e. the index exactly matches the pre-advance state). **Auto-sync attribution** — two fixes to `collectRecentMergeAdvances` in `register-git-github.ts`: - Auto-sync events are now matched by `(taskId, newSha)` instead of `taskId`-only. A task that produced multiple advances over time no longer has all its older entries mislabeled with the most-recent outcome. - `worktreePath` comparison now runs both sides through `fs.realpathSync` first. On macOS the merger emits canonicalized paths (via `canonicalizePath` in `worktree-pool.ts`) while the route was called with the store's raw `rootDir`; symlinked project paths caused every advance to be marked `needsAction: true` indefinitely. **Extended path no longer 500s on git failure** — the `?extended=1` branch wraps `computeExtendedGitStatus` in its own try/catch and falls back to the basic status shape on any unhandled failure. Previously an unguarded `git branch --show-current` throw escaped to the route's outer catch and returned HTTP 500, while the basic path returned 200 with the swallowed-failure shape — surface parity matters because the dashboard always passes `extended=1` and would otherwise render an error toast where it should render the degraded panel. Also wrapped the same call inside `computeExtendedGitStatus` so detached-HEAD / non-git states return an empty `currentBranch` instead of throwing. **Integration branch falls back to `refs/remotes/origin/`** — when the configured branch exists only as a remote-tracking ref (e.g. operator set `integrationBranch: "release/v2"` without ever `git switch`-ing it locally), `integrationTipSha` now resolves to the origin tip instead of being null. A new `integrationTipSource: "local" | "remote-only" | "missing"` field tells the UI which side won; the Git Manager surfaces this with a `(remote-only — run git switch to track locally)` sub-text and a `no ref found` error state when both refs are missing. **Copy commit hash shows two buttons** — the Copy button now copies `status.commit` (the short SHA actually displayed in the `` element). A second Copy-full button surfaces `status.headSha` for git operations that need the 40-char SHA. Previously the single button silently copied the full SHA when extended was on, so what the user saw on screen was no longer what they pasted. **Detached HEAD no longer shows misleading "(not on main)"** — `git branch --show-current` returns empty on detached HEAD; the route now leaves `isOnIntegrationBranch` as `undefined` (not `false`) in that case, and the UI's "(not on )" sub-text only renders when we know we're on a different branch — not when we're on no branch at all. - d5cfa92: fix(dashboard): close 7 review findings on the extended-status hardening pass Follow-up to the prior fix commit; closes 7 more issues that an independent code review surfaced. **Settings inheritance regression (high)** — `SettingsModal.handleSave`'s non-model project branch lost the "only write if changed" gate when the prior commit added null-as-delete support. Result: every effective/inherited project key was being persisted as an explicit project override on every save, silently breaking inheritance across ~30+ keys. Restored the `value !== initialProjectValue` gate, matched against the model-lane branch's existing pattern. **Git Manager `Local vs origin` card showed misleading "Synced" in remote-only mode** — when `integrationTipSource === "remote-only"`, both `aheadOfOriginIntegration` / `behindOriginIntegration` are deliberately undefined (there's no local branch to compare), but the card's render fell through to `(ahead ?? 0) === 0 && (behind ?? 0) === 0 → "Synced"`. Now renders an explicit "no local tracking" sub-text in that case, with a separate `HEAD vs origin/` card surfacing a meaningful distance. **`isIndexStale` extended to multi-hop and gated to integration-branch worktrees** — - Walks up to 16 `refs/heads/` reflog entries so an A→B→C burst whose middle sync also missed is detected (the prior check only consulted `@{1}`). - Only fires when `isOnIntegrationBranch === true`. Previously, a feature-branch worktree whose HEAD happened to descend from `@{1}` (e.g. `git switch -c hotfix main@{N}`) would trip the stale-index warning despite being perfectly healthy. **`enumeration-failed` auto-sync events no longer dropped** — the new `(taskId, newSha)` join filter required both `worktreePath` and `newSha` on every auto-sync event, which discarded the merger's early-failure events that emit neither. Now: events with both fields use the per-advance pair-key (with macOS realpath canonicalization on both sides); events with neither use a task-id fallback so the diagnostic outcome still surfaces on the matching advance. **`aheadOfIntegration` no longer silently shifts semantics** — split into three distinct distance fields so consumers don't have to read `integrationTipSource` to know which comparison they got: - `aheadOfIntegration` / `behindIntegration` — HEAD vs **local** integration tip; undefined when only the remote tip exists. - `aheadOfIntegrationRemote` / `behindIntegrationRemote` — HEAD vs `origin/`; defined whenever the remote tracking ref exists. - `aheadOfOriginIntegration` / `behindOriginIntegration` — local integration tip vs `origin/`; defined only when both refs exist. **`currentBranch` failure no longer masks wrong-branch state** — `git branch --show-current` returns empty on detached HEAD (success) and throws on transient git errors (lock contention, timeout). The prior catch collapsed both into `currentBranch = ""` so the UI couldn't distinguish them. New `currentBranchDetectionFailed?: boolean` field on `GitStatus` lets the UI surface "branch detection unavailable" on a real failure rather than silently hiding the wrong-branch warning. - 916047c: feat(dashboard): Integration branch setting is now a dropdown of local branches with Custom… fallback Replaces the plain text input with a `