Commit Graph

10907 Commits

Author SHA1 Message Date
gsxdsm
b7b1b71cae fix(FN-7692): recover blank mobile terminal when xterm screen collapses to 0x0
The mobile terminal rendered blank even though the WebSocket was Connected and
the shell prompt had already streamed in. Root cause (reproduced live): on the
mobile fullscreen layout xterm's CharSizeService can measure a 0-width character
cell, so FitAddon.fit() proposes 0 columns/rows and .xterm-screen (plus the WebGL
canvas) collapses to 0x0 — prompt bytes arrive and are written into xterm's row
DOM but paint into a zero-size box. Renderer-independent and mobile-layout-
specific; not fixed by resize/font-size re-fits because prior guards only validate
the container width and font load, never the resulting measured screen/cell width.

Add guardAgainstCollapsedTerminalScreen (app/utils/terminalPreferences.ts) and arm
it from both terminal surfaces (TerminalModal + SessionTerminal) right after their
initial fit. While the container has a width but .xterm-screen does not, it forces
a genuine DOM-strategy remeasure (forceTerminalFontRemeasure) + fit, re-driven by a
ResizeObserver until the screen has a real width. It waits (does not give up) while
the container is not yet measurable, is bounded so it never spins, and is disposed
on every re-init/close path. Recurrence of FN-7620/FN-7686.

- Add isTerminalScreenCollapsed + guardAgainstCollapsedTerminalScreen with tests
- Wire + dispose the guard across all xterm (re)init/close paths in both surfaces
- Add changeset (patch) and a docs/solutions write-up

Note: reproduced via mobile emulation (393px, iPhone UA, forced touch), not a
physical device; the guard is the structural fix — confirm on a real device.

Fusion-Task-Id: FN-7692

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 13:22:10 -07:00
gsxdsm
461a4a2711 FN-7689: add opt-in Anthropic-style prompt caching for custom providers
Custom providers previously never enabled prompt-cache control, so agent turns re-billed the full context every request even on cache-capable backends.

- Add `CustomProvider.anthropicPromptCaching` opt-in flag in @fusion/core types
- Set pi-ai `compat.cacheControlFormat="anthropic"` on opted-in models in both registration paths: custom-provider-registry `toProviderConfig` and pi.ts `createFnAgent`
- Expose the new toggle in the dashboard CustomProvidersSection UI (with supporting CSS) and thread it through the legacy API + custom-provider routes
- Update docs (dashboard-guide, settings-reference) to document the new setting
- Add engine test coverage for the caching flag across provider registration and pi-create-fn-agent paths
- Add changeset for the fix

Files changed:
 .changeset/fn-7689-custom-provider-prompt-caching.md          |   7 +
 docs/dashboard-guide.md                                       |   1 +
 docs/settings-reference.md                                    |   2 +-
 packages/core/src/types.ts                                    |  15 ++
 packages/dashboard/app/api/legacy.ts                          |  12 ++
 packages/dashboard/app/components/CustomProvidersSection.css  |  24 +++
 packages/dashboard/app/components/CustomProvidersSection.tsx  |  58 +++++-
 packages/dashboard/src/routes/register-custom-provider-routes.ts |  16 ++
 packages/engine/src/__tests__/pi-create-fn-agent.test.ts      |  71 +++++++
 packages/engine/src/__tests__/provider-registration.test.ts   | 204 ++++++++++++++++++++-
 packages/engine/src/custom-provider-registry.ts               |  71 +++++--
 packages/engine/src/pi.ts                                     |  27 ++-
 12 files changed, 473 insertions(+), 35 deletions(-)

Fusion-Task-Id: FN-7689

Fusion-Task-Lineage: b4f88f32-50da-4651-a546-432a95a1ab1c

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 13:09:10 -07:00
gsxdsm
64d4bb753a FN-7690: fix custom-provider anthropic-compatible apiType resolution
Reconciles a naming drift where resolveApiType() mapped anthropic-compatible custom providers to an unregistered pi-ai api key, causing streaming failures.

- resolveApiType() now maps anthropic-compatible to "anthropic-messages" (was "anthropic"), matching pi.ts's resolveCustomProviderApiType and the built-in Anthropic provider config
- Added FNXC:CustomProviders comment documenting why anthropic-messages is the only key pi-ai's ModelRegistry actually registers
- Added/updated regression tests in custom-provider-registry.test.ts and provider-registration.test.ts
- Added changeset (patch) documenting the fix

Files changed:
 .changeset/fn-7690-apitype-resolver-reconcile.md    |  7 +++++++
 packages/cli/src/commands/__tests__/custom-provider-registry.test.ts | 21 ++++++++++++++++++---
 packages/engine/src/__tests__/provider-registration.test.ts | 20 ++++++++++++++++++++
 packages/engine/src/custom-provider-registry.ts     | 15 ++++++++++++++-
 4 files changed, 59 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7690

Fusion-Task-Lineage: 461c340f-bc29-44a2-b8ec-19f0b03224d7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 13:06:37 -07:00
gsxdsm
6a81871781 chore: refresh weekly test-velocity baseline (2026-W28) — confirm gate regression resolved
Gate wall-time back down to 8.8s (was 36.3s this morning, pre-FN-7667/7669 fix landing). Cycle 2026-W28 re-measured post-fix.
2026-07-08 11:19:47 -07:00
gsxdsm
07507f5264 FN-7688: add slow login-shell profile latency hint and docs
Investigated whether --login in TerminalService first-prompt latency is a meaningful contributor and added a one-time diagnostic hint plus documentation of findings.

- Add SLOW_LOGIN_PROFILE_HINT_MS (2000ms) threshold and one-time, non-blocking console.info hint in createSession()'s PTY onData handler when a login shell is slow to produce first output
- Track spawnStartedAt and loginProfileHintLogged per session, and whether the succeeding spawn attempt used --login, without altering spawn args, timeouts, or the retry-without-login fallback
- Add regression tests covering the slow-login-profile hint behavior in terminal-service.test.ts
- Document the investigation and findings in docs/solutions/developer-experience/login-shell-profile-latency.md and link it from docs/dashboard-guide.md
- Add a patch changeset for @runfusion/fusion describing the new server-log hint

Files changed:
 .changeset/fn-7688-login-shell-profile-latency.md  |   7 ++
 docs/dashboard-guide.md                            |  17 +++
 .../login-shell-profile-latency.md                 |  79 ++++++++++++++
 .../src/__tests__/terminal-service.test.ts         | 121 +++++++++++++++++++++
 packages/dashboard/src/terminal-service.ts         |  57 ++++++++++
 5 files changed, 281 insertions(+)

Fusion-Task-Id: FN-7688

Fusion-Task-Lineage: 08d5dd47-ea9f-4973-9f0e-a8d5fdeae111

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 10:06:42 -07:00
gsxdsm
83e77431e2 FN-7684: move terminal shortcuts/zoom controls into footer on tablet widths
On tablet-width viewports (769-1024px) the terminal header was overcrowded, so shortcut/zoom/preference controls now move into the bottom status-bar footer instead, mirroring the existing FN-7560 mobile layout while true desktop keeps the header controls.

- Add an isTabletTerminal detection flag (769-1024px, non-mobile) alongside the existing mobile flag
- Render the shared terminalActionControls fragment in the .terminal-status-bar footer for tablet widths, keeping desktop pin/pop-out toggles available there too
- True desktop (>1024px) continues to render font size / clear / shortcuts / preferences controls in the header
- Update TerminalModal.css with footer layout styles for the tablet action controls
- Update TerminalModal tests to cover the new tablet breakpoint rendering
- Update docs/dashboard-guide.md to describe the tablet footer control location
- Add a changeset (@runfusion/fusion: patch) documenting the fix

Files changed:
 .changeset/fn-7684-tablet-terminal-footer.md       |   7 ++
 docs/dashboard-guide.md                            |   6 +-
 .../dashboard/app/components/TerminalModal.css     |  34 ++++++
 .../dashboard/app/components/TerminalModal.tsx     | 103 ++++++++++++-----
 .../components/__tests__/TerminalModal.test.tsx    | 126 ++++++++++++++++++---
 5 files changed, 231 insertions(+), 45 deletions(-)

Fusion-Task-Id: FN-7684

Fusion-Task-Lineage: 627c40af-a152-451a-a045-65ad8babea9e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:56:32 -07:00
gsxdsm
a832b7979f FN-7686: skip redundant session-list round trip on fresh terminal load
Speed up initial terminal load by short-circuiting the no-op server session list call when there are no persisted local tabs to validate.

- useTerminalSessions: when readTabsFromStorage returns zero tabs, skip the listTerminalSessions HTTP call entirely and mark bootstrap ready immediately, unblocking auto-create/WebSocket connect instead of serializing behind a provably-discarded round trip
- Reload-with-persisted-tabs path is unchanged and still awaits the list call since its result is decision-relevant there
- Add regression tests covering the fresh-load fast path and the persisted-tabs path
- Add changeset (patch) and a docs/solutions write-up of the bootstrap-list-serialized-before-auto-create issue

Files changed:
 .changeset/fn-7686-slow-terminal-initial-load.md   |  7 ++
 ...bootstrap-list-serialized-before-auto-create.md | 87 ++++++++++++++++++++++
 .../hooks/__tests__/useTerminalSessions.test.ts    | 73 ++++++++++++++++++
 .../dashboard/app/hooks/useTerminalSessions.ts     | 23 +++++-
 4 files changed, 189 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7686

Fusion-Task-Lineage: 9c708329-6362-4c2e-967f-aea12849c47c

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:53:39 -07:00
gsxdsm
fd541bbc04 FN-7687: pin mobile header to nowrap so fold/unfold refold cannot wrap it
Fixes the mobile top header wrapping onto a second line after a foldable phone is unfolded then refolded (a live resize, not a reload).

- .header now explicitly sets flex-wrap: nowrap instead of relying on the flex default
- .header-left gets flex: 1 1 auto; min-width: 0 promoted from the mobile-only media query to the base rule, so it shrinks/truncates during the resize before the width media query re-settles
- .header-actions gets flex: 0 0 auto; min-width: 0 so the action icon cluster stays at intrinsic size and is never squeezed off-row
- Added Header.test.tsx coverage asserting the nowrap/shrink contract across populated and empty header states, on mobile/tablet/desktop
- Added useViewportMode.test.ts regression reproducing a fold->unfold->refold visualViewport resize cycle, confirming mode resolves back to mobile
- Added changeset for @runfusion/fusion (patch/fix)

Files changed:
 .changeset/fn-7687-mobile-header-single-line-refold.md    |  7 ++
 packages/dashboard/app/components/Header.css              | 14 ++++
 packages/dashboard/app/components/__tests__/Header.test.tsx | 78 ++++++++++++++++++++++
 packages/dashboard/app/hooks/__tests__/useViewportMode.test.ts | 59 ++++++++++++++++
 4 files changed, 158 insertions(+)

Fusion-Task-Id: FN-7687

Fusion-Task-Lineage: 2b88a26e-d380-458c-b602-b6496e39311d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:47:19 -07:00
gsxdsm
9a5a8d2b5f FN-7683: fix Quick Add mobile Save button height overshoot with fixed action-row box height
Upgrades Quick Add action-row height parity from a bare min-height floor to a true fixed box height, then adds a mobile-only Save correction per operator feedback so Save matches its siblings at the <=768px breakpoint without touching desktop/tablet sizing.

- Pair min-height with an equal max-height (plus tokenized line-height and centered alignment) on `.quick-entry-actions .btn, .quick-entry-actions .wf-optional-steps-dropdown-trigger` at both the desktop base rule and the <=768px touch-target media block (FN-5751: never a breakpoint-only fix)
- Add a mobile-only override scoped to `[data-testid="quick-entry-save"]` inside the <=768px media query (zero vertical padding, line-height:1) so Save's text+icon content fits the same fixed box as its siblings, leaving desktop/tablet Save sizing untouched
- Add regression coverage asserting the fixed min-height==max-height contract at both breakpoints, that shared .btn-sm/.btn-icon/.dep-trigger rules are untouched, and that the Save-only override exists only inside the mobile media query
- Add a patch changeset documenting the fix and follow-up for @runfusion/fusion

Files changed:
 .changeset/FN-7683-quick-add-height-parity.md      |   7 +
 .../quick-entry-workflow-trigger-height.test.tsx   | 181 ++++++++++++++++++++-
 .../dashboard/app/components/QuickEntryBox.css     |  53 ++++++
 3 files changed, 234 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-7683

Fusion-Task-Lineage: d3df638a-812e-4fc2-aada-cfee58d29f2b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:43:19 -07:00
gsxdsm
2bea332ff2 FN-7685: make Planner Chat send button icon-only to match regular chat
Summary: The Planner Chat idle send button now renders icon-only, dropping its visible "Send" text span to match TaskChatTab's regular chat send button, while keeping the accessible name "Send" via aria-label.

- Removed `showSendText` prop from the planner chat send/stop button so the idle send button no longer shows a visible text span.
- Updated the CSS FNXC comment documenting that the send-text-hiding rule is now solely load-bearing for the streaming Stop button's icon-visibility contract, not the idle Send button.
- Updated the corresponding test to assert the send button has no visible text span (icon-only) while still asserting the accessible name resolves to "Send".

Files changed:
 packages/dashboard/app/components/TaskPlannerChatTab.css           | 7 +++++++
 packages/dashboard/app/components/TaskPlannerChatTab.tsx           | 5 ++++-
 .../dashboard/app/components/__tests__/TaskPlannerChatTab.test.tsx | 4 +++-
 3 files changed, 14 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7685

Fusion-Task-Lineage: 5ec896c5-7ca3-415b-bd5f-99a49f6a21ec

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:40:59 -07:00
gsxdsm
0f13079f54 FN-7682: replace raw px padding with spacing tokens in QuickEntryBox workflow trigger
Narrative: swaps a hardcoded px padding value for token-based spacing on the quick-entry workflow-selector trigger so it complies with the tokenized-CSS lint rule, while preserving visual sizing parity.

- Replace `padding: 4px 10px` with `padding: var(--space-sm) var(--space-md)` on `.quick-entry-workflow-trigger`.
- Add FNXC:QuickAddWorkflow comment explaining the token substitution and confirming height parity is governed by the existing FN-7680 min-height normalization, not this padding.

Files changed:
 packages/dashboard/app/components/QuickEntryBox.css | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7682

Fusion-Task-Lineage: e2213e7d-5c38-460b-9727-57199d86b153

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:27:38 -07:00
gsxdsm
5c0ed62001 FN-7681: Fix undefined --space-2xs token references in dashboard component CSS
Replaces undefined --space-2xs CSS custom property references with the smallest defined spacing token, --space-xs, across several dashboard component stylesheets, and hardens the regression test that guards against reintroducing --space-2xs by scanning components recursively.

- Replace var(--space-2xs) with var(--space-xs) in NewTaskModal.css, QuickEntryBox.css, TaskReviewTab.css, and McpServersCard.css
- Update QuickEntryBox.test.tsx assertion to expect --space-xs instead of --space-2xs
- Make space-token-defined.test.ts recursively walk components directory (previously only scanned the top level, missing nested files like settings/sections/McpServersCard.css)
- Add FNXC:DashboardTokens comments documenting the --space-2xs is-intentionally-undefined decision (FN-5934) and the recursive-scan fix rationale (FN-7681)

Files changed:
 .../app/__tests__/space-token-defined.test.ts      | 33 ++++++++++++++++++----
 packages/dashboard/app/components/NewTaskModal.css |  2 +-
 .../dashboard/app/components/QuickEntryBox.css     | 18 ++++++++----
 .../dashboard/app/components/TaskReviewTab.css     |  2 +-
 .../components/__tests__/QuickEntryBox.test.tsx    |  2 +-
 .../settings/sections/McpServersCard.css           |  4 +--
 6 files changed, 44 insertions(+), 17 deletions(-)

Fusion-Task-Id: FN-7681

Fusion-Task-Lineage: 59850d5c-09cc-4178-ac45-9792fa9aca59

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 09:19:42 -07:00
gsxdsm
a8c018f7d4 fix: prevent false "OAuth token expired" push on startup
Start OAuthRefreshScheduler before the refresh-blind OAuthExpiryMonitor so a
stale-but-refreshable access token is renewed before the monitor's first
awaited check() reads `expires`. Previously the monitor fired a false
"OAuth token expired" ntfy push on startup, moments before the refresher
silently renewed the token. Ordering locked by an invocationCallOrder
assertion in project-engine.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 09:14:14 -07:00
gsxdsm
01be51bf35 fix: require typed operator authorization for real releases
Remove the self-grantable FUSION_RELEASE_AUTHORIZED env signal and replace
it with an interactive prompt: a real release now requires a live human to
type "authorized" at a TTY. Releases can no longer run non-interactively
(no TTY is blocked outright), and --yes does not bypass the typed phrase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 09:00:47 -07:00
gsxdsm
13570e86aa FN-7680: fix Quick Add action row button height parity
Normalizes Quick Add action-row button heights (Save, Attach, Fast, workflow trigger) at desktop and mobile widths so all controls share one box height regardless of icon-only vs text content or .dep-trigger padding.

- Add scoped min-height rule for .quick-entry-actions .btn and .wf-optional-steps-dropdown-trigger in QuickEntryBox.css (desktop base rule, mirrors existing mobile touch-target block)
- Add regression test covering desktop + mobile action-row height parity across button variants
- Add changeset (patch) documenting the fix

Files changed:
 .changeset/FN-7680-quick-add-height-parity.md      |   7 +
 .../quick-entry-action-row-height-parity.test.tsx  | 256 +++++++++++++++++++++
 .../dashboard/app/components/QuickEntryBox.css     |  31 +++
 3 files changed, 294 insertions(+)

Fusion-Task-Id: FN-7680

Fusion-Task-Lineage: 73024883-08f3-41d0-a601-48e223eea57f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 08:59:06 -07:00
gsxdsm
297c7444f5 FN-7676: hide task-card steps breakdown while in Planning column
Task cards previously could show the progress/steps breakdown while still in the Planning (triage) column when a review gate was active; this changes it to only show once a task leaves Planning, matching ListView's behavior.

- TaskCard.showProgressSection now only shows for `in-progress`/`executing` tasks, dropping the special-case `triage` + active-progress-count branch
- Updated FNXC:TaskCardWorkflowProgress comment to document the FN-7676 requirement
- Updated TaskCard tests to cover the new triage-column behavior
- Added changeset for the patch

Files changed:
 .changeset/fn-7676-planning-steps-breakdown.md     |  7 +++
 packages/dashboard/app/components/TaskCard.tsx     |  7 ++-
 .../app/components/__tests__/TaskCard.test.tsx     | 50 ++++++++++++++++++----
 3 files changed, 51 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-7676

Fusion-Task-Lineage: 85fcf8f9-1010-4b68-9323-9d8ce03aa66f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 08:20:29 -07:00
gsxdsm
8ee8f15dc6 FN-7675: add agent runtime self-awareness to system prompts
Agents were composing plans (e.g. reboot/wait-and-retry loops) that assumed they could keep acting even after the Fusion platform itself shut down, since prompts never told them they run inside Fusion. This adds a shared, docs-grounded self-awareness preamble prepended to chat, heartbeat, and executor base prompts so agents know their own runtime constraints.

- Added FUSION_RUNTIME_SELF_AWARENESS shared preamble in packages/core/src/agent-prompts.ts, exported via packages/core/src/index.ts
- Prepended the preamble to the chat system prompt (packages/dashboard/src/chat.ts)
- Prepended the preamble to the heartbeat session prompt (packages/engine/src/agent-heartbeat.ts)
- Prepended the preamble to the executor base prompt (packages/engine/src/executor.ts)
- Updated docs/agents.md and CONCEPTS.md to document the new self-awareness/capability-grounding behavior
- Added regression tests across core, dashboard, and engine covering the new prompt content
- Added changeset for @runfusion/fusion (minor, fix category)

Files changed:
 .changeset/fn-7675-agent-runtime-self-awareness.md |  7 ++++
 CONCEPTS.md                                        |  4 +-
 docs/agents.md                                     | 17 ++++++++
 packages/core/src/__tests__/agent-prompts.test.ts  | 41 ++++++++++++++++++++
 packages/core/src/agent-prompts.ts                 | 32 ++++++++++++++-
 packages/core/src/index.ts                         |  1 +
 packages/dashboard/src/__tests__/chat-system-prompt.test.ts | 17 ++++++++
 packages/dashboard/src/chat.ts                     |  6 ++-
 packages/engine/src/__tests__/executor-prompt.test.ts       | 45 ++++++++++++++++++++++
 packages/engine/src/__tests__/heartbeat-session-prompt.test.ts | 35 +++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             | 10 +++--
 packages/engine/src/executor.ts                    |  7 +++-
 12 files changed, 213 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-7675

Fusion-Task-Lineage: 126d04a6-2c68-4347-9789-591b274277bf

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 08:16:30 -07:00
gsxdsm
a4fce60b18 FN-7677: fix quick-add workflow dropdown button height mismatch
Aligns the quick-entry workflow-trigger dropdown button height with the neighboring Save/Fast/Subtask buttons and adds a regression test plus changeset.

- QuickEntryBox.css: .quick-entry-workflow-trigger re-asserts .btn-sm's padding: 4px 10px locally so the shared global .dep-trigger padding: 3px 8px no longer shortens it by ~2px
- Add regression test packages/dashboard/app/__tests__/quick-entry-workflow-trigger-height.test.tsx covering the height parity
- Add changeset fn-7677-quick-add-workflow-trigger-height.md (patch, fix)
- Minor doc updates in cli skill fusion references (extension-tools.md, fusion-capabilities.md)

Files changed:
 .changeset/fn-7677-quick-add-workflow-trigger-height.md            |   7 +
 packages/cli/skill/fusion/references/extension-tools.md            |   6 +-
 packages/cli/skill/fusion/references/fusion-capabilities.md        |   4 +-
 packages/dashboard/app/__tests__/quick-entry-workflow-trigger-height.test.tsx | 248 +++++++++++++++++++++
 packages/dashboard/app/components/QuickEntryBox.css                |   5 +
 5 files changed, 266 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7677

Fusion-Task-Lineage: c8cc1b0d-f9c1-4691-a5b4-14ec8ac7e98e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 08:09:53 -07:00
gsxdsm
38d883d83e fix: surface model-lane drift when a workflow's default model changes (#1958)
## Problem

A task's model fields (`modelProvider`/`modelId` and the
planning/validator
equivalents) are snapshotted once at task-creation time from the
workflow's
model-lane default in `workflow_settings`. Nothing re-syncs, flags, or
surfaces drift when that default is later changed.

Concretely: the `builtin:coding` workflow's execution default was
`claude-sonnet-4-6` until it was corrected on 2026-07-05. Every task
created
before that correction stayed permanently, invisibly pinned to the stale
model id — 52 tasks were found silently stuck on it.

## Fix

- `TaskStore.getModelLaneDrift(workflowId, before, after)`
(`packages/core/src/store.ts`):
read-only diff over the three model lanes
(execution/planning/validator).
  For any lane whose provider+modelId actually changed, it lists the
non-terminal (`column` not `archived`/`done`, not soft-deleted) tasks on
  that workflow still pinned to the old value. Never mutates `tasks`.
- Wired into `PATCH /workflows/:id/setting-values`
(`packages/dashboard/src/routes/register-workflow-routes.ts`): captures
a
  `before` snapshot, runs the existing `updateWorkflowSettingValues`
unchanged, then attaches an optional `modelDrift` field to the response
when a lane change orphans existing tasks. Backward compatible — the
field
  is only present when non-empty.
- Operators can act on the surfaced drift via the existing
`POST /tasks/batch-update-models` endpoint; this change intentionally
does
  not auto-rewrite any task (avoids touching tasks mid-execution).

## Testing

- New tests in `packages/core/src/__tests__/workflow-settings.test.ts`
(`TaskStore.getModelLaneDrift`): verifies a task pinned to a changed
lane's
old value is surfaced, a task already on the new value and a
`done`-column
task are excluded, and an unrelated/unchanged lane produces no drift
entry.
- `packages/core`: `npx vitest run
src/__tests__/workflow-settings.test.ts` — 24/24 pass.
- `npx tsc --noEmit` clean in both `packages/core` and
`packages/dashboard`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Workflow setting updates can now return a lane-based model drift
summary (execution, planning, validator) showing task IDs still pinned
to the previous model configuration.
* The PATCH workflow setting response conditionally includes
`modelDrift` when impacted tasks are found.
* **Bug Fixes**
* Drift detection now compares a consistent “before” snapshot with the
updated values to avoid stale pairing.
* “No workflow selection” tasks are handled correctly based on the
default-workflow behavior.
* **Tests**
* Added coverage for lane drift across model changes and null-selection
inclusion rules.
* **Documentation**
  * Added a release note entry for the change.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-08 07:33:26 -07:00
gsxdsm
176222918a fix(dashboard): address model-lane drift review feedback
- getModelLaneDrift now takes an explicit includeNullSelection option; the
  setting-values route passes it when patching the project default workflow so
  no-workflow-selection tasks (which resolve through the default) are counted
  instead of silently dropped (Greptile P1).
- Add updateWorkflowSettingValuesWithPrevious so the drift baseline is captured
  inside the settings write transaction, removing the stale-read race against a
  concurrent patch of the same row (Greptile P2).
- Broaden getModelLaneDrift tests to cover planning and validator lanes and the
  null-selection/default-workflow case (FN-5893 invariant across surfaces).
- Add changeset.

CodeRabbit's effective-values suggestion is intentionally skipped: model-lane
declarations carry no declaration-level default (KTD-7), so effective == raw for
these keys and comparing effective values is a no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 07:26:26 -07:00
gsxdsm
be94f630ea Surface runtime-resolution fallback in dashboard; thread real FallbackReason (#1957)
Closes/relates to Runfusion/Fusion#1956.

## Summary

Surfaces silent runtime-resolution fallback in the dashboard, and
threads the real `FallbackReason` ("not_found" vs "factory_error")
through instead of hardcoding `"not_found"` for every fallback.

## Changes

- `packages/engine/src/runtime-resolution.ts`: `resolvePluginRuntime()`
now returns a tagged miss result (`{ ok: false, reason }`)
distinguishing "not found" from "factory/instantiation error" instead of
collapsing both to `null`. `resolveRuntime()` threads the real reason
through to `logRuntimeFallback(...)` and returns it via
`ResolvedRuntime.fallbackReason`.
- `packages/engine/src/agent-session-helpers.ts`:
`createResolvedAgentSession()` includes `fallbackReason` in the
`session:runtime-resolved` audit event metadata when present.
- `packages/dashboard/src/routes/register-task-workflow-routes.ts`: new
`GET /api/tasks/:id/runtime-fallback` endpoint, returning the most
recent `session:runtime-resolved` event normalized for UI consumption
(`wasConfigured`, `runtimeHint`, `reason`, `showFallbackBadge`).
- `packages/dashboard/app/hooks/useRuntimeFallbackStatus.ts` (new):
polls the endpoint, dedupes toast firing per audit-event-id.
- `packages/dashboard/app/components/RuntimeFallbackBadge.tsx` (new):
renders the badge + fires the toast; wired into `TaskCard.tsx`,
`ActiveAgentsPanel.tsx`, and `AgentsView.tsx` (board and list variants).

## Test plan

- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/runtime-resolution.test.ts` — 24/24 pass (21 pre-existing
+ 3 new, none weakened)
- `pnpm --filter @fusion/dashboard exec vitest run
src/routes/__tests__/register-task-workflow-routes.runtime-fallback.test.ts`
— 5/5 pass
(empty/configured-ok/fallback-with-hint/fallback-blank-hint/stale-superseded
states)
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/RuntimeFallbackBadge.test.tsx` — 8/8 pass (all
data states + mobile breakpoint + toast-fires-once)
- `pnpm --filter @fusion/dashboard run typecheck` and `pnpm --filter
@fusion/engine run typecheck` — both clean


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added runtime-fallback warning badges across task and agent views
(including board and “working on” sections) with automatic toast
notifications.
* Introduced a new backend API to surface the latest runtime-fallback
state for a task.
* Added runtime-fallback status polling and UI messaging to reflect the
most recent state.

* **Bug Fixes**
* Prevented repeated toasts by deduplicating notifications across
polling updates.
* Improved fallback reporting so the UI reflects the latest
runtime-resolved audit event.
* Enhanced diagnostics by distinguishing fallback reasons (e.g., missing
runtime vs factory failure) for clearer user guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-08 07:24:51 -07:00
gsxdsm
ec2aeed2bf fix(dashboard): drop unknown-rule eslint-disable in RuntimeFallbackBadge
The repo eslint config omits react-hooks/exhaustive-deps, so a disable
directive for it is itself a lint error (rule-not-found). Replace with a
plain comment documenting the intentional dep omission.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 07:16:53 -07:00
gsxdsm
fd11280ce3 FN-7673: document closure of single combined-entry engine-graph gate bundle experiment
Narrative: FN-7673 re-attempted the engine-core gate bundle lever with a single combined-entry engine-graph design (all 14 mock-safe roots redirected through a resolveId plugin to one synthetic packages/engine/.gate-bundle/engine.mjs) after FN-7670's 14-separate-root attempt was inconclusive. This update records the negative A/B result and closes the lever.

- Documented that the combined-entry design achieved its structural goal (149 first-party inputs -> 1 output file) and full 335/335 coverage parity
- Recorded a true interleaved A/B (5 warm + 1 cold pair) showing the combined-entry bundle is consistently slower than the @fusion/core-only baseline (warm median +29.1%, import-phase aggregate +74.0%)
- Captured the working theory: funnelling 14 relative-import sites through a resolveId-plugin redirect to one large synthetic export-* file adds more transform/resolution overhead than it saves, unlike @fusion/core's plain resolve.alias
- Noted the experiment was NOT landed; wiring (engine-graph scans, combined-entry builder, resolveId plugin) was fully reverted
- Marked this lever (bundling the @fusion/engine relative-import graph for the engine-core gate, in either 14-file or single-combined-entry shape) as CLOSED absent new evidence

Files changed:
 packages/engine/vitest.config.ts | 32 +++++++++++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7673

Fusion-Task-Lineage: 46951e5f-e7dc-4f7c-9601-0cfa0b082d70

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 06:35:22 -07:00
gsxdsm
b1b4735111 FN-7671: remove stale merger-post-merge entry from engine-core gate include
Removes a dead test-file reference from the engine-core vitest gate include list, with a code comment documenting why.

- Remove the nonexistent `src/__tests__/merger-post-merge.test.ts` entry from packages/engine/vitest.config.ts's engine-core include list (retired by FN-7039; graph is now sole post-merge owner)
- Add FNXC comment noting the entry matched zero files and that graph post-merge coverage lives in workflow-graph-post-merge.test.ts (engine-default)

Files changed:
 packages/engine/vitest.config.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7671

Fusion-Task-Lineage: 73447412-7b8a-4578-a2b8-07f83e381548

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 05:56:12 -07:00
gsxdsm
aa534c19af FN-7672: recover durable agents stuck in error state despite active manager
Root-causes 4 correlated CTO-report agent failures where durable non-ephemeral agents got stuck in `error` state indefinitely because the heartbeat scheduler stops ticking error-state agents entirely, and self-healing's recovery sweep previously only considered them when their manager row was missing.

- SelfHealingManager: scope the `managerMissing` gate to the "running" orphan-detection path only, so "error"-state durable agents with a present/active manager now fall through to the existing transient/operator-actionable/active-execution/cooldown/retry-budget recovery guards instead of being skipped outright
- Add FNXC:AgentHeartbeat comment documenting the FN-7672 incident and rationale for the scoping change
- Extend self-healing.test.ts with coverage for manager-present durable agents in error state
- Add changeset (patch) describing the fix for release notes
- Update docs/agents.md accordingly

Files changed:
 .changeset/fn-7672-durable-agent-recovery.md       |   7 ++
 docs/agents.md                                     |   2 +
 packages/engine/src/__tests__/self-healing.test.ts | 129 ++++++++++++++++++++-
 packages/engine/src/self-healing.ts                |  24 +++-
 4 files changed, 160 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7672

Fusion-Task-Lineage: 6676dc9e-66e7-4f70-804a-cccf77e8d337

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 05:38:01 -07:00
gsxdsm
b8fa2a6652 FN-7670: document negative result of extending engine-core pre-bundle to @fusion/engine relative-import graph
Prototyped extending the @fusion/core pre-bundle alias lever to @fusion/engine's relative-import production graph reached by the 18 gate files, but an A/B showed no clear win over the @fusion/core-only bundle, so the change was not landed and only the rationale is recorded.

- Added an FNXC:EngineTests comment block in packages/engine/vitest.config.ts documenting the FN-7670 prototype (171 first-party files → 35 output files via esbuild multi-entry splitting)
- Recorded the negative A/B result: byte-size growth of 14 separate large root bundles offset per-file-dispatch savings, with no clear win beyond host run-to-run noise
- Left the vitest alias wiring unchanged at the @fusion/core-only bundle state, pointing future attempts to FN-7670's task docs for full analysis and to consider a single combined engine-graph entry instead of 14 separate root entries

Files changed:
 packages/engine/vitest.config.ts | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

Fusion-Task-Id: FN-7670

Fusion-Task-Lineage: efd27f94-a6c4-49c7-a78e-50213fd42a24

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 05:20:47 -07:00
gsxdsm
ad9a72176c FN-7669: pre-bundle @fusion/core gate-safe barrel to cut engine-core gate import-phase cost
Prototype and land a rebuilt-every-run esbuild bundle of the @fusion/core gate-safe barrel closure, collapsing the engine-core gate's per-fork Vite SSR import-phase cost (18 forks x ~430-file closure re-resolved from scratch) into a single file load per fork.

- Add scripts/build-engine-core-gate-bundle.mjs: esbuild-bundles packages/core/src/index.gate.ts (220 first-party files, packages:"external" so third-party/node: imports stay external, treeShaking:false to preserve side effects) into packages/core/.gate-bundle/core.mjs + core.meta.json
- Wire the builder into packages/engine/vitest.config.ts's engine-core project globalSetup (alongside the existing vitest-teardown hook) so the bundle is rebuilt fresh before every gate invocation, and repoint the @fusion/core resolve.alias at the bundled output instead of index.gate.ts source
- Place the bundle output at packages/core/.gate-bundle/ as a sibling of packages/core/node_modules/ (not nested inside it) to avoid Vite SSR's external-dep heuristic, which would otherwise silently defeat vi.mock interception for imports nested in the bundle
- Gitignore packages/core/.gate-bundle/ and add a matching ESLint ignore entry so the generated bundle text is never linted or committed
- Add esbuild ^0.25.12 as a root devDependency (pnpm-lock.yaml updated accordingly)
- Document the pre-bundling rationale, placement constraints, and measured A/B wall-time results in docs/testing.md

Verified: pnpm test:gate passes (335/335 engine-core tests, 63/63 CLI ci-shape tests), engine package typecheck clean, eslint clean on touched files.

Files changed:
 .gitignore                                |  11 ++
 docs/testing.md                           |   3 +
 eslint.config.mjs                         |  10 ++
 package.json                              |   1 +
 packages/engine/vitest.config.ts          |  50 ++++++++-
 pnpm-lock.yaml                            |   3 +
 scripts/build-engine-core-gate-bundle.mjs | 174 ++++++++++++++++++++++++++++++
 7 files changed, 247 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7669

Fusion-Task-Lineage: 62b06b2a-4ac6-45ae-ac79-9771132bc303

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 04:24:30 -07:00
gsxdsm
6902077972 FN-7667: add gate-scoped @fusion/core barrel to decouple engine-core gate from full barrel growth
Introduces a project-scoped @fusion/core barrel used only by the engine-core
gate project, so new feature modules added to the full barrel don't silently
inflate the gate's transform/import cost.

- Add packages/core/src/index.gate.ts, a copy of the full @fusion/core barrel
  minus export statements for modules added since the last re-audit baseline
  (i.e. it still re-exports everything the full barrel does except newly
  added, gate-irrelevant feature modules).
- Update packages/engine/vitest.config.ts to add a project-scoped
  resolve.alias mapping @fusion/core -> packages/core/src/index.gate.ts for
  the engine-core project only; engine-default/engine-reliability/engine-slow
  and @fusion/engine continue to resolve the full barrel.
- Document the gate-safe barrel and its audit procedure in docs/testing.md.

Files changed:
 docs/testing.md                  |    3 +
 packages/core/src/index.gate.ts  | 2102 ++++++++++++++++++++++++++++++++++++++
 packages/engine/vitest.config.ts |   17 +
 3 files changed, 2122 insertions(+)

Fusion-Task-Id: FN-7667

Fusion-Task-Lineage: 054ec89a-d973-44dd-b9ac-ad266f553f01

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 03:28:37 -07:00
gsxdsm
845fad2aa4 FN-7666: record weekly test-velocity regression finding (gate 7.7s -> 36.3s)
Narrative: update the 2026-W28 test-velocity baseline report with the newly measured merge-gate/changed-test wall-time regression and document root-cause attribution.
- Bump baseline cycle to 2026-W28 (captured 2026-07-08T09:30:50.606Z)
- Update headline metrics table: gate 7.7s -> 36.3s (+28.6s), boot smoke 17.4s -> 25.3s (+7.9s), pnpm test 9.3s -> 43.4s (+34.0s), quarantine count unchanged at 1
- Add FN-7666 finding note attributing the regression to ~4,305 new re-exported source lines across 14 modules pulled through the @fusion/core / @fusion/engine barrels, inflating per-fork import/transform cost across the 18 curated engine-core gate files; confirms it is a genuine regression (not cache/env noise) via cold-vs-warm reruns; files follow-up FN-7667 for the barrel-decoupling fix
- Update weekly history table (previous/latest/delta rows) and the #leads post text block
- Append new row capturing this week's measurements to scripts/test-velocity-history.json

Files changed:
 docs/test-velocity-baseline.md     |  23 ++++----
 scripts/test-velocity-history.json | 112 +++++++++++++++++++++++++++++++++++++
 2 files changed, 123 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-7666

Fusion-Task-Lineage: 73ec627c-2fc6-4289-8e6e-3e839ed5c675

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 02:45:47 -07:00
gsxdsm
03161adfb9 FN-7659: paginate and sort the Archived column newest-first
Adds server-side pagination for the Archived task column, sorted by most-recently-archived first, with a Show more control on the dashboard.

- Add ArchiveDatabase.listPage and TaskStore.listArchivedTasks for a bounded SQL LIMIT/OFFSET read ordered by archivedAt DESC
- Add GET /tasks/archived route for paged archive fetches, leaving the legacy merged listTasks({includeArchived}) path unchanged
- Wire useTasks.loadArchivedTasks to fetch page 1 on first Archived-column expand and loadMoreArchivedTasks for subsequent pages
- Add a "Show more" affordance in Column.tsx/Board.tsx/MainContent.tsx to trigger loading additional archived pages
- Extend taskSorting.ts to keep archived task ordering stable with the new paged data
- Add core and dashboard tests covering archive pagination and store/route behavior
- Add changeset for @runfusion/fusion (minor) and update docs/storage.md and docs/dashboard-guide.md

Files changed:
 .changeset/FN-7659-archived-pagination.md          |   7 +
 docs/dashboard-guide.md                            |   4 +-
 docs/storage.md                                    |   7 +
 packages/core/src/__tests__/archive-db-pagination.test.ts    |  94 ++++++++
 packages/core/src/__tests__/store-archive-search.test.ts     |  63 ++++++
 packages/core/src/archive-db.ts                    |  18 ++
 packages/core/src/store.ts                         |  32 +++
 packages/dashboard/app/App.tsx                     |   5 +-
 packages/dashboard/app/api/legacy.ts               |  19 ++
 packages/dashboard/app/components/Board.tsx        |  19 +-
 packages/dashboard/app/components/Column.tsx       |  48 ++++-
 packages/dashboard/app/components/__tests__/Column.test.tsx       |  41 ++++
 packages/dashboard/app/components/__tests__/taskSorting.test.ts   |  27 +++
 packages/dashboard/app/components/dashboard/MainContent.tsx       |   9 +
 packages/dashboard/app/components/dashboard/types.ts    |   6 +
 packages/dashboard/app/components/taskSorting.ts   |  16 ++
 packages/dashboard/app/hooks/__tests__/useTasks.test.ts | 236 ++++++++++++++++++++-
 packages/dashboard/app/hooks/useTasks.ts           | 173 ++++++++++++++-
 packages/dashboard/app/test/mockApi.ts             |   3 +
 packages/dashboard/src/routes/__tests__/tasks-archived-pagination.test.ts |  94 ++++++++
 packages/dashboard/src/routes/register-task-workflow-routes.ts  |  32 +++
 21 files changed, 930 insertions(+), 23 deletions(-)

Fusion-Task-Id: FN-7659

Fusion-Task-Lineage: 7a5a1f62-277c-4f29-883c-62e75b269bc5

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 01:29:56 -07:00
gsxdsm
7cf70b3c59 FN-7665: rename Command Center SDLC funnel intake stage label to Planning
Rename the Command Center SDLC funnel's intake stage display text from "Triage" to "Planning" to match the renamed board column, updating English locale copy and tests; the underlying aggregator/i18n key names stay "triage".

- SdlcFunnel.tsx: stage label fallback, enteredInRange, and completionRateAria copy changed to Planning/Entered planning/in-range planning entrants; added FNXC comment explaining the key-vs-label distinction
- packages/i18n/locales/en/app.json: synced English strings for the same three keys
- SdlcFunnel.test.tsx: updated assertions to match new Planning copy
- Added changeset (patch) documenting the label rename

Files changed:
 .changeset/fn-7665-funnel-planning-label.md              |  7 +++++++
 .../app/components/command-center/SdlcFunnel.tsx         | 16 ++++++++++++----
 .../command-center/__tests__/SdlcFunnel.test.tsx         |  6 +++---
 packages/i18n/locales/en/app.json                        |  6 +++---
 4 files changed, 25 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-7665

Fusion-Task-Lineage: d8aec609-6b8b-4f36-bb43-61d7c7c421f9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:50:11 -07:00
gsxdsm
d2e38ba920 FN-7663: consolidate /automations and /routines run-stream SSE handlers into a shared factory
Removes duplicated SSE streaming logic between the /automations/:id/run/stream and /routines/:id/run/stream endpoints by extracting a single generic makeRunStreamHandler factory.

- Add makeRunStreamHandler<TStore, TEntity> factory in routes.ts, parameterized by store resolver, entity getter, and not-found message
- Replace the two near-identical inline SSE handlers (connect headers, buffered replay, run subscription, auto-attach, teardown, error handling) with calls to the shared factory
- Import ScopeValue type from ./routes/types.js to type the factory's scope parameter
- Add regression tests in routes-automation.test.ts covering the consolidated handler behavior for both routes

Files changed:
 .../src/__tests__/routes-automation.test.ts        |  35 +++
 packages/dashboard/src/routes.ts                   | 246 +++++++++------------
 2 files changed, 137 insertions(+), 144 deletions(-)

Fusion-Task-Id: FN-7663

Fusion-Task-Lineage: 55ede468-c4ed-4a72-a428-7d19686e8ea5

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:41:58 -07:00
gsxdsm
aae6a6eca2 FN-7662: harden boot-smoke temp dir cleanup against macOS ENOTEMPTY races
Boot-smoke's post-verdict temp-dir cleanup could throw ENOTEMPTY on macOS when async writers (fsevents/Spotlight, the just-killed child) still touched the throwaway HOME/project dirs, turning an already-decided PASS into a pnpm verify:fast failure.

- Add removeTempDir() helper in scripts/boot-smoke.mjs: wraps rmSync with maxRetries/retryDelay and swallows any residual error (never throws) since cleanup always runs after the smoke verdict is decided/printed
- Replace both isolatedHome/isolatedProject rmSync call sites (exit handler and EADDRINUSE retry-port branch) with removeTempDir()
- Guard main() behind an import.meta.url === pathToFileURL(process.argv[1]) check so requiring boot-smoke.mjs from tests doesn't spawn a real server
- Add scripts/__tests__/boot-smoke.test.mjs covering ENOTEMPTY tolerance, always-failing rm, retry/backoff params, and the no-boot-on-import guard

Files changed:
 scripts/__tests__/boot-smoke.test.mjs | 77 +++++++++++++++++++++++++++++++++++
 scripts/boot-smoke.mjs                | 44 +++++++++++++++++---
 2 files changed, 115 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-7662

Fusion-Task-Lineage: 3288d055-4f70-4487-8462-933563214ce5

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:37:07 -07:00
gsxdsm
dd9fa2d3cb FN-7661: expose removeLineageReferences on fn_task_archive/fn_task_delete
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>
2026-07-08 00:33:02 -07:00
gsxdsm
97e46674f4 FN-7660: rename remaining board-column "Triage" labels to "Planning"
Rename remaining "Triage" board-column label references to "Planning" across dashboard editors, locale strings, and docs.

- Fixed BUILTIN_WORKFLOW_COLUMN_LABELS.triage in board-workflows.ts (was still "Triage", clobbering FN-7599's IR rename); column id "triage" stays unchanged
- Updated dashboard components (AgentDetailView, RoutineEditor, ScheduleForm, ScheduleStepsEditor) to use the "Planning" label
- Updated en/app.json locale strings (board.triage, schedule.columnTriage/taskColumnTriage/triageColumn) to "Planning"
- Updated board-workflows tests to assert the new label
- Updated docs/dashboard-guide.md Triage/Planning column references to "Planning", preserving the FN-7653 intake-only gating correction
- Added a patch changeset documenting the label-consistency fix

Files changed:
 .changeset/fn-7660-planning-label-consistency.md               |  7 +++++++
 docs/dashboard-guide.md                                        |  9 +++++----
 packages/dashboard/app/components/AgentDetailView.tsx          |  2 +-
 packages/dashboard/app/components/RoutineEditor.tsx            |  2 +-
 packages/dashboard/app/components/ScheduleForm.tsx             |  2 +-
 packages/dashboard/app/components/ScheduleStepsEditor.tsx      |  2 +-
 .../dashboard/src/routes/__tests__/board-workflows.test.ts     |  7 ++++++-
 packages/dashboard/src/routes/board-workflows.ts               | 10 +++++++++-
 packages/i18n/locales/en/app.json                              |  8 ++++----
 9 files changed, 35 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-7660

Fusion-Task-Lineage: fc6c94d6-9876-4c0e-80b1-a5518903952e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:27:52 -07:00
gsxdsm
f7d9509294 FN-7658: gate same-agent duplicate auto-archiving behind opt-in setting
Duplicate tasks created by the same agent are no longer auto-archived by default; they are flagged for review instead, controlled by a new opt-in project setting.

- Add project setting `autoArchiveDuplicateTasksEnabled` (default false) gating the FN-4892 same-agent duplicate intake path
- Add `flagSameAgentDuplicate` path and `nearDuplicateOf` metadata used when auto-archive is disabled; tombstone-resurrection blocking is unchanged
- Wire the setting through core settings schema/types/store, dashboard SchedulingSection UI, and i18n strings
- Update docs (settings-reference.md, task-management.md) to describe the new default-off behavior
- Add a changeset for the @runfusion/fusion minor release
- Extend duplicate-intake, tombstone-window, store-parent-task-dedup, and reliability-interaction tests to cover both flag states

Files changed:
$(cat /tmp/fn7658_stat.txt)

Fusion-Task-Id: FN-7658

Fusion-Task-Lineage: 7d0d1074-1020-48a8-b96f-186154c2c408

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:23:32 -07:00
gsxdsm
511bcaf56d FN-7657: persist GitHub issue import modal state across close/reopen
Retains the GitHub Import Tasks modal's provider/tab/filter/selection state so returning to Import Tasks doesn't reset the user's in-progress import setup.

- GitHubImportModal now persists provider, active tab, label filter, remote, and issue selection per project via a new modalPersistence hook, restoring them on remount instead of always defaulting.
- Added packages/dashboard/app/hooks/modalPersistence.ts to encapsulate the persisted-state read/write logic backed by projectStorage.
- projectStorage.ts gains the `kb-dashboard-github-import-state` storage key.
- Falls back to the existing default-remote auto-detect behavior when no persisted state exists.
- Added extensive test coverage in GitHubImportModal.test.tsx for the new persistence behavior.
- Updated docs/dashboard-guide.md to describe the retained state.
- Added a patch changeset for @runfusion/fusion.

Files changed:
 .changeset/fn-7657-github-import-state-retained.md |   7 +
 docs/dashboard-guide.md                            |   2 +
 .../dashboard/app/components/GitHubImportModal.tsx | 204 +++++++++++++--
 .../__tests__/GitHubImportModal.test.tsx           | 282 +++++++++++++++++++++
 packages/dashboard/app/hooks/modalPersistence.ts   |  72 ++++++
 packages/dashboard/app/utils/projectStorage.ts     |   1 +
 6 files changed, 547 insertions(+), 21 deletions(-)

Fusion-Task-Id: FN-7657
Fusion-Task-Lineage: c8086340-368c-4efd-a12a-4cddeeb0aa26
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:14:52 -07:00
fusion-merge-train
83a9cf15d0 fix: surface model-lane drift when a workflow's default model changes
Task model fields (execution/planning/validator provider+modelId) are
snapshotted once at task-creation time from the workflow's model-lane
default and never re-synced. Changing a workflow's default (e.g. fixing
a stale model id) silently leaves already-created tasks pinned to the
old value with no visibility — this is exactly how 52 tasks stayed
pinned to a stale claude-sonnet-4-6 default after it was corrected.

Add TaskStore.getModelLaneDrift(workflowId, before, after), a read-only
diff over the three model lanes that lists non-terminal tasks still
pinned to a lane's old value. Wire it into
PATCH /workflows/:id/setting-values so the response includes
`modelDrift` whenever a lane change orphans existing tasks. Operators
can then act via the existing POST /tasks/batch-update-models.
2026-07-08 09:10:49 +02:00
Fusion
0bed997af8 feat: surface runtime-resolution fallback in dashboard, thread real FallbackReason
Fixes silent runtime fallback visibility (dashboard never read wasConfigured
or session:runtime-resolved) and threads the real FallbackReason
(not_found vs factory_error) through resolveRuntime()/logRuntimeFallback
instead of hardcoding "not_found" for every fallback.

- packages/engine/src/runtime-resolution.ts: resolvePluginRuntime() now
  returns a tagged miss result distinguishing not_found from factory_error;
  resolveRuntime() threads the real reason through and returns it as
  ResolvedRuntime.fallbackReason
- packages/engine/src/agent-session-helpers.ts: includes fallbackReason in
  the session:runtime-resolved audit event metadata
- packages/dashboard/src/routes/register-task-workflow-routes.ts: new
  GET /api/tasks/:id/runtime-fallback endpoint
- packages/dashboard/app/hooks/useRuntimeFallbackStatus.ts +
  packages/dashboard/app/components/RuntimeFallbackBadge.tsx: new polling
  hook + badge/toast component wired into TaskCard, ActiveAgentsPanel, and
  AgentsView

Ref: Fusion task FUX-022, investigations/FUX-017-hermes-runtime-fallback.md
recommendation #1
2026-07-08 03:09:29 -04:00
gsxdsm
e29fea38e0 FN-7656: restore chat working indicator when reattaching to an active generation
Fixes chat sessions not showing the working/"Thinking…" indicator when returning to a session whose generation was already in flight but hadn't emitted its first delta yet.

- useChat.ts: selectSession's authoritative fetchChatSession refresh now reattaches whenever refreshedSession.isGenerating===true, instead of also requiring a populated inFlightGeneration snapshot (which is null pre-first-delta)
- Added a guard so the reattach only proceeds if the user hasn't navigated away from the session while the refresh was in flight (activeSessionRef.current?.id === id)
- Calls attachIfGenerating(id, refreshedSession.inFlightGeneration, { silent: true }) when no stream is already attached, reusing existing double-attach guarding
- Added regression tests in useChat.test.ts covering the reattach-on-isGenerating-alone behavior and the stale-session navigation guard
- Added a patch changeset documenting the fix

Files changed:
 .changeset/fn-7656-chat-reattach-working-state.md  |   7 ++
 .../dashboard/app/hooks/__tests__/useChat.test.ts  | 113 +++++++++++++++++++++
 packages/dashboard/app/hooks/useChat.ts            |  22 +++-
 3 files changed, 141 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7656

Fusion-Task-Lineage: c923c16a-391f-4475-aab8-6194bbc675f7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:05:29 -07:00
gsxdsm
ebe9b9fa67 FN-7653: restrict plan auto-approve toggle to the intake/planning column
Fix the Board Auto-approve plan shortcut leaking onto hold (Todo-like) columns; it must render only on the intake/planning column.

- Board.tsx: gate the planAutoApproveEnabled/onTogglePlanAutoApprove prop pair on columnDef.flags.intake only (dropped the || columnDef.flags.hold clause) for both the aggregate and single-workflow column renders
- Column.tsx: update the FNXC comment to document that Board.tsx is the single source of truth for this intake-only gating; Column.tsx just renders whatever prop it receives
- docs/dashboard-guide.md: correct the Board guide to describe the toggle as intake/planning-column-only, not hold columns
- Board.test.tsx: fix the existing intake/hold test expectation to assert the hold column does NOT get the toggle, and add a regression test reproducing the built-in Coding workflow's Todo (hold) column symptom
- add a patch changeset documenting the fix

Files changed:
 .changeset/FN-7653-plan-auto-approve-intake-only.md  |  7 +++++++
 docs/dashboard-guide.md                              |  5 +++--
 packages/dashboard/app/components/Board.tsx          |  6 ++++--
 packages/dashboard/app/components/Column.tsx         |  5 ++++-
 .../app/components/__tests__/Board.test.tsx          | 20 +++++++++++++++++---
 5 files changed, 35 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-7653

Fusion-Task-Lineage: 10c5f01a-b00c-4039-9b83-a8a8965fc56c

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 23:55:50 -07:00
gsxdsm
7c7b22e511 FN-7652: fix live-run status falsely showing Run failed for successful automations
Reconcile the automation live-run panel's terminal status with the authoritative run result so successful runs no longer flash "Run failed".

- ScheduledTasksModal/RoutineCard now reconcile SSE terminal status against the POST/registry result instead of trusting raw stream teardown.
- Gate benign SSE teardown (post-terminal close, reconnect exhaustion) from being surfaced as a failure state.
- Apply the same reconciliation to both /routines/:id/run/stream and /automations/:id/run/stream routes in routes.ts.
- Add regression coverage in RoutineCard, ScheduledTasksModal, and routes-automation tests.
- Add a patch changeset documenting the fix.

Files changed:
 .changeset/fn-7652-automation-live-run-false-failure.md           |  7 ++
 packages/dashboard/app/components/ScheduledTasksModal.tsx         | 66 ++++++++++++++--
 packages/dashboard/app/components/__tests__/RoutineCard.test.tsx  | 37 +++++++++
 packages/dashboard/app/components/__tests__/ScheduledTasksModal.test.tsx | 78 +++++++++++++++++++
 packages/dashboard/src/__tests__/routes-automation.test.ts        | 87 ++++++++++++++++++++++
 packages/dashboard/src/routes.ts                                  | 52 ++++++++++++-
 6 files changed, 319 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-7652
Fusion-Task-Lineage: 61fc89f5-d25d-44de-9699-bc8ad2a0dea6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 23:35:05 -07:00
gsxdsm
dfb084c59e FN-7655: make planner chat stop button icon-only
Removes the visible "Stop generation" text label from the planner chat's stop button while keeping it accessible via aria-label.

- Add showStopText prop to StandardChatActionButton (defaults to showSendText) to independently control Send vs Stop visible text
- Set showStopText={false} in TaskPlannerChatTab so the streaming stop button renders icon-only
- Update TaskPlannerChatTab test to assert no visible text span on the stop button while aria-label is retained
- Add changeset for @runfusion/fusion (patch)

Files changed:
 .changeset/fn-7655-planner-stop-icon-only.md             |  7 +++++++
 .../dashboard/app/components/StandardChatSurface.tsx     | 16 ++++++++++++++--
 packages/dashboard/app/components/TaskPlannerChatTab.tsx |  3 +++
 .../app/components/__tests__/TaskPlannerChatTab.test.tsx |  4 +++-
 4 files changed, 27 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7655
Fusion-Task-Lineage: f68a8bfa-30ba-439e-97d0-28654a614c54
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 23:26:55 -07:00
gsxdsm
efabdd6f04 FN-7651: remove chat "Search in title only" toggle
Simplifies chat search UX by always matching both title and message content, removing the now-unneeded title-only toggle button and state.

- Removed the "Search in title only" toggle button and its CSS from ChatView
- Dropped searchInTitleOnly/setSearchInTitleOnly state and logic from useChat; content-search query params are now always-on
- Updated ChatView tests to drop title-only toggle interactions and assertions
- Removed the title-only-search i18n string across all locales
- Updated dashboard-guide.md docs to reflect the simplified search behavior
- Added a patch changeset documenting the removal

Files changed:
 .../fn-7651-remove-chat-title-only-toggle.md       |  7 +++
 docs/dashboard-guide.md                            |  4 +-
 packages/dashboard/app/components/ChatView.css     | 18 --------
 packages/dashboard/app/components/ChatView.tsx     | 26 +++--------
 .../__tests__/ChatView.autosize.test.tsx           |  2 -
 .../__tests__/ChatView.content-search.test.tsx     | 51 ++++++----------------
 .../components/__tests__/ChatView.draft.test.tsx   |  2 -
 .../__tests__/ChatView.hash-mention.test.tsx       |  2 -
 .../__tests__/ChatView.mobile-render.test.tsx      |  2 -
 .../components/__tests__/ChatView.rooms.test.tsx   |  2 -
 .../__tests__/ChatView.scroll-to-top.test.tsx      |  2 -
 .../components/__tests__/ChatView.test-harness.tsx |  2 -
 packages/dashboard/app/hooks/useChat.ts            | 39 ++++++++---------
 packages/i18n/locales/en/app.json                  |  1 -
 packages/i18n/locales/es/app.json                  |  1 -
 packages/i18n/locales/zh-CN/app.json               |  1 -
 packages/i18n/locales/zh-TW/app.json               |  1 -
 packages/i18n/locales/ko/app.json                  |  1 -
 packages/i18n/locales/fr/app.json                  |  1 -
 19 files changed, 47 insertions(+), 118 deletions(-)

Fusion-Task-Id: FN-7651

Fusion-Task-Lineage: 5a31825c-8e6e-441f-800d-6053f5f844ba

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 23:15:53 -07:00
gsxdsm
ac719d1203 fix: add usage_events to operational-log retention
usage_events was absent from Database.pruneOperationalLogs, so the
per-tool telemetry log grew unbounded (~187k rows / ~28MB observed) and
became a dominant driver of .fusion DB bloat once runAuditEvents was
already 30-day capped. Prune it on the same operationalLogRetentionDays
cadence, keyed off its `ts` column (not `timestamp`), alongside the other
column-name exceptions. Adds a regression test and changeset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 22:58:11 -07:00
gsxdsm
ce4f173d8f FN-7649: resolve settings landing view to board on project switch
Fixes project-switch hydration so a persisted "settings" view resolves to the Board instead of re-opening Settings.

- Extend resolveLandingTaskView() in useViewState.ts to treat "settings" the same as "command-center", resolving both to "board" for the auto-restored/hydrated landing view only
- Add regression tests covering the settings->board landing resolution in useViewState.test.ts
- Add changeset documenting the patch-level fix

Files changed:
 .changeset/fn-7649-project-switch-board-landing.md |  7 ++
 .../app/hooks/__tests__/useViewState.test.ts       | 74 ++++++++++++++++++++++
 packages/dashboard/app/hooks/useViewState.ts       |  5 +-
 3 files changed, 85 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7649

Fusion-Task-Lineage: 7179efd6-bb1b-4ea4-a062-479b9b1fffa3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:23 -07:00
gsxdsm
60bfb66c8c test(FN-5048): replace fixed WS badge subscription sleeps with deterministic event waits
Follow-through on FN-5048: the four residual fixed 100ms sleeps in the
websocket badge integration tests (3 subscription-establishment waits + 1
cross-instance pub/sub propagation wait) are replaced with deterministic
awaits on the server-side WebSocketManager subscription:changed event and the
shared pub/sub message event. Removes ~400ms of unconditional real-time waiting
and closes the ordering races the sleeps papered over.

Fusion-Task-Id: FN-5048
2026-07-07 22:06:22 -07:00
gsxdsm
563a8c6b7c FN-7647: route dashboard sync/mesh AuthStorage writes through createFusionAuthStorage
Route node settings-sync and mesh credential writes through the coordinated @fusion/engine auth store to prevent concurrent clobbers of ~/.fusion/agent/auth.json.

- register-settings-sync-routes.ts, register-settings-sync-inbound-routes.ts, and register-mesh-routes.ts now persist received credentials via createFusionAuthStorage() instead of raw AuthStorage.create(getFusionAuthPath())
- Shares FN-7646's reload-before-persist + per-provider locked-merge path, avoiding lost writes from concurrent Fusion processes
- Uses a static top-level import of @fusion/engine (not dynamic import) per FN-3049's bundler-safety rule
- Adds route-level regression coverage in mesh-routes.test.ts, routes-nodes-sync-contract.test.ts, and routes-nodes-sync.test.ts
- Adds a patch changeset for @runfusion/fusion

Files changed:
 .../fn-7647-auth-storage-routes-coordination.md    |   7 ++
 .../dashboard/src/__tests__/mesh-routes.test.ts    | 118 +++++++++++++++++++++
 .../__tests__/routes-nodes-sync-contract.test.ts   |  27 ++++-
 .../src/__tests__/routes-nodes-sync.test.ts        | 110 ++++++++++++++++++-
 .../dashboard/src/routes/register-mesh-routes.ts   |  14 ++-
 .../register-settings-sync-inbound-routes.ts       |  15 ++-
 .../src/routes/register-settings-sync-routes.ts    |  15 ++-
 7 files changed, 290 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-7647

Fusion-Task-Lineage: cfab9679-0baa-4eca-8c7a-23db017aa54f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:22 -07:00
gsxdsm
bec8987ce9 FN-7648: gate hold-release on trait-based unplanned-card check, not literal todo column
Blocks planning/intake column cards from entering processing columns regardless of literal column id, so renamed custom intake/planning columns are covered by the same guard as the legacy todo column.

- Add isUnplannedForExecution() in hold-release.ts: true when task.status==="planning", or when the card sits in the legacy todo column or a column carrying the intake trait AND its PROMPT.md still equals the bootstrap stub.
- Route issueRelease() (used by the sweep, promoteHeldTask, and releaseHeldTaskByEvent) through this guard before releasing into any countsTowardWip processing column.
- Update scheduler.ts's reserveSlot guard to use the same trait-based predicate instead of a hardcoded "todo" column id check.
- Add regression tests in hold-release.test.ts and scheduler-workflow-cutover.test.ts covering renamed intake/planning columns.
- Document the invariant in docs/architecture.md and docs/workflow-steps.md.
- Add changeset (patch) describing the fix.

Files changed:
 .changeset/fn-7648-unplanned-intake-cards-never-execute.md |   7 +
 docs/architecture.md                               |   2 +
 docs/workflow-steps.md                             |   2 +
 packages/engine/src/__tests__/hold-release.test.ts | 238 +++++++++++++++++++++
 packages/engine/src/__tests__/scheduler-workflow-cutover.test.ts |  60 +++++-
 packages/engine/src/hold-release.ts                |  60 ++++++
 packages/engine/src/scheduler.ts                   |  26 +--
 7 files changed, 378 insertions(+), 17 deletions(-)

Fusion-Task-Id: FN-7648

Fusion-Task-Lineage: a4b54d30-f86d-4eb9-9cf2-6ac55b6dbe58

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:21 -07:00
gsxdsm
009ce26fd0 FN-7646: prevent OAuth credential clobbering across concurrent Fusion processes
Fix API keys/OAuth credentials in ~/.fusion/agent/auth.json being clobbered when the desktop app and CLI-served web app run concurrently on one machine.

- Reload primary auth storage from disk (primary.reload()) before persisting a refreshed OAuth credential, so a concurrent process's newer login/refresh for the same provider isn't overwritten by this process's stale in-flight refresh.
- Re-check credential identity against the freshly reloaded disk state before writing the refreshed token back.
- Add cross-process regression coverage exercising concurrent auth.json read-modify-write scenarios.
- Add changeset documenting the fix and its dependency on the pi-coding-agent locked per-provider merge (>=0.80.x).

Files changed:
 .changeset/fn-7646-auth-storage-coordination.md    |   7 +
 .../src/__tests__/auth-storage-concurrency.test.ts | 234 +++++++++++++++++++++
 packages/engine/src/auth-storage.ts                |  27 +++
 3 files changed, 268 insertions(+)

Fusion-Task-Id: FN-7646

Fusion-Task-Lineage: de39f08d-2d9f-46ff-b293-c603e3268ecf

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:21 -07:00