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 -->
merger-ai's local-checkout sync stashed under fusion-ai-merge-sync-<taskId>,
a label none of merger.ts's reclamation machinery matches — every path keys
off the fusion-merger-autostash: prefix. Those entries were never classified,
never subsumed-dropped, never age-swept, and never surfaced as orphans holding
work, so they accumulated indefinitely: six entries dating back a month were
found on one working tree, and their age made real lost work indistinguishable
from litter. merger-ai now labels through buildAutostashLabel, and the legacy
prefix stays recognized so already-leaked entries are reclaimed rather than
stranded in developers' stash lists.
Routing them into that machinery first required fixing what it does with
untracked files. A stash created with --include-untracked keeps them in a
third parent (<sha>^3) that git stash show omits, so an untracked-only stash
read as empty — and all three copies of the liveness check treated empty as
"subsumed, safe to drop". Every leaked ai-sync stash carried untracked files,
so the fix would otherwise have destroyed the work it was meant to reclaim.
Liveness now resolves through one authority, classifyStashContent, which reads
both sides, diffs untracked paths against <sha>^3 rather than the stash commit
(whose tree never contained them), and treats unreadable state as unknown and
therefore undroppable.
Age-based sweeping is left alone: it drops by timestamp without consulting
content, which is deliberate bounded retention and the backstop against this
same accumulation, not a safety gap.
Regression test uses real git — the defect lives in git's stash object model,
so a mocked git can neither express nor catch it — and asserts the invariant
across tracked-only, untracked-only, and mixed stashes in both live and
subsumed states. The mixed shape (tracked subsumed, untracked live) is the one
that silently lost work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An operator was re-asked to approve a plan they had already approved and
that had not changed.
POST /tasks/:id/approve-plan fingerprints the on-disk PROMPT.md, so a plan
approved before the `## Original Description` hygiene injection
(applyOriginalDescription) shipped carries a hash over PRE-injection
content. On the task's next pass the injection rewrites PROMPT.md, the
fingerprint moves, and FN-7569's idempotency short-circuit misses — so the
manual gate re-parks an unchanged, already-approved plan.
finalizeApprovedTask now also compares the recorded fingerprint against the
as-read (pre-injection) content. This does not weaken the gate: `written`
diverges from `writtenInput` only via that injection, so both arms hash
bytes the operator actually approved — only the representation differs. A
genuinely changed plan matches neither arm and still parks.
On a legacy match the stored fingerprint is migrated forward, so the
reconciliation is one-time per task rather than a comparison carried
forever. The migration is a direct updateTask — the taskUpdates batch is
flushed well before this gate runs.
Covers both finalizeApprovedTask callers (direct + recoverApprovedTask),
asserts the changed-plan safety edge still parks, and asserts no redundant
fingerprint write when the approval is already post-hygiene.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recoverApprovedTask idempotency test failed deterministically, and its
siblings passed for the wrong reason. Both traced to the same stale fixture,
not a product defect.
finalizeApprovedTask injects `## Original Description` into PROMPT.md
(applyOriginalDescription) BEFORE computing the approval fingerprint, and
POST /tasks/:id/approve-plan fingerprints the on-disk file — so the
fingerprint an approval records is always over post-injection content. The
fixtures wrote RAW planner text and fingerprinted that, modelling a state
approve-plan can never produce: the injection then rewrote the content, the
fingerprint moved, and the short-circuit looked broken.
Verified the product is correct: the injection is idempotent, so the real
approve -> recover round-trip fingerprint matches (checked end to end).
- recoverApprovedTask test: write and fingerprint the approved on-disk
content. It now exercises the real short-circuit — the run logs "plan
unchanged since prior approval" then "recovered and moved to todo",
where before it logged "awaiting manual approval".
- same-plan test: it only passed because the injection's rewrite ENOENT'd
(no task dir), the failure was swallowed, and `written` stayed raw — so
the fingerprint matched by accident. Feed it the approved content so the
injection is a genuine no-op and the assertion means something.
Fixtures derive from applyOriginalDescription rather than hard-coding
post-injection text, so they keep meaning "the content the operator
approved" if the hygiene injection changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dev field described the pre-ACP adapter: stderr capture, NDJSON error
event bridging, and subprocess exit-code inspection. That implementation
was replaced by the native ACP transport rewrite (grok agent stdio) on
2026-07-11, which carries the FN-7779 invariant forward through onText
diagnostics rather than stderr scraping.
The user-facing summary was already accurate; only the developer-facing
mechanism was stale. Describe the paths that actually ship.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A rate-limited reviewer filled a task's Chat tab with 14 identical
"Reviewer using model: ..." markers and no review text, hammering an
already-limited provider.
Root cause: the reviewer was the only AI lane that never classified
provider errors, so a 429 became an UNAVAILABLE verdict. With no
validator fallback configured the fallback ladder re-ran the SAME model
instantly, and fn_review_step answered with "code review remains
blocking; retry once" — bounding the loop with prompt text rather than
code. The tool's catch-all also swallowed the error into tool output, so
withRateLimitRetry, UsageLimitPauser and RetryStormError never fired.
- reviewer: throw ReviewerProviderError for usage-limit/transient errors
instead of laundering them into UNAVAILABLE, and never spend the
fallback budget (which bounds bad reviews) on an outage.
- reviewer: absorb flaky-network blips in-lane via withRetry with
jittered backoff; rate limits still escalate immediately.
- executor: re-raise the fatal after the prompt via
throwDeferredReviewerFatal — pi-agent-core converts tool throws into
tool_error results, so a tool cannot throw out of session.prompt().
- executor: give code review a real MAX_CODE_REVIEW_UNAVAILABLE_RETRIES
counter, mirroring the plan/spec limiter.
- reviewer: dedupe the model marker on text, so same-model retries stay
silent while a genuine model switch still emits.
Also fixes the run-on rendering: AgentLogType gains `status` for complete
engine messages. `text` means "streamed delta" and is re-glued with
join(""), which is why N standalone markers rendered as one string. The
split is at the type, not a separator — a separator would reintroduce the
FN-5787/5789/5803 streamed-spacing regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both lines fired on every scheduler poll while nothing changed: a held
card re-attempts release each sweep, and every dispatch candidate logged
its resolved node. On a busy board that filled the operator log pane with
"Hold release for FN-XXXX deferred" and "routed to node=local" within
seconds, burying real scheduler events.
Add a Logger.debug() level, off by default and opted into per subsystem
via FUSION_DEBUG, and demote both lines to it. Routing to a remote node
stays at info since it explains where work actually went; only the local
default is demoted. Lines reporting a real transition (capacity
rejection, racing sweep, release failure) are untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make soft-delete return after the DB mutation while branch and agent cleanup run in the background.
- Schedule cleanupBranchForTask after the soft-delete transaction instead of awaiting it under withTaskLock
- Persist cleaned-branch log entries on the deleted row asynchronously; warn on deferred failures
- Respond from DELETE /tasks/:id after deleteTask and schedule execution-agent binding release off the HTTP path
- Add core and dashboard regression tests for non-blocking delete cleanup
- Document the fast-path contract in architecture.md and add a patch changeset
Files changed:
.changeset/fn-7968-task-delete-latency.md | 7 +
docs/architecture.md | 1 +
.../task-delete-nonblocking-cleanup.test.ts | 160 +++++++++++++++++++++
packages/core/src/task-store/archive-lifecycle.ts | 57 +++++++-
.../routes-task-delete-nonblocking.test.ts | 139 ++++++++++++++++++
.../src/routes/register-task-workflow-routes.ts | 19 ++-
6 files changed, 370 insertions(+), 13 deletions(-)
Fusion-Task-Id: FN-7968
Fusion-Task-Lineage: f218a91e-aee3-46c9-a80f-182751b3ccc4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Reconcile active task segment anchors on full Global/Engine unpause so stopped-engine wall-clock does not inflate execution time, reusing the FN-7011 downtime path with a transition-captured heartbeat.
- Pass optional engineLastActiveAtOverride into reconcileActiveTimingForEngineDowntime so unpause callers freeze the stopped-window proof against racing scheduler heartbeats
- Await downtime reconciliation in resumeAfterUnpauseAndSweepInReview before resuming agentic work or sweeping in-review tasks
- Fold Global/Engine unpause into the unified pause-lifecycle listener (single reconcile when both clear together; no-op while either pause remains)
- Soft-fail reconcile errors so unpause resume still proceeds
- Add store and project-engine coverage for override, await-before-resume, dual-source clear, and fail-soft paths; document FN-7975 in AGENTS.md run-audit notes
- Add patch changeset for the operator-facing timing fix
Files changed:
.changeset/fn-7975-engine-pause-active-timing.md | 7 ++
AGENTS.md | 2 +-
.../core/src/__tests__/store-active-timing.test.ts | 86 +++++++++++++
packages/core/src/store.ts | 23 ++--
.../project-engine-unpause-active-timing.test.ts | 94 ++++++++++++++
.../engine/src/__tests__/project-engine.test.ts | 139 +++++++++++++++++++++
packages/engine/src/project-engine.ts | 64 +++++-----
packages/engine/src/self-healing.ts | 6 +-
8 files changed, 378 insertions(+), 43 deletions(-)
Fusion-Task-Id: FN-7975
Fusion-Task-Lineage: 84a46e6f-92bf-452a-ab67-c25ba85cbffb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix task chat showing a previous agent message while a new reply is generating by clearing streaming carriers on fresh generations.
- Clear streamingThinking and the streaming-assistant row when starting a generation without an in-flight snapshot
- Preserve text/thinking/tool restore only when attaching to a live in-flight generation
- Add regression coverage for consecutive replies that must not reuse prior stream content
- Add patch changeset for the planner chat stale-message fix
Files changed:
.changeset/fn-7982-planner-chat-stale-message.md | 7 ++++
.../app/components/TaskPlannerChatTab.tsx | 19 ++++++++--
.../__tests__/TaskPlannerChatTab.test.tsx | 44 ++++++++++++++++++++++
3 files changed, 67 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7982
Fusion-Task-Lineage: 71daaa80-6d66-4940-a656-a2c42a6b03bb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Teach idle heartbeat patrol prompts to skip fn_task_create when recent model-availability failures are visible, and to base progress claims only on board state fetched in the current heartbeat.
- Add standard and concise triage heartbeat guidance to check for model-availability, fallback exhaustion, 429/rate-limit, and 404/model-unavailable failures before creating work
- Require existing-task progress/status claims to come from fn_task_list or fn_task_show results in the current heartbeat run
- Extend agent-prompts tests to cover both template variants
- Add patch changeset for the published package
Files changed:
.changeset/fn-7962-idle-heartbeat-patrol-backoff.md | 7 +++++++
packages/core/src/__tests__/agent-prompts.test.ts | 14 ++++++++++++++
packages/core/src/agent-prompts.ts | 8 ++++++++
3 files changed, 29 insertions(+)
Fusion-Task-Id: FN-7962
Fusion-Task-Lineage: 0a233b5d-00d8-4dda-b627-95d4b8122a55
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Ease cramped mobile agent detail identity and Overview hero rows while preserving the non-overlap header grid.
- Increase mobile header padding, column/row gaps, and identity gap for breathing room
- Add badge wrap gaps and keep lifecycle controls on the FN-6865 non-overlap grid
- Give Overview hero heading/meta/skills deliberate row gaps and wrap long health text
- Nudge summary-card padding and hero gaps further under 480px
- Extend mobile scroll and core tests for spacing invariants
Files changed:
packages/dashboard/app/components/AgentDetailView.css | 51 +++++-
.../__tests__/AgentDetailView.core.test.tsx | 4 +
.../AgentDetailView.mobile-scroll.test.tsx | 182 ++++++++++++++++++++-
3 files changed, 230 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7958
Fusion-Task-Lineage: c555811f-9dce-4725-81b5-2705aeaaa1db
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Code review follow-up for the dual-boot hang fixes:
- Outer tool wrap aborts a linked AbortController on timeout so nested work (npx) stops
- fn_skills_install uses SIGTERM then delayed SIGKILL instead of immediate double-kill
- clearHostTaskStores only drops external entries (does not wipe unrelated CLI boot state)
- Align import/browse schema max with the 50-item hard clamp
- Tests for host-store cache injection and timeout-driven signal abort
Kill the dual-boot FN-7956 class hang for in-process agent tools:
- setHostTaskStore/clearHostTaskStores inject the live dashboard/serve/daemon store
- Prefer host-injected store over createTaskStoreForBackend; race-safe with external overwrite
- fn_skills_install kills npx on abort/timeout so orphan install processes cannot outlive the turn
- Raise budgets for task plan, experiment finalize, and mission backfill
- Hard-cap import/browse batch size at 50 (GitHub + GitLab)
Host-extension research tools dual-booted a second TaskStore and could wedge
agent turns via wait_for_completion polling (same hang class as FN-7956).
Leave research available only when the engine injects createResearchTools under
experimentalFeatures.researchView. Operators still use fn research CLI and the
dashboard Research view. Regen fusion skill docs from extension.ts.
Address review findings on the FN-7956 hang fix:
- Per-tool outer timeouts so fn_research_run(wait_for_completion) is not clipped by a flat 60s budget
- Longer budgets for skills install, import/browse, and web_fetch
- Boot-failure cooldown + orphan-boot log when store boot times out
- Log timeout/abort/errors from the extension wrap; clearer host-extension skip reason
- Tests for budgets, research wait, and sessionPurpose forwarding
Plan Review REVISE previously fed feedback without the rejected plan body, so triage rewrote from title/description and looped. Seed the draft for surgical revision, use reviewType spec for the pre-execution gate, and tighten planner/reviewer prompts toward blocking-only REVISE with concrete edits.
Give planner/reviewer pairs more room to converge before escalating, and surface why a task is parked for plan approval—especially plan-review-replan-cap non-convergence—on cards, detail, and notifications.
AI merge review could park forever when the host fusion extension loaded
fn_task_show and booted a second TaskStore without a tool timeout (FN-7956).
- Skip host @runfusion/fusion extensions for sessionPurpose "merger"
- Forward sessionPurpose into createFnAgent for that policy
- Coalesce + 30s-bound extension TaskStore boots; ALS-propagate AbortSignal
- Wrap every extension registerTool execute with 60s timeout/abort fail-closed
- Unit tests for merger host-extension skip and tool timeout helpers
AI merge spends most of its time in reviewing and landing, not merging.
Treat the full merge pipeline as active so cards, workflow switcher, and
stall suppression show Merging… while the pump owns a task.
Focus the Planning compose textarea when New session is pressed, even if blank compose is already active, and preserve in-progress draft text.
- Add a click-driven newSessionFocusSignal so New session always re-focuses the compose textarea after rAF (mobile detail pane visibility)
- Preserve initialPlan when starting a new session from the already-active blank compose view
- Cover focus, caret placement, draft preservation, and mobile show-detail surfaces in PlanningModeModal tests
- Add patch changeset for @runfusion/fusion
Files changed:
.changeset/fn-7959-planning-new-session-focus.md | 6 +
.../dashboard/app/components/PlanningModeModal.tsx | 37 ++++-
.../__tests__/PlanningModeModal.initial.test.tsx | 174 +++++++++++++++++++++
3 files changed, 213 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7959
Fusion-Task-Lineage: ad71ad28-3e22-454a-907f-3423475180f9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>