Introduces a Goals REST API (`GET/POST/PUT /api/goals` and `GET/PUT /api/goals/:id`) backed by a new `@fusion/core` goal store and typed goal types, including comprehensive route and store test coverage. Documentation on architecture and storage is updated to reflect the new domain, and a changeset
Fusion-Task-Id: FN-5622
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5622
Implements source issue delete handling in the task dashboard, including prompt-based user interaction for source-linked issues, new GitHub tracking state logic in `packages/dashboard/src/github-tracking-state.ts`, and corresponding test coverage, with documentation added to `docs/task-management.md
Fusion-Task-Id: FN-5618
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5618
Added OpenAI Responses API as a new custom provider type, wiring `apiType: "responses"` through the core registry, engine routes, and dashboard UI with a dropdown selector; includes test coverage across the registry, routes, and component layers.
Fusion-Task-Id: FN-5601
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5601
This merge implements an OAuth relogin banner feature (FN-5595) that displays in the dashboard when OAuth tokens expire. The feature includes a new `OAuthReloginBanner` component with styling and tests, an OAuth validity logger in the engine for tracking token state, and corresponding API route inte
Fusion-Task-Id: FN-5595
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5595
- fix(perf): use messagesRef.current in loadMoreMessages to avoid
recreating callback on every streamed token; messagesRef is already
kept in sync on every render so no useEffect needed — removes
`messages` from useCallback deps
- fix(api): reject invalid order query param with 400 instead of
silently ignoring; valid values are 'asc' and 'desc'
Tests:
- useChat: loadMoreMessages identity is stable when messages array changes
- chat-routes: GET /messages?order=invalid returns 400
Multiple coordinated fixes for the perceived "dashboard takes forever to
load" complaint. Per-page-load HTTP requests drop from ~177 to ~101 and
duplicate per-project InProcessRuntime creation is eliminated.
- engine: shouldUseHybridExecutor no longer auto-enables for local-only
multi-project setups (set FUSION_HYBRID_EXECUTOR=1 to force). The
duplicate-runtime path was running self-healing twice per project and
contending on the same SQLite file. ProjectEngineManager already
handles N local projects with one InProcessRuntime each.
- dashboard cli: parallelized independent store inits, started
CentralCore.init early in background, ran plugin loading concurrently
with extension resolution. Sequenced SQLite store inits to avoid a
TOCTOU race in addColumnIfMissing migrations across TaskStore /
AutomationStore / PluginStore / AgentStore (all open the same
.fusion/fusion.db). Restored try/catch around HybridExecutor.initialize
and engineManager.ensureEngine so a paused or broken cwd project no
longer aborts dashboard startup.
- dashboard client: added in-flight request dedupe wrapped around the
top API offenders. /api/plugins/ui-slots drops from 17x to 1x per load.
dedupe.forceFresh redirects ALL in-flight waiters to receive the fresh
post-mutation response, not just the forcing caller. Generation
counters in useAgents and AgentListModal protect against slow polls
overwriting fresh state.
- dashboard SSE: agent event handler now debounces 250ms with a
trailing-edge guard so multi-agent activity bursts coalesce to at
most 2 refetches per burst.
- dashboard route: PATCH /api/projects/:id with isolationMode change
returns 503 with actionable guidance when HybridExecutor is
unavailable, instead of silently persisting a config the live runtime
won't honor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move resolveScopedChatManager() before res.flushHeaders() in the
POST /messages route so that failures (e.g. project DB cannot be
opened) produce a proper HTTP error instead of silently closing the
SSE connection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve conflict in register-chat-routes.ts: keep per-project
ChatManager routing via resolveScopedChatManager while preserving
main's resolveProjectChatContext-based store resolution for read
routes. The auto-merged chat-project-services.ts correctly combines
main's fallback-safe resolveProjectChatContext with the PR's new
getOrCreateScopedChatManager cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The session existence checks in GET /stream and POST /messages were using
options?.chatStore (global DB), so secondary-project sessions would throw
notFound before the scoped ChatManager ran. Replaces with resolveScopedChatStore.
In multi-project mode the global chatManager was backed by ~/.fusion/fusion.db.
Secondary project chat sessions live in per-project DBs, so
chatManager.sendMessage() failed with 'Chat session not found' for any
project that is not the daemon's CWD.
Fix: add getOrCreateScopedChatManager to chat-project-services.ts (cached by
fusionDir, same pattern as the existing chatStore cache). POST /messages,
GET /stream, POST /cancel, and GET /sessions isGenerating enrichment all
resolve the per-project ChatManager when projectId is provided.
Fallback to global chatManager when no projectId (preserves single-project mode).
Tests: added multi-project chat routing describe block in chat-routes.test.ts.
Group A (2 failures): resolveProjectChatContext was calling
getOrCreateProjectStore even when no engine existed for the
project. This triggered real FS lookups in tests, causing the
catch to return the default store instead of the engine store.
Fix: only take the engine path when an engine is actually found.
Group B (1 failure): createSSERequest() returned a bare
EventEmitter with no .query property. The refactored handler
reads req.query.projectId at the top, throwing TypeError.
Fix: initialise .query = {} in createSSERequest().
Group C (4 failures): createMockStore() in routes-agents.test.ts
was missing getFusionDir(), which getOrCreateScopedChatStore()
calls to compute the cache key. The resulting TypeError caused
every lookup=resume handler invocation to 500.
Fix: add getFusionDir() to the mock.
Adds planning branch controls to the PlanningModeModal, extending the planning API and routes with supporting tests and documentation. The changeset bumps `@runfusion/fusion` as a minor release.
Fusion-Task-Id: FN-5585
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5585
Replace execFileAsync("mkdir", ["-p", ...]) with fs.mkdir({ recursive: true })
in the cloudflared install fallback path. The -p flag is Unix-only and breaks
on Windows cmd.exe ("A subdirectory or file -p already exists"). Test mocks
updated to verify the fs.mkdir call instead of the shelled-out mkdir.
The original report covered both this site and packages/engine/src/worktree-hooks.ts;
the latter was already converted to fs.mkdir independently, so only the
dashboard route change is needed.
Co-Authored-By: kenlin8827 <kenlin8827@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, getProjectContext awaited ensureEngine() which could block the
HTTP request handler for several seconds while a project engine initialised
(e.g. for newly-registered projects). This caused curl --max-time timeouts
and empty responses on the first request to a new project.
Change: replace await ensureEngine() with onProjectAccessed() (fire-and-forget).
- First request to an unstarted project uses getOrCreateProjectStore directly
(fast, reads from SQLite) — no blocking.
- Engine starts in background; subsequent requests use the running engine.
- Eliminates timing-dependent restart workarounds in init scripts.
Fixes: fn settings import followed by immediate API calls returning empty.
Adds integration test coverage for the GitHub tracking delete flow in `github-tracking-delete.test.ts`.
Fusion-Task-Id: FN-5582
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5582
Added GitHub tracking reconciliation to sync hidden and deleted archived tasks on engine startup, spanning a new reconcile task listing method in the core store, a reconciler pass in the dashboard, and comprehensive test coverage for both the store listing and the deleted-archived reconciliation log
Fusion-Task-Id: FN-5577
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5577
The Git Manager's recent integration-advances list derived `needsAction`
purely from the original `merge:auto-sync` audit-event outcome, so it kept
showing "(N need action)" after the operator clicked "Sync working tree" or
fixed up the worktree by hand. `collectRecentMergeAdvances` now also checks
whether each advance's `toSha` is reachable from HEAD — if it is, the
worktree already contains that advance and `needsAction` is false
regardless of what the audit trail recorded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the allow-resurrection toggle for task deletion, letting users prevent deleted tasks from being automatically restored. Changes span the `ConfirmDialog` component, `TaskDetailModal`, and the `useConfirm` hook, with comprehensive test coverage across the dashboard API and UI layers.
Fusion-Task-Id: FN-5475
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5475
Two cleanups in Git Manager → Status:
- Removed the duplicate "Sync local tip" button (gm-integration-actions)
and the second "Recent integration advances" list (gm-recent-advances)
that rendered above the highlighted block. Also dropped the dead
mergeAdvanceEvents state, fetcher, and SSE subscription that only
fed the deleted UI.
- Sync working tree is now pure-local. Added skipOriginFetch to
PullGitBranchOptions.integration (and the matching POST /api/git/pull
body field). When set, pullGitBranch skips tryFastForwardFromOrigin
entirely — the sequence is just auto-stash → git reset --hard
refs/heads/<integration> → restore stash. The Sync button passes
skipOriginFetch: true because the "N need action" recovery is for
catching the worktree up to a *local* merger ref-advance; touching
origin could silently pull in unrelated remote commits.
Help disclosure rewritten to reflect the pure-local behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the merger advances local `refs/heads/<integrationBranch>` via
`update-ref` without pushing, the user's project-root worktree HEAD
(symbolic to that branch) follows immediately to the new sha, but the
working files and index don't. The integration-mode pull only ran
`git merge --ff-only origin/<branch>`, which short-circuits as
"already up to date" when local is ahead of origin — leaving the
worktree visibly stale even though "Pull completed" was reported.
Pull now explicitly `git reset --hard <localIntegrationTip>` after
the origin fast-forward step. The autostash above protects user edits,
so the reset is safe regardless of whether the origin FF ran.
Regression test in routes-git.test.ts simulates the
local-ahead-of-origin scenario and asserts the reset-to-local-tip is
issued.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a fast-path method for scopes-related settings calls in the memory routes handler, replacing the previous implementation with a more efficient approach; tests are updated to cover the new route behavior.
Fusion-Task-Id: FN-5563
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5563
Settings inheritance (high):
- Restored the value !== initialProjectValue gate on the non-model
project branch. Previously every effective/inherited project key was
persisted as an explicit override on every save.
Git Manager UI lie in remote-only mode:
- "Local <branch> vs origin" card now renders "no local tracking"
instead of a green "Synced" badge when integrationTipSource ===
"remote-only" (no local branch to compare).
- New dedicated "HEAD vs origin/<branch>" card surfaces a meaningful
distance in remote-only mode.
isIndexStale correctness:
- Walks up to 16 reflog entries so multi-hop misses (A→B→C without
sync) are detected; the prior check only consulted @{1}.
- Gated on isOnIntegrationBranch === true so a feature-branch worktree
whose HEAD happens to descend from <integration>@{1} no longer trips
the FN-INDEX-DESYNC warning.
Enumeration-failed events surfaced:
- collectRecentMergeAdvances pairs events with (taskId, newSha) when
both are present, falls back to taskId-only for early-failure events
(e.g. "enumeration-failed") that have neither path nor newSha. The
diagnostic outcome now surfaces on the matching advance instead of
being silently dropped.
aheadOfIntegration semantics no longer shift:
- Split into three distinct fields: aheadOfIntegration (HEAD vs local),
aheadOfIntegrationRemote (HEAD vs origin/<branch>),
aheadOfOriginIntegration (local vs origin). Consumers no longer have
to read integrationTipSource to know which comparison they got.
currentBranch failure no longer masks wrong-branch state:
- Distinguish "command threw" (transient git error) from "command
succeeded with empty stdout" (legitimate detached HEAD). New
currentBranchDetectionFailed field lets the UI surface "branch
detection unavailable" on a real failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settings (data-loss):
- Non-model project keys now use null-as-delete instead of dropping
undefined via JSON.stringify, so clearing Integration branch (Use
dropdown / auto-detect) actually clears the persisted value.
isIndexStale (false-positive AND false-negative):
- Replaced the empty-worktree heuristic with a reflog-anchored check:
stale iff refs/heads/<integration>@{1} exists, HEAD is descendant of
it, and `git diff-index --cached <prevTip>` is empty.
Auto-sync attribution in collectRecentMergeAdvances:
- Match auto-sync events by (taskId, newSha) instead of taskId-only;
re-merged tasks no longer have older advances mislabeled with the
newest outcome.
- Compare worktreePath after realpathSync on both sides; macOS symlink
paths no longer cause permanent "needs action" false positives.
Extended path no longer 500s:
- Route wraps computeExtendedGitStatus in try/catch and falls back to
basic status on failure. Inner `branch --show-current` wrapped too
so detached HEAD / non-git rootDir doesn't throw.
Integration branch falls back to remote-only ref:
- When refs/heads/<branch> is missing, use refs/remotes/origin/<branch>
as the integration tip. New `integrationTipSource` field
("local"|"remote-only"|"missing") drives a UI badge.
Copy commit hash:
- Short-SHA copy is the default and matches what's displayed; a
separate "full" button copies the 40-char headSha. Previously the
single button silently copied the full SHA when extended was on.
Detached HEAD:
- isOnIntegrationBranch left undefined when currentBranch is empty so
the UI doesn't render "(not on <integration>)" against a
no-branch state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the FN-5233 tombstone system for soft-delete resurrection: a configurable `tombstoneWindowSeconds` deduplicates recreation of recently deleted tasks, with an `allowResurrection` flag that permits explicit resurrect-on-recreate, tombstone recreate guards in the store layer, and cleanup of
Fusion-Task-Id: FN-5233
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 is dismissed.
GET /api/git/status accepts ?extended=1 and returns additional optional
fields:
- integrationBranch + integrationBranchSource (settings|origin-head|fallback)
- integrationTipSha / originIntegrationTipSha
- aheadOfIntegration / behindIntegration (HEAD vs local integration tip)
- aheadOfOriginIntegration / behindOriginIntegration (local tip vs origin)
- dirtyDetails {staged, modified, untracked, conflicted, sample}
- indexStaleVsHead (surfaces the FN-INDEX-DESYNC scenario)
- stashCount
- recentMergeAdvances: up to 5 merge:integration-ref-advance events
joined with merge:auto-sync outcomes; needsAction flag flips when
auto-sync didn't successfully bring this worktree forward
GitManagerModal renders all of it:
- Existing cards get sub-text: branch shows "not on <integration>",
Working Tree shows staged/modified/untracked/conflicted breakdown
- Second row: Integration branch + source, HEAD-vs-integration,
local-vs-origin, stash count
- Yellow warning panel when indexStaleVsHead surfaces the merger's
stale-index situation with a recovery hint
- Recent integration-branch advances list, color-coded by needsAction,
shows the per-advance auto-sync outcome so operators can audit
even after dismissing the banner
All fetchGitStatus calls in GitManagerModal switched to extended:true.
Other callers unaffected — extra fields are optional.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Data-loss fixes in syncWorktreeToHead:
- Untracked-restore checks `git ls-tree -r --name-only HEAD` to skip
paths the new tip added as tracked files; user bytes stay in the
stage dir instead of clobbering merged content.
- Apply-failure on a deleted/renamed file: conflictedFiles falls back
to parsing `diff --git a/<p> b/<p>` headers when --diff-filter=U
returns nothing.
- All git invocations pass `-c core.quotePath=false` so non-ASCII
paths round-trip through copyFileSync.
- Stash-and-ff re-verifies rev-parse HEAD === newSha right before
each `reset --hard HEAD` (TOCTOU). On mismatch we bail with patch
preserved on disk.
- Stage dir lifecycle moved into try/finally with preserveStageDir
flag — kept whenever the user's edits live only in patchPath; rm'd
on all clean exits.
- Patch written to disk before the apply attempt, not only on
failure, so a crash between snapshot and apply doesn't lose edits.
Multi-worktree-same-branch fix:
- New getRegisteredWorktreeBranches returns Array<{branch,path}>
instead of collapsing into a Map. Multiple worktrees can share a
branch via `git worktree add --force -b`; merger now syncs all of
them rather than silently skipping all but the last.
Contract + surfacing fixes:
- JSDoc on merge:auto-sync GitMutationType now lists the actually-
emitted outcome strings + stage enum.
- GET /api/tasks/merge-advance-events joins merge:auto-sync events
within ±5min of the advance and returns them in a new
`autoSync: AutoSyncOutcome[]` field; useMergeAdvanceNotice exposes
the same shape so the banner can surface pop-conflicts (including
patchPath) instead of dropping them.
Hygiene:
- Merger now reads the setting via normalizeMergeAdvanceAutoSyncMode
instead of an inline check + `as unknown` cast.
New tests:
- Untracked-collides-with-tracked preserves merged content.
- Apply failure on deleted file populates conflictedFiles from
patch header.
- Route surfaces autoSync outcomes (clean-sync + pop-conflict)
joined within the time window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The route was extracting getRunAuditEvents off scopedStore and calling it as
a bare function, which made this.db.prepare(...) throw. useMergeAdvanceNotice
silently swallowed the error, so the banner never rendered after merges.
Call the method on the store reference instead so this is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements a pull-based merge workflow by wiring the merger pull helpers from the engine, extending the git pull and stash routes, and aligning the `MergeAdvanceNotice` and `StashConflictModal` components to gate dismissal on stash drop. The `run-audit` module is updated with pull mutation documenta
Fusion-Task-Id: FN-5419
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5419
Adds a push-to-origin workflow to the merge notice system, introducing a new `useMergeAdvanceNotice` hook, a `merge-advance-push-origin` route handler, and corresponding UI affordance in the `MergeAdvanceNotice` banner component. The engine gains TOCTOU and refusal audit assertions, and coverage exp
Fusion-Task-Id: FN-5359
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5359
Implements project identity tracking and recovery across the Fusion system (FN-5411), enabling persistent identity for projects across storage migrations, daemon reattaches, and CLI session management. Adds a project identity metadata API and central reattach ensure mechanism, wires identity stampin
Fusion-Task-Id: FN-5411
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5411
Surface the live mode / modalOpen / keyboardOpen / footerVisible / view
values that MobileNavBar uses for its early-return so the ?vpdebug overlay
can show which one is hiding the bar on Android. Also dumps the
.project-content className so we can correlate with `--with-mobile-nav`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds retry-reset logic that clears the user-paused flag on tasks, ensuring they can resume automatically after a retry is triggered, with regression tests covering the behavior across the CLI extension and core manual-reset module.
Fusion-Task-Id: FN-5485
In multi-project mode, all /chat/sessions* handlers used options.chatStore
(the home-dir project's store) regardless of the projectId query param.
Sessions in secondary projects were invisible via the API.
Root cause: registerChatRoutes accessed options.chatStore directly instead
of routing through resolveProjectChatContext (already used correctly in
registerChatRoomRoutes for the rooms API).
Fix: introduce resolveScopedChatStore(projectId) helper that delegates to
resolveProjectChatContext, replacing all ten options.chatStore usages.
Falls back to default store when engineManager is absent (backward compat).
Tests: add two cases to verify engine-scoped chatStore is used when
engineManager is configured for the requested projectId.
Implement the hard-archived delete contract: add a typed `ArchivedTaskDeleteError`, map hard-archived task deletes to HTTP 410 Gone at both the store and routes layers, and document the invariant in the soft-delete verification matrix.
Fusion-Task-Id: FN-5196
Adds a manual merge blocker mode (FN-5438) that prevents automatic merging and provides a bypass mechanism to resume, wired through the merger, project engine, and task workflow API routes. Includes tests across core, engine route registration, and project engine layers, plus a changeset and documen
Fusion-Task-Id: FN-5438
Implements stash conflict detection and resolution with a new smart pull API route, resolution endpoints, and a dedicated conflict modal UI — backed by extended git audit taxonomy and route tests.
Fusion-Task-Id: FN-5358
Raised room transcript defaults (`messagesBefore` and `daysBefore`) in the core settings schema and updated project-level setting defaults, with corresponding documentation refresh in the settings reference. Added full test coverage for room compaction defaults, pinned room default settings in Setti
Fusion-Task-Id: FN-5374
Scheduler now gates task update invalidation, preventing spurious invalidations when engine lifecycle changes (soft-delete, lease recovery) touch task metadata, with coverage via new scheduler invalidation tests and a small dashboard server test adjustment.
Fusion-Task-Id: FN-5430