Commit Graph

940 Commits

Author SHA1 Message Date
Fusion
dca07892ff feat(FN-3396): bundle Cursor CLI as a plugin provider with dashboard auth w
Merges FN-3396's full Cursor CLI provider integration (Steps 1–4): defines a CLI-backed provider contract, adds the `fusion-plugin-cursor-runtime` plugin package with process management and runtime probes, wires dashboard auth flows and UI (ProviderCard, onboarding modal, settings), and bundles the

Fusion-Task-Id: FN-3396
2026-05-07 04:17:52 -07:00
Fusion
5299745fc4 feat(FN-3077): enforce plugin AI security scan gate across install flows
- Add core plugin AI security scan module and schema support for scan toggle/state metadata
- Enforce scan checks during CLI and dashboard plugin install flows, with preserved API error status on scan failures
- Expose plugin scan toggle and rescan actions in dashboard/plugin manager with route and UI coverage
- Update plugin authoring and CLI/dashboard docs, plus add changeset for published CLI package

Fusion-Task-Id: FN-3077
2026-05-07 02:15:14 -07:00
Fusion
a466416bd0 feat(FN-3082): restructure dependency graph plugin with modular architectur
Implements a modular dependency graph feature for the Fusion dashboard plugin, replacing the monolithic `DependencyGraphView` component with a factored architecture: graph types and filtering (Step 1), a data hook (Step 2), auto-layout engine (Step 3), SVG edge rendering (Step 4), an interaction hoo

Fusion-Task-Id: FN-3082
2026-05-07 01:42:01 -07:00
Fusion
1a0124c5da feat(FN-3080): document graph navigation canonicalization in plugin authori
Adds documentation for graph navigation canonicalization (FN-3080) covering plugin authoring and dashboard usage, along with a changeset for the `@runfusion/fusion` package release.

Fusion-Task-Id: FN-3080
2026-05-06 22:59:51 -07:00
gsxdsm
ecb5f60152 chore(release): v0.23.0
Version bump via changesets.
2026-05-06 22:47:53 -07:00
gsxdsm
8bdac36c54 Add changeset 2026-05-06 22:46:25 -07:00
gsxdsm
c9bbd7d43e fix(dashboard): parse Codex reset_at as epoch ms or seconds
Codex returns weekly window reset_at in milliseconds in some cases,
which was being multiplied by 1000 and producing nonsensical reset
countdowns and pace calculations. Route through _parseResetTimestamp
so both formats work, and add a regression test.

Also tightens comments in useMobileKeyboard / ChatView around why
visualViewport scroll events skip offsetTop updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:17:35 -07:00
gsxdsm
3f5d01f4d4 fix(dashboard): lazy-init useMobileKeyboard so remount has no stale render
When ChatView remounted (e.g. tab switch with keyboard still up), the
hook started with keyboardOpen=false and corrected itself only after
the effect ran. That single stale-state render briefly unhid the
executor status bar, which appeared as a blank pane covering half the
input box before the next state update settled it.

useState initializers now call getKeyboardMetrics() lazily on first
render so the very first paint already reflects the live keyboard
state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:38:50 -07:00
Fusion
fca8d27342 feat(FN-3619): preserve agent memory across import/export flows
- Extend agent company manifest contract to include memory payloads during export and parse
- Align dashboard agent import/export route generation to pass memory through unchanged
- Add parser, exporter, and route-level tests covering memory passthrough behavior
- Document memory import/export parity in agent and CLI documentation
Ref: Runfusion/Fusion#53

Fusion-Task-Id: FN-3619
2026-05-06 21:36:31 -07:00
gsxdsm
92d40bc519 fix(dashboard): keep keyboard up on send tap; hide status bar with keyboard
Two mobile chat regressions addressed together:

1) ChatView send button: preventDefault now fires on pointerdown for
   touch pointers, before iOS blurs the textarea. The previous
   onMouseDown.preventDefault was too late on iOS (mousedown is
   synthesized after touchend, by which point the keyboard has
   already started dismissing). Click still runs the action so quick
   taps remain reliable.

2) ExecutorStatusBar: hidden on mobile while keyboard is open,
   mirroring MobileNavBar. The bar is position:fixed against the
   layout viewport, which iOS leaves anchored below the keyboard;
   during a swipe/pan it would slide over the messages list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:34:35 -07:00
gsxdsm
ddf9f03ec8 revert(dashboard): undo imperative visualViewport write in ChatView
The imperative useLayoutEffect approach made mobile worse — first tap
flickered and didn't bring up the keyboard, while the original
swipe-overlap symptom remained. Restoring the previous React-state
flow until a better fix is identified. Removes the changeset that
shipped with the failed attempt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:29:12 -07:00
gsxdsm
6e38dad1c3 fix(dashboard): write ChatView visualViewport vars imperatively
The mobile composer/footer slid over the message list when the user
swiped with the keyboard up. Cause: --vv-height / --vv-offset-top were
routed through React state via useMobileKeyboard, so on iOS — which
fires visualViewport scroll/resize on the same frame as its keyboard
animation — the .chat-thread translation lagged by one paint, visible
as the composer momentarily floating over messages.

Now those two vars are written imperatively in a useLayoutEffect
directly to the .chat-thread DOM node on every visualViewport event,
mirroring the working pattern at QuickChatFAB.tsx:1032-1052 (which
already works correctly on mobile). Only --keyboard-overlap (a
structural open/close signal, not per-frame) still flows through
React state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:25:38 -07:00
gsxdsm
74378dcfc7 fix(dashboard): make ChatView mobile send fire on quick tap
The mobile send button used pointerdown + touchstart with preventDefault
and a focus-preservation dance to keep the keyboard up while sending.
That path silently failed on quick taps on iOS — only a long press
registered. Switching to plain onClick (with touch-action: manipulation
to skip the click delay) fires reliably on tap. The soft keyboard may
dismiss on send now, which is a minor regression vs. the previous
intent but vastly preferable to silent failure.

QuickChat is unchanged because it already works on mobile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:15:30 -07:00
gsxdsm
073c5047ea Revert "fix(dashboard): stop mobile send-button double-fire that aborted chats"
This reverts commit 35ea499e08.
2026-05-06 20:02:02 -07:00
gsxdsm
35ea499e08 fix(dashboard): stop mobile send-button double-fire that aborted chats
The ChatView and QuickChat send/stop buttons each had both an
onPointerDown and an onTouchStart handler invoking the action. On a
quick mobile tap both fire, so handleSend / handleSendMessage /
stopStreaming ran twice in rapid succession. The second invocation
closed the first's SSE stream (streamRef.current.close()), the server
treated that as a cancel via beginGeneration, and the chat ended with
no output — exactly matching the reported "tap silently fails, long
press works" symptom (long press happened to suppress one of the two
events).

Both handlers now early-return when the existing handledMobile*Ref
flag is already set, so only the first event for a given tap fires the
action. The send button additionally gets touch-action: manipulation
(removes the click delay that lets the textarea blur win the race) and
an expanded invisible hit area via ::before so slightly-off taps don't
land on the surrounding textarea and dismiss the keyboard without
sending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:55:55 -07:00
Fusion
d791fa90ab feat(FN-3608): update dashboard chat documentation
Updated the dashboard chat documentation in the user guide with a minor addition.

Fusion-Task-Id: FN-3608
2026-05-06 19:55:55 -07:00
gsxdsm
6e8689ae3d feat(tui): split narrow main view to surface live logs
When the dashboard TUI collapses to single-pane mode (narrow width or
short terminal), show a horizontal log strip at the bottom of the screen
while the active section renders on top. The split is content-driven:
the top pane gets exactly the rows it needs to render its section
without truncating (computed from SystemPanel's chip wrap at the current
width, or each panel's known row count for Stats/Utilities/Settings),
and the log strip absorbs every remaining row to maximize log
visibility. Down-arrow shifts sub-focus into the log strip with the
same key bindings as the dedicated Logs section (j/k, Home/G, Enter to
expand, w to wrap, c to copy, f to filter, mouse wheel). Up-arrow at
the top of the strip or Esc returns focus to the main pane.
Right/Left/Tab continue to cycle sections, so the dedicated full-screen
Logs view is unchanged. The split auto-disables if the log strip would
get fewer than 6 rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:11:49 -07:00
gsxdsm
e6dc3c7ccc fix(merger): address code-review findings on autostash + observer
P0 — parsePorcelainZ rename/copy handling
  Git's -z porcelain emits `R  <new>\0<old>\0` for renames (and
  C for copies). The naive split-and-slice treated <old> as an
  independent dirty path, which made runObservedDestructiveSyncOp
  warn about phantom "cleared paths" whenever a rename was in
  flight. Now we detect R/C status and skip the trailing entry.

P1 — race-rescue loop unstages between attempts
  `git stash create` snapshots the index without clearing it, so
  iteration 2's `git add -A` would re-stage atop iteration 1's
  leftovers. Tree differences inside the loop then reflected stale
  staging rather than genuine new writes. Added a `git reset` at
  the top of each iteration so every attempt starts from a clean
  index baseline.

P1 — writeActiveMergerStatus is now atomic
  Switched from in-place writeFileSync to temp-file + renameSync.
  POSIX guarantees rename atomicity on the same filesystem, so a
  reader can no longer catch the file mid-flush and return a
  false-negative "no merger active" advisory.

P2 — Step regex em-dash clarity
  `[—\-:]` is functionally fine but obscures intent; switched to
  `(?:—|-|:)` so the em-dash branch is obvious. Added a test case
  for the em-dash separator.

New tests:
- parse-porcelain-z.test.ts (8 cases including renames + copies)
- em-dash case added to derive-subject-summary.test.ts

247/247 merger-suite tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:04:19 -07:00
gsxdsm
fd7c88c2d5 fix(merger): de-dupe race-rescue against primary stash path set
The race-rescue loop was firing on every merger run because
`git add -A && git stash create` does not clean the working tree —
files stay dirty post-stash, so a subsequent `snapshotDirtyFiles` saw
the SAME paths the primary stash had just captured and stashed them
again, producing identical-tree race-rescue duplicates (visible in
git stash list as `fusion-merger-autostash:FN-XXXX:race-rescue-0`
sitting next to its identical `fusion-merger-autostash:FN-XXXX:`).

Fix: list the path set captured by the primary stash via
`git stash show --name-only`, and only rescue paths in the current
dirty snapshot that are NOT in that set — those are genuine
late-dirty writes from concurrent dev edits or interleaved ops.
Also drop any rescue whose tree-SHA exactly equals the primary,
as a defensive belt-and-braces.

Existing duplicate race-rescue stashes are harmless (identical
content to their primaries) and can be dropped manually.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:57:42 -07:00
gsxdsm
2dd87b7c8b fix(merger): add active-merger advisory + observe destructive ops + log rescue stashes
- writeActiveMergerStatus: writes .git/.fusion-merger-active.json
  (taskId, pid, hostname, startedAt) at merge entry, deleted in finally.
  Not a lock — purely informational so dashboards / status lines /
  pre-Edit hooks can warn devs that rootDir is volatile during the run.
  readActiveMergerStatus(rootDir) is exported for consumers.
- runObservedDestructiveSyncOp: snapshot-before/after wrapper around
  destructive rootDir ops that are *supposed* to preserve unrelated
  working-tree edits. resetMergeWithWarn now uses it — any future
  silent wipe of dirty paths surfaces as an actionable warning instead
  of going unnoticed. Not applied to the autostash's own reset
  --hard / clean -fd; those are intentionally destructive and already
  protected by the race-rescue stash.
- Race-rescue stashes from stashUnrelatedRootDirChanges are now
  attached to the AutostashHandle and surfaced via store.logEntry so
  the recovery command lands on the task feed instead of only
  mergerLog.warn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:25:19 -07:00
gsxdsm
923411a940 fix(merger): prefer step headline subject + add autostash race-rescue
- deriveDeterministicSubjectSummary now picks the lowest-numbered
  `complete Step N` headline (or the oldest commit) instead of the most
  recent commit, so trailing quality-gate revisions stop hijacking the
  squash-merge subject (FN-3617 landed as "align mailbox modal css..."
  when 4 of 5 commits were the actual Claude OAuth fix).
- AI subject + body system prompts in ai-summarize.ts now weight by
  commit theme rather than file size, so a small token cleanup that
  touches a large CSS file no longer dominates the summary.
- stashUnrelatedRootDirChanges adds a bounded re-snapshot loop after
  the primary stash is persisted but before \`git reset --hard\`. Any
  late-dirty paths (concurrent dev edits during a long merger run,
  parallel merger runs racing on rootDir, late test/build artifacts)
  get captured in labeled \`race-rescue-N\` stashes recoverable from
  \`git stash list\`, instead of being wiped by the destructive reset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:06:46 -07:00
Fusion
cabd6be84b feat(FN-3617): align mailbox modal css with design tokens
Updated MailboxModal CSS to use design tokens instead of hardcoded values, and adjusted the co-located test to match.

Fusion-Task-Id: FN-3617
2026-05-06 16:14:29 -07:00
Fusion
2fce7b36cf feat(FN-3591): split TaskDetailModal test monolith into focused suites
The monolithic `TaskDetailModal` test suite (6,745 lines) was split into six focused test files covering attachments/tabs, definition/actions, inline editing/integrations, models/progress/workflow, rendering, and responsive/dependencies, with a shared test helpers module added for common utilities.

Fusion-Task-Id: FN-3591
2026-05-06 15:57:08 -07:00
Fusion
85381df153 feat(FN-3602): compact mobile chat tool-call density and layout
Merged FN-3602: Compact tool-call summaries in the ChatView with responsive mobile layout improvements, including new regression tests and documentation in the dashboard guide.

Fusion-Task-Id: FN-3602
2026-05-06 13:57:30 -07:00
Fusion
8df5d2617c feat(FN-3612): preserve fusion context in hermes runtime skill forwarding
Merges five commits implementing centralized runtime skill forwarding that preserves Fusion context across the Hermes runtime layer. The engine's `agent-runtime` and `agent-session-helpers` were updated to forward skills at runtime, with `runtime-adapter.ts` and its types extended to carry context.

Fusion-Task-Id: FN-3612
2026-05-06 13:38:23 -07:00
gsxdsm
cd845d39be perf(merger): skip redundant in-merge verification and lockfile-stable installs
Two cuts to wasted work in the merge verification loop:

1. After the in-merge fix agent runs, fingerprint the working tree
   (`git diff HEAD` + `git status --porcelain`, sha256). If the post-fix
   fingerprint matches pre-fix and is non-empty, the agent didn't actually
   change anything — re-running the same failing command can only yield
   the same failure, so log and report the attempt as unsuccessful without
   paying the test/build cost. Empty fingerprints (snapshot tooling failed)
   fall through to the existing re-run path so we never silently swallow a
   real fix.

2. Inside `syncDependenciesForMerge`, hash the active lockfile and compare
   against `node_modules/.fusion-install-marker` (written after each
   successful install). When they match, skip `pnpm install
   --frozen-lockfile` even if `package.json` is staged. Covers the common
   case where `package.json` changes but the lockfile doesn't, and
   amortizes install across auto-recovery re-enqueues that hit the same
   worktree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:24:57 -07:00
gsxdsm
9087239078 chore(dashboard): drop dead reportDashboardPerf client and its log file
The /_perf/dashboard-load server route no longer exists, so every
reportDashboardPerf() call was a silently-swallowed 404. Remove the helper
in legacy.ts plus its five call sites in App.tsx / useProjects.ts (the
companion console.log lines stay), and drop the dashboard-perf.log entry
from the test-isolation runtime ignore list since nothing writes that file
anymore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:14:26 -07:00
gsxdsm
593b42ea79 chore(test-isolation): broaden runtime ignore list for live fusion app paths
Live fusion instances running on a shared HOME write to tasks/, messages/,
memory-insights.md, test-cache.json, HEARTBEAT.md, kb.db.backup-*, and
fusion.db.pre-* snapshots; none were in the runtime ignore list, so the
script flagged them as test-driven mutations and failed merge build
verification. Recursion in collectFusionSignature only filters via top-level
matches, so adding these top-level patterns short-circuits descent into
live-app dirs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 11:53:28 -07:00
Fusion
a8bfb32c70 feat(FN-3590): prioritize workspace source entry for bundled plugins and ad
This merge introduces eval score categorization with a new `eval-scoring.ts` module (FN-3601, FN-3390), hardened bundled plugin entry resolution to prioritize workspace source over installed copies (FN-3590), and documented reply-link threading behavior in the mailbox (FN-3598). It also adds mobile

Fusion-Task-Id: FN-3590
2026-05-06 11:47:41 -07:00
gsxdsm
a14ef9e4a8 chore(test-isolation): tolerate live fusion app noise on shared HOME
Local `pnpm test:isolated` was failing because a concurrently-running
fusion app continually mutates `~/.fusion` (databases, agent sessions,
memory, automations, plugins, logs). Filter those runtime-owned paths
from the protected-dir signature, widen the baseline-stability sampling
window, and re-sample on suspected violations so transient app activity
doesn't masquerade as test pollution.

Tests still cannot legitimately write into these paths — they're skipped
because the *running app* is expected to.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 10:39:54 -07:00
Fusion
a31c4323a8 feat(FN-3580): restore canonical agent lifecycle and remove terminated agen
This merge restores the canonical agent lifecycle with termination scoped at the run level (FN-3580, 4 steps), adds sender-side wake recipient override for messages, and introduces test isolation CI enforcement with a stuck-requeue race fix. UI changes remove terminated-agent indicators from AgentDe

Fusion-Task-Id: FN-3580
2026-05-06 10:18:27 -07:00
Fusion
7f90308485 feat(FN-3595): document live reviewer override behavior in settings and tas
Documents the live reviewer override behavior in the settings reference and task management guides, adding two lines to each file for a total of 4 lines of documentation.

Fusion-Task-Id: FN-3595
2026-05-06 10:05:40 -07:00
Fusion
4556df5954 feat(FN-3593): add test isolation CI enforcement, fix stuck-requeue race, a
This merge lands five FN-3593 commits establishing a test isolation contract with a new `scripts/check-test-isolation.mjs` guard that scans for accidental `beforeEach`/`afterEach`/`beforeAll`/`afterAll` in setup helpers, plus per-package `setup-test-isolation.ts` bootstraps that canonicalize the pat

Fusion-Task-Id: FN-3593
2026-05-06 09:43:51 -07:00
gsxdsm
8c18b45750 feat(messages): add sender-side wake recipient override
Senders can now force the recipient agent to wake on receipt regardless
of the recipient's `messageResponseMode`. Surfaced as a "Wake recipient
immediately" checkbox in MessageComposer and as a `wake_recipient`
boolean param on the `fn_send_message` agent tool. Carried as
`metadata.wakeRecipient: true` on the message; the heartbeat hook
treats forced wakes as `message_received_urgent` in the wake delta so
agents can distinguish them from normal `messageResponseMode: immediate`
wakes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 09:26:15 -07:00
gsxdsm
0d1591665a fix(engine): stuck-requeue no longer clobbers concurrently-recovered tasks
When SelfHealingManager.recoverCompletedTasks moved a task from
in-progress to in-review, the executor's stuck-kill cleanup running in
execute()'s finally block could fire 20s later, see a stale captured
task.column = "in-progress", and overwrite the recovery by tearing down
the worktree and moving the task back to todo with all step progress
reset. Both the outer-finally and step-session requeue blocks (and the
force-requeue setTimeout in markStuckAborted) now re-read the latest
column and skip cleanup entirely if the task has moved past
in-progress/todo.

Adds a new preserveProgressOnStuckRequeue setting (default: true,
toggle in Settings near the Stuck Task Timeout) so stuck-requeue passes
{ preserveProgress: true } to moveTask. Completed step statuses now
survive the bounce so the agent resumes from where it left off instead
of restarting every step from pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 09:07:52 -07:00
gsxdsm
22250ebd19 fix(dashboard): respond to manual heartbeat run as soon as run record exists
POST /api/agents/:id/runs previously awaited resolvedMonitor.executeHeartbeat
end-to-end before sending the response. For real provider runs that take
tens of seconds to minutes, Safari (and intermediate proxies) drop the
client socket and the dashboard surfaces "Failed to start heartbeat run:
load failed" — the run is actually in flight, but the toast suggests it
failed to start.

The route now kicks off executeHeartbeat in the background, polls briefly
for the active-run record (created synchronously inside executeHeartbeat
→ startRun), and returns 201 with that record. Synchronous failures of
executeHeartbeat are still surfaced to the client; background failures
are logged via runtimeLogger.child("heartbeat"). The 409 active-run
conflict contract is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 08:31:39 -07:00
gsxdsm
3e68271693 fix(dashboard): split active vs running counts in agents overview label
The Overview dropdown previously rendered "X active · Y running" where
both X (stats.activeCount) and Y (activeAgents.length) counted agents
whose state was either "active" or "running" — so an agent that was
merely enabled but idle would still inflate the "running" tally. The
label now counts each state distinctly so "running" only reflects
agents that are mid-heartbeat. Adds AgentsOverviewBar.test.tsx to lock
in the new contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 08:04:47 -07:00
gsxdsm
6f46ab017f fix(dashboard): don't mark agent inbox messages read when user views them
When the dashboard user browses another agent's mailbox (e.g. the CEO's
inbox), opening a message no longer triggers POST /messages/:id/read.
The previous behavior silently consumed the agent's unread state, so the
agent's heartbeat never surfaced the message and fn_read_messages (which
defaults to unread_only=true) returned nothing. Auto-mark-read now only
fires on the dashboard user's own inbox tab. Adds regression tests in
MailboxView.test.tsx and MailboxModal.test.tsx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 08:01:30 -07:00
gsxdsm
4dc91edfb2 fix(engine): wire TaskStore into runtime AgentStore so heartbeat auto-claim works
The InProcessRuntime constructed its AgentStore with only `rootDir`, leaving
task-claim/checkout/release operations unconfigured. As a result, the
heartbeat auto-claim scan logged "TaskStore not configured for task-claim
operations" whenever a relevant todo was found. Pass the runtime's TaskStore
through to the AgentStore so claimTaskForAgent succeeds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 08:01:03 -07:00
Fusion
270823dcc9 feat(FN-3584): add memory file markdown preview to agent detail and log vie
This merge introduces a memory file markdown preview feature (FN-3584) with corresponding documentation, refines the AgentDetailView and AgentLogViewer components in the dashboard, and adds defensive collision handling for worktree operations during manual task moves (FN-3583).

Fusion-Task-Id: FN-3584
2026-05-06 07:52:58 -07:00
gsxdsm
1100b39cff fix(engine): prevent worktree collisions on manual task moves
Two related bugs let two in-progress tasks share a single
.worktrees/<name> directory:

1. The dashboard POST /tasks/:id/move route promoted tasks to
   in-progress without allocating a fresh worktree path, so a queued
   task carrying a stale worktree field from a prior preserveResumeState
   requeue could land in-progress on a directory already held by another
   active task.

2. moveTask({preserveResumeState:true}) kept the worktree pointer on
   requeue. When the on-disk checkout was later removed or reassigned,
   the next dispatch collided with a worktree the scheduler had handed
   to another task.

moveTask now releases the worktree pointer on every reopen-to-todo hop
(branch is kept so committed progress survives via git worktree add
<path> <branch>). A new preserveWorktree option opts internal bounces
out of the release. moveTask also accepts an allocateWorktree callback
that runs under a new cross-task allocation lock in TaskStore, so two
concurrent moves cannot pick the same name from a stale snapshot. Both
the manual-move route and the scheduler dispatch path flow through the
allocator and share the lock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 07:23:38 -07:00
gsxdsm
a143bcc4f5 fix(engine): make heartbeat helper imports static to fail fast on partial dist
Convert the dynamic await import("./agent-session-helpers.js") and
await import("./session-skill-context.js") calls inside the heartbeat
executor to static top-level imports, matching the rationale of
38933c770 (which already made the sibling pi.js import static).
This surfaces ERR_MODULE_NOT_FOUND at engine load time rather than
mid-heartbeat when a worktree is on a branch missing the helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 06:49:30 -07:00
gsxdsm
9be551b3a2 fix: make agent error modal taller on mobile so full message is visible from top
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 06:40:44 -07:00
Fusion
2b7b92229c feat(FN-3400): add native shell connection handoff, plugin management CLI/l
This merge adds a complete plugin management system (FN-3565) with CLI commands, a loader, runner, and dashboard routes, along with project-scoped auth storage (FN-3544), native shell connection support for mobile (FN-3400) spanning onboarding, connection manager, and remote desktop handoff, and ref

Fusion-Task-Id: FN-3400
2026-05-06 05:29:56 -07:00
Fusion
8f812e2f89 feat(FN-3565): add plugin management CLI, loader, runner, and dashboard rou
This merge adds a complete plugin management system to Fusion: a new `fn plugin` CLI command for installing/removing plugins, a plugin loader in core, a plugin runner in engine, and dashboard routes for plugin management UI, along with a plugin management guide in docs. It also documents task evalua

Fusion-Task-Id: FN-3565
2026-05-06 04:32:29 -07:00
Fusion
35d5590d4c feat(FN-3079): add changeset for plugin dashboard views delivery
Completes the plugin dashboard views feature (FN-3079) by adding the changeset that documents the change for the next release.

Fusion-Task-Id: FN-3079
2026-05-06 03:20:15 -07:00
gsxdsm
79ee217d9f chore(release): v0.22.0
Version bump via changesets.
2026-05-06 00:11:20 -07:00
gsxdsm
76e5f510ca fix(dashboard): primary mission CTA, auto-select first, richer empty state
- Promote the sidebar "Plan New Mission" CTA to a btn-primary (matching the
  chat sidebar's "New Chat") and drop the dashed icon buttons; full-width
  progress bar and Activity row each get their own line in the card.
- Auto-select the first mission in the inline desktop view so users land
  in detail rather than the empty placeholder. Skipped in mobile and the
  standalone modal so existing flows and unit tests stay intact.
- Replace the bare "No missions yet" line with a richer empty state that
  explains what missions are and offers an inline Plan New Mission CTA.
- Guard loadMissionDetail against malformed responses (missing milestones)
  so racing fetch fallbacks don't crash the detail render.
- Update three MissionManager tests to match the new copy/structure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:05:55 -07:00
gsxdsm
b2aed0fd42 fix(engine): stop tears down in-progress merger and triager sessions
TriageProcessor.stop() previously only halted the polling loop, so
in-flight specify sessions and their reviewer subagents kept streaming
past shutdown. Extracted the existing global-pause teardown into
abortAndDisposeActiveSessions() and call it from stop() too.

aiMergeTask creates three sessions during a merge — autostash resolver,
in-merge verification fix agent, and pull-rebase conflict resolver — but
only the autostash one was registered via onSession. The other two are
now registered (with onSession threaded through pushToRemoteAfterMerge
into the rebase resolver chain), so ProjectEngine.stop() actually
disposes whichever merger session is running when shutdown lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:05:55 -07:00
gsxdsm
24017b8353 fix(dashboard): drop residual terminated-state refs lost in merger autostash
Recovers cleanups from stash@{2}/stash@{3} (FN-3530 merger autostashes)
that the merger never restored: 4 dead `[data-state="terminated"]`
selectors in AgentListModal.css, 3 CSS-class assertions in
agent-css-classes.test.ts targeting classes the runtime no longer emits,
and a `state: "terminated"` fixture in routes-agents.test.ts now flipped
to `paused` so the "invalid state transitions" test exercises a real
rejection (paused→paused is not in AGENT_VALID_TRANSITIONS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:05:55 -07:00