- Add severity control markers to core and engine structured loggers while keeping info logs on stderr transport
- Parse and strip internal severity markers in dashboard TUI console capture so logger.log entries render with info icons instead of error icons
- Expand dashboard TUI tests to cover captured console severity mapping and structured logger behavior, plus logger unit tests in core/engine
- Add a patch changeset for @runfusion/fusion describing the TUI log severity icon fix
- 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
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>
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>
- Widen config match from top-level *.cjs to **/*.cjs so nested CommonJS
scripts (e.g. mcp-schema-server.cjs) get Node globals and require() allowance.
- Replace lazy require() in claude-skills.ts with a normal top-level fs import.
- Tighten an any-typed tool map in pi-claude-cli to { name: string }.
Clears the remaining 12 lint errors; workspace now has 0 errors, 450 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eliminates no-unused-vars warnings across the CLI package by dropping
dead type imports, unused destructured helpers, and simplifying try/catch
blocks whose caught errors and intermediate results were never read.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ModelRegistry's public constructor became private in pi-coding-agent 0.64.
Direct `new ModelRegistry(...)` calls no longer compile. Switch the five
production sites to the factory (`ModelRegistry.create`) and update the
four test modules that mocked the class as a constructor to now mock it
as an object with `create` and `inMemory` static methods.
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>
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>
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>
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 root verify:workspace script enforcing lint -> test -> build order
- Update CI workflow to run verify:workspace as the single workspace gate before binary packaging
- Add CLI guardrail tests for workflow sequencing and root script contract invariants
- Document deterministic workspace bootstrap expectations in contributing guide
- Clarify bundle-output test bootstrap intent for explicit artifact setup
- Add a shared runtime logger contract and dashboard runtime logger implementation for structured diagnostics
- Route dashboard CLI/runtime logs through the TUI sink and replace ad-hoc console diagnostics in server paths
- Update CLI and dashboard tests to assert structured runtime logging behavior across sync and error flows
- Document the structured logging architecture updates and include a changeset for @runfusion/fusion
The dashboard TUI Logs tab only showed the handful of lines emitted
through DashboardLogSink directly. Everything from @fusion/engine
(scheduler, executor, triage, merger, PR monitor, heartbeat, etc.)
logs via createLogger() which writes straight to console.error —
bypassing the sink. Under the TUI's alt screen those writes either
overdrew the frame or scrolled off, leaving the Logs tab near-empty.
DashboardLogSink now offers captureConsole() / releaseConsole():
while the TUI is running, console.log/warn/error are intercepted
and routed into the ring buffer. A leading `[prefix]` tag is
extracted so entries carry the subsystem name. Original console
functions are restored (by identity) on TUI shutdown.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Keep the Logs tab viewport anchored to selection so users can navigate the full in-memory ring buffer with arrow keys and Home/End
- Route streamed merge agent text through the dashboard log sink via a buffered line assembler to avoid raw fragment writes in TTY mode
- Expand dashboard and dashboard-tui command tests to cover viewport behavior, expanded log interactions, and streamed merge log handling
- Update CLI reference docs and add a @runfusion/fusion patch changeset describing the TUI log behavior fixes
- Reorder dashboard TUI sections so System is tab [1] and the initial active view
- Update header rendering across wide, medium, and narrow terminal layouts to reflect the new tab order
- Expand dashboard TUI tests to assert system-first labels and default active section behavior
- Document system-first startup behavior in CLI reference and add a patch changeset for @runfusion/fusion
- Add rich Logs tab keyboard controls including arrow/j-k movement, Home/End jumps, and Enter/Space/e expansion toggles
- Introduce expanded log inspection view with escape-to-close behavior and wrap mode support for long entries
- Improve log wrapping to hard-wrap long unbroken tokens like URLs and stack traces at terminal width
- Expand dashboard TUI command tests and document the new interactive log navigation shortcuts in CLI reference
- Add two patch changesets for @runfusion/fusion covering log inspection and navigation fixes
- Add buildHelpLines() to construct box and compact help overlay rows from shared width-safe logic
- Replace manual padRight-based row padding with visibleLength-driven padding for consistent right border alignment
- Compute overlay centering from raw uncolored line widths and apply coloring during render
- Add targeted tests validating box row geometry, border width consistency, and padding calculations
The Plugin Manager section had a global .settings-section-heading { margin: 0; }
rule that was unintentionally removing margins from all settings pane headings,
not just plugin detail headings. Scoped the rule to .plugin-settings-form so
other settings panes inherit the base spacing again.
- Update dashboard TUI header copy from "fn board" to "fusion" branding
- Add CLI tests that validate the new header text and related rendering behavior
- Include a changeset documenting the published @runfusion/fusion patch for the branding update
- Add a new dashboard TUI renderer with logs, system, utilities, stats, and settings sections
- Wire runDashboard to auto-enable TUI in TTY sessions with reactive task/agent updates and utility keybindings
- Keep non-TTY behavior unchanged by falling back to the existing plain-text startup output
- Add CLI tests and docs for TUI behavior, keyboard shortcuts, and auth/usage guidance
- Ensure dashboard WebSocket auth checks respect --no-auth consistently
Three orthogonal fixes bundled together so they re-land as a unit after
earlier worktree-based reverts kept wiping them individually.
1. `--no-auth` flag now actually disables auth. Previously a stale
FUSION_DAEMON_TOKEN in .env silently re-armed bearer-token auth despite
the CLI flag. Added a `noAuth` option to ServerOptions; auth-middleware's
isDaemonAuthActive/getDaemonToken short-circuit to false/undefined when
set; CLI plumbs opts.noAuth through both createServer call sites.
2. Workflow review failures no longer reset every completed step. Previously
a single CSS nit from a workflow reviewer could drag 5+ already-approved
steps back through plan review, code review, and re-execution because
determineRevisionResetStart fuzzy-matched feedback tokens against step
names. handleWorkflowRevisionRequest, handleWorkflowStepFailure, and
sendTaskBackForFix now call a new reopenLastStepForRevision helper that
flips only the last non-pending step back to pending (with currentStep
rewind via a newly-accepted updateTask field) — all earlier done steps
stay done, and the agent applies the feedback as an in-place patch per
the updated PROMPT.md instructions. determineRevisionResetStart stays
exported as @deprecated so existing unit tests still link.
3. Heartbeat scheduling is now state-driven. Previously a non-ephemeral
agent with a stale runtimeConfig.enabled=false on disk would never tick
and the Pause/Resume button couldn't arm the timer without also flipping
that hidden flag. HeartbeatTriggerScheduler's watchAgentLifecycle now
registers on transitions into active/running and clears on transitions
out; the tick and assignment-trigger guards key off state + ephemeral
classification. InProcessRuntime's created/updated listeners and startup
scan mirror the same semantics. runtimeConfig.enabled is only retained
for ephemeral (task-worker) opt-out.
Tests updated: agent-heartbeat.test.ts — one test renamed from "skips
registration when enabled is false" (obsolete behavior) to
"registers regardless of the legacy enabled flag"; 4 assignment-watching
tests now pass a realistic `state: "active"` on mock agents. 207 heartbeat
tests + 330 executor tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-release polish. Two related changes bundled because they both land the
project on public-release footing:
Dashboard auth
- fn dashboard now gates the HTTP API + terminal/badge WebSockets behind a
bearer token by default. Token resolution order: --token flag,
FUSION_DASHBOARD_TOKEN env, FUSION_DAEMON_TOKEN env (back-compat), or an
auto-generated fn_<32 hex>. --no-auth disables. The startup banner prints
a click-to-open URL with ?token=<token> embedded.
- Auth middleware now also accepts fn_token=<token> as a query-string
fallback so EventSource and WebSocket clients (which can't set custom
headers) still authenticate.
- setupTerminalWebSocket / setupBadgeWebSocket now refuse unauthenticated
upgrades with a proper 401 + socket close.
- Frontend: new auth.ts module captures ?token= off the URL into
localStorage (key fn.authToken), strips it from the visible URL via
replaceState, and installs a window.fetch wrapper that injects
Authorization: Bearer <token> on every same-origin /api/* request.
EventSource/WebSocket URL builders (api.ts, sse-bus.ts, useTerminal,
useBadgeWebSocket) route through appendTokenQuery().
MIT license
- LICENSE file at repo root.
- license: "MIT" on root package.json and every packages/*/package.json,
plus description/bugs metadata on the CLI package.
Docs
- docs/cli-reference.md documents --token / --no-auth / FUSION_DASHBOARD_TOKEN
and the click-to-open auth flow.
- docs/getting-started.md, docs/docker.md, README.md point at the new flow
and the CLI reference section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the engine restarts mid-step, an in-progress step may have already
passed plan + code review but not yet been flipped to done by the agent's
next task_update call. Previously, the next executor pass re-entered the
step and replayed both reviews — measured at 5-20 min of pure waste per
restart (observed in FN-2215 Step 1 and FN-2207 Step 6).
recoverApprovedStepsOnResume scans the task log for any in-progress step
whose most recent "code review Step N: APPROVE" entry is newer than its
most recent "Step N → pending" transition, and marks those steps done
before execute() runs. Safely skips steps that were reset after approval
(e.g. by a workflow revision) or only received REVISE verdicts.
Called from both the engine-restart path (resumeOrphaned) and the
unpause path, matching the two places the task log shows as vulnerable
to this race.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename core loader, dashboard server chat/planning routes, and frontend agent IDs/storage keys from kb-agent to fn-agent naming
- Update dashboard hooks and components (agent list, chat view, quick chat) to use the new fn agent key prefixes consistently
- Refresh engine, dashboard, core, and CLI tests/mocks to remove remaining kb-agent route and temp prefix references
- Update storage/gap-analysis docs to reflect fn agent key names and add a @gsxdsm/fusion patch changeset for the rename
- Return the created task ID directly from runTaskPlan and propagate it through fn_task_plan
- Remove hardcoded FN-### log parsing so CLI extension supports structured IDs like PROJ-042
- Add heartbeat fallback parsing from task_create text output when details.taskId is absent
- Expand engine and CLI tests to cover structured task IDs and updated task-plan return behavior
Introduce a shared test-utils module and global vitest setup that
guarantee tests never write to the real .fusion directory or leak temp
directories under /tmp.
Infrastructure:
- packages/core/src/__test-utils__/workspace.ts — tempWorkspace(),
useIsolatedCwd(), trackForCleanup(), assertOutsideRealFusion() with
auto-cleanup in afterEach.
- packages/core/src/__test-utils__/vitest-setup.ts — per-worker guard:
chdirs each worker into an isolated tmp dir, wraps process.chdir to
refuse the real .fusion, scopes tmp dirs under fusion-test-workers/
(skips cwd change in thread-pool workers where chdir isn't supported).
- packages/core/src/__test-utils__/vitest-teardown.ts — globalSetup
hook that wipes the shared parent even when workers are SIGKILLed.
- scripts/check-test-isolation.mjs + `test:isolated` / `test:check-
isolation` scripts for CI.
- @fusion/test-utils alias + setupFiles + globalSetup wired into core,
cli, engine, dashboard, tui vitest configs; matching tsconfig paths.
Test refactors (no behavior change):
- cli provider-settings, auth-paths, provider-auth — switch leaking
mkdtempSync calls to tempWorkspace().
- core migration, first-run, store-backward-compat — replace manual
process.chdir save/restore with useIsolatedCwd().
- tui fusion-context — replace 9 hardcoded tmp paths (collision-prone
under parallelism) with tempWorkspace().
- dashboard useTheme, FileBrowser, TaskCard — resolve source-file reads
against a PACKAGE_ROOT computed from import.meta.url instead of cwd,
so tests don't depend on the process working directory.
Verified: full suite (~15,500 tests across 8 packages + plugins) passes
and the orphan-detector reports zero leaked temp directories after a
complete run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>