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>
- Exclude revoked/suspended/disabled/deactivated keys, inactive subscriptions,
and locked accounts from the transient-auth classifier: no retry fixes those,
so they stay operator-actionable even inside an authentication_error envelope.
- Self-healing sweep logs unrecoverable-error parks separately from
recovered-to-active agents (return value still counts actions taken).
- Document same-session retry continuation semantics at the heartbeat
withRateLimitRetry call site (side-effect replay concern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A routine Claude Max OAuth token rotation (~8h) fails the in-flight call with
401 authentication_error "Invalid authentication credentials" even though
refreshed credentials already exist on disk. Three compounding defects turned
that into a fleet-wide operator-action park:
- The heartbeat prompt path never ran under withRateLimitRetry (executor/
triage/merger all do), so the 401 immediately failed the run. Now wrapped.
- The 401 matched the operator-actionable /credential/ pattern and defaulted
to "permanent", so FN-7859 parked agents paused/error-unrecoverable. A new
shared isTransientAuthCredentialError classifier (also used by
rate-limit-retry) classifies rotation 401s transient + not operator-
actionable; OAuth scope-grant and API-key failures still park.
- Heartbeat failure classification ran on the stack-bearing error detail;
stack frames like "at withRateLimitRetry (.../rate-limit-retry.ts)" match
the usage-limit /rate[_\s]?limit/ pattern. Classification and
agent.lastError now use the message; stderrExcerpt keeps the full detail.
Self-healing additionally un-parks agents previously paused with
error-unrecoverable whose lastError now classifies recoverable, bounded by
the shared heartbeat error-recovery budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends the embedded Task Detail SessionTerminal to surface the shared, user-defined terminal shortcuts (from FN-7872's kb-terminal-preferences localStorage) as tappable buttons in its mobile accessory key bar.
- Read customShortcuts via the shared readTerminalPreferences() store on mount and refresh live on the storage event
- Render each custom shortcut as a mobile-only accessory-bar button that injects decodeTerminalShortcutSequence(value) through the focus-preserving keepFocus + sendInput path, clearing sticky Ctrl like the built-in ^C key
- Suppress the buttons for read-only/replay/idle/ended sessions via the existing canAcceptInput gate; desktop embedded terminals get no key bar
- Add .cli-terminal-key--custom styling for the new buttons
- Update docs/dashboard-guide.md to describe the mobile custom-shortcut key bar behavior
- Add a minor changeset for @runfusion/fusion documenting the feature
- Add SessionTerminal.mobile.test.tsx coverage for rendering, injection, live updates, and read-only suppression
Files changed:
.changeset/fn-7876-session-terminal-custom-shortcuts.md | 7 ++
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/SessionTerminal.css | 5 ++
packages/dashboard/app/components/SessionTerminal.tsx | 31 +++++++
packages/dashboard/app/components/__tests__/SessionTerminal.mobile.test.tsx | 94 ++++++++++++++++++++++
5 files changed, 138 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7876
Fusion-Task-Lineage: 61d62ad2-7357-4eb4-b542-6e17deea1e5e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a hardened Discord support link and clarifies that project databases move to the central Fusion database in the storage update notice banner.
- Add a "Get help" button/link to the Fusion Discord (https://discord.gg/ksrfuy7WYR) in StorageMigrationNoticeBanner, with new .storage-migration-notice-banner__actions/__help CSS (desktop + mobile layout)
- Revise the banner body copy to state that project databases will be served from the central Fusion database instead of each project's local .fusion/fusion.db SQLite file
- Add storageMigrationNotice.getHelp/getHelpLabel i18n keys and update en/app.json body copy; sync placeholder keys across es/fr/ko/zh-CN/zh-TW locales and regenerate resources.d.ts
- Extend StorageMigrationNoticeBanner tests to cover the new help link (href/target/rel) and updated body copy, including dismissed/error-path cases
- Add a minor changeset for @runfusion/fusion documenting the banner change
Files changed:
.changeset/fn-7874-storage-banner-get-help.md | 7 ++++++
.../components/StorageMigrationNoticeBanner.css | 26 ++++++++++++++++++++++
.../components/StorageMigrationNoticeBanner.tsx | 16 ++++++++++++-
.../StorageMigrationNoticeBanner.test.tsx | 16 +++++++++++--
packages/i18n/locales/en/app.json | 4 +++-
packages/i18n/locales/es/app.json | 7 ++++++
packages/i18n/locales/fr/app.json | 7 ++++++
packages/i18n/locales/ko/app.json | 7 ++++++
packages/i18n/locales/zh-CN/app.json | 7 ++++++
packages/i18n/locales/zh-TW/app.json | 7 ++++++
packages/i18n/src/resources.d.ts | 7 ++++++
11 files changed, 107 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7874
Fusion-Task-Lineage: baa88812-18ca-4910-a641-8d6e23f602a6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a deterministic changelog-archive split so scripts/release.mjs stops regenerating one ever-growing root CHANGELOG.md and instead keeps only current release notes at the root while durably archiving pre-0.50.0 history.
- Add scripts/lib/changelog-archive.mjs with partitionVersionsByCutoff (splits a version-ordered list at the 0.50.0 cutoff, preserving order and treating non-parseable keys as archived) and archivePointerLine (renders the "older releases" pointer appended to the current changelog).
- Rework scripts/release.mjs's syncRootChangelog to build CHANGELOG.md (current versions + archive pointer) and a new CHANGELOG-archive.md (versions before 0.50.0) via a shared buildRootChangelogLines/normalizeChangelogLines helper instead of one monolithic file.
- Add scripts/__tests__/changelog-archive.test.mjs covering cutoff partitioning, boundary/patch handling, non-parseable keys, custom cutoffs, and the archive pointer text.
- Regenerate CHANGELOG.md (now only 0.50.0+) and add CHANGELOG-archive.md containing the pre-0.50.0 history moved out of the root file.
Files changed:
CHANGELOG-archive.md | 10882 +++++++++++++++++++++++
CHANGELOG.md | 11717 ++-----------------------
scripts/__tests__/changelog-archive.test.mjs | 58 +
scripts/lib/changelog-archive.mjs | 56 +
scripts/release.mjs | 49 +-
5 files changed, 11710 insertions(+), 11052 deletions(-)
Fusion-Task-Id: FN-7875
Fusion-Task-Lineage: 220e6aa1-54fb-4800-a86e-6d8d21f6bf18
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Restores reliable single-finger dragging of movable FloatingWindow headers on mobile by reasserting touch-action: none at the mobile breakpoint, and adds regression coverage for the touch drag path.
- Reassert touch-action: none on movable FloatingWindow headers within the mobile media query, excluding full-screen sheet variants (chat, task-detail, workflow-editor, automation, mission-interview, file-browser, pr-create, artifacts-gallery), so the drag-handle contract isn't overridden by the global pan-y lockdown on mobile.
- Add a test verifying the movable mobile drag-handle selector keeps touch-action: none alongside the other opted-out draggable selectors (right-dock-expand-modal header, terminal header).
- Add a test exercising the captured pointermove drag path (pointerdown/move/up with pointer capture) confirming the window repositions correctly on touch input.
- Add a patch changeset for @runfusion/fusion documenting the fix.
Files changed:
.changeset/fn-7873-mobile-modal-header-drag.md | 7 +++
packages/dashboard/app/components/FloatingWindow.css | 8 +++
packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx | 59 ++++++++++++++++++++++
3 files changed, 74 insertions(+)
Fusion-Task-Id: FN-7873
Fusion-Task-Lineage: 96f2b199-549e-4cab-a42b-05946a7bae86
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Center the task-id chip with the header badge row on TaskCard.
- Add a subtle translateY nudge (calc(var(--space-xs) / 4)) to .card-id in TaskCard.css so the mono task id optically aligns with the first-row badges/icons, while preserving the existing FN-7862 flex-start anchor and FN-7837 badge-wrap contract.
- Add an FNXC:TaskCardLayout comment documenting the FN-7871 requirement and rationale for the nudge.
- Update TaskCard.badge-wrap.test.tsx to assert the new transform value on the rendered .card-id element and to assert the tokenized transform rule (and absence of a raw px translateY) directly in the loaded CSS.
Files changed:
packages/dashboard/app/components/TaskCard.css | 5 +++++
.../app/components/__tests__/TaskCard.badge-wrap.test.tsx | 9 ++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7871
Fusion-Task-Lineage: 0c79d482-42d3-4e34-8844-3113c3e75ad7
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a per-project Hide done / Show done toggle to the Todo list items header so operators can declutter long selected lists while completion counts still reflect all items.
- Add hideDone state persisted per project via localStorage (kb-dashboard-todo-hide-done key registered in projectStorage)
- Filter rendered todo items to hide completed ones when the toggle is active, while keeping list stats/progress counts based on all items
- Adjust up/down item reordering to operate correctly against the visible (filtered) list while still reordering the underlying full item list
- Add an empty-state message when all items are hidden by the toggle, with Eye/EyeOff icon + i18n strings (todo.hideDone, todo.showDone, todo.allDoneHidden)
- Add regression tests covering the toggle, persistence, filtering, and empty state
- Document the Hide done / Show done control in the dashboard guide
Files changed:
docs/dashboard-guide.md | 3 +
packages/dashboard/app/components/TodoView.css | 45 ++++++++++
packages/dashboard/app/components/TodoView.tsx | 66 +++++++++++++--
.../app/components/__tests__/TodoView.test.tsx | 99 ++++++++++++++++++++++
packages/dashboard/app/utils/projectStorage.ts | 1 +
packages/i18n/locales/en/app.json | 3 +
6 files changed, 210 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7869
Fusion-Task-Lineage: 9c334aba-e802-43b8-963c-0f2daf727583
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a shared, persisted auto-save preference for workspace text-file editing, defaulted to on, surfaced as a toolbar toggle in both the Files modal and right-dock Files view.
- Add useAutoSavePreference hook: persists the fn-file-editor-auto-save localStorage preference, broadcasts same-window changes via a custom event (storage events only reach other documents), and defaults to true.
- Extend useWorkspaceFileEditor with an autoSave flag that debounces (800ms) and triggers save() for a loaded, editable file with real pending changes, keyed by workspace+file+content to avoid re-firing on failed writes.
- Add an Auto-save toggle button to FileEditor's toolbar (autoSaveEnabled/onToggleAutoSave/canToggleAutoSave props), hidden for read-only/preview/binary files.
- Wire the shared preference into FileBrowserModal and DockFilesView, disabling auto-save for binary files in the modal.
- Add fileEditor.autoSave / fileEditor.toggleAutoSave i18n strings and document the new default behavior in docs/dashboard-guide.md.
- Add/extend tests covering the new hook, debounced auto-save behavior, and toolbar toggle wiring across FileEditor, FileBrowserModal, and DockFilesView.
Files changed:
docs/dashboard-guide.md | 3 +
.../dashboard/app/components/DockFilesView.tsx | 6 +-
.../dashboard/app/components/FileBrowserModal.tsx | 20 ++--
packages/dashboard/app/components/FileEditor.tsx | 17 +++-
.../components/__tests__/DockFilesView.test.tsx | 37 ++++++-
.../components/__tests__/FileBrowserModal.test.tsx | 86 +++++++++++++---
.../app/components/__tests__/FileEditor.test.tsx | 56 +++++++++++
.../hooks/__tests__/useAutoSavePreference.test.ts | 66 +++++++++++++
.../hooks/__tests__/useWorkspaceFileEditor.test.ts | 108 +++++++++++++++++++++
.../dashboard/app/hooks/useAutoSavePreference.ts | 79 +++++++++++++++
.../dashboard/app/hooks/useWorkspaceFileEditor.ts | 47 ++++++++-
packages/i18n/locales/en/app.json | 2 +
12 files changed, 500 insertions(+), 27 deletions(-)
Fusion-Task-Id: FN-7866
Fusion-Task-Lineage: 0604de18-666d-4872-abce-2a3886c9ea55
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
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>
Flattens the Todo sidebar list rows so active/hover states read through background and text color only, without per-row borders or the active-row left accent stripe.
- Removed `border: 1px solid transparent` and hover `border-color` from `.todo-list-item`
- Added hover text color instead of hover border to keep hover state visible
- Removed the `border-color` and inset box-shadow accent stripe from `.todo-list-item--active`
- Added a CSS contract test asserting sidebar list rows stay flat (no left accent stripe, no per-row borders)
Files changed:
packages/dashboard/app/components/TodoView.css | 9 +++++----
.../components/__tests__/TodoView.mobile-css.test.ts | 19 +++++++++++++++++++
2 files changed, 24 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7870
Fusion-Task-Lineage: 78db20c0-7e53-427c-95da-7c9d5fa828d7
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a one-click commit-and-push affordance to the Git Manager modal's Changes panel, letting users commit staged changes and immediately push the active branch without a separate step.
- Add handleCommitAndPush callback in GitManagerModal that reuses createCommit and pushBranch, refreshes file changes/status on success, and surfaces a distinct toast (with the local commit hash preserved) if the push step fails after a successful commit
- Wire commitAndPush through to ChangesPanel and render a new "Commit and Push" button beside the existing Commit button, disabled while committing, when the message is empty, or when there are no staged files
- Add test coverage for the commit-and-push flow, including the partial-failure case where commit succeeds but push fails
- Add a minor changeset documenting the new Git Manager feature
Files changed:
.changeset/fn-7868-git-manager-commit-push.md | 7 +++
.../dashboard/app/components/GitManagerModal.tsx | 64 +++++++++++++++++++-
.../components/__tests__/GitManagerModal.test.tsx | 68 ++++++++++++++++++++++
3 files changed, 138 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7868
Fusion-Task-Lineage: 8b6913db-6f2a-45ab-b99d-a5c11ce53439
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Board TaskCard priority badges no longer render visible priority text, so the .card-meta-badges row cannot wrap onto a new line when a priority label widens it.
- TaskCard.tsx: drop the visible priority-label <span>, add title/aria-label with the full priority label, and keep the label reachable via a visually-hidden span for assistive tech
- TaskCard.css: update the FNXC comment on .card-priority-badge to reflect the icon-only rationale (spacing/geometry rules unchanged, still shared with the Task Detail chip/select)
- TaskCard.test.tsx: assert icon-only rendering (no visible text node/span), title/aria-label correctness, visually-hidden label content, and that the badge is absent for tasks without a priority; loosen the mocked lucide icons to forward arbitrary props
- Add a patch changeset documenting the icon-only priority badge fix
Files changed:
.changeset/icon-only-priority-badges.md | 7 +++++
packages/dashboard/app/components/TaskCard.css | 2 +-
packages/dashboard/app/components/TaskCard.tsx | 12 ++++++---
.../app/components/__tests__/TaskCard.test.tsx | 30 +++++++++++++++++-----
4 files changed, 39 insertions(+), 12 deletions(-)
Fusion-Task-Id: FN-7867
Fusion-Task-Lineage: 5c845c23-0a9f-47b4-9a75-6c410b507ef4
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>
Fixes the artifact viewer FloatingWindow (image/video/PDF/document) so it opens as a full-screen sheet on mobile instead of a small draggable/resizable desktop-style window.
- Add a mobile-breakpoint override for .artifacts-gallery-window that clamps the FloatingWindow to inset:0/100vw/100dvh with no border/radius/shadow
- Hide the FloatingWindow resize handle and disable header drag cursor/touch-action on mobile so the sheet can't be dragged or resized like the desktop window
- Add a CSS-contract regression test (ArtifactsGallery.css.test.ts) asserting the mobile sheet rules exist while desktop keeps the header drag affordance
- Update dashboard-guide.md docs to describe desktop draggable/resizable behavior vs. mobile full-screen sheet behavior
- Add a patch changeset for @runfusion/fusion documenting the fix
Files changed:
.changeset/fn-7865-artifact-viewer-mobile-sheet.md | 7 +++
docs/dashboard-guide.md | 5 +-
.../dashboard/app/components/ArtifactsGallery.css | 28 +++++++++-
.../__tests__/ArtifactsGallery.css.test.ts | 63 ++++++++++++++++++++++
4 files changed, 100 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7865
Fusion-Task-Lineage: 0b5dfaee-3f7b-4a2e-968c-324182ae7953
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Artifact-registration mailbox notifications now render a shared inline preview and open-artifact link instead of plain text metadata.
- Add MailboxArtifactAttachment component rendering an inline image/document preview plus an "open artifact" link from message.metadata (artifactId/artifactType/mimeType) via artifactMediaUrl
- Wire MailboxModal and MailboxView to render the new attachment for artifact-registered messages, with supporting CSS
- Emit metadata.mimeType from notifyArtifactRegistered in agent-tools.ts so mailbox surfaces can pick the right preview affordance without an extra artifact fetch
- Add/extend tests for the new component and for MailboxView/agent-artifact-tools coverage
- Update dashboard guide docs and add a changeset for the feature
Files changed:
.changeset/fn-7864-artifact-mail-link.md | 7 ++
docs/dashboard-guide.md | 2 +-
.../app/components/MailboxArtifactAttachment.tsx | 103 +++++++++++++++++++++
packages/dashboard/app/components/MailboxModal.css | 74 +++++++++++++++
packages/dashboard/app/components/MailboxModal.tsx | 15 +++
packages/dashboard/app/components/MailboxView.tsx | 15 +++
.../__tests__/MailboxArtifactAttachment.test.tsx | 65 +++++++++++++
.../app/components/__tests__/MailboxView.test.tsx | 93 +++++++++++++++++++
.../src/__tests__/agent-artifact-tools.test.ts | 32 ++++++-
packages/engine/src/agent-tools.ts | 5 +
10 files changed, 409 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7864
Fusion-Task-Lineage: a6502e18-5f7f-4c67-80fb-a709e4a52c50
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Bounds the execute->pause-abort->todo dispatch loop so a task can no longer requeue forever with no visible signal or terminal state.
- Track a progress-anchored `executeRequeueLoopCount`/`executeRequeueLoopSignature` pair on the task row (current step + step statuses) so slow no-progress requeue cycles are counted independently of the scheduler's wall-clock `dispatchStormCount` guard.
- Warn visibly in the task log at `EXECUTE_REQUEUE_LOOP_VISIBLE_THRESHOLD` (3) and terminalize non-paused, non-terminal tasks at `MAX_EXECUTE_REQUEUE_LOOP_CYCLES` (6) with `status:"failed"` and an `EXECUTION_DISPATCH_LOOP_EXHAUSTED:` error, preserving worktree/branch/step progress.
- Emit a new `task:execution-dispatch-loop-terminalized` run-audit mutation type with ids/counts/outcomes-only metadata.
- Reset the loop counters on real progress, manual retry, forward moves (in-review/done/archived), and unpause, in both the executor and scheduler.
- Add DB migration 142 (`executeRequeueLoopCount`, `executeRequeueLoopSignature` columns) plus store read/write/reset plumbing.
- Add reliability-interactions coverage for the new loop guard and extend store-persistence tests for the new columns.
- Document the new behavior in AGENTS.md and docs/architecture.md.
Files changed:
AGENTS.md | 1 +
docs/architecture.md | 2 +
packages/core/src/__tests__/store-persistence.test.ts | 45 +++++
packages/core/src/db.ts | 17 +-
packages/core/src/manual-retry-reset.ts | 1 +
packages/core/src/store.ts | 22 ++-
packages/core/src/types.ts | 11 ++
.../execute-requeue-loop-guard.test.ts | 188 +++++++++++++++
packages/engine/src/executor.ts | 67 +++++++-
packages/engine/src/run-audit.ts | 2 +
packages/engine/src/scheduler.ts | 8 +-
11 files changed, 355 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-7863
Fusion-Task-Lineage: db40507f-5851-435e-8854-c1ed695b4154
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Aligns the task card header so the id chip, first badge row, and right-side size/action controls share a consistent centered vertical baseline, using the existing card chip height tokens for row rhythm on both desktop and mobile while preserving FN-7837 wrapping behavior.
- Give .card-header-badges and .card-id a shared min-height (--card-chip-height) and switch .card-id to inline-flex with centered items and line-height:1 so it lines up with sibling chips instead of sitting on its own baseline
- Add min-height (--card-chip-height) to .card-header-actions so the right-aligned size/action cluster matches the id/badge row height
- Add a mobile-breakpoint override giving .card-id, .card-header-badges, and .card-header-actions a shared --card-chip-height-mobile min-height, extending the existing FN-4365/FN-4351/FN-7837 compact mobile header comment
- Add/extend TaskCard.badge-wrap tests: a shared expectSharedHeaderBaseline() assertion helper, new in-progress and no-badges alignment test cases, and a test asserting the mobile header rhythm CSS rule is present in loaded app CSS
Files changed:
packages/dashboard/app/components/TaskCard.css | 23 +++--
.../__tests__/TaskCard.badge-wrap.test.tsx | 100 ++++++++++++++++++++-
2 files changed, 116 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7862
Fusion-Task-Lineage: 1d66fbd6-30bc-4a76-9eb4-3b8734ddadc3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
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>
Fix useChat so already-rendered user/assistant messages no longer flicker away while an agent turn is actively streaming.
- useChat.ts: during an active streaming turn for the current session, treat stale/empty/cross-session loadMessages responses as append-only against the visible thread instead of replacing it, merging any genuinely new same-session messages in and skipping the session-cache write when the active thread is being preserved.
- ChatView.streaming-thread.test.tsx: add coverage asserting the rendered thread stays visible across mid-turn session-update/tool-call/stale-reload churn.
- useChat.test.ts: add hook-level regression tests for the append-only/merge/cache-skip behavior during active streaming.
- docs/architecture.md, docs/dashboard-guide.md: document the append-only mid-turn thread-stability behavior.
- Add changeset (patch) for @runfusion/fusion describing the user-facing fix.
Files changed:
.../fn-7853-chat-mid-turn-message-stability.md | 7 +
docs/architecture.md | 1 +
docs/dashboard-guide.md | 1 +
.../__tests__/ChatView.streaming-thread.test.tsx | 130 +++++++++++++
.../dashboard/app/hooks/__tests__/useChat.test.ts | 208 +++++++++++++++++++++
packages/dashboard/app/hooks/useChat.ts | 35 +++-
6 files changed, 380 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7853
Fusion-Task-Lineage: d9909469-082c-4eeb-81fb-b36d1a9e4705
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Patch useTasks pauseTask/unpauseTask to update local hook state and the project SWR task cache immediately on API success, instead of waiting for SSE/poll to reconcile paused state.
- pauseTask/unpauseTask now bump fetchVersionRef, patch the in-memory tasks list, and patch/clear the project SWR cache the same way retryTask/bypassReview already do
- guards against stale in-flight fetches clobbering the just-applied paused/unpaused state and against missing-id cache entries
- adds regression tests covering immediate local+cache reflection for pause and unpause, stale in-flight fetch ordering, and missing-id stability
- adds a patch changeset documenting the user-facing fix
Files changed:
.changeset/fn-7861-immediate-pause-state.md | 7 +
.../dashboard/app/hooks/__tests__/useTasks.test.ts | 151 +++++++++++++++++++++
packages/dashboard/app/hooks/useTasks.ts | 66 ++++++++-
3 files changed, 222 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7861
Fusion-Task-Lineage: fefbaf4f-8eb7-44a7-a1e2-ac8471a726bd
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>
Path-registered plugin reload/restart now refreshes persisted manifest metadata instead of leaving stale version/settingsSchema in the store.
- PluginLoader.loadPlugin/reloadPlugin call a new refreshPersistedManifestMetadata helper after each fresh module import, generalizing the previously bundled-only refresh to path-registered plugins
- Refresh is metadata-only (version, settingsSchema) via a stable-JSON comparison, preserving per-project enablement and saved setting values, and is a no-op when nothing changed
- PluginStore.PluginUpdateInput/updatePlugin gain a settingsSchema field (undefined = unchanged, null = explicitly clear) so updatePlugin can persist manifest schema changes independently of setting values
- Docs: add a "Updating path-registered plugins" section to docs/PLUGIN_AUTHORING.md describing the new reload/refresh loop
- Tests: add coverage in plugin-loader.test.ts and plugin-store.test.ts for manifest metadata refresh on load/reload and settingsSchema persistence
- Add a patch changeset for @runfusion/fusion
Files changed:
.changeset/fn-7855-plugin-manifest-refresh.md | 7 +
docs/PLUGIN_AUTHORING.md | 10 ++
packages/core/src/__tests__/plugin-loader.test.ts | 177 ++++++++++++++++++++++
packages/core/src/__tests__/plugin-store.test.ts | 42 +++++
packages/core/src/plugin-loader.ts | 53 +++++++
packages/core/src/plugin-store.ts | 10 ++
6 files changed, 299 insertions(+)
Fusion-Task-Id: FN-7855
Fusion-Task-Lineage: f4d94023-5a27-4059-a7a5-61f524c171b8
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>
Pausing an in-progress task never stuck: the pause teardown re-queued the
row to todo with a plain engine move, and the reopen block wiped
paused/pausedByAgentId/pausedReason. The graph-failure classifier then saw
an unpaused row, misread the hard-cancel as an engine-internal abort, and
auto-continued the session (graphResumeRetryCount 1/2, 2/2); once the
budget was exhausted the benign re-queue left the row dispatchable and the
scheduler re-dispatched it seconds later — an indefinite pause/resume
bounce, burning a fresh worktree + pnpm install per cycle.
- store: new moveTask option `preservePause` keeps the pause park across a
reopen-to-todo/triage move (flag-ON trait hook + flag-OFF legacy inline,
kept in sync). It never SETS a pause, only prevents clearing one.
- executor teardown: when the pause that caused the abort is still in
force, move with preservePause so the row lands in todo still parked
(scheduler skips paused/userPaused rows until explicit unpause).
- classifier: a live task pause is labeled operator intent, never
"engine abort during pause/resume"; the benign log now says
"parked … awaiting explicit unpause" instead of the contradictory
"cleared for normal scheduling" for parked rows.
Surfaces covered by tests: flag-ON hook (preserve + never-set + default
clear), classifier no-auto-continue for task-pause/user-pause/global-pause
rows in todo, provenance labels.
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.
Fix chat image/file attachments not rendering until re-entering the thread by reconciling the optimistic temp user bubble with the persisted user-message SSE echo during active streaming.
- In useChat, when a persisted user-role message arrives via chat:message:added while the active session is streaming, replace the optimistic temp-* bubble with the reconciled persisted message (real id + attachment filenames) instead of leaving the temp bubble in place with no refetch.
- Add regression coverage: persisted user attachment echo reconciles without duplicate/refetch, attachment-only echo reconciles content+attachments, and text-only echo still reconciles without duplicating messages.
- Extend test helper makeMessage to pass through attachments overrides.
Files changed:
.../dashboard/app/hooks/__tests__/useChat.test.ts | 160 +++++++++++++++++++++
packages/dashboard/app/hooks/useChat.ts | 13 ++
2 files changed, 173 insertions(+)
Fusion-Task-Id: FN-7849
Fusion-Task-Lineage: dbabda9c-5842-4d16-bafb-776a6536e51f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
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>
AcpRuntimeAdapter.promptWithFallback ignored options, so dashboard chat
images never became ACP ContentBlock image entries. Extract images from
prompt options and pass them through buildPromptBlocks for both acp-runtime
and the Grok vendored client.
Grok emits `_x.ai/session_notification` / `_x.ai/session/update` for
hook_execution status. The ACP SDK routes those to Client.extNotification;
without it, every successful post_tool_use hook logged -32601 Method not
found. Implement no-op extMethod/extNotification on default and bridging
handlers in acp-runtime and the Grok vendored copy.
Matches pi wrapToolsWithActionGate semantics: callers that omit
actionGateContext (chat/triage) intentionally leave tools ungated.
Add a content-free warn when a non-pi runtime receives customTools without
gate context so the path is visible without inventing deny-all defaults.