The merge node could not observe a graph abort. WorkflowPrimitiveContext
carried no signal, so requestMerge raced the merge only against its own
30-minute GRAPH_MERGE_TIMEOUT_MS using a controller it owned. A hard-cancel
(user cancel, engine restart, pause/resume) aborted the graph controller and
the walk kept sitting inside the merge node for the full timeout. When the
timeout finally fired it aborted the still-running AI merge -- surfacing as
"Manual-merge failed: Request was aborted" -- and the walk reported
value=merge-timeout for a cancellation it had missed half an hour earlier.
An abort landing between merger-ai's `worktree: null` write and
mergeConfirmed then stranded the card as no-worktree-no-merge-confirmed.
Thread the graph AbortSignal from WorkflowNodeExecutionContext (where it
already existed) through primitiveNodeContext/primitiveContextForNode into
the primitives, and honor it on both merge surfaces:
- requestMerge fails fast when the walk is already cancelled, before
ensureWorkflowMergeBoundaryTask mutates the row or the requester enqueues
a merge, and links the graph signal into its timeout controller via
AbortSignal.any -- raced separately so the walk returns on the abort
rather than waiting on a requester that may never settle.
- The legacy merge seam had the identical unguarded race and gets the same
treatment.
The timeout stays: it bounds a wedged merge queue, which is a different
failure from cancellation. Both signals must stay live -- dropping either
silently restores the stall with no type error.
Cancellation returns a distinct `merge-cancelled` rather than reusing
merge-timeout. Returning `data.status: "failed"` would let classifyMergeFailure
read the unknown reason as merge-failed and route the cancellation into
bounded auto-merge retry, re-requesting the merge the operator just cancelled.
Regression test covers both merge surfaces, both cancel timings (pre-flight
and mid-flight), the no-signal back-compat path, the signal plumbing itself,
and the classification boundary. Verified by removing the fix: 7 of 9 cases
fail, with the mid-flight cases hanging until timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
My previous commit's comments claimed GitHubIssueCommentService "effectively
never fires" and was not the surface that posts. That is wrong, and acting on it
would have deleted working, documented behavior.
The issue-comment and tracking-comment services gate on different, disjoint
linkages:
- {GitHub,GitLab}IssueCommentService -> task.sourceIssue, the IMPORT linkage set
unconditionally by buildGitHubIssueSource, plus the documented
githubCommentOnDone / gitlabCommentOnDone settings (settings-reference.md:637,
651 — no Settings UI, but reachable via the settings API/file).
- {GitHub,GitLab}TrackingCommentService -> githubTracking.enabled /
gitlabTracking.item, the explicit TRACKING linkage.
resolveImportedIssueGithubTracking() returns undefined unless
githubLinkImportedIssuesToTracking or the tracking defaults resolve on, so an
imported issue with tracking off has sourceIssue and NO tracking linkage — the
issue-comment service is then the ONLY surface that comments. Neither service is
redundant; record that so neither is deleted as a "duplicate" later.
Also records the pre-existing overlap: a task carrying BOTH linkages with
comment-on-done enabled receives two comments, one per service. Orthogonal to the
release lines and left undeduped.
Comments only — no behavior change; the 76 comment-surface tests still pass.
Fusion-Task-Id: FN-7575
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FN-7575 (issue #1916) added "Current version:" / "Target release:" lines to
GitHubIssueCommentService, but that service is gated on `githubCommentOnDone`
— default false, with no Settings UI — so it effectively never fires. The
"✅ Done —" comments on linked issues are posted by GitHubTrackingCommentService,
which had no version logic. The lines were invisible in production for ~10 days;
issue #1916's own close comment is the proof.
Extract the self-repo check and next-minor computation into a shared
fusion-release-version.ts and apply it across all four done-comment surfaces
(GitHub/GitLab x tracking/issue) so they cannot drift again.
- Release lines join `optionalLines` rather than being appended to the finished
string, so they count against DONE_COMMENT_MAX_LENGTH and shrink the title
budget; appending would silently blow the cap on long titles.
- Version resolution is a lazy resolver, so getCliPackageVersion()'s filesystem
walk only runs for self-repo comments.
- GitLab self-repo matching uses item.projectPath: resolveGitLabTargetFromItem()
prefers the numeric projectId, which never matches the slug.
- Non-self repos stay byte-for-byte unchanged (asserted).
Per the Surface Enumeration rule, regression tests assert the invariant across
every done-comment surface — both the pure formatters and the services that
post — plus case-insensitive slug matching (issue #1916 is "Runfusion/Fusion"),
in-progress transitions, the 0.0.0 sentinel, unparseable versions, the
lazy-resolution guarantee, and the truncation ladder under the length cap.
Verified non-vacuous: 10 of the new tests fail against the pre-fix source.
Fusion-Task-Id: FN-7575
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to #2141 (merged). Three operator-reported problems with the
shipped import auto-translate feature, plus a real bug found while
testing them.
## 1. The settings were unfindable
> "where are the translate settings? I can't find them and search in
settings isn't finding them"
The controls rendered fine — but Settings **search** only matches
curated keywords and advertised i18n keys per section, and "Project
General" says nothing about translation. So searching `translate`
matched nothing.
Now advertised on **Project General** (where the controls live) and on
**Project/Global Models** (where the lane is picked). Verified against
the real `filterSettingsSectionsForSearch`, not by eye:
| query | surfaces |
|---|---|
| `translate` / `translation` | general, project-models, global-models |
| `auto translate`, `target language` | general |
For reference, they live in **Settings → Project General**, directly
below "Always link imported GitHub issues to GitHub tracking".
## 2. The checkbox looked foreign
> "the auto translate checkbox needs to be the left of the text and it
needs to be styled like other check boxes"
It used `SettingsToggleRow`, which renders a **right-aligned toggle
switch**, while every other GitHub/import setting in that section uses a
plain `checkbox-label` with the input **before** its text. Two checkbox
idioms in one section read as a bug regardless of which is nicer in
isolation.
Both controls now use the section's native `form-group` +
`checkbox-label` / `select.select` markup. A test asserts my checkbox's
class and structure are **identical to the neighbouring
`githubLinkImportedIssuesToTracking` checkbox**, so it can't silently
drift back.
## 3. Auto-translation was async but not incremental
> "ensure the auto translate is non blocking and runs async in the
background"
The list never blocked (it rendered originals immediately; import is
cache-read only). But a **single request translated all 50 issues**, so
nothing appeared until every issue finished — minutes on a large page —
and one timeout discarded the whole page's work.
It now streams in chunks of 8: titles appear as each chunk lands, a
failure costs one chunk instead of the page, and chunks are sequential
so opening the panel can't fan 50 model calls at the provider at once.
## Also: a real infinite-render loop (found by testing #3)
`items` and `eligible` are fresh **array identities** on most renders,
and both sat in the effect's dependency list — effect → `setState` →
re-render → new array → effect. It manifested as a **heap OOM** under
`renderHook`.
Effect dependencies are now string/scalar only, with live issue data
read from a ref, and the reset path preserves state identity so it
cannot re-trigger itself. This bug shipped in #2141; it needed a
re-render with a fresh `issues` identity to trigger, but it was live.
## Changeset
Folded into the **existing unreleased** `github-import-auto-translate`
changeset rather than adding a second one for the same unshipped
feature.
## Verification
- ✅ `pnpm lint`, root `pnpm typecheck`, `pnpm verify:fast`, `pnpm
test:gate` (479 tests)
- ✅ 54 tests across the translate suites, including new coverage pinning
the checkbox idiom + neighbour parity, and that translations **stream
per chunk** rather than all-or-nothing (a regression to one request
fails these)
## Reviewer note
Worth knowing for future test-writing here: `beforeEach(() =>
mock.mockReset())` **implicitly returns the mock**, and vitest treats a
function returned from `beforeEach` as a teardown callback — it then
invokes the mock with zero arguments and corrupts `mock.calls`. The test
file uses a block body and says why.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added project settings to enable GitHub import auto-translation and
choose a target language, including “follow dashboard language”.
* Auto-translation now runs in the background, chunked, and streams
translated issue content progressively into imported tasks.
* Translations are cached and reused to speed up repeated imports, with
incremental updates shown as they arrive.
* Settings search now includes translation and import auto-translation
terms.
* **Bug Fixes**
* Improved update handling so changes to issue text refresh
translations, while closed issues are never requested and failures don’t
erase already translated results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
- preserve valid onboarding JSON returned in Pi thinking-only assistant
blocks
- retry one bounded JSON-only reformat turn when the model returns prose
or malformed output
- keep streamed output as a final extraction fallback instead of
overwriting it with an empty content array
## Verification
- `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/agent-onboarding.test.ts` — 20 passed
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm lint`
- `pnpm check:changesets --strict`
- live local-runtime AI Interview produced a structured
Hermes/computer-use onboarding question after restart
Follow-up to #2142, which fixed the missing planning-model fallback and
runtime-hint prompt.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved agent onboarding recovery when assistant replies include
thinking-only content or malformed JSON.
* Added a single automatic retry that re-formats invalid output into
valid onboarding JSON.
* Preserved structured “thinking” content as part of valid onboarding
responses.
* Normalized optional onboarding fields so null/empty/whitespace-only
values are treated as missing.
* Tightened Hermes automation so the runtime hint is set exactly to
`hermes`.
* **Tests**
* Added onboarding event synchronization and expanded coverage for
recovery and field normalization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Skipping a stale planning-state write is the expected outcome of a normal
scheduler advancement, not an anomaly, so the warn was pure log noise.
Behavior is unchanged; only the two planLog.warn emissions are removed.
Fusion-Task-Id: FN-8024
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gate Planning Mode's "Reconnecting…" indicator to the loading view so persisted awaiting-input questions stay free of transient SSE reconnect noise.
- Show planning.reconnecting only when view.type is "loading"
- Cover desktop and mobile resumed question screens without the hint
- Keep the hint during active generation loading reconnects
- Add patch changeset for the user-facing fix
Files changed:
.changeset/FN-8002-planning-reconnecting-hint.md | 7 +++
.../dashboard/app/components/PlanningModeModal.tsx | 6 +-
.../PlanningModeModal.planning-flow.test.tsx | 73 ++++++++++++++++++++++
3 files changed, 85 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-8002
Fusion-Task-Lineage: 0a8682ee-922d-4acd-b7ce-bbf4f25dfce7
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix ChatView mobile streaming tests that failed when chat-back-btn was missing after remount with an active session restored by useChat.
- Open the session via sidebar click before asserting chat-back-btn in streaming mobile tests
- Populate sessions/filteredSessions fixtures for the silent-request mobile case
- Assert empty-state copy stays hidden once the thread is open
- Document remount/sidebar restore requirement with FNXC comment
Files changed:
packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-8000
Fusion-Task-Lineage: 8893884c-b5ed-4c5a-8962-31688b859b9a
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Expose a richer Task Failed banner with tool-error diagnostics and one-click retry using a different model or node.
- Always show the failed banner for failed tasks (including errorless failures) with a generic reason fallback
- Surface the latest agent-log tool_error detail and a retry hint for workflow/step-execute failures
- Add Retry and Retry with a different model/node actions with deferred model/node override save on confirm
- Style the banner recovery controls and cover them in TaskDetailModal tests
- Add minor changeset for @runfusion/fusion
Files changed:
.changeset/fn-7999-failed-banner-retry.md | 7 +
.../dashboard/app/components/TaskDetailModal.css | 47 +++++++
.../dashboard/app/components/TaskDetailModal.tsx | 143 ++++++++++++++++++++-
.../__tests__/TaskDetailModal.test-helpers.ts | 3 +-
.../components/__tests__/TaskDetailModal.test.tsx | 59 ++++++++-
5 files changed, 248 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7999
Fusion-Task-Lineage: d4268c43-442f-4f39-afbe-c6f583ec0fc0
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
`deriveSignalAndSources`'s executor branch never read `task.status`, so a row
parked `status: "failed"` — e.g. the terminal fn_task_done refusal/invariant
park — reported `signal: "progressing"` with the reason "Task is actively
executing in-progress work". The overseer observed a dead task as healthy and
took no action. `failed` was only ever derived for the merger/pull-request
stages, so the sole backstop was the FN-7743 2h stall proxy firing hours later.
This is exactly what FN-7965's audit trail shows: every intervention on a
terminally-parked task was action="observe", reason="Task is actively executing
in-progress work".
Report `failed` so recovery engages on the next poll. This adds no new policy:
a failed executor observation already routes to `retry_step` (executor sources
are `agent-log`, never an ERROR_SOURCE_KIND), bounded by
PLANNER_RECOVERY_MAX_ATTEMPTS and escalated on exhaustion.
Precedence and dedup preserved: `paused` still wins, so an operator/user-paused
row stays `blocked` and is never routed into autonomous recovery; and the reason
is a constant (never interpolating task.error/status) so the FN-7577
`stage|signal|reason` feed dedup still suppresses repeat observations.
Verified: the repro test fails with the branch disabled; paused-precedence,
healthy-card (FN-7577) and dedup guards added; overseer/recovery surfaces
93 passed + core planner-recovery 20/20; engine + dashboard typecheck clean;
`pnpm test:gate` green (294+122+63).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FN-7987 added the shared fusion toolset imports to chat.ts without extending
chat.test.ts's hardcoded `vi.mock("@fusion/engine")` factory, which red-lit
`check-mock-completeness` and blocked the whole merge gate on main.
Add the 11 missing exports with shapes matching their call sites: the singular
`create*Tool` factories return one tool each, while the plural factories are
spread (and `createMemoryTools(...)` is `.filter`ed by `tool.name`), so they
return arrays.
Test-only; no changeset. Verified: both mock-completeness checks green,
chat.test.ts 14/14, and `pnpm test:gate` now passes end-to-end (294+122+63).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The in-session `fn_task_done` handler parks a task terminally (status=failed,
worktree/branch/sessionFile cleared) once its refusal/invariant retry budget is
exhausted. That write happens inside the live agent session, so the executor's
no-fn_task_done retry loop never observed it and spawned a fresh session anyway.
The retry completed, marked the task done, and dragged a worktree-less row into
the pre-merge graph, where the first write-capable node failed on
`no-worktree-for-write-node` — surfacing as a misleading "Workflow graph
terminated with failure at node 'code-review-remediation'" instead of the real
refusal. Observed on FN-7965 and again live on FN-7981.
Re-read state at the top of the retry loop and honor the park. The status probe
covers all three park sites (invariant-check, explicit refusal, implicit
refusal) rather than the single reported repro.
Deliberately not routed through the FN-4806 reclaim branch: its silent todo
requeue would clear the park and, with the budget already spent, re-park on the
next pickup in a todo->execute->park loop.
The pre-existing reclaim probes could not catch this — they test
`worktree === null`, but the store maps a cleared column to `undefined`
(`task-store/serialization.ts`: `row.worktree || undefined`), so the existing
test only passed because its mock returned a value production never emits.
Tightening that probe regressed 7 fixtures and is left as separate work.
Verified: new tests fail with the guard disabled; engine reliability surfaces
show zero regressions vs baseline (17 pre-existing failures unchanged, 495->499
passing); engine-core gate suite 294/294.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lock GitHub import routes so tasks use cached translated title/body with source URL appended, and fail open to original prose when translation is off, missing, or for closed issues.
- Mock getImportTranslation on the route test store
- Cover single-issue and batch import happy paths for cached translations
- Assert fail-open when auto-translate is off, cache misses, or issue is closed
- Prefer persistent getSettings mocks so translation settings survive multi-call import flows
- Document CLI/GitLab exclusion in FNXC surface note
Files changed:
.../dashboard/src/__tests__/routes-github.test.ts | 216 ++++++++++++++++++++-
1 file changed, 214 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7979
Fusion-Task-Lineage: 7b0fe0a5-44f3-4131-b535-6b4c7602b5a8
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Mark successful GitHub/GitLab import rows as Imported right away via optimistic local URL state, without waiting for the parent tasks prop round-trip.
- Add optimisticImportedUrls unioned with tasks-derived importedUrls via isUrlImported
- Populate on successful GitHub issue/PR and GitLab imports; clear on modal reset and source change
- Disable re-import and show Imported badge on rows, counts, and import buttons for optimistic URLs
- Cover optimistic import surfaces in GitHubImportModal tests
- Document the behavior in the dashboard guide and add a patch changeset
Files changed:
.changeset/fn-7991-import-screen-optimistic-imported.md | 7 +++
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/GitHubImportModal.tsx | 57 +++++++++++++++++-----
packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx | 56 ++++++++++++++++++---
4 files changed, 103 insertions(+), 19 deletions(-)
Fusion-Task-Id: FN-7991
Fusion-Task-Lineage: ddfb249a-e2e8-4723-a86d-7f6edc74305c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Move the Project Summarization model lane and title-summarizer fallback next to the AI title/commit summarization controls in Project Models.
- Extract shared project-lane renderer and keep default/merger/import-translate in the general Model Lanes list
- Render summarization + title-summarizer fallback inside the AI summarization section with the same models-available guard
- Add regression tests for colocation and empty-models guard
- Update settings reference docs and add a patch changeset
Files changed:
.changeset/fn-7983-summarization-lane-colocation.md | 7 ++
docs/settings-reference.md | 6 +-
.../app/__tests__/settings-sections.test.tsx | 61 ++++++++++
.../settings/sections/ProjectModelsSection.tsx | 126 ++++++++++++---------
4 files changed, 142 insertions(+), 58 deletions(-)
Fusion-Task-Id: FN-7983
Fusion-Task-Lineage: c473ba61-f003-401d-bc66-86f2078ba047
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
- reuse the dashboard server's initialized `centralCore` for all
node-management routes
- preserve the legacy fallback only when no shared central authority is
provided
- never close the server-owned central authority from an individual
request
- cover node list and registration with regression tests that fail if a
route constructs its own `CentralCore`
## Problem
With the dashboard running on the PostgreSQL backend, `/api/nodes` and
`POST /api/nodes` bypassed the server's initialized PostgreSQL-backed
`centralCore` and constructed a separate legacy `CentralCore()`. Reads
could hit the wrong registry, while writes failed with a null SQLite
handle (`Cannot read properties of null (reading 'prepare')`). The same
pattern affected node detail, health, path-mapping, version,
plugin-sync, and Docker-config endpoints.
## Verification
- targeted regression: 2 tests passed
- dashboard typecheck passed
- ESLint passed for changed TypeScript files
- strict changeset validation passed
- dashboard production build passed
- `pnpm test:gate` passed against an isolated PostgreSQL 16 cluster:
- engine core: 294 tests
- PostgreSQL gate: 122 tests
- CLI CI-shape: 63 tests
## Changeset
Patch release for `@runfusion/fusion`.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved multi-node management by keeping node operations connected to
the active PostgreSQL registry.
* Updated node listing, registration, configuration, health, version,
plugin, path, and Docker configuration routes for more consistent
behavior.
* Preserved Docker configuration validation and safe response handling.
* **Tests**
* Added coverage for retrieving and registering nodes through the active
registry.
* Verified successful node creation responses and handling of optional
configuration fields.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Verification of the Fusion Linux AppImage + embedded Postgres packaging
surface (follow-on to #2106 Mac packaging and #2117 Windows PG work).
### What we found
1. **Published `v0.60.0` AppImages are broken for Local/embedded
Postgres** (pre-#2106):
- No `embedded-postgres` / `@embedded-postgres/*` in `app.asar` or
`app.asar.unpacked`
- `package.json` `main` is still `dist/main.js` (no
`main-bootstrap.cjs`)
- No `omp-runtime` packaged
- `asar.unpacked` only has incidental natives (pi-tui, esbuild,
node-pty)
2. **Current main (post-#2106) packaging config is correct** (verified
via mac `--dir` pack on this host):
- `main` → `dist/main-bootstrap.cjs`
- Full `asarUnpack` of `embedded-postgres` + `@embedded-postgres/**`
- Native bins present under `app.asar.unpacked`
- `omp-runtime` dist present in asar
3. **Linux arm64 native PG binary smoke**
(`@embedded-postgres/linux-arm64` 15.18) in Docker: initdb → start →
create DB → persist across restart → **OK** (requires postinstall soname
symlinks from `hydrate-symlinks.js` / `pg-symlinks.json`).
4. **Host blocker:** this machine is macOS arm64 — cannot produce or
execute a Linux AppImage end-to-end. Linux packaging must run on
`ubuntu-latest` CI.
### Fix in this PR
Release jobs only checked that `*.AppImage` files existed — which is how
v0.60.0 shipped empty of Postgres. Add:
- `scripts/verify-desktop-linux-pg-packaging.mjs` — inspects
`linux-*-unpacked` trees for:
- `app.asar.unpacked` embedded-postgres + `@embedded-postgres/linux-*`
bins
- `dist/main-bootstrap.cjs` + `package.json` main
- `omp-runtime` presence
- Wire into `release.yml` + `test-release.yml` after AppImage artifact
checks
- Unit test lock in `release-workflow.test.ts`
## Test plan
- [x] `pnpm --filter @fusion/core test:embedded-postgres` (33/33 with
60s timeout; default 15s flaked under load)
- [x] Desktop packaging unit tests (`electron-builder-config`,
`build-bundling`, `release-workflow`)
- [x] Inspected published `Fusion-0.60.0-linux-arm64.AppImage` (checksum
OK; PG packaging absent)
- [x] Post-#2106 `electron-builder --mac --dir`: asar.unpacked has PG +
bootstrap + omp
- [x] Docker linux-arm64 native binary lifecycle smoke
- [ ] CI `build-desktop-linux` on this PR (runs the new verifier against
real linux-unpacked)
## Gaps remaining (not fixed here)
| Gap | Notes |
|-----|-------|
| Full AppImage launch + `/api/health` on Linux | Needs Linux host/CI
with display or headless Electron |
| No post-#2106 published AppImage yet | Next release will include
packaging fixes; this PR stops empty AppImages |
| README still says `linux-x64.AppImage` | Actual name is
`linux-x86_64.AppImage` (workflow already correct) |
| Windows packaged Local | Tracked by #2117 / verify-desktop |
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Added verification for Linux AppImage packaging to ensure embedded
Postgres binaries, platform files, symlinks, and runtime assets are
included correctly.
* Confirmed packaged application metadata points to the expected startup
entry point.
* Improved detection of invalid, missing, or incorrectly formatted
packaging artifacts.
* **Tests**
* Added coverage for architecture-specific binaries, exact ASAR paths,
executable files, and symlink metadata.
* Verified packaging checks run after Linux desktop artifacts are
created.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- append the existing same-origin daemon token fallback to artifact
media URLs used by image, video, and link navigation
- preserve project scoping and artifact ID encoding
- add a focused regression test and patch changeset
## Root cause
Artifact metadata loads through authenticated `fetch`, but previews and
links use raw browser navigation (`<img src>`, `<video src>`, and
anchors), which cannot attach the dashboard bearer header. The media
endpoint therefore returned `401 Valid bearer token required` even
though the dashboard itself was authenticated.
## Verification
- `pnpm --filter @fusion/dashboard exec vitest run --project
dashboard-app-quality-foundation-api app/__tests__/api-artifacts.test.ts
--reporter=dot`
- `pnpm lint`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm check:changesets --strict`
- `pnpm build`
- `FUSION_PG_TEST_URL_BASE=postgresql://plarson@127.0.0.1:55432
VITEST_MAX_WORKERS=1 nix shell nixpkgs#postgresql --command pnpm
test:gate`
The broader dashboard foundation API shard was also attempted but
aborted in Node after repeated unmanaged-file-descriptor warnings; the
focused regression and canonical merge gate both pass.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed protected artifact images and links so they load correctly in
authenticated dashboards.
* Added authentication tokens to generated artifact media URLs for
reliable previews and navigation.
* **Tests**
* Added coverage verifying authenticated artifact media URLs include the
expected token and parameters.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Resolve direct binary manifests before scanning shim contents so dashboard status polling does not run the expensive shim regex against JavaScript entrypoints.
Stop hiding agent badge labels as icon-only pills on mobile and narrow task cards; rely on existing ellipsis truncation instead.
- Remove @container and max-width media rules that set .card-agent-badge-text { display: none }
- Document the no-icon-only-label requirement with an FNXC comment
- Add regression coverage that label text stays visible with ellipsis and that CSS no longer hides it
Files changed:
packages/dashboard/app/components/TaskCard.css | 15 +++-------
.../__tests__/TaskCard.badge-wrap.test.tsx | 33 ++++++++++++++++++++++
2 files changed, 37 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7997
Fusion-Task-Lineage: 3add9a91-adef-4299-a638-d71257d26633
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Register mobile task popups on the Fusion nav stack so browser Back, iOS edge-swipe, and Android Back close the popup and keep the board/list visible.
- Push a modal nav entry when opening a mobile task popup and clean it up on close
- Route FloatingWindow and shortcut closes through nav-aware popup close
- Add swipe-back tests for board and list popup dismissal
- Document popup Back behavior in the dashboard guide
Files changed:
docs/dashboard-guide.md | 3 +-
packages/dashboard/app/App.tsx | 35 +++++++--
.../__tests__/TaskDetail.swipe-back.test.tsx | 84 +++++++++++++++++++++-
3 files changed, 114 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7980
Fusion-Task-Lineage: e321a1df-e271-41c0-81af-3560d759f7bb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Restore horizontal concurrency thumb drags on mobile by opting range inputs out of the pan-y ancestor lock.
- Set touch-action:none on Engine Control menu and Command Center concurrency range inputs
- Update geometry/touch contract test to assert none and reject pan-y
- Add patch changeset for the mobile slider fix
Files changed:
.changeset/fn-7973-mobile-concurrency-sliders.md | 7 +++++++
packages/dashboard/app/components/EngineControlMenu.css | 5 ++++-
.../app/components/__tests__/EngineControlMenu.test.tsx | 11 ++++++++---
.../app/components/command-center/CommandCenterControls.css | 5 ++++-
4 files changed, 23 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7973
Fusion-Task-Lineage: bf36e544-c24f-49ae-beae-b11707be9c79
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Elevate the Agents controls popover stacking so it layers above agent cards and token usage on desktop and mobile.
- Add `.agents-view-primary-actions--controls-open` with z-index 50 when the controls panel is open
- Toggle the elevated class from AgentsView when the panel opens
- Extend AgentsView tests for open-state class and stacking CSS invariant
- Add patch changeset for the operator-facing fix
Files changed:
.changeset/fn-7972-agents-controls-overlap.md | 7 +++++++
packages/dashboard/app/components/AgentsView.css | 8 ++++++++
packages/dashboard/app/components/AgentsView.tsx | 2 +-
.../app/components/__tests__/AgentsView.test.tsx | 17 ++++++++++++-----
4 files changed, 28 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7972
Fusion-Task-Lineage: e6cca457-1cd1-4577-83a7-d7de7e484580
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Why
The Import Tasks panel routinely lists issues in languages the operator
cannot read. Translation already shipped in #2128, but deliberately
**opt-in and preview-only** — its header comment read *"Translation is
opt-in (never automatic) so import provenance stays faithful until the
operator asks."*
This reverses that decision **behind a default-off setting**, so
operators who never opt in keep byte-faithful import provenance. The
superseded comment is kept and annotated rather than deleted, so the
reason the rule changed stays in the code.
### The structural gap #2128 left
`POST /github/issues/import` accepts only `{owner, repo, issueNumber}`
and **re-fetches the issue server-side**. A translation held in React
state could never reach the created task, and the in-memory cache died
with the modal. That is why the cache here is server-side rather than in
the hook — it's what makes "imported issues carry the translated
version" actually true.
## What operators get
Auto-translate is **off by default**. When enabled:
- The **50 most recent OPEN** foreign-language issues translate on panel
load — **list titles**, not just the preview, so the list reads in your
language before you click anything.
- Translations show **by default**, with a toggle back to the original
(hover a translated list title to see the original).
- Translations **persist until the issue closes**, so re-opening the
panel neither waits nor re-bills.
- **Both single and batch import** carry the translation, so the created
task reads like the preview you approved.
- A **target language** setting (unset = follow the dashboard language)
and a dedicated **model lane**, so you can pin a cheap/fast model
without dragging the summarization lane onto it.
## Notable decisions
| Decision | Why |
|---|---|
| Detect **before** the model | An issue already in the target language
is never sent. Without this, an English repo with the setting on would
bill every issue to return its input unchanged. |
| Detection moved to `@fusion/core` | The panel and the server must not
disagree about which issues are foreign; two copies of a heuristic
drift. |
| Own rate-limit budget | Translation shared a 10/hour budget with
refine/goal-draft. Fanning out per-issue would fail partway **and**
starve refine for the hour. |
| Cache keyed on a **source hash** | An edited issue misses the cache
and re-translates instead of serving stale prose. |
| Import is **cache-read only** | A miss imports the original. Import
must never block on, or fail because of, translation. |
| `project_id` leads the cache PK + full RLS contract | All projects
share one flat `project` schema. `verification_cache`'s PK predates that
discipline; this table does not copy that mistake. |
## Verification
- ✅ `pnpm lint`, `@fusion/core` + `@fusion/dashboard` typecheck
- ✅ `pnpm verify:fast` — build + scoped typecheck + real boot smoke
(`/api/health`)
- ✅ `pnpm test:gate` — 479 tests
- ✅ 19 new tests covering the billing invariants
(off/closed/same-language ⇒ **no model call**), cache hit/miss-on-edit,
the 50 cap, and per-item fail-soft
- ✅ `schema-applier` real-Postgres suite (46 tests) exercises migration
`0010` and its isolation invariant
**Pre-existing failures NOT touched** (confirmed red on `HEAD` before
this branch): `AppearanceSection`'s task-popup test, and two PG-cutover
keys (`sqliteMigrationNotice`, `postgresMigrationInboxMessageSentAt`)
missing description mappings. I left the latter rather than guess an
allowlist entry that could mask a real coverage gap.
## Reviewer notes
- Short Latin-script prose (a one-line Spanish title) rates only
*medium* confidence and won't auto-translate — the existing heuristic is
deliberately conservative so English issues are never billed. CJK
detects regardless of length. The threshold is the knob if you'd rather
bias toward translating.
- The RLS/isolation contract in migration `0010` is the part most worth
a careful look.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Speeds up **time-to-HTTP-ready** for `fn dashboard` and `fn serve` after
the PostgreSQL cutover without reintroducing the historical 3s
cwd-engine race that degraded webhooks.
- **Dashboard store share (serve parity):** inject the factory-booted
`TaskStore` as `externalTaskStore` so cwd `ensureEngine` does not open a
second pool; share only when store root matches project working
directory (multi-project safe).
- **Serve multi-project:** stop awaiting `startAll()` before listen;
await only the primary engine; background the rest + reconciliation.
- **Defer non-route-critical engine work:** ordered OAuth (refresh →
monitor), automation schedule syncs, and auto-merge **enqueue** after
the engine handle is returnable.
- **Critical-path merge status clear:** still clear stale
`merging`/`merging-pr` before ready so manual merge is not blocked after
crash.
- **Serve `--paused`:** apply `enginePaused` before
`ensureEngine`/`startAll` (dashboard ordering).
- **Stop safety:** generation counter so deferred tails cannot resume
after `stop()` clears `shuttingDown`.
- **Phase timing:** shared `phaseTime` helper, factory substep logs,
serve time-to-listen.
Plan: `docs/plans/2026-07-14-001-feat-faster-startup-plan.md`
## Test plan
- [x] `packages/engine` — `project-engine-manager.test.ts` (path-matched
external store)
- [x] `packages/engine` — `project-engine-deferred-startup.test.ts`
(status clear, OAuth order, stop generation)
- [x] `packages/cli` — `startup-phase.test.ts`
- [x] `packages/cli` — `serve.test.ts` (60 tests, including `--paused`)
- [ ] Local: warm `fn dashboard` / `fn serve` and compare `startup phase
*` / `time-to-listen` logs
- [ ] `pnpm smoke:boot` (real serve `/api/health` on ephemeral port)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance**
* Improved dashboard and serve startup times, including faster
time-to-listen and time-to-ready.
* Moved non-essential background initialization off the critical startup
path.
* Parallelized dashboard service initialization where possible.
* **Reliability**
* Improved multi-project startup handling and project selection.
* Prevented cross-project task-store sharing.
* Added safer shutdown behavior for partially completed startup.
* **Diagnostics**
* Added startup phase timing logs to help identify performance
bottlenecks.
* **Tests**
* Expanded coverage for deferred startup, shutdown, project isolation,
and startup timing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- resolve the configured planning model when agent onboarding requests
omit an explicit override
- align the onboarding prompt with supported runtime/model hint fields,
allowing AI-created agents to select runtimes such as Hermes
- refresh the generated GitHub issue import limits required by the
repository sync gate
## Root cause
The agent onboarding route loaded project settings but passed only
request-body model fields. The AI Interview UI omits those fields, so
`createFnAgent` was called with `provider=undefined, model=undefined`;
the session returned no usable assistant JSON. The prompt catalog also
prohibited `runtimeHint` despite the parser and form already supporting
it.
## Verification
- targeted agent onboarding tests: 22 passed
- `pnpm --filter @fusion/core typecheck`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm lint`
- `pnpm build`
- `pnpm smoke:boot`
- engine merge-gate subset: 294 passed
Full `pnpm test` reached the PostgreSQL gate but this host has no `psql`
binary, so 23 PostgreSQL suites could not start; this is an environment
prerequisite failure, not a test assertion failure.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Agent onboarding interviews now use the configured planning model when
no override is provided.
* Runtime suggestions and runtime-hint guidance are preserved during
onboarding and reflected in generated configurations.
* On onboarding start streaming, planning provider/model resolution now
comes from settings with stricter override validation, and test mode
continues to take priority.
* **Documentation**
* Updated onboarding prompt guidance to support additional configuration
fields and optional runtime draft hints.
* Reduced the maximum GitHub issue import/browse limit from 100 to 50.
* **Tests**
* Added coverage for runtime-hints prompting and planning-model override
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- **Windows CI:** run the embedded Postgres smoke as non-admin
`fusion-pg` (with profile prewarm) so elevated `windows-latest` runners
stop failing with PostgreSQL’s admin-token refusal. Packaging still runs
as the job user.
- **Linux AppImage:** add a packaging content verifier for
`main-bootstrap`, `@embedded-postgres` natives, and `omp-runtime` dist
entrypoints; wire it into `release.yml`, `test-release.yml`, and the
advisory **Desktop packaging** PR lane (after `electron-builder --dir`).
- Fix eslint `no-undef` on bare `URL` in the verifier script (was red on
#2131).
## Context
Desktop packaging on Ubuntu was mostly green; Windows desktop builds and
the AppImage packaging PR (#2131 lint) were the remaining red paths. The
win-pg-diag pivot (run smoke as non-admin) proved green on CI; this
ports that approach without removing main’s elevated-token product path
for end-user “Run as administrator” cases (smoke simply does not take
that path when the process is non-admin).
## Test plan
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm exec eslint scripts/verify-desktop-linux-pg-packaging.mjs`
- [ ] Desktop packaging workflow on this PR
- [ ] Desktop Windows Build (workflow_dispatch)
- [ ] Confirm #2131 supersession if this lands the same AppImage checks
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Strengthened Linux desktop AppImage validation to confirm embedded
PostgreSQL artifacts, required binaries, symlink hydration, and the
expected app entrypoints are present after packaging.
* Improved Windows embedded PostgreSQL smoke testing by running under a
non-administrator helper user with a prewarmed profile environment.
* **Tests**
* Added automated packaging/release workflow verification steps (Linux
and Windows) to catch embedded PostgreSQL content regressions earlier,
including during artifact build and release verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Treat **shared PostgreSQL** (`DATABASE_URL`) as the multi-node durable
data plane; mesh HTTP is membership + optional auth, not task/settings
replication.
- **Peer exchange**: under Postgres backend mode, write queue is
**topology/auth-only**; non-topology pending rows fail rather than
replaying multi-leader task/settings payloads.
- **Mesh routes**: task-ID reserve/commit/abort always hit local shared
allocator rows (ignore remote `coordinatorNodeId`); mesh sync ignores
settings and only exchanges `authMaterial`.
- **Docs**: rewrite multi-project runbook, shared cluster protocol, and
architecture mesh sections for shared-Postgres + claims/leases.
## Context
Follows the SQLite→Postgres cutover. Multiple Fusion nodes can share one
external Postgres while keeping **per-node execution** (worktrees,
processes, claims via `central.task_claims`). Explicit non-goals remain:
scheduler failover and live process migration.
Plan:
`docs/plans/2026-07-15-001-refactor-mesh-shared-postgres-multinode-plan.md`
## Test plan
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/peer-exchange-service.test.ts`
- [x] `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/mesh-routes.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/shared-mesh-state.test.ts`
- [ ] CI gate (lint/typecheck/build/gate)
- [ ] Manual (optional): two processes, same `DATABASE_URL`, create task
on A visible on B; settings change without mesh settings sync; claim
exclusivity
## Operator note
Multi-node shared board requires **external** `DATABASE_URL` on every
node. Default embedded Postgres is still single-host.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Improved multi-node deployments using shared PostgreSQL as the durable
source of execution state.
* Task ID reservation/commit/abort now run locally (no remote
coordinator forwarding).
* Mesh syncing now prioritizes topology visibility and authentication
material; settings replication is disabled in shared-Postgres mode.
* **Bug Fixes**
* Prevented task/settings replication over mesh HTTP in shared-Postgres
deployments.
* Refined lease ownership, recovery, and reconciliation to converge via
shared-database primitives.
* **Documentation**
* Updated architecture and shared-mesh protocol guidance, including
multi-node setup and lease/task-ID allocation behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->