Each agent now gets its own .fusion/agents/<id>/HEARTBEAT.md procedure
file instead of sharing a single project-wide file. A one-shot
migration in AgentStore.init() re-points existing agents off the legacy
shared path and copies the legacy file's contents into each agent's
new per-agent location so operator edits are preserved.
The HeartbeatTriggerScheduler now phase-aligns the first tick to
lastHeartbeatAt + intervalMs so a process restart resumes each agent's
existing schedule rather than waiting up to a full interval before
firing again. Overdue ticks fire promptly within a small jitter window
to avoid a thundering herd at boot.
Also fixes three pre-existing QuickChatFAB test failures introduced by
ad4db8243: auto-select default model now switches to model mode whether
or not agents are present, the model tag only renders in model mode,
and one test scopes its option lookup to role="option" to disambiguate
the in-header tag from the dropdown entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The merge commit message was built from `commitLog`/`diffStat` computed
against `merge-base(branch, main)`. Under squash-merge workflows, when an
earlier task is squash-merged onto main first, branches that forked off
the pre-squash main no longer share ancestry with it — `merge-base`
resolves to a point before the earlier task, and the message describes
work already merged via the prior squash. FN-2952's commit body claimed
11 files / 557 insertions when the actual diff was 2 files / 55 lines.
Subject was also a generic `merge <branch>` regardless of content.
- packages/engine/src/merger.ts: new `computeActualMergeCommitContext`
helper that derives commitLog/diffStat from the actual integration
delta (`git diff --cached <integrationTarget> --stat`), filtering
branch commits by patch-id against the target's recent history to
drop already-squashed siblings. Wired into both commit-finalization
sites (`commitOrAmendMergeWithFixes` uses `preAttemptHeadSha`; the
final amend in `runMergeAttempt` uses `HEAD~1`). Agent-context use of
the wide range is unchanged.
- packages/engine/src/merger.ts: `buildDeterministicMergeMessage` now
generates subject and body in parallel via `Promise.all`. Subject is
composed as `feat(taskId): <ai summary>`, capped at 72 chars, with
fallback to the legacy `merge <branch>` form on any AI failure.
- packages/core/src/ai-summarize.ts: new `summarizeCommitSubject` and
`sanitizeCommitSubject` mirroring the body summarizer's structure.
Same title-summarizer lane, 15s timeout. Sanitizer strips quotes,
bullets, re-added conventional-commit prefixes, and trailing periods;
hard-caps at 60 chars.
- packages/core/src/__tests__/ai-summarize.test.ts: 9 tests covering
the sanitizer's behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves the commit-body AI helper out of merger.ts into the existing
core/ai-summarize.ts module so all short-summary AI work (titles,
chat titles, fallback merge commit bodies) shares one home with
consistent dispatch semantics, error handling, and session lifecycle.
Core (ai-summarize.ts):
- New `summarizeCommitBody(diffStat, rootDir, provider, modelId, opts)`
exported alongside `summarizeTitle`. Same shape (provider/modelId
args), same get-engine-or-bail dynamic loading via `getFnAgent`,
same readonly-tools session, same disposal-in-finally pattern.
- Differs from `summarizeTitle` in three deliberate ways suited to the
commit-body job:
1. Returns null on any failure instead of throwing — the caller is
always the merger, which has a deterministic fallback chain
behind it. Throwing would force the merger to wrap every call
in try/catch.
2. Accepts an optional `signal` to forward engine-pause / shutdown
cancellation, plus a configurable `timeoutMs` (default 30s)
so a wedged AI session can't stall a merge indefinitely.
3. Larger output ceiling (2000 chars vs title's 60) and larger
input ceiling (4000 chars truncated diff) — commit bodies are
multi-line and need more room than a 60-char title.
- Exported alongside `summarizeTitle` from `@fusion/core`. Constants
(`COMMIT_BODY_SYSTEM_PROMPT`, `MAX_COMMIT_BODY_INPUT_LENGTH`,
`MAX_COMMIT_BODY_LENGTH`, `DEFAULT_COMMIT_BODY_TIMEOUT_MS`) re-exported
for callers that want to override behavior.
Engine (merger.ts):
- Dropped the local `aiGenerateCommitBody` function (~70 lines) — it
duplicated the session-creation pattern from `summarizeTitle` while
living in a place where future maintainers wouldn't think to look.
- `resolveSafeCommitBody` now imports `summarizeCommitBody` from
`@fusion/core` and delegates. The cascade behavior is unchanged
(commitLog → AI → diff stat → synthetic) and the title-summarizer
model preference is preserved (provider/modelId resolved here, then
passed through).
Tests:
- 6 new test cases in `ai-summarize.test.ts` covering:
empty input → null, missing engine → null (graceful, never throws),
missing engine + model selection → null, pre-aborted signal → null,
custom timeout (returns quickly under 1s ceiling), exposed constants.
- Core: 3136/3136 pass (was 3130 — +6 new). Engine: 2887/2887 pass.
- Typecheck clean, workspace lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single "smart" strategy is now two flavors with the new default flipped
to prefer-main. Both share a pre-cascade `git fetch origin <currentBranch>`
+ best-effort fast-forward so a freshly-pushed sibling commit doesn't get
clobbered when the fallback resolves a conflict against a stale base.
- "smart-prefer-main" (new default): -X ours fallback. Protects just-merged
sibling work from being regressed by a concurrent task branch.
- "smart-prefer-branch": -X theirs fallback. Equivalent to legacy "smart".
Legacy "smart" / "prefer-main" enum values are accepted and normalized via
`normalizeMergeConflictStrategy()` so existing settings.json files migrate
seamlessly. The fast-forward step gracefully degrades on fetch failure or
divergent local main (logs and continues).
Updates settings UI dropdown, test helpers, and adds 5 fetch+ff regression
tests + 7 normalize-helper tests. Lint cleanup of two empty catch blocks
in scripts/release.mjs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The engine's merger imports MergeConflictStrategy from @fusion/core, but it
was defined in core/src/types.ts without being re-exported from index.ts,
breaking dashboard typecheck against a clean checkout (no engine dist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On a fresh install useAuthOnboarding's effect ran at mount before the
setup wizard's 500ms auto-open timer fired. The one-shot ref locked,
and the resolved fetch could either stack model onboarding on top of
the wizard or never re-trigger after the wizard closed.
- Gate the trigger on projectId being set so the wizard owns the
bootstrap phase; the auth check only fires once a project exists.
- Re-check setupWizardOpen via a ref when the auth fetch resolves to
avoid stacking onboarding on top of a wizard opened mid-fetch.
- Release the one-shot in that suppressed branch so the effect retries
when the wizard closes.
Adds two regression tests: fresh-install handoff and mid-fetch wizard
suppression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a core node-override-guard module, export it from @fusion/core, and enforce conflicts in store updates
- Add API route and CLI extension safeguards so nodeId override updates are blocked when ownership would conflict
- Wire node override routing and validation through dashboard quick-create, list, modal, settings, and task form/detail surfaces
- Add focused unit and integration tests for core guard logic, workflow routes, and dashboard node override UX
- Define and export unavailable node policy types in core settings interfaces
- Add project-level unavailable node policy default and runtime validation helper
- Add core unit coverage for unavailable node policy parsing and acceptance cases
- Guard Paperclip mint requests to include companyId only when available for type-safe payloads
- Document unavailable node policy in the settings reference
The dashboard modules used a variable-specifier dynamic import
(`const m = "@fusion/engine"; await import(m)`) to defeat bundler static
analysis. tsup honored that and left the dynamic import in dist/bin.js,
so the published `@runfusion/fusion` package failed at runtime with
"createFnAgent2 is not a function" — `@fusion/engine` isn't on npm and
the silent catch set the binding to undefined. Replaces the trick with
static imports across planning, chat, subtask-breakdown, mission-interview,
agent-generation, ai-refine, roadmap-suggestions, milestone-slice-interview,
and routes. Core can't statically import engine (cycle), so it now exposes
setCreateFnAgent and engine wires itself in at module load. Documents the
pattern in AGENTS.md.
FixesRunfusion/Fusion#9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When users have an external pi-claude-cli (e.g. a global `npm install -g
pi-claude-cli`, or `npm:pi-claude-cli` in ~/.pi/agent/settings.json packages),
pi's extension discovery loaded the upstream copy and shadowed our fork. The
upstream has a once-and-lock MCP-config bug that throws "Extension runtime not
initialized" during early streamSimple calls and never recovers.
Adds reconcileClaudeCliPaths in @fusion/core, used by both the daemon's
extension assembly and the engine's per-session registerExtensionProviders, to
drop any path with a `pi-claude-cli` segment that isn't our vendored fork and
prepend the vendored path. Engine resolves the fork via require.resolve and
gracefully no-ops when it isn't reachable (e.g. embedded standalone usage).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add TaskSourceIssue contract and thread sourceIssue through Task, TaskCreateInput, archived task entries, and TaskStore serialization paths.
- Extend SQLite schema to v45 with sourceIssue* columns and add migration coverage for v44 upgrades plus legacy JSON migration import.
- Persist, update, clear, and archive/unarchive sourceIssue metadata in TaskStore with dedicated regression tests.
- Update core and dashboard tests to schema v45 expectations and stabilize flaky modal assertions with async waits.
- 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
- Add task-priority contract, normalization helpers, and exports in @fusion/core types/index
- Store task priority in SQLite and migrate existing databases with default values
- Update task store behavior and sorting tests to preserve and order by persisted priority
- Add migration/regression coverage for archived tasks and refresh storage/task-management docs
Designed to replace the \`catch (err: any) { ... err.message ... }\` pattern
across the repo. Keeps the catch binding typed as \`unknown\` (TS default)
while still producing a readable message.
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>
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>
- HeartbeatMonitor.executeHeartbeat() now checks if resolved task is done/archived
and exits before session creation with reason 'task_closed'
- When stale task came from persisted agent.taskId, clears the linkage to prevent
repeated stale activations
- HeartbeatTriggerScheduler.watchAssignments() now skips assignment callback dispatch
when assigned task is done/archived (when taskStore is available)
- Added comprehensive tests for stale-task activation guard behavior
- Extend core/plugin-sdk types with runtime manifest metadata, runtime factory, and runtime registration exports
- Add runtime validation in plugin manifest parsing, including runtimeId slug and semver checks
- Add PluginLoader.getPluginRuntimes() and PluginRunner runtime cache/invalidation plumbing across plugin lifecycle events
- Expand plugin loader/runner test coverage for runtime discovery and cache behavior, and document runtime registration in PLUGIN_AUTHORING.md
- Add ExecutionMode type contracts and executionMode field to core task interfaces
- Persist executionMode through SQLite schema mappings and TaskStore read/write paths
- Validate executionMode in dashboard route handlers and API request handling
- Expand core and dashboard test coverage for executionMode persistence and route behavior
- Add core helpers to list, read, and write .fusion/agent-memory/{agentId} files with strict path validation and exports
- Add dashboard API routes and client methods for agent memory file listing and single-file read/write operations
- Expand AgentDetailView memory tab with file selection, editing, save states, and inline validation feedback
- Add route/backend coverage for agent memory file endpoints and include a @gsxdsm/fusion minor changeset
- Emit chat:session:updated from ChatStore when message deletion mutates a session and cover deleteMessage false returns
- Pass ChatStore into SSE setup and forward chat session update events to connected clients
- Update useChat to consume SSE updates in real time with safer EnrichedChatSession typing
- Add core and dashboard tests for chat-store emissions, SSE forwarding, route wiring, and hook behavior
Why: tests were leaking ~700 per-temp-dir collection registrations
into the user's global ~/.config/qmd/index.yml because searchWithQmd
and the agent-memory qmd path called execFile("qmd", ...) with the
real binary, bypassing shouldSkipBackgroundQmdRefresh() which only
guarded the schedule* wrappers.
Guard both direct-execFile paths under the same skip flag and fall
back to file-based agent memory search when skipping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add Memory section controls for enabling auto-summarize with threshold and cron schedule inputs
- Wire ProjectEngine to sync auto-summarize automation on startup and when related settings change
- Reuse a single startup settings snapshot when syncing insight extraction and auto-summarize automations
- Add SettingsModal and ProjectEngine tests covering auto-summarize UI persistence and automation re-sync behavior
- Emit fix-feature mission events and forward validator telemetry through mission execution
- Add core and dashboard API types plus a validation telemetry route in mission-routes
- Update SSE mission event handling so validation telemetry updates stream reliably to clients
- Add telemetry surfaces in MissionManager with corresponding mission-specific UI styling
- Expand MissionManager and mission e2e test coverage for validation telemetry flows