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>
- 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>
Plugin-contributed skills previously registered only a name for sessions and the dashboard, so their SKILL.md bodies were never actually loaded — fix threads real body paths through to both session creation and the Skills UI.
- Resolve each enabled plugin skill's body path via @fusion/core's resolvePluginSkillBodyPath and thread its body dir (plus parent dir) into every session-creating lane (executor primary/retry/verification-fix/step/child-agent, triage, reviewer, merger, agent-heartbeat, cron-runner) as additionalSkillPaths, unioned with existing CE skill dirs.
- Add collectPluginSkillNames/mergePluginSkills additionalSkillPaths plumbing in session-skill-context.ts so plugin skill discovery paths flow the same way as native/role-fallback skills.
- Update dashboard skills-adapter.ts to read plugin skill SKILL.md and reference files from disk (via the traversal-guarded reader) instead of returning a runtime-placeholder/"not found" response for plugin-sourced skills.
- Document the plugin skill body delivery mechanism in docs/PLUGIN_AUTHORING.md.
- Add regression coverage: plugin-skill-body-delivery.test.ts, expanded session-skill-context.test.ts and skills-adapter.test.ts.
- Add changeset fn-7857-plugin-skill-body-delivery.md (minor, fix).
Files changed:
.changeset/fn-7857-plugin-skill-body-delivery.md | 7 ++
docs/PLUGIN_AUTHORING.md | 3 +
.../dashboard/src/__tests__/skills-adapter.test.ts | 92 ++++++++++++++++------
packages/dashboard/src/skills-adapter.ts | 33 ++------
.../__tests__/plugin-skill-body-delivery.test.ts | 75 ++++++++++++++++++
.../src/__tests__/session-skill-context.test.ts | 84 +++++++++++++++++++-
packages/engine/src/agent-heartbeat.ts | 3 +-
packages/engine/src/cron-runner.ts | 2 +
packages/engine/src/executor.ts | 25 ++++--
packages/engine/src/merger.ts | 10 ++-
packages/engine/src/reviewer.ts | 2 +
packages/engine/src/session-skill-context.ts | 43 ++++++++--
packages/engine/src/step-session-executor.ts | 5 +-
packages/engine/src/triage.ts | 3 +-
14 files changed, 318 insertions(+), 69 deletions(-)
Fusion-Task-Id: FN-7857
Fusion-Task-Lineage: 9ba4c305-8b38-4ae8-85b3-4c87205ef767
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Project-scoped chat managers can be cached before the project engine boots, so fn_send_message/fn_read_messages were silently dropped for lazily-booted (desktop) sessions while browser sessions kept them; the fix refreshes the cached manager's MessageStore post-construction and surfaces a diagnostic + chat-stream warning when the reduced tool schema condition occurs instead of failing silently.
Key changes:
- ChatManager gains setMessageStore() to refresh a cached manager's MessageStore post-construction, mirroring the existing setPluginRunner() refresh seam
- getOrCreateScopedChatManager()/resolveScopedChatManager() now accept and wire an optional MessageStore, upgrading already-cached managers instead of leaving them stale
- register-chat-routes.ts now passes engine.getMessageStore() through to the scoped chat manager resolver
- ChatManager emits a new 'warning' chat-stream event (code: tool-schema-reduced) plus a diagnostics.warn() call when a bound agent has no MessageStore, so reduced tool schema is agent-visible instead of a silent per-call failure
- Added regression tests covering MessageStore wiring/refresh in chat-project-services and chat-manager, plus a patch changeset documenting the fix
Files changed:
.changeset/fn-7854-chat-tool-schema-parity.md | 7 ++
.../dashboard/src/__tests__/chat-manager.test.ts | 124 ++++++++++++++++++++-
.../src/__tests__/chat-project-services.test.ts | 67 +++++++++++
packages/dashboard/src/chat-project-services.ts | 10 +-
packages/dashboard/src/chat.ts | 39 +++++++
.../dashboard/src/routes/register-chat-routes.ts | 2 +-
6 files changed, 245 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7854
Fusion-Task-Lineage: 1d1ee3e7-608b-4b7d-be45-138b38b27f17
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Session skill merging (collectPluginSkillNames) previously ignored per-project
Skills view enable/disable toggles and only consulted each plugin's static
default, so a user disabling a plugin skill in the Skills view would still see
it merged into live agent sessions. Extracted the effective-enablement
resolver shared by dashboard discovery and engine session assembly into
@fusion/core so both surfaces stay in sync.
- Added packages/core/src/skill-settings.ts with computeSkillId/parseSkillId/
normalizeStoredSkillPath/getSkillSettingState/resolvePluginSkillEnabled,
exported from @fusion/core's index.
- packages/dashboard/src/skills-adapter.ts now re-exports and delegates to the
shared @fusion/core resolver instead of duplicating its own
getSkillSettingState/computeSkillId/parseSkillId implementations.
- packages/engine/src/session-skill-context.ts: collectPluginSkillNames now
accepts a projectRootDir, reads project settings via skill-resolver's newly
exported readProjectSettings/resolveProjectRoot, and calls
resolvePluginSkillEnabled instead of only checking the plugin's static
skill.enabled flag; mergePluginSkills passes projectRootDir through.
- packages/engine/src/skill-resolver.ts: exported readProjectSettings and
ProjectSkillSettings for reuse by session-skill-context.
- Updated docs/plugin-management.md to document that per-project Skills view
toggles now apply to runtime agent sessions, not just discovery.
- Added unit tests for the new core resolver and updated dashboard/engine
tests to cover per-project toggle overrides in session merging.
- Added a patch changeset for @runfusion/fusion.
Files changed:
.changeset/fn-7858-plugin-skill-session-toggle.md | 7 ++
docs/plugin-management.md | 4 +-
packages/core/src/__tests__/skill-settings.test.ts | 62 +++++++++
packages/core/src/index.ts | 8 ++
packages/core/src/skill-settings.ts | 102 +++++++++++++++
.../dashboard/src/__tests__/skills-adapter.test.ts | 60 ++++++++-
packages/dashboard/src/skills-adapter.ts | 107 +++-------------
.../src/__tests__/session-skill-context.test.ts | 140 ++++++++++++++++++++-
packages/engine/src/session-skill-context.ts | 23 +++-
packages/engine/src/skill-resolver.ts | 4 +-
10 files changed, 409 insertions(+), 108 deletions(-)
Fusion-Task-Id: FN-7858
Fusion-Task-Lineage: 90e44d24-e385-4a74-b8e4-3c864ec39a95
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>
Grok advertises promptCapabilities.image=false and ignores ACP image
ContentBlocks (live probe: NO_IMAGE). Path-based vision works when the
agent is given an absolute file path. Include path hints in chat prompts
from .fusion/chat-attachments and carry path on ChatImageContent for
file:// uris.
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.
pushAfterMerge was only implemented in the soft-deprecated legacy aiMergeTask
pipeline, so after master-plan U0 made runAiMerge the sole merge path the
setting silently did nothing and origin fell permanently behind local main.
- runAiMerge now runs a post-finalize push step: working-tree-independent
ref-to-ref push fast path; on remote divergence a detached clean-room
pull --rebase (with AI conflict resolution) pushes HEAD and CAS-advances
the local integration ref (explicit non-FF opt-in, push path only), then
runs merge-advance auto-sync and refreshes mergeDetails.commitSha.
- Push failures stay non-fatal (task finalizes done) with push:origin
run-audit events and PushToRemoteFailed task-log entries.
- Merge settings: Push Remote free-text replaced by remote + target-branch
dropdowns (Custom… escape, free-text fallback when no remotes), persisting
to the same pushRemote setting string. New GET /api/git/remotes/:name/branches
endpoint lists remote-tracking branches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude per-model weekly usage is parsed generically from the OAuth payload's
limits[] scoped entries (live probe disproved the seven_day_fable key guess).
Grok now prefers ~/.grok/auth.json OIDC credentials against
cli-chat-proxy.grok.com/v1/billing?format=credits for a real percent-used
weekly credits window, falling back to the xAI API-key validity card.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consolidates Cursor Admin API key resolution onto one documented env var so the usage/admin credential path is reachable and unambiguous, replacing the prior dual CURSOR_ADMIN_API_KEY/CURSOR_API_KEY and multi-provider-id lookup.
- Replace CURSOR_ADMIN_API_KEY (preferred) + CURSOR_API_KEY alias with a single CURSOR_API_KEY env var, mirroring the GROK_API_KEY precedent
- Simplify readCursorApiKey to check CURSOR_API_KEY then fall back to the single "cursor" authStorage entry via readConfiguredApiKey (drop the cursor/cursor-cli/cursor-agent provider-id loop)
- Export readCursorApiKey and fetchCursorUsage for direct test coverage
- Update the no-auth error message and settings-reference.md docs to reference only CURSOR_API_KEY, clarifying cursor-cli OAuth/session auth vs the separate Admin API usage-metering credential
- Add changeset (@runfusion/fusion: minor) documenting the credential-path change
- Add/adjust usage.test.ts coverage for readCursorApiKey precedence (env over authStorage) and the updated credential-absent error message
Files changed:
.changeset/fn-7817-cursor-api-key.md | 7 ++++
docs/settings-reference.md | 8 ++--
packages/dashboard/src/__tests__/usage.test.ts | 53 +++++++++++++++++++++++++-
packages/dashboard/src/usage.ts | 52 ++++++++-----------------
4 files changed, 77 insertions(+), 43 deletions(-)
Fusion-Task-Id: FN-7817
Fusion-Task-Lineage: 86ac3d47-8e80-4159-abee-6c41aae56407
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a Cursor provider fetcher to the dashboard usage aggregator so operators with a Cursor Admin API key see spend-based usage alongside the other providers.
- usage.ts: add fetchCursorUsage() using the Cursor Admin API POST https://api.cursor.com/teams/spend with Basic auth (API key as username), resolving the key from CURSOR_ADMIN_API_KEY (preferred) or CURSOR_API_KEY, falling back to fusion-auth/pi-configured api keys; maps teamMemberSpend overallSpendCents/spendCents and hardLimitOverrideDollars/monthlyLimitDollars into a "Monthly spend" usage window with a reset derived from subscriptionCycleStart
- usage.ts: wire fetchCursorUsage into fetchAllProviderUsage's parallel provider fetch list (with withTimeout + no-auth demotion) and update the provider-list comment
- UsageIndicator.tsx: map the "Cursor" provider name to the existing cursor-cli icon token/SVG
- usage.test.ts: add CURSOR_ADMIN_API_KEY/CURSOR_API_KEY env stubbing and a full fetchCursorUsage regression suite (ok/zero-utilization/no-auth/error/expired-key/parse-failure cases)
- UsageIndicator.test.tsx: cover the Cursor icon mapping
- docs/settings-reference.md: document that the Usage dropdown Cursor card requires a Cursor Admin API key (session-only cursor-agent login is insufficient)
- add a minor changeset for @runfusion/fusion documenting the new Cursor usage card
Files changed:
.changeset/fn-7816-cursor-usage.md | 7 +
docs/settings-reference.md | 4 +
packages/dashboard/app/components/UsageIndicator.tsx | 7 +
packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx | 26 +++
packages/dashboard/src/__tests__/usage.test.ts | 157 ++++++++++++++
packages/dashboard/src/usage.ts | 240 ++++++++++++++++++++-
6 files changed, 440 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7816
Fusion-Task-Lineage: 4cec63d8-4ddc-40f3-8d16-5e4078da5eba
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a Grok (xAI) provider fetcher to the dashboard's usage aggregation so a Grok card now appears in the Usage dropdown when credentials are configured.
- Add fetchGrokUsage() in usage.ts: resolves the API key from GROK_API_KEY env, then ~/.grok/user-settings.json, then grok-cli auth storage, and validates it against GET https://api.x.ai/v1/api-key
- Since xAI exposes no subscription usage meter for inference keys, the card reports auth-validity status (ok/no-auth/error) with an empty usage-window list rather than fabricating quota data
- Surfaces clear error messages for expired/blocked keys and non-200 responses; omits the card entirely when no credentials are found
- Register fetchGrokUsage in fetchAllProviderUsage's parallel provider fetch list alongside Claude, Codex, Gemini, Minimax, Zai, and GitHub Copilot
- Add extensive test coverage in usage.test.ts for key-source precedence, ok/error/no-auth states, and blocked/expired key handling
- Add changeset (.changeset/fn-7814-grok-usage.md) documenting the new minor feature
Files changed:
.changeset/fn-7814-grok-usage.md | 7 ++
packages/dashboard/src/__tests__/usage.test.ts | 157 +++++++++++++++++++++++++
packages/dashboard/src/usage.ts | 95 ++++++++++++++-
3 files changed, 257 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7814
Fusion-Task-Lineage: cac497a9-5a57-4ba5-a7ea-8a01b89a0cbd
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- contain fn_artifact_register path payloads: realpath-canonicalized
containment before stat/read — relative paths require and must stay
inside baseDir, absolute paths allowed only under baseDir or the OS
temp dir (deliberate allowance for browser/screenshot tooling);
the process.cwd() fallback is gone, symlink escapes rejected
- bind task-scoped heartbeat artifact registration to the acquired
worktree (baseDir: sessionCwd rebind after acquisition); no-task
heartbeat prompt now says to pass absolute temp-dir paths
- enforce exactly-one payload source (content/uri/dataBase64/path);
content+uri combos are now rejected to match the documented contract
- add FNXC rationale comments at both visual-artifact instruction sites
in the planning prompts (sync contract with the executor prompt)
- media route: statSync -> await stat from node:fs/promises
- range tests ride the in-memory MockSocket harness (TestResponse gains
binary-safe bodyBuffer; real-TCP helper deleted) and assert the full
206 Content-Range/Content-Length contract for every range form
- add PdfViewer coverage (iframe src/title) in DocumentsView tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Reclassifies Gemini usage fetch outcomes so unconfigured/unauthenticated Gemini no longer shows a noisy error card in the usage dropdown; transient failures of a configured token still surface as errors.
- fetchGeminiUsage() in packages/dashboard/src/usage.ts now sets status to `no-auth` (instead of `error`) for unsupported auth types (api-key/vertex-ai) and for HTTP 401/403 auth-expired responses, so fetchAllProviderUsage omits Gemini from the aggregate list in those cases
- HTTP 5xx, network, timeout, and parse failures for a configured Gemini token remain `error` and visible, per the existing FN-7798 keep-auth-expired-visible convention for other providers
- Added FNXC:UsageProviders comments documenting why Gemini deliberately diverges from that convention
- Updated packages/dashboard/src/__tests__/usage.test.ts to cover the new no-auth classification
- Added changeset .changeset/fn-7806-gemini-usage.md (patch) documenting the fix for release notes
Files changed:
.changeset/fn-7806-gemini-usage.md | 7 +
packages/dashboard/src/__tests__/usage.test.ts | 211 +++++++++++++++----------
packages/dashboard/src/usage.ts | 15 +-
3 files changed, 148 insertions(+), 85 deletions(-)
Fusion-Task-Id: FN-7806
Fusion-Task-Lineage: e86b23ea-14e9-472d-8b44-3951fa02ae6c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- 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>
#5 root cause (reproduced): getTask — the shared load for the entire per-task
API — plus the mutation helpers updateTaskUnlocked, updateStep,
readPromptForArchive, and resetPromptCheckboxes all read PROMPT.md unguarded.
An unreadable PROMPT.md (root-owned from a prior `sudo` run -> EACCES, PROMPT.md
being a directory -> EISDIR, transient FS error) threw and 500'd every per-task
operation (GET/DELETE/PATCH/retry/reset/archive) for every task, while the
PROMPT.md-free board list and create kept working. These reads are now
best-effort: degrade (empty prompt / unsynced steps / skipped cosmetic sync)
and log, so a PROMPT.md hiccup can never brick task management. Added a symptom-
verification test that forces EISDIR and asserts getTask/updateTask/archiveTask
still succeed.
#10c: the dashboard badge-snapshot cache only evicted on hard-delete, so
archived tasks were re-cached via task:updated and retained for the daemon's
lifetime — a slow memory leak. New isBadgeEligibleTask predicate gates the
create/update listeners so archived tasks are evicted (matching the startup
prime's includeArchived:false). Added a unit test for the invariant.
Updates the #5 changeset to cover the real fix; adds a badge-eviction changeset.
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>
Only show usage meters for AI providers the user has actually configured, instead of surfacing entries for providers with no meterable data.
- fetchGitHubCopilotUsage now demotes GitHub's 404 "No Copilot subscription found" response (both the Fusion-credential HTTP path and the gh-CLI fallback path) to a `no-auth` status instead of `error`, so it is treated as no meterable entitlement.
- fetchAllProviderUsage's existing `status !== "no-auth"` filter now also excludes these no-entitlement Copilot results, so they no longer appear in the usage list.
- Configured-but-failing providers (expired auth returning 401/403, transient HTTP 5xx, or other errors) keep `status: "error"` and remain visible with their diagnostic message.
- Added regression tests covering: Fusion-credential 404 omitted, Fusion-credential 500 surfaced as error, gh-CLI 404 omitted, gh-CLI 401 surfaced as "GitHub auth expired" error.
- Added a changeset documenting the usage-view behavior change as a patch/fix.
Files changed:
.changeset/fn-7798-usage-configured-providers.md | 7 +++
packages/dashboard/src/__tests__/usage.test.ts | 71 ++++++++++++++++++++++--
packages/dashboard/src/usage.ts | 19 ++++++-
3 files changed, 90 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7798
Fusion-Task-Lineage: 18835b48-68f3-48aa-a03c-cc85772778a9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The reliability GET/reset handlers referenced getScopedStore, which is
defined inside setupBadgeWebSocket and is not visible in the createServer
scope where these handlers live — so the scoping change did not typecheck.
Switch to the in-scope resolveProjectScopedStore helper (used by the other
realtime endpoints), which also routes through engineManager for correct
per-project resolution.
Guard store resolution with try/catch returning a targeted 500, mirroring
the project SSE handler, instead of falling through to the generic error
handler. Add project-scoping regression tests: GET reads the project store,
GET without projectId falls back to root, and reset writes the project store.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reliability GET and reset endpoints were always using the server's
root store, ignoring projectId. Mirror the Command Center pattern by
using getProjectIdFromRequest and getScopedStore so multi-project
servers report per-project reliability stats.
Refs FUX-042
Grok CLI chat failed two different ways depending on the surface:
1. Default (no-project) chat errored with "requires the bundled Grok CLI
runtime". The default ChatManager was handed a bare PluginLoader, but Grok
routing (deriveGrokRuntimeHintForNoVisibleKey -> resolveRuntime) needs a
PluginRunner's getRuntimeById/createRuntimeContext; the unguarded call threw
"getRuntimeById is not a function". New resolveChatManagerPluginRunner()
prefers the engine's PluginRunner (same runner the project-scoped path uses),
falling back to the loader only in UI-only mode.
2. Project-scoped chat returned empty replies. The CLI-bundled Grok plugin
(packages/cli/dist/plugins/.../bundled.js, gitignored) was stale vs the
FN-7796 single-JSON adapter source; the running server loads that bundle,
not the plugin's own dist. `pnpm build` regenerates it. Noted in the
changeset that the freshness guard only warns and the dev prebuild does not
rebuild the CLI tsup bundle.
Verified end-to-end on a live dashboard: both default and project-scoped
grok-cli/grok-4.5 chats now stream thinking + text.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Image attachments added via TaskStore.addAttachment now surface as first-class image artifacts, reusing the existing artifact listing/SSE/media pipeline instead of duplicating bytes.
- addAttachment() registers a URI-only "image" artifact (metadata.source: "attachment") pointing at the already-written attachments/<file> path whenever an image mimeType is attached; registration is best-effort and swallows the archived/soft-deleted-task rejection so addAttachment keeps its always-succeeds contract for valid images.
- deleteAttachment() now removes any bridged artifact rows for a filename before deleting the attachment file, so /api/artifacts/:id/media can never point at a deleted attachment.
- register-task-workflow-routes.ts's resolveArtifactMediaPath now accepts task-scoped attachments/<file> URIs (in addition to artifacts/<file>) so the media route can stream bridged image-attachment artifacts; task-less artifacts remain restricted to .fusion/artifacts/.
- docs/storage.md documents the attachment→artifact bridge behavior and the media route's accepted URI prefixes.
- Added a changeset (@runfusion/fusion: minor) describing the user-facing Artifacts view change.
- Extended store-attachments and artifacts-route-integration tests to cover the new bridging and deletion behavior.
Files changed:
.changeset/fn-7791-image-attachments-artifacts.md | 7 +++
docs/storage.md | 3 +-
packages/core/src/__tests__/store-attachments.test.ts | 59 +++++++++++++++++++++-
packages/core/src/store.ts | 58 ++++++++++++++++++++-
packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts | 40 +++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 9 +++-
6 files changed, 172 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7791
Fusion-Task-Lineage: 4df47880-6161-4a8b-933a-2f6fc2fed953
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
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>
Fix useArtifacts fetching/subscribing only when a projectId is present, which left the Artifacts tab stuck at 0 on single-project dashboards where currentProject is unset at mount.
- useArtifacts now builds a cache key and fetches/subscribes even without a projectId, scoping the cache under a __default__ key
- SSE subscription omits the projectId query param when unset (default/unscoped /api/events) and only filters incoming events by projectId when one is set
- Added/updated tests covering the default-scope fetch, cache, and SSE subscription paths
- Added a changeset documenting the fix
Files changed:
.changeset/fn-7767-artifacts-default-scope.md | 7 ++++
.../app/hooks/__tests__/useArtifacts.test.ts | 42 +++++++++++++++++---
packages/dashboard/app/hooks/useArtifacts.ts | 37 ++++++------------
.../__tests__/artifacts-route-integration.test.ts | 45 ++++++++++++++++++++++
4 files changed, 100 insertions(+), 31 deletions(-)
Fusion-Task-Id: FN-7767
Fusion-Task-Lineage: b4ea9b1f-2908-4f5b-bf75-d6fdc45f9340
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix a "Response failed" crash for plugin CLI runtime chats (grok/droid/cursor) whose sessions expose top-level `messages` and stream via `onText` without a pi-shaped `session.state`.
- Read messages/errorMessage null-safely from `session.state`, falling back to top-level `session.messages` when state is absent, in both the room responder and streaming response extraction paths
- Keep `state.errorMessage` optional so successful streams from state-less sessions no longer throw TypeErrors, while pi/openclaw/hermes provider errors still surface correctly
- Add regression tests covering state-less plugin CLI sessions in chat-manager.test.ts
- Add changeset for the fix
Files changed:
.changeset/fn-7765-grok-cli-chat-crash.md | 7 ++
packages/dashboard/src/__tests__/chat-manager.test.ts | 90 ++++++++++++++++++++++
packages/dashboard/src/chat.ts | 35 ++++++---
3 files changed, 121 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7765
Fusion-Task-Lineage: 80e33a97-e971-4aec-a4cf-29d97c5c5e62
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Extend artifact test coverage to pin creation, listing, and viewing across every artifact type and payload variant on both the agent/dashboard-chat tool surface and the dashboard artifacts route.
- Add a route-level integration test covering list/serve for all artifact types (document, image, video, audio, other) across inline content, uri reference, and binary data payloads, including task-scoped filtering, registry-level (task-less) artifacts, and 404 behavior for uri-only artifacts requested via /media.
- Add an engine-level real-TaskStore test exercising fn_artifact_register/list/view (agent tools) and the dashboard-chat artifact tool for every artifact type and content/uri/dataBase64 variant, asserting list and view output correctness.
- Factor out shared PNG_IMAGE_BYTES fixture and per-type MIME/binary fixtures to keep new assertions concise.
Files changed:
.../__tests__/artifacts-route-integration.test.ts | 150 ++++++++++++++++++++-
.../src/__tests__/agent-artifact-tools.test.ts | 142 ++++++++++++++++++-
2 files changed, 287 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7764
Fusion-Task-Lineage: 187b3f0f-d1b4-42fe-9658-1ee67870b524
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>
Fix Grok CLI 'missing API key' false-positive by extending the no-visible-key routing invariant to configured fallback models, not just the default provider.
- deriveGrokRuntimeHintForNoVisibleKey now also triggers when the fallback provider is grok-cli
- new applyGrokCliNoKeyRuntimeOptions promotes a grok-cli fallback model into the primary session (stripping the provider-qualified model prefix) instead of leaving it as an unused fallback
- docs/grok-cli-contract.md updated to describe the fallback-routing contract
- added regression tests in grok-runtime-routing.test.ts, chat-manager.test.ts, and chat-manager-room-hybrid.test.ts
- added changeset fn-7758-grok-cli-no-key-routing.md (patch)
Files changed:
.changeset/fn-7758-grok-cli-no-key-routing.md | 7 ++
docs/grok-cli-contract.md | 29 +++--
.../src/__tests__/chat-manager-room-hybrid.test.ts | 36 +++++++
.../dashboard/src/__tests__/chat-manager.test.ts | 41 +++++++
packages/dashboard/src/chat.ts | 16 ++-
.../src/__tests__/grok-runtime-routing.test.ts | 118 +++++++++++++++++++++
packages/engine/src/agent-session-helpers.ts | 36 ++++++-
7 files changed, 266 insertions(+), 17 deletions(-)
Fusion-Task-Id: FN-7758
Fusion-Task-Lineage: 6b564b21-50ab-4c3e-b0d7-7fded2091d90
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the dashboard showing empty/dash estimated costs for runs on newer OpenAI Codex models, whose pricing was missing from the static model-pricing table.
- Add pricing entries for gpt-5.3-codex-spark, gpt-5.4, gpt-5.4-mini, and gpt-5.5 under the openai-codex provider, sourced from the pinned pi-ai 0.80.5 model catalog.
- Correct the previously-guessed gpt-5.6-luna/sol/terra rates to match their actual distinct published rates instead of all sharing the gpt-5.3-codex rate.
- Update/add tests across model-pricing, team-analytics, token-analytics, and workflow-analytics to cover the new/corrected pricing entries.
- Add dashboard tests (TaskSummaryTab prior attempts, CommandCenter mobile scroll, TokensArea, task-planner chat metrics) verifying costs render correctly instead of falling back to unavailable.
- Add a patch changeset documenting the estimated-cost fix.
Files changed:
$(cat /tmp/diffstat.txt)
Fusion-Task-Id: FN-7757
Fusion-Task-Lineage: e0f2e9d5-e922-4878-82d0-40c7d9899fdb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Registers the three GPT-5.6 codenamed OpenAI Codex model variants (luna, sol, terra) so they appear in the model picker, since pricing alone did not make them selectable.
- Add packages/core/src/openai-models.ts with SUPPLEMENTAL_OPENAI_CODEX_PROVIDER_REGISTRATION and mergeSupplementalOpenAiCodexModels(), mirroring the existing Anthropic supplemental-merge seam; additive and dedupe-safe against the pinned pi-ai catalog
- Wire mergeSupplementalOpenAiCodexModels into GET /api/models via packages/dashboard/src/routes/register-model-routes.ts, alongside the existing Anthropic supplemental merge
- Export new symbols from packages/core/src/index.ts and packages/core/src/index.gate.ts
- Add unit tests for the merge helper (packages/core/src/__tests__/openai-models.test.ts) and the route wiring (packages/dashboard/src/__tests__/register-model-routes-openai-codex-supplemental.test.ts)
- Document the new supplemental catalog entries in docs/settings-reference.md
- Add changeset .changeset/fn-7745-gpt-5-6-codenamed-model-selector.md (minor, @runfusion/fusion)
Files changed:
.../fn-7745-gpt-5-6-codenamed-model-selector.md | 7 +
docs/settings-reference.md | 2 +
packages/core/src/__tests__/openai-models.test.ts | 134 +++++++++++++++++
packages/core/src/index.gate.ts | 9 ++
packages/core/src/index.ts | 9 ++
packages/core/src/openai-models.ts | 166 +++++++++++++++++++++
...-model-routes-openai-codex-supplemental.test.ts | 143 ++++++++++++++++++
.../dashboard/src/routes/register-model-routes.ts | 10 +-
8 files changed, 479 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7745
Fusion-Task-Lineage: 74c83182-df81-4606-a4dc-0da3ee4cae83
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes desktop provider API keys not persisting when a host wires an engine into createServer() but forgets to pass its own authStorage, which previously caused register-auth-routes.ts to throw "Authentication is not configured".
- Add ProjectEngine.getAuthStorage() exposing the OAuth subsystem's raw createFusionAuthStorage() instance
- In createServer(), derive options.authStorage from engine.getAuthStorage() when not explicitly provided (mirrors existing engine-derivation pattern for onMerge/automationStore/etc.); explicit authStorage still overrides
- Add regression tests covering the fallback-derivation and explicit-override behavior
- Add changeset (patch) documenting the fix for @runfusion/fusion
Files changed:
.changeset/fn-7747-derive-authstorage-from-engine.md | 7 ++
packages/dashboard/src/__tests__/server.test.ts | 119 +++++++++++++++++++++
packages/dashboard/src/server.ts | 28 ++++-
packages/engine/src/project-engine.ts | 25 +++++
4 files changed, 178 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7747
Fusion-Task-Lineage: f8e72b15-d084-4e8d-89db-47453d57b41b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The triage release-authorization gate (FN-6481/FN-6469) false-flagged any
spec that merely mentioned release tooling (scripts/release.mjs, pnpm release)
and, because non-user sources made the in-band authorization marker inert,
stranded ordinary tasks in awaiting-approval with no exit.
- Delete triage-release-authorization.ts + its test and the finalizeApprovedTask
parking block; release-class specs now flow through triage normally.
- Remove the dashboard approve/reject-plan API guards and UI gating so tasks
still carrying the legacy awaitingApprovalReason="release-authorization" hold
render as ordinary manual plan-approval holds and can be resolved.
- Keep the awaitingApprovalReason field + activity label for backward-compat.
- Replace the engine gate with agent instruction (AGENTS.md -> Releasing):
agents must never run a release from inside a Fusion task.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Grok CLI provider readiness now mirrors the Cursor CLI provider: it is derived from the `grok` binary being available rather than requiring a Fusion-visible GROK_API_KEY or ~/.grok/user-settings.json, since the CLI manages its own auth.
- probeGrokBinary now derives `authenticated` from binary availability (readiness) instead of API-key/user-settings presence; key detection surfaces as a non-blocking `apiKeyDetected` hint
- /auth/status treats the grok-cli provider as authenticated when enabled + binary available
- GrokCliProviderCard drops the blocking "Set GROK_API_KEY" state
- Direct xAI streaming path is unchanged and still uses $GROK_API_KEY when present (FN-7711/FN-7714)
- Added changeset for @runfusion/fusion (patch)
Files changed:
$(cat /tmp/diffstat_fn7716.txt)
Fusion-Task-Id: FN-7716
Fusion-Task-Lineage: ac0efc79-2510-465e-9cd2-4938c08989c9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>