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>
Vite/Vitest's resolver treats `#` as part of the filesystem path in some
environments, causing ERR_MODULE_NOT_FOUND on plugin reload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every agent-facing quality gate that used to pair tests with typecheck now
also includes lint. Specifically:
- core/src/types.ts: QA Check skill prompt runs lint, tests, typecheck (was
tests only) and gates task_done() on all three.
- core/src/agent-prompts.ts + engine/src/reviewer.ts: "Do NOT issue REVISE"
exclusion list now covers lint as well, so out-of-scope fixes that
restore lint remain allowed (matches the already-lint-aware completion
gate text at the top of the same prompts).
- engine/src/executor.ts: task_done() pre-flight checklist adds an explicit
"if the repo has a lint command, run it and fix failures" bullet, mirrors
the typecheck bullet, and expands the CRITICAL line from "ALL test
failures" to "ALL lint, test, and typecheck failures".
- core/src/store.ts: default Step 2 checklist (Testing & Verification) now
includes Lint and Typecheck alongside "All tests pass".
- cli/src/commands/plugin-scaffold.ts: generated plugin README and the
"Next steps" CLI output include \`pnpm lint\` between install and test.
Existing prompts that already paired lint with tests+typecheck (the
Completion section, hard quality gates, triage testing requirements) are
unchanged.
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>
- no-useless-escape: drop needless backslashes in character classes and
URL/path regexes (gh-cli, store, task, modelFilter, useFileMention,
RoutineEditor, ScheduleForm).
- no-case-declarations: wrap case bodies in ProjectOverview and
SettingsModal with block scopes.
- prefer-const: convert a never-reassigned slug binding in agent-import;
annotate legitimate forward-declared let bindings in dashboard.ts that
callbacks close over before assignment.
- no-fallthrough: add missing break after settings-subcommand error.
- no-empty-interface/no-empty-object-type: convert ProjectManifest from
empty interface extension to a type alias.
- no-unused-expressions: replace `x && x.method()` short-circuits in
TerminalModal with optional chaining.
Then ratchet these rules from warn → error so regressions are blocked.
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>
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>
PluginStore's constructor treats its rootDir arg as a project root and
internally appends `.fusion` before opening the SQLite DB. Several CLI
call sites were passing the already-resolved `.fusion` directory,
producing a doubled `.fusion/.fusion/fusion.db` that the dashboard
process kept recreating on every project load.
Pass the project root instead so the DB lands in the canonical
`.fusion/fusion.db` alongside the rest of the project's state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a dedicated "View Details" button to each agent list card while preserving clickable identity behavior
- Style list card actions so lifecycle controls and the new details action align cleanly across desktop and mobile layouts
- Expand AgentsView coverage to verify button rendering, correct detail-target opening, and backward-compatible identity click behavior
- Stabilize MissionManager activity pagination test timing and document the new list-view details action in the dashboard README
- Update AgentLogViewer to anchor scroll position when new streamed entries prepend while the user is reading older logs
- Keep live-follow behavior near the top and avoid false scroll adjustments when loading older history
- Add focused auto-scroll regression tests covering near-top follow, anchored reading, and history pagination cases
- Improve log readability styling by using theme tokens and consistent text color for normal and thinking log lines
- Resolve release changelog conflicts and retain both existing 0.1.1 notes and FN-2300 changelog entry
- Update AgentImportModal preview to preselect all agents and skills, support per-item toggles plus select-all/clear controls, and keep directory preview enabled when parsed files exist.
- Send selectedAgents and selectedSkills from paste, directory, and browse import flows, with dynamic import CTA labels and guardrails when nothing is selected.
- Extend /agents/import to accept selection filters, allow skill-only imports, and parse downloaded company archives directly without shell tar extraction.
- Expand dashboard import tests to cover directory parsing, subset selection, browse-mode skill visibility, and related UI/style assertions.
- Restore full-suite CLI test reliability by adding workspace alias mappings in vitest config and retrying slow cross-platform binary help probes.
On iOS Safari/PWA, tapping the terminal opened the on-screen keyboard but
keystrokes were silently dropped. After the earlier CSS fix (c7266b7f) the
helper textarea now covers the terminal surface and iOS focuses it natively
on tap — but the bubble-phase onPointerDown/onTouchStart handler kept
re-focusing xterm + the textarea and calling setSelectionRange during the
touch gesture. That is the same class of re-focus-mid-gesture that the
prior commit identified as disrupting iOS input attribution; moving from
capture to bubble phase wasn't enough.
- Early-return from handleTerminalGestureFocus on
(hover: none) and (pointer: coarse), so iOS handles focus with no JS
interference.
- Desktop (fine pointer) keeps the existing behavior because the textarea
stays 1x1 off-screen and still needs programmatic focus on canvas click.
- Add a regression test covering the no-op path with the media query mocked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace free-text Reports To input with a manager select populated from fetched agents
- Exclude the current agent from manager candidates and preserve unknown manager IDs as a fallback option
- Save selected manager IDs through updateAgent, including clearing reportsTo when No manager is selected
- Expand AgentDetailView settings tests for manager selection behavior and increase task document cascade test timeout stability
- Move the project management entry into the desktop project selector dropdown UI
- Simplify Header rendering and remove redundant standalone project management navigation controls
- Update Header and multi-project flow tests to validate the new dropdown interaction pattern
- Refresh header styles to support the revised selector layout and menu behavior
- Replace the server-computed settings flag with prAuthAvailable and document it in shared Settings types
- Compute PR auth availability from gh CLI auth first, with GITHUB_TOKEN as fallback in GET /api/settings
- Rename dashboard settings state/props/hooks from githubTokenConfigured to prAuthAvailable and strip server-owned auth fields on save
- Update PR section messaging to guide users to run 'gh auth login' and refresh related route/component tests
- Add startFreshSession to useQuickChat so users can explicitly create a new persisted session for the current agent/model target
- Refactor session creation into a shared helper and track current session target to support fresh-thread creation without changing selection
- Add a New chat action to the QuickChatFAB header that starts a fresh thread while preserving active model/agent context
- Update quick chat hook/component tests to verify new-session behavior and message streaming targets
- Add header action styling for the new quick-chat controls
- Raise mobile .toast-container bottom offset by adding --space-2xl to clear footer/nav overlap
- Update utility-mobile CSS regression assertion to match the new toast offset formula
- Extend build-exe test timeout from 20s to 90s to reduce flaky CLI packaging failures
- Remove the Chat action from the mobile More sheet and drop the unused onOpenQuickChat prop wiring
- Keep chat access through the dedicated mobile Chat tab and update helper copy in Settings to match
- Update MobileNavBar and mobile feature-access regression tests to assert chat is not listed in More
- Stabilize MissionManager activity pagination test by adding an explicit waitFor timeout
- Add "package" as a supported Pi extension source type and show a human-readable label in the manager
- Extend PiExtensionsManager tests to cover package-sourced extensions in name, source, path, and toggle assertions
- Add an accessible aria-label to each extension toggle control
- Resolve toggle switch CSS conflict by adopting token-based sizing, stronger focus styling, and disabled-state visuals
- Add explicit sidebar section class names in ChatView for header, search, list, and footer layout control
- Add a mobile-only footer New Chat action while keeping the desktop header button to avoid duplicate desktop CTAs
- Update mobile chat sidebar CSS to use full-height layout with hidden header/search and a scrollable session list
- Expand ChatView tests to validate sidebar structure, mobile New Chat behavior, and mobile CSS contract rules
- Replace inline heading margin styles in SettingsModal with reusable settings-section-heading--spaced class
- Tokenize settings pane horizontal spacing and margins in styles.css
- Add symmetric gutters so section headings align consistently across the settings modal
- Replace remaining hardcoded spacing and transition values in plugin-related CSS with dashboard design tokens
- Refine plugin manager layout states by reusing shared settings empty-state styling and removing redundant local heading markup
- Show the project scope banner in the Plugins settings tab for consistency with other project-scoped sections
- Add PluginManager and SettingsModal test coverage for empty/loading class conventions, header structure, and scope banner rendering
- Add heartbeat multiplier global control UI to AgentsView with supporting behavior and tests
- Remove heartbeat multiplier field from Settings modal scheduling section and update related test coverage
- Add mobile-specific CSS adjustments for agent global controls and extend mobile view assertions
- Update docs to reflect the new heartbeat multiplier location in agents and settings references
- Expand heartbeat interval presets to enforce a 5-minute minimum and include extended 48h/72h/1w options
- Add custom heartbeat entry in AgentsView with typed minute input, save/cancel actions, and validation/clamp behavior
- Show the Custom option only when the current interval is a preset, while preserving explicit custom interval labels when needed
- Update dashboard tests and heartbeat utility tests to cover new preset boundaries, custom option behavior, and health staleness expectations
- Document the 5-minute minimum heartbeat clamp behavior in agents documentation
- Add .gm-modal to the shared mobile modal sizing rule used at the main mobile breakpoint
- Apply full-width and full-height viewport constraints so the modal no longer renders off-screen
- Add CSS coverage assertions in core-modals-mobile tests for Git Manager modal mobile sizing behavior
findLandedTaskCommit only fell back to scanning all of HEAD on git-log
exceptions. When the bounded base..HEAD range is valid but empty — e.g.
the merger fast-forward-rebased the task branch and set baseCommitSha
to the new HEAD, or later commits moved HEAD past the merge — recovery
silently returned null and re-queued the merge even though the commit
had already landed. Add an empty-stdout fallback that re-scans HEAD,
with a regression test covering the FN-2221 scenario.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new worktree setting that fetches the configured remote and rebases
the task branch onto the latest default-branch tip before the merger attempts
to merge it back. Catches concurrent pushes from other collaborators or
fusion workers on other hosts before they surface as merge conflicts —
anything the rebase can't fast-forward flows into the existing smart/AI
resolve pipeline (attempts 1–3) rather than needing new handling.
- `settings.worktreeRebaseBeforeMerge` (bool, default true) — gates the step.
- `settings.worktreeRebaseRemote` (string, default "") — which remote to
fetch; empty falls back to git's configured remote for the default branch,
then to the sole remote if there's only one, then to "origin".
- Rebase runs inside the task's worktree; failure aborts and falls through
to the merge cascade. Rebase errors are warn-logged but never throw.
- Dashboard SettingsModal Worktrees section now has a toggle for the setting
plus a remote dropdown populated from `/api/git/remotes/detailed`. The
dropdown defaults to "Use git default" so no explicit selection is needed
on first configure.
Also aligns the Last/Next heartbeat spans on the agent list card — both now
share the `.agent-heartbeat-last, .agent-heartbeat-next, .agent-heartbeat-saving`
font-size rule with a consistent line-height and inline-flex alignment so
the labels don't drift vertically when they share a row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
agentHealth.tsx:
- Drop the "Disabled" label and isHeartbeatEnabled helper. Server-side
heartbeat scheduling is driven by agent.state (active/running = armed),
not by the legacy runtimeConfig.enabled flag, so surfacing "Disabled" for
agents with a stale flag on disk was misleading.
- Replace the elapsed > heartbeatTimeoutMs check with elapsed > 2× effective
interval (with a 60s floor). heartbeatTimeoutMs is the per-run work budget,
not a between-tick freshness window; using it made any agent configured
with a multi-minute+ interval show Unresponsive the moment it got more
than 60s old. The effective interval resolver now falls back to the
server-side 1h default, so agents never configured via the dropdown and
agents with an explicit interval get consistent treatment, differing only
by scheduled cadence.
AgentDetailView.tsx:
- Remove Stop buttons from active/paused/running/error (mirrors the earlier
AgentsView cleanup — Pause/Resume/Retry cover reversible transitions,
Delete stays on idle/terminated for teardown).
- Add a "Next Heartbeat" info row alongside "Last Heartbeat" for ticking
agents (active/running), computed as lastHeartbeatAt + effective interval.
Tests rewritten to cover the new semantics; dropped 14 obsolete cases that
were pinned to the old heartbeatTimeoutMs-based check and the "Disabled"
branch. 42 agentHealth tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The compact board view was a duplicate of the list view with fewer affordances
— no heartbeat dropdown, no Last/Next indicators, and its own copy of the
action buttons that kept drifting out of sync with the list view's. Drop it
entirely and migrate any saved "board" preference to "list" on load.
- Remove Board toggle button (LayoutGrid icon) and the "board" state value.
- Remove the entire board rendering branch.
- Simplify getStateCardClass to the single "agent-card" prefix.
- Remove unused LayoutGrid import.
Tree and Org Chart views remain unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove Stop buttons from active/paused/running/error in both grid and list
views; Pause/Resume/Retry cover the reversible transitions and Delete stays
available on idle/terminated for teardown. Matches the existing shape of
AgentDetailView after the earlier cleanup.
- Drop the redundant interval badge next to the heartbeat dropdown — the
selected <option> already shows the current value.
- Add a "Next: HH:MM:SS" indicator alongside "Last:" for active/running
agents, derived from lastHeartbeatAt + configuredIntervalMs (full timestamp
in the tooltip). Paused/idle/error/terminated agents show Last only since
there's no scheduled next tick.
- Remove now-unused Square and Heart icon imports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backup automation now emits `npx runfusion.ai backup --create` so scheduled
backups work for users on the zero-install path where `fn` is not on PATH.
`runfusion.ai` also exposes `fn` and `fusion` bins, so `npm i -g runfusion.ai`
puts them on PATH (npm does not link dep bins globally). Alias only defaults
to the dashboard when invoked as `runfusion.ai` / `runfusion`; `fn` / `fusion`
forward args verbatim so bare `fn` still prints help.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- DevServerView.preview.test.tsx: drop manual-preview-override assertion
(the component hard-codes isManualPreviewOverride=false under the new
session model, so the badge is always "Auto"), provide both legacy and
current-API fields from createDevServerHookState, and mirror
embedContext into blockReason in createPreviewEmbedState so the
fallback panel picks up the reason text under the new destructure.
- runtime-adapter.test.ts: skip the createSession / promptWithFallback /
describeModel blocks with a TODO — the adapter loads pi.js via
CommonJS require() which vi.mock does not intercept, so the mocked
module is never actually installed. Needs a dynamic import seam
before these can run; tracking separately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace public logo.svg with the header ring+swoosh geometry to keep branding consistent
- Refresh icon-192.png and icon-512.png assets to match the updated dashboard mark
- Add PWA tests that validate logo.svg structure and ensure install icon files exist
- Document favicon/PWA brand alignment expectations in the dashboard README
- Move inline styles from AgentListModal and AgentGenerationModal into token-based stylesheet classes
- Polish NewAgentDialog step-zero layout, role grid, and AI generation button/summary presentation
- Improve AgentImportModal result stat visual hierarchy and semantic status coloring
- Expand dashboard modal CSS coverage with sectioned rules and mobile-specific refinements
- Update component and stylesheet tests to assert new class usage and modal responsive behavior