Adds a best-effort, idempotent dashboard inbox notice announcing the upcoming embedded-Postgres storage migration, delivered once per project on the first engine start under the Fusion 0.59.x release line.
- New `deliverPostgresMigrationNoticeIfNeeded` in `@fusion/engine` (`postgres-migration-notice.ts`) builds and sends a `system` -> `user` inbox message via `MessageStore`, gated to version `0.59.x` by `isPostgresMigrationNoticeVersion`
- Idempotency via existing inbox message `metadata.kind = "postgres-migration-notice"` marker (no new settings key or table), so restarts never duplicate the notice
- Delivery is fully best-effort: any `MessageStore` failure is caught, logged as a warning, and never blocks or fails `ProjectEngine.start()`
- `ProjectEngine.start()` invokes the notice after runtime start, using an injected `cliPackageVersion` threaded from the CLI layer through `EngineManagerOptions` / `ProjectEngineOptions` so the engine never imports CLI/dashboard code directly
- `daemon.ts`, `dashboard.ts`, and `serve.ts` resolve the published `@runfusion/fusion` version via `getCliPackageVersion` / `isUnresolvedCliPackageVersion` and pass it into `ProjectEngineManager`
- Exported new symbols (`POSTGRES_MIGRATION_HELP_URL`, `POSTGRES_MIGRATION_NOTICE_KIND`, `deliverPostgresMigrationNoticeIfNeeded`, `isPostgresMigrationNoticeVersion`, related types) from `@fusion/engine`, and `isUnresolvedCliPackageVersion` from `@fusion/dashboard`
- New unit tests covering version matching and single-delivery/idempotency behavior
- Docs updated (`docs/agents.md`, `docs/dashboard-guide.md`) to describe the one-time notice and its dedup key
- Changeset added for `@runfusion/fusion` (minor, feature)
Files changed:
.changeset/fn-7879-postgres-migration-inbox-notice.md | 7 ++
docs/agents.md | 1 +
docs/dashboard-guide.md | 1 +
packages/cli/src/commands/daemon.ts | 6 +-
packages/cli/src/commands/dashboard.ts | 5 +
packages/cli/src/commands/serve.ts | 6 +-
packages/dashboard/src/index.ts | 2 +-
packages/engine/src/__tests__/postgres-migration-notice.test.ts | 140 +++++++++++++++++++++
packages/engine/src/index.ts | 9 ++
packages/engine/src/postgres-migration-notice.ts | 107 ++++++++++++++++
packages/engine/src/project-engine-manager.ts | 6 +
packages/engine/src/project-engine.ts | 12 ++
12 files changed, 299 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7879
Fusion-Task-Lineage: 201877e5-6bdc-4168-a8ac-ae0e50ec8308
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Move redactSecrets to the log/warn/error entry points so both the
recorded history (served over /system/logs) and the TUI/console output
are masked — previously only the stored entry was redacted while the raw
message still printed to the terminal. Add assertions for both the
console and TUI-target output paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pnpm dev / new pnpm start default to the dashboard command
- fn dashboard (and bare fn/fusion/npx, incl. packaged binaries) now runs
supervised by default via an attached foreground child (TUI-safe);
--no-supervise opts out; FUSION_RESTART_EXIT_CODE=86 = intentional restart
- New /api/system routes: info, restart, rebuild jobs with SSE output,
engine restart, agents restart-all, plugins reload-all, log tail
- System tab: rebuild & restart (source checkouts only, hidden elsewhere),
restart server/engine/agents, backup DB, live server logs, copy
diagnostics, report bug; new Plugins tab reusing PluginManager
- Desktop restart via Electron app.relaunch(); DashboardLogSink now keeps a
bounded history + listener feed for the log viewer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Debug org agents error state recovery regression: durable heartbeat-managed
agents with a non-recoverable error (permanent/credential/model-access/
config, not stale-worktree/module-resolution) were previously left
indefinitely in bare `state:"error"` with no operator-visible reason,
and CLI agent inspection tools did not surface error/pause diagnostics.
- Timer path (`HeartbeatMonitor`) and run-entry recovery now classify
non-recoverable durable heartbeat errors and park the agent `paused`
with `pauseReason:"error-unrecoverable"` instead of restart-looping or
sitting in `error` forever.
- `SelfHealingManager` mirrors the same non-recoverable classification in
its recovery sweep, parking with the same reason/metadata and skipping
the exhausted/next-retry gates for that terminal bucket.
- New `agent:error-parked-unrecoverable` run-audit event type emitted by
both the heartbeat and self-healing paths (ids/counts/outcomes-only
metadata).
- `fn_agent_show` now prints `Last Error`, `Pause Reason`, and a compact
`Error Recovery` counter line; `fn_list_agents` prints the same
diagnostics only for agents currently in `error`/`paused`.
- Updated `AGENTS.md`, `docs/agents.md`, and `docs/architecture.md` to
document the new terminal-park behavior and CLI diagnostics surface.
- Added a changeset (`@runfusion/fusion` patch) describing the
operator-facing fix.
Files changed:
.changeset/fn-7859-org-agent-error-diagnostics.md | 7 ++
AGENTS.md | 2 +-
docs/agents.md | 3 +-
docs/architecture.md | 4 +-
packages/cli/src/__tests__/extension.test.ts | 68 ++++++++++++++++
packages/cli/src/extension.ts | 64 +++++++++++++++
.../src/__tests__/heartbeat-error-recovery.test.ts | 47 ++++++++++-
packages/engine/src/__tests__/self-healing.test.ts | 94 ++++++++++++++++++----
packages/engine/src/agent-heartbeat.ts | 71 +++++++++++++++-
packages/engine/src/run-audit.ts | 1 +
packages/engine/src/self-healing.ts | 46 +++++++++--
11 files changed, 375 insertions(+), 32 deletions(-)
Fusion-Task-Id: FN-7859
Fusion-Task-Lineage: 09b2035d-e8a0-438f-b1ab-1b0048b35c76
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Issue #2015: product-code executor tasks were repeatedly routed to a
liaison-only agent because every routing path gated only on the coarse
role field, and several binding primitives had no guard at all.
- Add runtimeConfig.assignmentPolicy ("auto" | "explicit-only" | "none");
"none" can never be bound to implementation tasks by ANY path — no
override bypasses it (the liaison guarantee)
- Route every binding surface through one shared evaluator
(evaluateImplementationTaskBind): claimTaskForAgent, the previously
unguarded checkoutTask/assignTask primitives, selectNextTaskForAgent
(including the in-progress re-selection loop), scheduler auto-assign
pool, heartbeat inbox/auto-claim, fn_delegate_task, CLI agent-id
validation, and dashboard assign/checkout/inbox routes
- Lock project isolation with a regression test: a foreign-project
agent id is rejected by every binding primitive
- Expose Assignment Policy in Agent Detail settings; document in
docs/agents.md; add changeset
Fusion-Task-Id: FN-7851
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update the TUI splash tagline to match the README's current product positioning, with matching test and changeset.
- Changed FUSION_TAGLINE in packages/cli/src/commands/dashboard-tui/logo.ts from "multi node agent orchestrator" to "software factory", with an FNXC comment explaining the rationale
- Updated the dashboard TUI smoke test assertion to expect "software factory" instead of the old tagline
- Added a patch changeset documenting the tagline change
Files changed:
.changeset/fn-7850-tui-tagline.md | 7 +++++++
packages/cli/src/commands/dashboard-tui/__tests__/app.test.tsx | 2 +-
packages/cli/src/commands/dashboard-tui/logo.ts | 6 +++++-
3 files changed, 13 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7850
Fusion-Task-Lineage: d8517d7c-f7dd-43af-bb57-0092c7339aad
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Replace one-shot grok -p JSON with native grok agent stdio (ACP) for realtime
streaming, tool visibility, and multi-turn sessions. Vendor the ACP client
into fusion-plugin-grok-runtime, forward Fusion fn_* tools and operator MCP,
stage Fusion skills via --plugin-dir, authenticate per xAI headless docs, and
align project chat manager store resolution so Grok chat sessions can send.
Video was registrable but effectively unusable, and HTML/PDF deliverables
had no first-class path from agents to the gallery.
- media route now serves HTTP byte ranges (Accept-Ranges, 206 +
Content-Range, 416 on unsatisfiable) so <video>/<audio> seeking works
and Safari plays media at all
- video attachments (mp4/webm/mov, 100MB cap vs 5MB for other types)
bridge into the artifact registry like images; multer transport ceiling
raised to 100MB with per-type caps enforced in the store
- fn_artifact_register path payloads are signature-validated for video
(ftyp box / EBML header) and PDF (%PDF- prefix), mirroring images
- HTML doc artifacts (mimeType text/html) render as live sandboxed
iframe previews by default in the doc viewer, with a Preview/Source
toggle and the same FileEditor edit mode
- executor/heartbeat/planning prompts and tool descriptions now cover
the full type matrix: images, videos, audio, HTML mockups, PDFs, and
markdown docs, each with the registration recipe
Verified live: range requests (200/206/416) via curl, an ffmpeg-generated
mp4 playing to completion in the gallery lightbox, and an interactive
HTML mockup rendering in the sandboxed preview.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agents could never get screenshots/wireframes/mocks into the Artifacts view:
fn_artifact_register was gated on assignedAgentId (never set in default
ephemeral mode), the only image payload source was inline base64, and no
prompt ever told agents to register visual deliverables.
- always expose fn_artifact_register to executor sessions ("executor" author
fallback), resolve relative paths against the task worktree, and default
taskId to the executing task (heartbeat task lane too)
- add a `path` payload source: file read with 50MB cap, extension MIME
inference, PNG/JPEG/GIF/WebP signature + SVG sniff validation, persisted
through managed artifact storage
- executor/heartbeat/planning prompts + engine-tools reference now instruct
agents to register screenshots, wireframes, mockups, and recordings
- new ArtifactsGallery: Images/Docs/PDFs/Videos/Audio/Other category sections
and filter chips, visual tile grid + lightbox, embedded PDF viewer, audio
player rows, download rows; mobile-responsive down to the 768px breakpoint
- doc artifacts open a full viewer rendered as markdown by default with an
in-place edit mode using the shared CodeMirror FileEditor; persisted via new
GET/PATCH /api/artifacts/:id + TaskStore.updateArtifact and live-refreshed
through the new artifact:updated SSE event
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- store.ts: guard parseStepsFromPrompt in listTasks and searchTasks too, so one
unreadable PROMPT.md can't reject the Promise.all and 500 the whole board
list/search (CodeRabbit). Matches the getTask fallback.
- update-check.ts: isHomebrewInstall now resolves symlinks and matches the real
Cellar/opt install roots, fixing Intel-macOS Homebrew detection that only
checked /usr/local/Homebrew/ (brew's repo dir) and would have shown npm/sudo
guidance instead of `brew upgrade` (CodeRabbit).
- task-detail-prompt-resilience.test.ts: extend to assert the invariant across
all surfaces — listTasks(slim)/searchTasks, reopen-to-todo moveTask
(resetPromptCheckboxes), and deleteTask — not just getTask/updateTask/archive
(CodeRabbit; Surface Enumeration rule).
- serve.test.ts: add SIGINT/SIGTERM exit-code assertions (130/143) so the serve
path's POSIX exit contract can't regress independently of daemon (CodeRabbit).
- update-check.test.ts: add Intel-Homebrew remediation test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses three user-reported bugs:
- API 500 diagnosability: rethrowAsApiError now preserves the original error
as Error `cause` and the /api boundary logs stack + cause for 5xx, so the
opaque "task write API returns 500 for every task" failures are traceable
(client body stays generic in production).
- In-app "Update now": detect EACCES/EPERM install failures and return
actionable remediation (sudo fn update / reinstall without sudo / brew
upgrade) instead of raw npm stderr; do not retry --force for this class.
- Daemon restart: `fn daemon` and `fn serve` exit 128+signal (SIGTERM=143,
SIGINT=130) on signal-initiated shutdown so Restart=on-failure restarts a
memory-pressure kill. Interactive `fn dashboard` TUI intentionally unchanged.
Adds regression tests (update-check EACCES/EPERM, daemon exit codes) and three
@runfusion/fusion patch changesets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the durability gap behind "grok chat returns empty replies". In a source
checkout the dashboard resolved the staged CLI tsup bundle
(packages/cli/dist/plugins/<id>/bundled.js), which resolvePluginEntryPath prefers
verbatim with no freshness check. The FN-7779 dev prebuild rebuilds each plugin's
own plugins/<id>/dist but never the staged tsup bundle, so a source-only plugin
fix ran stale until a manual `pnpm build`.
getCandidatePluginDirs now probes the live workspace source dir (<repo>/plugins/<id>)
before the staged bundle, so dev loads the freshness-checked live plugin (dist-vs-src),
self-healing even when the prebuild is skipped. The global-staged dir stays first,
so published installs (no workspace dir) are unaffected — asserted by the retained
global-install regression test plus a new source-checkout preference test.
Verified on a live dashboard: grok chat streams text and the loader now writes its
reload copies under plugins/fusion-plugin-grok-runtime/dist, not the staged bundle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two failure modes wedged the PR-based merge flow (dormant under the
current direct-merge config, but the rollback target):
1. Empty-diff branches ("No commits between ...") were marked `failed`,
pinning the serial merge slot + file leases on a task that is a
legitimate no-op. Now finalized as a terminal DONE via
`finalizeNoOpMergeTask`, mirroring the engine's canonical
`noOpResult` decision (merger-ai.ts).
2. A stale-base PR that GitHub reports CONFLICTING never becomes
mergeable on its own (nothing in the PR path rebases the head), so
`awaiting-pr-checks` waited unbounded — no escape, because the PR
path never incremented `mergeRetries`. Now each conflicting poll
counts against `mergeRetries`, so the existing
`getInReviewStallReason` "merge-retries-exhausted" escape disposes
the task after `maxAutoMergeRetries` cycles. Pending/behind PRs
still wait (checks legitimately run; "behind" is fixed by rebase).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Grok stale-dist bug was possible because the dev/build path never
refreshed plugin dist:
- The `client` prebuild (default `pnpm dev dashboard`) rebuilt only
@fusion/core + @fusion/engine + @fusion/dashboard, never plugins.
- The FN-6638 stale-dist startup warning only scanned packages/, never
plugins/, so a source-ahead plugin dist ran phantom-old with no warning.
Changes:
- build-workspace.mjs: add `--plugins-only` to plan/build just the plugins
that changed, reusing the existing content-hash skip cache (cheap no-op when
unchanged).
- scripts/dev-prebuild-client.mjs: new orchestrator — fast core/engine/
dashboard build, then incremental changed-plugin rebuild. The `client`
prebuild now runs this single cross-platform command.
- dist-freshness.mjs: scan plugin roots (plugins/, plugins/examples/) so a
stale plugin dist is warned like a stale package dist; the warning names the
plugin dir.
Verified: --plugins-only plans only plugins, skips unchanged on the second
run, and re-plans exactly the one plugin whose source changed. All script and
CLI lib tests pass.
Fusion-Task-Id: FN-7779
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes plugin skills silently disappearing when the fn daemon is started outside the project that enabled the contributing plugin, by making skill resolution project-aware instead of scoped to the daemon's root PluginLoader.
- getPluginSkills now resolves per requesting rootDir against project_plugin_states rather than the daemon-root PluginLoader scope
- Plugins skipped as disabled are now logged at load time for visibility
- Wired the new project-aware resolution through dashboard.ts, serve.ts, and daemon.ts CLI commands
- Added regression coverage in plugin-loader.test.ts and skills-adapter.test.ts
- Documented the project-scoped behavior in docs/PLUGIN_AUTHORING.md and docs/agents.md
- Added a patch changeset for @runfusion/fusion
Files changed:
.changeset/fn-7778-plugin-skills-project-scope.md | 7 +++
docs/PLUGIN_AUTHORING.md | 2 +
docs/agents.md | 2 +-
packages/cli/src/commands/daemon.ts | 68 +++++++++++++++++++--
packages/cli/src/commands/dashboard.ts | 71 ++++++++++++++++++++--
packages/cli/src/commands/serve.ts | 68 +++++++++++++++++++--
packages/core/src/__tests__/plugin-loader.test.ts | 69 +++++++++++++++++++++
packages/core/src/plugin-loader.ts | 29 ++++++---
.../dashboard/src/__tests__/skills-adapter.test.ts | 29 +++++++++
packages/dashboard/src/skills-adapter.ts | 19 ++++--
10 files changed, 337 insertions(+), 27 deletions(-)
Fusion-Task-Id: FN-7778
Fusion-Task-Lineage: 5d9a8ff2-ed0e-4859-bf9c-a16f715b081d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Broadens regression coverage for the npm-release update-check invariant so it holds across every consuming surface, not just the reported repro.
- Replace the update-check route/service semver spot-checks with a parametrized case matrix (equal, newer, older, prerelease/build metadata, short/long version segments) to close false-positive/false-negative gaps.
- Add dedicated route-level tests asserting the update-check API route surfaces the same invariant.
- Add CLI update command tests covering notification rendering across version-comparison cases.
- Add desktop native update-check tests covering the same invariant on the desktop shell.
- Add dashboard useUpdateCheck hook tests verifying consistent notification behavior for the hook consumers.
Files changed:
packages/cli/src/commands/__tests__/update.test.ts | 59 ++++++++++++++++++
.../app/hooks/__tests__/useUpdateCheck.test.ts | 25 ++++++++
.../src/__tests__/update-check-route.test.ts | 71 ++++++++++++++++++++++
.../dashboard/src/__tests__/update-check.test.ts | 42 +++++++------
packages/desktop/src/__tests__/native.test.ts | 27 ++++++++
5 files changed, 206 insertions(+), 18 deletions(-)
Fusion-Task-Id: FN-7762
Fusion-Task-Lineage: 6ab34312-fb4e-487a-aefc-2ab133bf79af
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Packaged fn serve/daemon/dashboard hosts previously failed with a misleading missing-API-key error for grok-cli agents even though the operator was already logged in via the Grok CLI. This fixes routing so those hosts eagerly ensure the bundled Grok Runtime plugin is installed/loaded before session creation, and no longer silently falls back to the key-requiring direct endpoint when no key is visible.
- Eagerly ensure the bundled fusion-plugin-grok-runtime in serve, daemon, and dashboard commands before loadAllPlugins() so runtime id "grok" is available on fresh installs without manual plugin-settings setup.
- agent-session-helpers.ts: deriveGrokRuntimeHintForNoVisibleKey now throws an actionable error (naming both remediations: install/enable the Grok CLI runtime plugin, or set GROK_API_KEY) instead of silently falling through to the key-requiring pi/openai-completions path when the runtime can't be loaded.
- Update docs/grok-cli-contract.md to document the FN-7761 packaged-host wiring and new no-silent-fallback behavior.
- Add regression tests for the packaged bootstrap behavior and bundled-plugin install path.
- Add changeset for @runfusion/fusion (patch, category: fix).
Files changed:
.changeset/fn-7761-grok-cli-packaged-routing.md | 7 +++++
docs/grok-cli-contract.md | 19 +++++++++----
.../__tests__/grok-runtime-bootstrap.test.ts | 31 ++++++++++++++++++++++
packages/cli/src/commands/daemon.ts | 17 +++++++++++-
packages/cli/src/commands/dashboard.ts | 20 +++++++++++++-
packages/cli/src/commands/serve.ts | 19 +++++++++++--
.../__tests__/bundled-plugin-install.test.ts | 17 ++++++++++++
.../src/__tests__/grok-runtime-routing.test.ts | 17 ++++++------
packages/engine/src/agent-session-helpers.ts | 15 +++++++++--
9 files changed, 142 insertions(+), 20 deletions(-)
Fusion-Task-Id: FN-7761
Fusion-Task-Lineage: 3be5f054-965c-4e8a-ad91-6e61d4dc4a42
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Bumps the pi SDK dependencies to 0.80.6 across the CLI, dashboard, engine, and pi-claude-cli packages, and adapts the Claude CLI thinking-effort mapper for the new `max` ThinkingLevel.
- Bump @earendil-works/pi-ai and @earendil-works/pi-coding-agent from ^0.80.5 to ^0.80.6 in packages/cli, packages/dashboard, packages/engine, and packages/pi-claude-cli (dependency/peerDependency/devDependency entries)
- Regenerate pnpm-lock.yaml for the new SDK versions
- Map the new `max` ThinkingLevel in packages/pi-claude-cli/src/thinking-config.ts: non-Opus models downgrade to `high` (effort max unsupported), Opus models map to `max`
- Extend packages/pi-claude-cli/src/__tests__/thinking-config.test.ts with coverage for the `max` level
- Add .changeset/fn-7755-pi-sdk-bump.md (patch) documenting the SDK bump
Files changed:
.changeset/fn-7755-pi-sdk-bump.md | 7 ++
packages/cli/package.json | 4 +-
packages/dashboard/package.json | 2 +-
packages/engine/package.json | 4 +-
packages/pi-claude-cli/package.json | 8 +-
.../src/__tests__/thinking-config.test.ts | 12 +++
packages/pi-claude-cli/src/thinking-config.ts | 10 ++-
pnpm-lock.yaml | 92 +++++++++++-----------
8 files changed, 82 insertions(+), 57 deletions(-)
Fusion-Task-Id: FN-7755
Fusion-Task-Lineage: f6a9084b-dfb9-4ad5-bd99-4627dae4c666
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Close leaked TaskStore/AgentStore handles and retry lock errors across fn research, settings import, agent export, git, and project CLI commands.
- fn research create/list/show/export/cancel/retry now close their resolved store on every exit path except the intentional fire-and-forget --wait-for-completion-less create, which stays open to avoid truncating the background run
- fn settings import retries importSettings through a momentary database-is-locked window and closes the store before every process.exit()
- fn agent export closes both the project TaskStore and the AgentStore it opens on every exit path, including the no-agents-to-export guard
- fn git status/fetch/pull/push and fn agent export switch to a path-only project resolution helper so no cached, never-closed TaskStore is left behind for these read/write-nothing-to-board commands
- fn project list/show compute per-project task counts against an uncached TaskStore that is now closed after every call, and the count read retries a momentary lock instead of silently reporting zero tasks
- Adds dedicated lock-retry regression tests for each touched command plus a changeset documenting the fix
Files changed:
.changeset/fn-7740-cli-cmd-lock-retry.md | 7 +
docs/cli-reference.md | 28 +++
.../__tests__/agent-export-lock-retry.test.ts | 122 ++++++++++
.../src/commands/__tests__/git-lock-retry.test.ts | 129 +++++++++++
packages/cli/src/commands/__tests__/git.test.ts | 12 +
.../commands/__tests__/project-lock-retry.test.ts | 195 ++++++++++++++++
.../cli/src/commands/__tests__/project.test.ts | 8 +
.../commands/__tests__/research-lock-retry.test.ts | 248 ++++++++++++++++++++
.../cli/src/commands/__tests__/research.test.ts | 16 +-
.../__tests__/settings-import-lock-retry.test.ts | 170 ++++++++++++++
.../src/commands/__tests__/settings-import.test.ts | 34 +++
packages/cli/src/commands/agent-export.ts | 67 ++++--
packages/cli/src/commands/git.ts | 18 +-
packages/cli/src/commands/project.ts | 40 +++-
packages/cli/src/commands/research.ts | 254 ++++++++++++++-------
packages/cli/src/commands/settings-import.ts | 61 ++++-
16 files changed, 1284 insertions(+), 125 deletions(-)
Fusion-Task-Id: FN-7740
Fusion-Task-Lineage: 5c572332-b81c-4e16-9748-ce8639f53ff3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Audit non-task.ts CLI command families for store-leak / no-retry-on-lock gaps and fix branch-group.ts and pr.ts to match the FN-7731 retryOnLock + closeProjectStore pattern.
- Add retryOnLock handling (honoring FUSION_CLI_LOCK_RETRY_MS) around board DB access in branch-group.ts and pr.ts so a locked store retries and exits promptly instead of hanging.
- Ensure both cached and uncached CWD-fallback project stores are closed on every exit path (success, error, early return) to stop store leaks.
- Extend project-context.ts with shared helpers used by both commands.
- Add regression tests: branch-group-lock-retry.test.ts and pr-lock-retry.test.ts, plus additional coverage in branch-group.test.ts.
- Document the new lock-retry behavior in docs/cli-reference.md.
- Add a patch changeset for @runfusion/fusion describing the user-facing fix.
Files changed:
.changeset/fn-7738-cli-cmd-lock-retry.md | 7 +
docs/cli-reference.md | 11 +
.../__tests__/branch-group-lock-retry.test.ts | 221 ++++++++
.../src/commands/__tests__/branch-group.test.ts | 10 +
.../src/commands/__tests__/pr-lock-retry.test.ts | 226 ++++++++
packages/cli/src/commands/branch-group.ts | 389 +++++++++-----
packages/cli/src/commands/pr.ts | 575 +++++++++++++--------
packages/cli/src/project-context.ts | 26 +
8 files changed, 1122 insertions(+), 343 deletions(-)
Fusion-Task-Id: FN-7738
Fusion-Task-Lineage: 0bbc8fa2-c4f9-49ed-adb6-7dab57eaee13
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Applies the FN-7731 retryOnLock + closeProjectStore pattern to the ~26 runTask* handlers in task.ts so every 'fn task' subcommand retries a momentarily locked board database and exits promptly instead of hanging or leaking a store handle.
- Generalize retryOnLock/closeProjectStore usage across all runTask* handlers in packages/cli/src/commands/task.ts
- Honor FUSION_CLI_LOCK_RETRY_MS for configurable retry timing
- Close both cached and uncached CWD-fallback stores on exit
- Multi-step flows (create/retry/delete/merge/imports) retry each discrete write independently instead of retrying the whole flow
- Add task-lock-retry.test.ts covering the new retry/close behavior across subcommands
- Update docs/cli-reference.md and task.test.ts for the new behavior
- Add changeset fn-7734-task-cmd-lock-retry-generalized.md (patch)
Files changed:
.../fn-7734-task-cmd-lock-retry-generalized.md | 7 +
docs/cli-reference.md | 18 +-
.../src/commands/__tests__/task-lock-retry.test.ts | 282 ++++
packages/cli/src/commands/__tests__/task.test.ts | 9 +-
packages/cli/src/commands/task.ts | 1541 +++++++++++---------
5 files changed, 1173 insertions(+), 684 deletions(-)
Fusion-Task-Id: FN-7734
Fusion-Task-Lineage: cd5c864b-8a33-4962-803e-bbb353a41085
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add a policy-gated review-lane bypass primitive so operators can unstick cards stranded by a failed pre-merge review step (e.g. the no-feedback review-engine defect), without exposing it to agent-driven lanes.
- Add `store.bypassFailedPreMergeReviewStep(id, { reason, actor })` in @fusion/core plus `getLatestFailedPreMergeReviewStep` in task-merge.ts, and new `bypassedBy`/`bypassedAt`/`bypassReason`/`bypassedFromStatus`/`bypassedFromVerdict` fields on `WorkflowStepResult`
- Add operator-only `fn_task_bypass_review` CLI/pi-extension tool; explicitly withheld from executor/reviewer/triage agent tool lists
- Add `POST /tasks/:id/bypass-review` dashboard API route and wire it through `register-task-workflow-routes.ts` and legacy API compatibility layer
- Add dashboard UI affordance (context menu action + task detail modal + right-dock controller wiring) to trigger the bypass with a reason
- Add i18n strings for the bypass action/labels across en/es/fr/ko/zh-CN/zh-TW locales
- Update `gating-classifications.ts` to recognize the bypassed state
- Add unit tests: `store-bypass-review.test.ts`, `task-merge-bypass.test.ts`, extension test coverage, and `useTasks` hook test coverage
- Update docs (`docs/workflow-steps.md`, `docs/dashboard-guide.md`, AGENTS.md, fusion skill references) to describe the new bypass tool/route
- Add changeset `.changeset/fn-7720-review-lane-bypass-primitive.md` (minor)
Files changed:
$(git diff --cached --stat)
Fusion-Task-Id: FN-7720
Fusion-Task-Lineage: 590b020a-ae02-4b51-8189-df8f54bf3044
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix CLI process exit so `fn agent stop`/`fn agent start` no longer hang up to 60s and eventually time out on repeated retries against the same agent.
- Root cause: `resolveProject()` cached an unclosed `TaskStore`, and `createAgentStore()` never closed the `AgentStore` it opened, leaving SQLite handles alive after the command's real work was done.
- Add `resolveProjectPathOnly`/`closeProjectStore` helpers in `project-context.ts` so path-only callers never leak a `TaskStore`.
- Explicitly close `AgentStore` on every exit/return path in `agent.ts`, since `process.exit()` skips pending `finally` blocks.
- Add a bounded fast-fail timeout around the state-store write (default 10s, overridable via `FUSION_AGENT_CMD_TIMEOUT_MS`) so a genuinely stuck operation fails fast with a clear error and non-zero exit instead of hanging.
- Add regression tests covering process-exit/store-closing behavior and update CLI reference docs.
- Add changeset for the patch release.
Files changed:
.changeset/fn-7704-agent-cmd-hang-fix.md | 7 +
docs/cli-reference.md | 3 +
.../commands/__tests__/agent-process-exit.test.ts | 114 +++++++++++
packages/cli/src/commands/__tests__/agent.test.ts | 111 +++++++++-
packages/cli/src/commands/agent.ts | 223 ++++++++++++++++-----
packages/cli/src/project-context.ts | 44 ++++
6 files changed, 444 insertions(+), 58 deletions(-)
Fusion-Task-Id: FN-7704
Fusion-Task-Lineage: 4679d1a0-3ab8-48ce-86b7-5919bba805fb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
A Staff Engineer pre-landing review (Greptile/CodeRabbit) on #1957
(merged) flagged four structural issues. This PR fixes the three that
were confirmed still present on `main`; the fourth (an unregistered-rule
`eslint-disable-next-line react-hooks/exhaustive-deps` comment) was
already fixed in #1957's second commit before merge and needed no
further change.
1. **`resolvePluginRuntime()` mislabeled "found but failed to init" as
`not_found`.** When a `runtimeHint` plugin registration is found but
`pluginContext`/`createRuntimeContext(...)` comes back falsy, the
resolver returned `reason: "not_found"` — indistinguishable from "never
registered" — defeating the point of a distinct `FallbackReason`. Now
returns `reason: "init_error"`. Updated the existing test that wrongly
asserted `"not_found"` for this path, and added a new test asserting all
three reachable `FallbackReason` values (`not_found`, `init_error`,
`factory_error`) are pairwise distinct.
2. **`ActiveAgentsPanel.tsx`/`AgentsView.tsx` hardcoded
`isInViewport={true}`.** Every agent card (live-agent header, board
card, list card) polled the runtime-fallback endpoint every 30s forever,
even scrolled off-screen — unlike `TaskCard.tsx`'s correct
`IntersectionObserver`-gated pattern. Both files now thread a real
`IntersectionObserver`-backed viewport signal into
`RuntimeFallbackBadge`. Added regression tests proving polling stops
once a badge instance's `isInViewport` transitions to `false` and
resumes once it goes back to `true` (desktop + a mobile-breakpoint
variant), plus verified via `tsc --noEmit` for `@fusion/dashboard`.
3. **Toast dedupe was per-hook-instance, not shared.**
`useRuntimeFallbackStatus`'s `lastToastedEventIdRef` was a local
`useRef`, so the same task rendered simultaneously in two card surfaces
(e.g. `ActiveAgentsPanel` + `AgentsView`) fired two separate toasts for
one fallback event. Dedupe now lives in module-level shared state (a
bounded `Map` keyed by `taskId:eventId`, FIFO-evicted past 500 entries)
so a fallback event toasts exactly once across every
simultaneously-mounted badge instance for the same task. Added a
cross-instance regression test mounting two badges for the same
`taskId`/`eventId` and asserting exactly one toast fires.
## Test evidence
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/runtime-resolution.test.ts --reporter=dot` — 25/25 pass
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/RuntimeFallbackBadge.test.tsx --reporter=dot` —
11/11 pass
- `pnpm --filter @fusion/dashboard run typecheck` — clean
- `pnpm --filter @fusion/engine run typecheck` — clean
## Scope
Isolated 6-file diff on top of current `main`
(`packages/engine/src/runtime-resolution.ts`,
`packages/engine/src/__tests__/runtime-resolution.test.ts`,
`packages/dashboard/app/hooks/useRuntimeFallbackStatus.ts`,
`packages/dashboard/app/components/ActiveAgentsPanel.tsx`,
`packages/dashboard/app/components/AgentsView.tsx`,
`packages/dashboard/app/components/__tests__/RuntimeFallbackBadge.test.tsx`).
No behavior outside the three findings above was touched.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- The desktop dashboard now supports plugin-backed runtime features,
improving how plugin-enabled workflows are loaded and run.
- Agent cards now pause background fallback polling when they’re
off-screen, helping the dashboard feel smoother and more responsive.
- **Bug Fixes**
- Improved runtime fallback handling so missing runtimes and
initialization failures are reported more accurately.
- Toast notifications are now better deduplicated, reducing repeated
alerts when multiple views show the same fallback state.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes fn_task_archive and fn_task_delete rejecting tasks still referenced as a lineage parent, with no tool-exposed way to clear that reference.
- Add optional removeLineageReferences boolean param to fn_task_archive and fn_task_delete tool schemas, forwarded to store.archiveTask/store.deleteTask
- Update tool descriptions and prompt guidelines to advertise the recovery path (removeLineageReferences:true) when a lineage-parent block occurs
- Add task-lineage-unlink.test.ts covering the new parameter behavior
- Document the change in docs/storage.md
- Add changeset (@runfusion/fusion minor, category: fix)
Files changed:
.changeset/fn-7661-lineage-unlink-tools.md | 7 +
docs/storage.md | 1 +
packages/cli/src/__tests__/task-lineage-unlink.test.ts | 199 +++++++++++++++++++++
packages/cli/src/extension.ts | 28 ++-
4 files changed, 232 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7661
Fusion-Task-Lineage: 414c046c-43df-4995-85a5-ff00b345de50
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Move the host-agnostic bundled-plugin auto-install logic (manifest loading, entry-path
resolution, install/update/enable flow) out of the CLI package into @fusion/core so the
desktop embedded runtime can auto-install bundled runtime plugins without depending on
the CLI package; the CLI module becomes a thin adapter that supplies its own bundle-dir
resolution to the shared helper.
- Add packages/core/src/plugins/bundled-plugin-install.ts with the shared, host-agnostic
ensureBundledPluginInstalled / ensureBundledDependencyGraphPluginInstalled /
ensureBundledCursorRuntimePluginInstalled implementation and BUNDLED_PLUGIN_IDS/
isBundledPluginId/resolvePluginEntryPath, exported from @fusion/core's index.
- Slim packages/cli/src/plugins/bundled-plugin-install.ts to a CLI-specific
candidate-bundle-dir resolver that delegates to @fusion/core and re-exports the same
public surface dashboard.ts/serve.ts/daemon.ts already depend on.
- Remove the now-redundant packages/cli/src/plugins/__tests__/resolve-plugin-entry-path-sync.test.ts
(coverage moved with the implementation to @fusion/core).
- Add packages/desktop/src/bundled-plugin-dirs.ts to resolve each bundled plugin's staged
package directory via import.meta.resolve, mirroring the CLI's dist/plugins/<id> resolver.
- Wire local-runtime.ts and local-server.ts to call ensureBundledPluginInstalled before
loadAllPlugins() and expose a lazy-install callback for PUT /api/plugins/:id/settings,
mirroring the CLI dashboard command's startup auto-install pass.
- Update docs/PLUGIN_AUTHORING.md to describe the shared bundled-plugin-install location.
Files changed:
docs/PLUGIN_AUTHORING.md | 11 +
.../__tests__/bundled-plugin-install.test.ts | 619 ++-------------------
.../resolve-plugin-entry-path-sync.test.ts | 97 ----
packages/cli/src/plugins/bundled-plugin-install.ts | 250 +--------
packages/core/src/index.ts | 8 +
.../__tests__/bundled-plugin-install.test.ts | 391 +++++++++++++
.../core/src/plugins/bundled-plugin-install.ts | 186 +++++++
.../src/__tests__/bundled-plugin-dirs.test.ts | 59 ++
.../desktop/src/__tests__/local-runtime.test.ts | 183 +++++-
.../desktop/src/__tests__/local-server.test.ts | 96 +++-
packages/desktop/src/bundled-plugin-dirs.ts | 61 ++
packages/desktop/src/local-runtime.ts | 66 ++-
packages/desktop/src/local-server.ts | 36 +-
13 files changed, 1171 insertions(+), 892 deletions(-)
Fusion-Task-Id: FN-7637
Fusion-Task-Lineage: 953c5b82-a079-4600-b3af-45c974cd5014
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add a path-containment check to fn_task_attach so it can no longer read files outside the task's worktree via traversal or absolute paths.
- Resolve the requested path and confine it to ctx.cwd (the task worktree) before any readFile call, rejecting "../" traversal, absolute paths, and other boundary-escaping inputs
- Add regression tests in extension.test.ts covering traversal/absolute-path attack vectors
- Add changeset (patch, category: security) documenting the fix
Files changed:
.changeset/fn-7619-attach-boundary.md | 7 ++
packages/cli/src/__tests__/extension.test.ts | 133 ++++++++++++++++++++++++++-
packages/cli/src/extension.ts | 23 ++++-
3 files changed, 161 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7619
Fusion-Task-Lineage: d35d9218-d678-4989-945d-6c1e1a322c5c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Task creation surfaces stopped hardcoding column:"triage", so new tasks now land in the selected-or-default workflow's resolved intake column instead of always jumping to Planning/triage.
- Removed hardcoded column:"triage" override in engine's createTaskCreateTool (fn_task_create), letting TaskStore.createTask resolve the landing column from the workflow's intake-trait column.
- Removed the equivalent hardcoded override in the pi extension's fn_task_create, and updated its response text to echo the actual landing column instead of a fixed "Column: triage" string.
- Fixed signal-route, GitHub-import, and planning-subtask-route task creation to stop forcing column when no workflowId is given (or, for planning subtask routes, even when one is provided).
- Custom workflows with a non-triage intake column (e.g. Inbox) now correctly capture new cards inert until released, while the default builtin:coding workflow still resolves to "triage" byte-identically.
- Added regression coverage (agent-tools-intake-column.test.ts, extension-workflow-tools.test.ts) and a patch changeset documenting the fix.
Files changed:
.changeset/fn-7611-intake-column.md | 7 ++
.../src/__tests__/extension-workflow-tools.test.ts | 70 +++++++++++
packages/cli/src/extension.ts | 10 +-
.../src/__tests__/register-signal-routes.test.ts | 8 +-
.../dashboard/src/__tests__/routes-github.test.ts | 2 -
.../dashboard/src/routes/register-git-github.ts | 16 ++-
.../src/routes/register-planning-subtask-routes.ts | 24 +++-
.../dashboard/src/routes/register-signal-routes.ts | 8 +-
.../__tests__/agent-tools-intake-column.test.ts | 138 +++++++++++++++++++++
packages/engine/src/__tests__/agent-tools.test.ts | 1 -
packages/engine/src/agent-tools.ts | 21 +++-
11 files changed, 288 insertions(+), 17 deletions(-)
Fusion-Task-Id: FN-7611
Fusion-Task-Lineage: daf7f755-b1c7-4859-b74f-f15593d5e79e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes workspace-mode (workspaceWorktrees) tasks failing auto-merge under mergeStrategy=pull-request, where processPullRequestMergeTask threw "could not determine repository" because the workspace root is a container of independent git sub-repos, not itself a git repo.
- Hoist an isWorkspaceTask check in ProjectEngine's merge dispatch (project-engine.ts) before the mergeStrategy branch, so workspace tasks always fall through to the existing direct/landWorkspaceTask path regardless of configured mergeStrategy.
- Add processPullRequestMergeTask and syncGroupPrCallback defense-in-depth guards (task-lifecycle.ts) that throw the new named WorkspaceTaskMergeError if a workspace task ever reaches the PR-merge path.
- Add engine tests covering multi-repo, single-repo, and zero-commit no-op workspace tasks under mergeStrategy=pull-request, plus a non-regression test for the legacy single-worktree PR path.
- Add CLI tests asserting the new guards throw WorkspaceTaskMergeError.
- Add a patch changeset describing the fix.
Files changed:
.changeset/fn-7610-workspace-pr-merge-routing.md | 7 ++
.../src/commands/__tests__/task-lifecycle.test.ts | 56 +++++++++
packages/cli/src/commands/task-lifecycle.ts | 33 ++++-
.../engine/src/__tests__/project-engine.test.ts | 140 +++++++++++++++++++++
packages/engine/src/project-engine.ts | 18 ++-
5 files changed, 252 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7610
Fusion-Task-Lineage: 31768b77-d9a9-4a79-a055-bbc6b228a1c4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>