Stages the droid runtime as a bundled plugin in the CLI's tsup configuration, gates the bundle bootstrap on droid asset availability, and adds tests asserting correct staging behavior in bundle output.
Fusion-Task-Id: FN-3718
Stabilizes the OpenClaw bundle by hardening the asset copy logic and gating the bootstrap flow on the openclaw bridge asset being present, with regression tests covering the new bundle-output helpers and a patch changeset for `@runfusion/fusion`.
Fusion-Task-Id: FN-3717
Merges FN-3620, completing the Hermes chat mailbox integration with wired message tools, locked sender/recipient contract tests, and mailbox behavior documentation, plus a small CSS polish adding `focus-visible` styles and token radius to the render toggle. A changeset for FN-3710 (cluster task ID o
Fusion-Task-Id: FN-3620
Adds an agent workspace memory reader so agents can access workspace context, and extends the planning mode modal with priority controls for task routes and subtasks (FN-3716, FN-3718). Minor CSS token and mobile focus fixes are also included (FN-3710).
Fusion-Task-Id: FN-3710
Merged branch lands two features: FN-3718 wires workspace memory into the agent instruction pipeline — adding a reader helper, injecting workspace memory into agent instructions and identity snapshots, and documenting the resolution order — and FN-3716 adds planning mode priority controls for task r
Fusion-Task-Id: FN-3718
The merge introduces a research tool surface gating mechanism: shared availability helpers in core and engine, applied to the executor and triage agent so research prompts and tool exposure are gated behind experimental-feature flags, with tests covering the new logic.
Fusion-Task-Id: FN-3706
Adds a WhatsApp chat plugin (`plugins/fusion-plugin-whatsapp-chat/`) as the first bundled plugin, including the plugin package with UI rendering, dashboard plugin manager integration, CLI bundling support, and plugin route runtime context. Also updates documentation to cover plugin management and ap
Fusion-Task-Id: FN-3714
Consolidated graph position storage into a canonical shared helper (`projectStorage.ts`) with hardened persistence logic and removed the duplicate `scopedStorage` module. Updated integration tests and documented the canonical storage approach. A smaller fix (`FN-3628`) addressed mobile touch targets
Fusion-Task-Id: FN-3711
Adds a changeset for feature FN-3429 to track the branch/baseBranch task fields, marking this work for the next `@runfusion/fusion` release.
Fusion-Task-Id: FN-3429
Added agent permission policy model with persistence in core, and wired web-builtin tool opt-in enabling planning and synthesis web tools in the dashboard with corresponding docs and a changeset. Also added an inline fast-mode toggle wired into peer executor state, retry logic for cluster task-ID ov
Fusion-Task-Id: FN-3713
Adds retry logic for distributed task ID overlap conflicts across the cluster, hardening the ID allocator with exponential-backoff recovery, wiring the retry into task workflow registration, and documenting the behavior in architecture docs. Minor CSS tokenization on the Mailbox and Plugin Manager m
Fusion-Task-Id: FN-3710
The merge delivers three feature themes: FN-3708 restores and tests a narrow logs/system mouse auto-toggle policy in the TUI app with documentation, FN-3087 adds immediate wake controls for the agent inbox and messaging API routes, and FN-3704 separates plugin lifecycle management from setup probe s
Fusion-Task-Id: FN-3708
This merge ships five distinct features: experimental-flag gating for research tools in the CLI extension and core settings, immediate wake controls for the agent inbox and message API enabling on-demand heartbeat triggers, separation of plugin lifecycle from setup probe state in the dashboard, grap
Fusion-Task-Id: FN-3087
The merge delivers four major bodies of work. The dominant theme is FN-3588, which adds an "immediate wake" override to the message inbox API, exposes it in the MailboxModal UI, and updates `agent-heartbeat.ts` and `executor.ts` to honor the override alongside timer/signal triggers. FN-3705 gates re
Fusion-Task-Id: FN-3588
This merge introduces two major themes. First, research tools in both the engine and CLI are now gated behind an experimental flag, using a shared helper from core — the research tools are documented as experimental and the dashboard settings reference is updated. Second, the testing suite receives
Fusion-Task-Id: FN-3705
The dashboard task-create route now uses the distributed task ID allocator
(FN-3450). On projects whose tasks had been allocated through the legacy
config.nextId counter, the allocator's `ensureStateRow` was seeding a fresh
prefix at sequence 1, so new tasks restarted at FN-001 even when FN-3700
already existed. ensureStateRow now seeds past:
- the legacy config.nextId counter (when configured taskPrefix matches), and
- one past the highest numeric suffix on any existing tasks/archivedTasks row
for the prefix.
A regression test seeds FN-3700 in a fresh DB and asserts the next reservation
returns FN-3701, not FN-001.
Plugin dashboard view contracts are now exposed via a slim type-only module
(`@fusion/dashboard/app/plugins/types`). External plugin tsc builds previously
imported `pluginViewRegistry`, transitively pulling in dashboard runtime
sources (React components, CSS, lucide-react). The dependency-graph plugin's
import + path mapping is updated to use the new module.
Schema housekeeping: drop unused scaffolding tables left over from an earlier
migration via a new idempotent migration (v67). Fresh DBs see no change;
existing DBs that ran the older migration get the orphan tables dropped on
next init.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documentation-only finish for FN-3087, adding changeset and README updates for the dependency graph plugin and plugin authoring guide.
Fusion-Task-Id: FN-3087
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
- 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
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
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
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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
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
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
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>
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>
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>
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
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>