- Stop injecting a hardcoded Browser Verification option in WorkflowResultsTab and rely only on fetched workflow step definitions
- Normalize workflow step checkbox test IDs to be id-based for all options, including template-backed Browser Verification steps
- Extend WorkflowResultsTab coverage to assert Browser Verification renders exactly once when provided by API data
- Increase dashboard Vitest timeout defaults and update the slow route test timeout to reduce flaky failures under concurrent workspace runs
- Widen the desktop terminal modal to improve usability and align with UX review feedback
- Stabilize terminal input lifecycle handling to avoid focus and interaction regressions
- Add regression coverage for terminal modal input behavior and mobile keyboard layout scenarios
- Include a changeset documenting the terminal modal desktop width and input fixes
The /planning/:sessionId/stream route only replayed buffered events when
the client sent a Last-Event-ID (resume case). On a fresh subscription it
just attached as a live subscriber. On slower devices (mobile especially),
the agent often streamed several thinking deltas into the buffer before
the SSE connection finished establishing — so the user saw the spinner
sit there, then a question appear out of nowhere, with no streaming
"thinking" content.
When a session has no current question or summary yet (i.e. the agent is
generating its first response) and the client connects without a
Last-Event-ID, replay every buffered event before attaching the live
subscription. This catches up the user on the in-flight thinking stream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related planning-mode issues:
1. The streaming "thinking" panel only showed for models that emit explicit
thinking_delta events (e.g. Anthropic Extended Thinking). For every other
model the user saw a spinner with no streaming content because text_delta
was being accumulated server-side without being broadcast over SSE. Now
onText also forwards deltas through the same stream channel so any model
surfaces its in-flight output.
2. Returning to the planning screen after the browser tab was backgrounded
long enough for the SSE socket to time out would land the user in a
permanent error view ("Session failed while contacting the AI") even
though the server session was still alive. The onError handler now first
re-fetches the AI session row; if the server still reports the session as
generating or awaiting_input it silently reconnects without surfacing the
transient error. Only genuine server-side failures still surface.
Tests for the obsolete manual-retry recovery path were rewritten as
auto-recovery assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add refresh-page actions alongside retry/reinitialize in TerminalModal bootstrap and xterm init error states
- Add terminal error action-group styling so multiple recovery buttons render cleanly
- Extend TerminalModal tests to cover new refresh controls and verify window reload behavior
- Remove unused TaskLogEntry import and drop the stale hooks eslint suppression in dashboard-tui app
- Add a new /api/git/diff/file endpoint with path and staged query handling, including validation and untracked-file diff support
- Introduce fetchGitFileDiff API client wiring and update GitManagerModal to load diffs for selected staged/unstaged file rows
- Improve file-row UX with active/focus styling, keyboard activation, and guarded async diff loading/error states
- Expand dashboard route and modal tests to cover per-file diff behavior, query validation, and stage/unstage interaction isolation
The global SSE broadcast called res.write() without checking the return
value, so a paused or backgrounded client would silently accumulate
every store event for every entity (tasks, missions, plugins, agents,
chat, ...) into res.outputData until the dashboard process OOMed.
Add a 4 MB writableLength threshold; when exceeded, tear down the
connection so the OS releases the buffer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add cross-browser scrollbar styling for settings sidebar and content panes using existing design tokens
- Extend settings mobile CSS tests to assert the new scrollbar rules for WebKit and Firefox-compatible properties
- Refactor dashboard TUI app tests to render via React.createElement for compatibility with current test tooling
- Tidy mission e2e mock ID generators to satisfy formatting/quality gate checks
Mostly mechanical cleanup left over from the earlier test-consolidation pass:
- Update import paths to ../../ for mocks now that test files moved deeper
- Simplify mock setup (drop usePluginUiSlots inline mock, etc.)
- Move engine ipc + runtimes tests into __tests__/ subdirs
- Move dashboard utils tests into __tests__/ subdir
- Refresh fusion-plugin-hermes-runtime/dist artifacts
build-exe.test.ts: spawn-import fix from a parallel branch (resolved during
worktree merge of the CSS extraction work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move all co-located *.test.* files into sibling __tests__/ directories so the
layout is consistent across packages (159 renames + content-rewrite moves).
Updates relative imports, vi.mock specifiers, and __dirname/import.meta.url
path resolutions where tests read fixtures from disk.
- Drop tracked tsc-emit alongside engine .ts sources (auth-storage/logger/
skill-resolver/context-limit-detector/pi.{js,d.ts,*.map}). These were
accidentally committed in a merge and the stale pi.js was masking a real
test-mock vs source mismatch (tests imported "../pi.js" and vite preferred
the stale build over pi.ts).
- Add packages/engine/.gitignore to block future src/*.{js,d.ts,map}.
- Refactor plugin pi-module seams (openclaw/paperclip/hermes) to ESM-import
createFnAgent / promptWithFallback / describeModel from @fusion/engine
instead of require()-ing packages/engine/src/pi.js. Adds @fusion/engine to
the two plugin package.jsons that were missing it; exports describeModel
from the engine public API.
- Fix engine test mocks now that they run against current pi.ts: add
ModelRegistry.create static to mocks in pi.test.ts and pi-create-fn-agent
.test.ts; switch three boundary-result toEqual assertions to toMatchObject
so the new content/isError fields don't trip exact-match comparison.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add ntfy base URL to global settings schema/types with persistence coverage and regression tests
- Extend dashboard settings API/routes and Settings modal UI to edit and save a custom ntfy server
- Update notifier runtime to use configured ntfy base URL when sending notifications
- Document the new setting and include a changeset for @runfusion/fusion
- Split New Agent step-0 into Presets and Custom tabs with presets-first default behavior
- Refine tab layout and styling in dashboard styles to address review feedback and improve responsive presentation
- Expand NewAgentDialog and AgentsView tests to cover tab switching flows and related route behavior
- Update agent documentation to reflect the revised new-agent dialog flow
The FN-2370 auto-resolved squash (7c1a1c36c) reverted three commits' worth
of refinements to the claude-cli provider because the branch contained
rebased duplicates of commits already on main. The auto-resolver picked
the older side line-by-line and dropped the newer.
Restored:
- /api/models filter logic (was inverted; emptied every model picker)
- Claude Opus 4.7 catalog entry in pi-claude-cli
- Provider card status text and toast messages (no longer claim a restart
is needed — the extension is always-loaded now)
- POST /api/auth/claude-cli returns restartRequired: false
Prevention:
- Regression tests on the /api/models useClaudeCli filter
- scripts/audit-squash-merge.mjs flags duplicate-cherry-pick risk and
touched-file overlap on any squash commit
- AGENTS.md documents the rebase-before-squash rule and requires the
merging agent to run the audit and triage every flagged item itself
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace deferred OpenClaw placeholder runtime with an executable adapter that creates sessions, prompts with fallback, describes models, and disposes safely
- Add a dedicated pi-module seam and local runtime contract types to decouple plugin compilation from engine internals while preserving runtime behavior
- Update OpenClaw plugin metadata, manifest/package descriptions, and README to reflect active execution support instead of deferred status
- Harden dashboard DevServerStore save flow against ENOENT temp-directory races and add regression coverage for project-directory removal during save
- Add schema v44 migration to persist task-level token usage totals and first/last usage timestamps on tasks
- Extend core task types, store create/update flows, and exports to round-trip token usage data
- Add migration and TaskStore regression tests for token usage persistence, null clearing, and reinitialization behavior
- Update dashboard async handling and tests to prevent post-unmount state updates and reduce flaky assertion timing
- Update node management UI components to use shared form/button classes, semantic color tokens, and accessible interactions
- Refine mesh topology rendering and related dashboard tests for improved readability and behavior coverage
- Adjust discovery routes to reuse injected centralCore instances without unnecessary init/close cycles
- Add/extend tests for shared CentralCore discovery flows and model settings scope save behavior
- Extend TaskStore deleteTask with a safe default that blocks deleting tasks still referenced by live dependents
- Add an opt-in removeDependencyReferences path that rewrites dependent tasks atomically before deletion
- Update dashboard API/routes to surface TASK_HAS_DEPENDENTS as a 409 with structured details and a delete query flag
- Add TaskCard/TaskDetailModal confirmation-retry UX plus coverage in core, dashboard route/API, and component tests
- Document the new delete semantics and opt-in behavior in the dashboard API README
- Rework logs viewport calculation to budget by rendered rows and keep content above footer hints
- Add zero-row safety handling for short terminals with a dedicated expansion hint instead of overlapping output
- Cap wrapped log rendering by remaining row budget so long messages cannot overflow into footer space
- Add regression tests for footer-safe list windowing and wrapped-line truncation, plus a patch changeset for @runfusion/fusion
- Add dashboard route and server handling to reinstall the Fusion Pi extension
- Introduce a client API helper and wire a reinstall action into PiExtensionsManager
- Polish action spacing and document Fusion reinstall recovery in the CLI README
- Expand route, API, and component tests including reinstall refresh stability coverage
- Add a changeset for @runfusion/fusion patch release
- Extend dashboard file service and routes to support includeHidden when scanning markdown files
- Update API client, markdown hook, and Documents view UI to expose a hidden files toggle
- Add coverage for hidden-file filtering in file-service, hook, and DocumentsView tests
- Stabilize related SettingsModal and TaskForm tests and document toggle behavior in the dashboard README
- Update Fusion skill docs, prompts, and capability references to use public fn_* tool names consistently
- Align engine system prompts and tool schemas for messaging/task actions with fn_send_message, fn_read_messages, fn_task_* naming
- Refresh related tests across CLI, engine, dashboard, and core to match normalized tool naming and behavior
- Add a patch changeset for @runfusion/fusion describing the skill-tool namespace normalization
- Move AgentsView to consume agents, loading state, and reload logic directly from useAgents
- Extend useAgents with filterState/showSystemAgents options and always pass includeEphemeral in fetch filters
- Remove duplicate initial fetch/SSE path in AgentsView and rely on hook-managed refresh behavior
- Add regression coverage for single initial load and system-agent visibility toggling behavior
- Update useAgents hook tests to assert the new includeEphemeral fetch contract
- Add a shared auth-sync audit log helper that emits structured settings-sync/auth events with operation, direction, route, node IDs, and provider metadata
- Replace string-based auth sync logs in push, pull, and auth-receive routes with the new structured diagnostic event emission
- Ensure only provider names and counts are logged while filtering out credentials and other sensitive auth material
- Expand node sync route tests to capture runtime log sink events and assert structured fields plus redaction guarantees for push, pull, and receive flows
- Add explicit operation metadata to agent-generation and ai-session-store diagnostics for cleanup, recovery, and scheduled cleanup paths
- Extend dashboard guardrail coverage to explicitly enforce diagnostics protection for agent-generation.ts and ai-session-store.ts
- Stabilize plugin module reload imports by using temporary reload files with deterministic file URLs and cache updates
- Tighten CLI Vitest workspace cleanup to safely skip missing hidden dist directories during restore
- Add remote route diagnostic helpers in dashboard routes to classify timeout, transport, and unexpected errors with consistent context fields
- Apply structured runtime logging to mesh sync settings handling and both proxy paths (SSE and wildcard) for fetch and stream failure stages
- Preserve existing HTTP behavior while enriching diagnostics with node ID, upstream path, operation stage, and normalized error metadata
- Expand mesh and proxy route test suites with runtime logger harness assertions covering structured warn/error diagnostics
After ~3 refreshes, the dashboard would hang on "Initializing dashboard..."
with all /api/* fetches stalling. Root cause: Chrome keeps HTTP/1.1 sockets
in its keep-alive pool across page navigations even after EventSource is
garbage-collected. Once 6 (the per-origin limit) are held, every new fetch
queues indefinitely and the app can't finish booting.
Fix, layered:
1. sse-bus.ts — pagehide/beforeunload listeners close all active channels
and send a sendBeacon to /api/events/disconnect so the server forces
the socket closed (socket.destroy) rather than waiting for the browser
to notice. Uses a sessionStorage clientId to correlate.
2. api.ts — createResilientEventSource (used by planning / mission / slice
stream endpoints) registers every handle in a module-level set and
closes them all on pagehide/beforeunload. sse-bus doesn't see these
streams, so it needs its own teardown.
3. sse.ts — server-side connection bookkeeping. Tracks managed SSE
connections by clientId, supports client-triggered disconnect via
POST /api/events/disconnect, stale-timer cleanup, and supersedes
older streams when a client reconnects.
4. server.ts — exposes /api/events/disconnect and /api/events/keepalive
under a dedicated 300 req/min rate limit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add structured ai-session diagnostics for summarize-title and agent-generation error paths in dashboard routes
- Emit debug-gated summarize request/model resolution diagnostics when FUSION_DEBUG_AI is enabled
- Add route tests that assert diagnostics payloads for summarize and agent generation failures
- Reduce test flakiness by increasing core Vitest timeouts and relaxing brittle extension-discovery argument matching
The PATCH /tasks/:id/assign and Task checkout routes describe blocks
re-initialized a real AgentStore (sqlite init + createAgent) in their
beforeEach hooks, even though no test in either block mutates the agent
rows. Moving the agent setup to beforeAll while keeping the per-test
mock-store reset cuts ~20 sqlite init cycles across these two blocks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the vendored @fusion/pi-claude-cli extension was conditionally
loaded based on GlobalSettings.useClaudeCli. That forced a Fusion restart
every time the user toggled the provider card — confusing UX.
Key insight: pi-claude-cli registers a NEW provider id ("pi-claude-cli")
rather than overriding "anthropic", so loading it unconditionally is
safe — direct Anthropic auth and CLI-routed models coexist peacefully.
The extension also gracefully no-ops when the `claude` binary is missing
(see packages/pi-claude-cli/index.ts:106 — the throw is caught locally).
Changes:
- serve/daemon/dashboard: always append the resolved pi-claude-cli path
to discoverAndLoadExtensions, no settings lookup.
- resolveClaudeCliExtensionPaths() takes no args now; always returns the
resolved path.
- /api/models filter flipped: hide provider === "pi-claude-cli" when
the toggle is OFF (previously: restricted to those models when ON).
- POST /api/auth/claude-cli drops restartRequired semantics — toggling
now has immediate effect on the picker.
- Provider card UX updated to match: "Claude-CLI-routed models are
now visible/hidden from the model picker" instead of "Restart Fusion
to activate".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared per-IP mutation bucket (30/min) made common dashboard actions
like Respecify fail with "Too many requests" after light activity. Raise
mutation to 600/min, api to 1000/min, sse to 60/min for the local-first
use case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TaskCard: four catch((err: any) => err.message) promise handlers in
archive/unarchive/delete/move → catch((err) => getErrorMessage(err)).
- InlineCreateCard + QuickEntryBox: .catch((err: any)) model-load handlers
→ getErrorMessage(err) with existing @fusion/core import.
- TerminalModal: drop (navigator as any).maxTouchPoints — modern lib.dom
types already expose the property.
- serve.ts: remove unused any annotation on OpenRouter model mapper; the
array element type is already inferred from json.data.
- pi.js, runtime-resolution.ts, dashboard.ts, serve.ts, dev-server-port-
detect.ts, devserver-manager.ts: drop now-stale eslint-disable comments
that the cleanup made redundant.
Fix a prompt-builder regression surfaced by agent's `any` cleanup: toolCall
with a raw string `arguments` field must be preserved verbatim (JSON-quoted)
rather than coerced to `{}`; restores a previously-passing test.
Then promote @typescript-eslint/no-explicit-any from warn → error. Future
new anys must either come with a one-line disable + justification or use a
real type. Workspace is now lint-clean (0 problems).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parallel subagent pass: four typescript-pro agents on non-overlapping scopes.
Patterns applied:
- catch (err: any) { ... err.message ... } → catch (err) { ... getErrorMessage(err) ... }
using the new @fusion/core helper. Bare catch {} where the error was unused.
- SQLite row types: defined typed XxxRow interfaces per table and cast
.all()/.get() results via `as unknown as XxxRow[]` (the double cast is
required because better-sqlite3 returns Record<string, SQLOutputValue>).
- rowToX(row: any) converters: typed argument with the matching row interface.
- Dynamic settings key writes: (settings as Record<string, unknown>)[key].
- React event handlers and setState callbacks: inferred types or concrete
React.{Mouse,Change,Form}Event<...> where needed.
- pi-claude-cli: local PiMessage / PiContext duck types to avoid re-typing
pi-ai concrete shapes; typed Claude stream event message fields.
72 files changed, ~400 anys eliminated. Typecheck passes across the workspace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clears the remaining no-unused-vars warnings across the dashboard app and
server, desktop main, and engine sources. Dead React state destructures are
collapsed to setter-only, unused props are underscore-prefixed to preserve
API shape, and unreferenced catch bindings are dropped. No behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three API shifts converge here:
- DefaultResourceLoaderOptions.agentDir is required as of 0.68 (the
old process.cwd() fallback was removed). Pass getFusionAgentDir()
explicitly in createFnAgent.
- createAgentSession({ tools }) is now a string[] allowlist of tool
names, not a Tool[] array (0.68). Our boundary-wrapping via
wrapToolsWithBoundary produces Tool instances, so we can no longer
pass them through \`tools\`. Move them into \`customTools\` and
suppress the built-in defaults with \`noTools: "builtin"\`. The
wrapped tools keep the same names (read, bash, ...) as the built-ins
they replace, so no call-site or prompt changes are needed.
- SettingsManager.create's first arg (cwd) became required (was
optional before). Dashboard routes that previously passed
\`undefined\` for a process-global settings view now pass
process.cwd() to match the existing DefaultPackageManager call below.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the .kb/kb.db migration path, legacy backup filename handling, and
backward-compat test suites. Renames internal kbDir identifiers to
fusionDir and hasKbProject/isValidKbProject to their fusion equivalents.
- Remove needsCentralMigration, autoMigrateToCentral, and the
"needs-migration" FirstRunState; checkAndMigrate and KB_SKIP_MIGRATION
env var are gone
- Remove LEGACY_BACKUP_DIR and canonicalizeBackupDir; listBackups no
longer matches kb-* filenames
- Delete backward-compat.test.ts and store-backward-compat.test.ts;
update remaining tests to new 3-state first-run model
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the stray useClaudeCli settings checkbox + onboarding question
with a proper provider-card UX. The card lives next to OAuth + API-key
cards in onboarding and settings, with Enable/Disable + Test actions.
Backend:
- Vendors rchern/pi-claude-cli@0.3.1 as packages/pi-claude-cli
(MIT, attribution in UPSTREAM.md). Lets us bump peer-dep on
pi-coding-agent in lockstep with Fusion (upstream pinned ^0.52.0
vs ours ^0.62.0) and fix bugs without waiting on upstream.
- Adds @fusion/pi-claude-cli as a workspace dep of @runfusion/fusion
so users don't have to `npm install -g pi-claude-cli` manually.
- serve/daemon/dashboard conditionally load the extension via
discoverAndLoadExtensions() when GlobalSettings.useClaudeCli is on;
no side-effects on user ~/.fusion/agent/settings.json.
- New GET /api/providers/claude-cli/status: claude --version probe
+ toggle state + cached extension resolution.
- New POST /api/auth/claude-cli: flips useClaudeCli, refuses if the
claude binary is missing, fires the existing skill-backfill hook.
- /api/auth/status now injects a synthetic {id:"claude-cli", type:"cli"}
provider entry so onboarding + settings see a consistent list.
Frontend:
- New ClaudeCliProviderCard component shared between ModelOnboardingModal
and SettingsModal's Authentication section.
- New AuthProvider.type = "cli" variant.
- Removed the old "Route AI calls through the Claude CLI" checkbox from
Global Models settings and the opt-in step from the onboarding wizard.
- ProviderIcon gets a composite Anthropic-mark-plus-terminal glyph for
the claude-cli provider id.
Tests:
- 8 unit tests for extension resolution (@fusion/pi-claude-cli is
workspace-linked so these run in-tree).
- 2 unit tests for the binary probe.
- Existing /auth/status tests filter out the new synthetic entry so
they keep asserting structural OAuth/API-key behavior in isolation.
- The vendored package's own 296 tests still pass unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The in-progress/in-review Changes tab was inflating file counts by
preferring a stale task.baseCommitSha over the live merge-base with the
base branch. Once upstream commits are merged into a feature branch,
baseCommitSha..HEAD includes every upstream file as well, producing
counts far larger than the branch's own changes.
resolveDiffBase now prefers merge-base(HEAD, [origin/]baseBranch), falling
back to baseCommitSha only when no merge-base is available or when the
merge-base equals HEAD (task sitting on the base branch with no
divergence, e.g. unit-test scenarios).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PUT /api/settings/global now fires an onUseClaudeCliToggled hook on an
actual transition so the UI toggle has immediate effect — serve/daemon/
dashboard wire it to ensureClaudeSkillsForAllProjectsOnStartup so every
registered project picks up .claude/skills/fusion without a restart.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>