CLI JSON/create success lines write via result() (raw stdout) so quiet mode
cannot drop machine-readable output; capture that seam in research/update/task
tests instead of console.log. Allowlist nested voiceInput settings for the
FN-7505 default-description guard and ship locale keys for Voice Input UI.
A paused engine with nothing running derived executorState "idle", so the
footer badge was indistinguishable from a healthy engine waiting for work.
That is exactly the state a pause settles into once in-flight tasks drain:
triage and planning stall while the board keeps moving, with the pause only
visible by opening the Engine Control menu. Pause state now dominates run
state; the adjacent counters still report throughput.
In the CLI TUI, the global `t` (Git view) branch returned before the Utilities
dispatch in the same key handler, so the advertised "[t] Toggle Engine Pause"
was unreachable dead UI. It now yields when Utilities owns input, and because
the shortcut can stop the board, pausing takes a second `t` within 5s while
resuming stays single-press.
Tests assert the invariant across the whole state matrix (both pause flags x
0/1/5 running), not just the zero-running repro, plus the TUI routing, the
two-press pause, single-press resume, and re-arm behavior.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Full Suite shard 4 timed out runTaskShow lock-exhaustion at the default 5s
budget (run 30096660913): each case re-imported the heavy task.js graph under
fake timers, so cold CI workers spent the whole budget on transform and left
unhandled store.getTask / process.exit races after the timeout.
Import task.js once per cached-store describe under real timers via a mutable
store holder, and enable fake timers only around the backoff body. Exhaustion
cases now finish in ~1ms locally while keeping the shipped entry path.
Stale-test drift, no product changes: FN-8424 inbox reply help text and
fn message inbox --user routing, beta-track prerelease suffixes in version
regexes (0.73.0-beta.N), FN-8399 onMigrationProgress in createTaskStoreForBackend,
#2400 workflow-docs heading, and the durable planning-session store mocks for
fn task plan (0412113de/fdd120232).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reported bug (screenshot): deleting the task created from a plan left the
session permanently stuck on PLANNING_CREATED_TASK_MISSING — Retry create
replayed the same 409 forever. A linked task absent from the
include-archived scan (task-row authority; a successful scan proves
deletion, not a flaky read) now clears the stale linkage and creates a
fresh task, in both the create-task route and createTaskFromPlanSession;
a still-listed-but-unreadable task keeps failing closed.
Multi-agent review of fdd120232 (correctness/adversarial/reliability):
- P1: CLI planning sessions were memory-only — setAiSessionStore only ran
in the dashboard server, so --resume could never find a session across
invocations. New ensureDurablePlanningSessionStore wires the durable
AiSessionStore over the board store's public asyncLayer in runTaskPlan.
- P1: resume failures now THROW instead of process.exit (fn_task_plan
runs inside the pi host — an exit killed the whole agent session), and
a no-question resume requires an explicit refine focus (the provided
description) so merely resuming never rotates the epoch.
- P2: claim and finalize CAS gained the same expected-epoch WHERE guard
as reconcile, so a stale-epoch creator can no longer finalize an
old-epoch task onto a rotated session.
- Side-effect failures (documents, logEntry, validate, reconcile) are now
logged instead of swallowed; post-insert failures no longer mislabel
the just-created task alreadyCreated:true; the keep-refining readline
closes on thrown prompts and a failed refine after creation returns the
created task id with a resume hint; cross-process generating guard
added to createTaskFromPlanSession.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes the P1 agent-native gap from the multi-task review: the CLI and
fn_task_plan pi tool created tasks via a raw store.createTask with no
proposalClaimId — no idempotency, no session linkage, and tasks outside
the epoch sequence, so a later dashboard Proceed would duplicate them.
- New shared createTaskFromPlanSession in @fusion/dashboard/planning:
the agent-surface twin of POST /planning/create-task (epoch-derived
claim key, claim/finalize/reconcile/release CAS lifecycle with the 30s
stale-lease takeover, formatPlanningPlanMd task shape, plan/original-
description documents, validate-on-create, generating guard).
- runTaskPlan creates through it (making the FN-7734 retry wrapper
genuinely safe), prints the session id, and offers an interactive
keep-refining loop that creates further tasks from the evolved plan.
- fn task plan --resume <sessionId> / fn_task_plan resumeSessionId reopen
an existing session — even a validated one whose task exists — and the
no-question resume regenerates the interview via a refine turn, which
rotates the creation epoch server-side.
Tests: CLI suite pins claim-aware creation, the continue prompt, and the
resume flow; dashboard suite pins createTaskFromPlanSession idempotent
replay and epoch-aware second creation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary
Every PR's blocking checks were dominated by redundant full rebuilds,
not by tests. Measured on recent runs: the Gate job spent ~6 of its ~7.5
min on a cold `pnpm build` (the exact-key dist cache missed on virtually
every PR) for ~45s of actual boot smoke + gate tests; Build ran ~8 min
and Typecheck ~4 min, both fully cold every time. Expected end state
once the warm job has run on main: all four blocking checks in roughly
2–4 min wall-clock.
### Gate job
- New `gate-dist-*` cache namespace with `restore-keys`, additionally
caching `.fusion/cache/plugin-build-cache.json` (build-workspace's
per-package content-hash skip cache) and `packages/cli/dist`. The
always-run `pnpm build` reconciles a near-match restore by content hash
and rebuilds only the packages the PR touched. This is safe *because*
the gate builds after restoring — the shard jobs' "no restore-keys" rule
(FN-4232/FN-4605 stale-dist incidents) still stands there, since they
consume dist without building.
- `FUSION_CLI_FULL_PACKAGE=0` on the gate build: skips the multi-minute
CLI desktop/plugins/DTS packaging tail nothing in the gate consumes
(same shape `pnpm verify:fast` proves locally). Full CLI packaging
coverage stays blocking in the Build job.
### Build job
- Restore-only tap (`actions/cache/restore`) of the same warmed cache.
Restore-only because this job runs FULL CLI packaging (`CI=true`) and
saving that shape would swap the cache's canonical fast-CLI contents out
from under the Gate job. Its distinctive coverage is preserved:
`ensureFullPackageCliPlanned` force-plans the CLI in full mode
regardless of cache state.
### Typecheck job
- Caches per-package tsc incremental buildinfo — self-validating (tsc
hashes every input against it and re-checks whatever changed), so
`restore-keys` is correctness-neutral by construction.
- **Fixes a real incrementality bug:** `tsconfig.json` and
`tsconfig.app.json` in the dashboard both inherited
`${configDir}/dist/.tsbuildinfo` from `tsconfig.base.json`, so the two
typecheck programs clobbered each other's buildinfo and re-checked the
full program every run — incremental typechecking never worked for the
dashboard, in CI or locally. `tsconfig.app.json` now writes
`dist/.tsbuildinfo-app`. Measured: dashboard typecheck 44s cold → 5.6s
warm.
### Warm job (full-suite.yml, push to main)
- New `warm-gate-build-cache` job saves both caches from main on every
push. Caches saved on a PR merge ref are invisible to other PRs, so
without this every PR's *first* run would still build/check cold.
## Guardrails
`ci-workflow.test.ts` pins the coupled invariants so they can't drift
apart silently:
- restore-keys requires the reconciling `pnpm build` after restore,
before boot smoke
- the mtime-defeating seed step stays exact-hit-only
- byte-identical cache path lists between the Gate/Build/warm blocks
(actions/cache versions caches by path list — a drifted list makes
caches mutually invisible)
- Build stays restore-only and must NOT opt out of full CLI packaging
- Typecheck cache shape + the distinct dashboard app buildinfo path
## Notes
- First PR runs after this lands still build cold until the warm job has
run once on main.
- No changeset: CI config + test-only per AGENTS.md.
## Verification
- `ci-workflow.test.ts` + `package-config.test.ts`: 106 tests pass
- Dashboard typecheck run twice locally: 44s cold → 5.6s warm, both
`.tsbuildinfo` and `.tsbuildinfo-app` written, exit 0
- Cache block path/key parity verified programmatically across both
workflow files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance**
* Improved CI build and type-check performance through incremental
caching.
* Added cache warming from the main branch to speed up pull request
checks.
* Enabled faster CLI packaging during gate validation while retaining
full packaging coverage elsewhere.
* **Bug Fixes**
* Prevented dashboard TypeScript build information from being
overwritten, preserving incremental type-checking reliability.
* **Tests**
* Added coverage to verify CI cache behavior, build ordering, cache
paths, and packaging modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Fusion npm installs now expose a working `agent-browser` command on
Windows, Linux, and macOS. Fusion publishes a top-level shim backed by
the exact pinned native package, preserving the existing Browser
Verification probe contract on every supported desktop OS.
## Validation
- Added a packed consumer-install matrix for `ubuntu-latest`,
`macos-latest`, and `windows-latest`.
- Each platform executes npm's generated shim, verifies `agent-browser
0.26.0`, and asserts the matching native binary is installed.
- Confirmed the packed Fusion manifest, installed dependency, and
command output all retain the exact declared version pin.
- Passed 101 focused CLI workflow/package tests, full workspace lint and
typecheck, strict changeset validation, and a real macOS ARM64
packed-install smoke.
## Post-Deploy Monitoring & Validation
- Search task logs for `agent-browser not found on PATH`, `Missing
native executable`, and Browser Verification availability warnings.
- Healthy signal: npm installs on Windows, Linux, and macOS resolve
`agent-browser --version` without missing-shim or native-payload errors.
- Failure signal: command resolution errors, version-pin mismatches,
missing native payloads, or increased browser-verification fast-bails.
- Validation window: first release cycle after publish; owner: Fusion
maintainers.
- Mitigation: revert the dependency and top-level shim if install
compatibility regresses.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added cross-platform `agent-browser` installation through Fusion CLI.
* Exposed an `agent-browser` command for Windows, macOS, and Linux.
* Pinned the included `agent-browser` version to ensure consistent
installations.
* **Bug Fixes**
* Improved reliability of platform-specific executable selection and
command setup across supported operating systems.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Post-extension modelRegistry.refresh() had no timeout, so a hung remote
catalog fetch left the TUI on "Loading extensions…" forever. Use a shared
15s-bounded refresh across dashboard/serve/daemon and related registration paths.
## Summary
Fusion can now ship on two release tracks. Betas are cut from `main` as
`vX.Y.Z-beta.N` (npm dist-tag `beta`, GitHub prerelease), stable
releases are promoted to a long-lived `release` branch and published to
`latest`, and users pick their track with the new `updateChannel` global
setting — via **Settings → General → Release channel** or `fn update
--channel <stable|beta>`. Previously everything was single-track: every
publish landed on `latest` and every update surface could only see it.
| | beta | stable |
|---|---|---|
| Cut from | `main` | `release` branch |
| Version | `X.Y.Z-beta.N` (changesets pre-mode) | `X.Y.Z` |
| npm dist-tag | `beta` | `latest` |
| GitHub Release | prerelease | latest |
| Homebrew tap / X draft | skipped | bumped / printed |
## How releasing works now
`pnpm release` prompts for the channel and **defaults to beta**, so
day-to-day releases are betas; stable is always an explicit choice.
Choosing stable from `main` triggers assisted promotion: the script
proposes the newest beta tag reachable from HEAD, verifies `release`
fast-forwards to it, then runs the whole stable release inside a
temporary git worktree on `release` — the primary checkout never leaves
`main`. Changesets pre-mode preserves changeset files across betas, so
the promoted stable release aggregates every changeset since the last
stable into one clean changelog entry.
## Design decisions
- **Every publish path names an explicit `--tag`.** A beta accidentally
landing on `latest` is the one unrecoverable failure of a dual-track
scheme, so nothing relies on npm's implicit default (`release.mjs`,
`version.yml`).
- **Beta channel resolves to semver-max of `latest` and `beta`**, so
beta users are offered each promoted stable once it overtakes their
prerelease. Switching beta → stable never downgrades; `fn update
--channel stable --force` is the explicit escape hatch.
- **One comparator instead of three.** CLI, dashboard, and desktop each
had their own `isRemoteNewer` that ignored prerelease identifiers —
`0.73.0-beta.2`, `-beta.3`, and `0.73.0` all compared equal, which
breaks the moment any beta exists. They now share full SemVer-precedence
helpers (`compareVersions`, `resolveUpdateTargetVersion`) from
`@fusion/core`.
- **Installs pin exact versions** (`@runfusion/fusion@0.73.0-beta.2`),
never a dist-tag, so an install can't silently land on the wrong track.
- **Desktop channels via electron-updater manifests.** Beta tags build
desktop artifacts with `publish.channel=beta` (emitting `beta*.yml`);
the app sets `channel`/`allowPrerelease` from the shared setting,
re-read on every manual check.
- **Update caches are channel-stamped** — a cache written for one
channel is never served to the other, so switching tracks takes effect
on the next check instead of after TTL.
## Test plan
- New unit coverage: SemVer precedence + channel resolution in
`@fusion/core` (30), channel behavior of the dashboard update check (28,
incl. 9 new) and `fn update` (16, incl. 8 new: persist `--channel`,
no-downgrade, `--force`, cache channel mismatch).
- `pnpm verify:fast` green (scoped typecheck, builds, CLI build, boot
smoke); desktop + settings-section suites green.
- `release.mjs` dry-run matrix exercised by hand: channel prompt
(default/override/invalid), branch preflights per channel,
assisted-promotion target selection, fast-forward guard against a
diverged `release` branch, and bootstrap when no `release` branch
exists.
- Not exercised live: an end-to-end publish (needs TTY authorization +
real npm publish). First real run is the first `pnpm release --channel
beta`.
---
[](https://github.com/EveryInc/compound-engineering-plugin)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added beta and stable release channels across CLI, dashboard, and
desktop updates.
* Users can select a channel via Settings or `fn update --channel
<stable|beta>` (stored as a global default).
* Desktop beta releases now generate beta update manifests and publish
as prereleases.
* **Documentation**
* Expanded release-track, settings, and CLI references to explain
channel semantics and workflows.
* **Bug Fixes**
* Updates now pin the resolved version per channel, improve version
comparison, and prevent unintended cross-channel downgrades unless
`--force` is used.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Windows installs with slow native dependencies now get five minutes to
finish, and a real timeout is reported as an actionable terminal retry
instead of a wall of preceding npm deprecation warnings. Registry
`ETIMEDOUT` errors keep their network diagnosis, including after the
legacy-bin `--force` retry.
Compound Engineering personas are now included in the published CLI
bundle, with complete source-to-staged coverage for all persona
definitions and a clear startup error if the bundled assets are missing
or empty.
The PostgreSQL statement visible in the report was validated by the
existing real-Postgres schema reapply test. Its actual `caused by`
detail was truncated, so this PR deliberately makes no speculative
database change.
## Validation
- Dashboard updater tests: 22 passed
- CLI updater tests: 16 passed
- CE persona installer tests: 7 passed
- Published bundle persona assertion: passed against every source
persona
- CLI and CE plugin typechecks: passed
- Changed production/config lint and strict changeset validation: passed
- Real PostgreSQL schema reapply integration test: passed
---
[](https://github.com/EveryInc/compound-engineering-plugin)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Windows CLI and dashboard updates now allow up to five minutes for
installation and restore Compound Engineering agent personas during npm
installs.
* Update failures now surface clearer, terminal timeout guidance (while
preserving specific network connection diagnostics) and avoid misleading
“deprecated”/generic timeout text.
* Persona assets are reliably included in plugin builds and bunded
persona installation now errors clearly when definitions are missing or
empty.
* **Tests**
* Expanded update and bundling coverage for the new 5-minute timeout and
error-handling scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Repeated review and executor sessions could replay a follow-up creation
step and produce another live task whenever the wording changed. In the
incident behind this fix, 21 creation calls for three intended
follow-ups left 18 duplicate tasks.
Agent-created tasks now retain their parent and agent provenance across
step sessions, heartbeats, and the published CLI surface. Same-parent
paraphrases converge on the existing task through a serialized pre-check
and a database-backed intent claim, while distinct sibling actions
remain separate. Candidate lookup is parent-indexed, uniqueness failures
abort creation, and canonical reuse no longer emits misleading creation
audit events or workflow claims.
Related: FN-8277
## Validation
- Core duplicate guard and intake: 24 tests passed
- Engine task creation and heartbeat: 154 tests passed
- CLI extension: 66 tests passed, 95 skipped
- Core, engine, and CLI typechecks passed
- Core, engine, and CLI builds passed before rebase; the rebase was
conflict-free
- Scoped lint, strict changeset validation, and diff checks passed
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented retried agent steps from creating duplicate follow-up tasks.
* Improved parent-scoped deduplication for paraphrased follow-ups while
preserving distinct sibling actions.
* Preserved parent-task and agent context for created follow-ups.
* Concurrent follow-up requests are now serialized/deduplicated so
duplicates link to the existing task instead of showing as newly
created.
* Updated agent follow-up/heartbeat activity so reused follow-ups no
longer appear in run results as fresh creations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The one-time SQLite→PostgreSQL migration runs inside createTaskStoreForBackend
before any HTTP server listens, so browsers saw "connection refused" and open
tabs failed silently for minutes. Now:
- CLI: a temporary holding server binds the dashboard port for the boot window,
serving an auto-reloading "Database migration in progress" page and an
/api/health payload with status "migrating" + structured progress; the port
is handed off (awaited) to the real app.listen().
- Dashboard SPA: already-open tabs render the new MigrationInProgressBanner
from the 15s health poll when status is "migrating".
- Desktop: LocalRuntimeManager publishes migration progress on
DesktopRuntimeStatus via the new core onMigrationProgress option;
DesktopLaunchGate shows the live label and extends its 30s startup timeout
while progress advances (2min stall cap), in both boot and first-run flows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary
In a centrally-installed, multi-project Fusion server (one process
serving several repos, `process.cwd()` = the install dir, not any repo),
every task under `mergeStrategy: "pull-request"` fails at the auto-merge
stage with:
```
Could not determine repository. Specify owner/repo in params or run from a git repository with a GitHub remote.
```
PR creation from the dashboard and status polling work; only the
engine's automatic PR path fails. This is the **non-workspace sibling of
#1924** (FN-7610 routed workspace-mode tasks to direct merge but does
not cover regular multi-project tasks) and the completion of
#1797/FN-7133 (which fixed only the `getPrMergeStatus` arguments).
## Root cause
`GitHubClient.resolveRepo()` (`packages/dashboard/src/github.ts`) falls
back to a cwd-less `getCurrentRepo()` — i.e. `git remote get-url origin`
in `process.cwd()` — whenever a PR method is called without explicit
`owner`/`repo`. The engine merge path already resolves the correct repo
from the per-project cwd (`prRepo = getCurrentRepo(cwd)`, FN-7133) but
only threaded it into `getPrMergeStatus`. Every other GitHub call
omitted it:
- `processPullRequestMergeTask`: `findPrForBranch` / `createPr` /
`mergePr` on both the per-task and shared-branch-group paths
- `createGroupPrCallback` (group-PR promotion): `findPrForBranch` /
`createPr`
- `createPrNodeGithubOps` (`pr-create`/`pr-merge` workflow nodes):
cwd-less `getCurrentRepo()` persisted `entity.repo` as `""` (poisoning
the downstream `splitRepoSlug` consumers), and the git
push/`createPr`/`mergePr` ran against `process.cwd()`
- the engine's review-response run (`buildRespondCallback`):
`respondOps.getCwd` collapses to `process.cwd()` because no CLI
composition site wires `getTaskWorktree`, so its git ops and response
agent ran outside the project repo
In a central install the fallback throws; worse, if `process.cwd()`
happens to be inside some *other* git repo, it silently targets the
**wrong repository**.
## What changed
- `fix(pr-merge): thread repo identity into PR auto-merge GitHub calls`
— widens the CLI-local `GitHubOperations` interface (optional
`owner`/`repo`, already accepted by `GitHubClient`'s
`FindPrParams`/`CreatePrParams`/`MergePrParams`) and passes `prRepo` at
all six call sites in `processPullRequestMergeTask`.
- `fix(pr-merge): resolve group-PR repo from project cwd in
createGroupPrCallback` — resolves via `getCurrentRepo(cwd)` from the
callback input (same T4 pattern as `syncGroupPrCallback`) with a loud
failure instead of a silent wrong-repo fallback.
- `fix(pr-merge): resolve PR-node repo from task worktree instead of
process cwd` — `resolvePrSource` resolves from `task.worktree`, git ops
run in `getTaskWorktree(...) ?? task.worktree ?? process.cwd()`, and
`createPr`/`mergePr` pass `owner`/`repo` parsed from `entity.repo`.
- `fix(pr-merge): resolve review-response run cwd from the task
worktree` — the engine owns the store, so `buildRespondCallback` prefers
the task's recorded `worktree` for the response run's git ops + agent,
keeping `ops.getCwd` as the single-project fallback (defensive against
structural `PrNodeStore`s without `getTask`).
- Changeset (`@runfusion/fusion` patch, structured body) included.
Deliberately **not** done: a constructor-scoped default repo on
`GitHubClient` — one client instance is shared across all projects in a
central install (`serve.ts`/`daemon.ts`/`dashboard.ts`), so per-call
`owner`/`repo` is the only correct scope.
## Testing
- New regression tests simulate the central-install topology
(`getCurrentRepo` mocked as `(cwd?) => cwd ? repo : null`, exactly the
failing environment) and drive the merge flow end-to-end on the per-task
path, the shared-branch-group path, `createGroupPrCallback`, and all
three `createPrNodeGithubOps` ops, asserting every GitHub call carries
explicit `owner`/`repo` (45 tests in
`packages/cli/src/commands/__tests__/task-lifecycle.test.ts`, all
green).
- `packages/engine/src/__tests__/pr-respond-cwd-resolution.test.ts`
covers the respond-run cwd: worktree preferred, `ops.getCwd` fallback
when the task has no worktree, when the lookup fails, and when a
structural store has no `getTask`.
- Existing exact-argument assertions were extended to the new call
contract (no assertions weakened or removed).
- `pnpm lint`, `pnpm typecheck`, and `pnpm build` green locally; `pnpm
test:gate`'s engine-core suite green (294/294) — its PostgreSQL-backend
lane needs local PG credentials this environment lacks, so that lane
defers to CI. `pnpm verify:fast` (scoped typecheck/build + CLI build +
boot smoke) also passes.
## Repro
1. Install the CLI centrally; run the server from a dir that is not a
git repo, serving ≥1 project with a GitHub `origin` and `mergeStrategy:
"pull-request"`.
2. Run a task to completion and let it reach the merge stage.
3. Before this fix: the auto-merger throws `Could not determine
repository …` (tasks with a persisted PR poll fine but never merge).
Merging the same task from the Pull Requests tab succeeds, because the
dashboard route resolves the repo explicitly (`parseBadgeUrl(...) ??
getCurrentRepo(rootDir)`).
Full analysis: https://github.com/Tchori-Labs/Fusion/issues/4
---
Developed with Claude (co-authored on all commits).
https://claude.ai/code/session_01ChEa8SHFYNAzjCdFbwFMfh
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Resolved pull request auto-merge failures in centrally installed,
multi-project deployments.
- Ensured explicit repository context (`owner/repo`) is used for pull
request lookup, creation, and merging throughout the merge workflow.
- Improved pull request response handling to prefer the task worktree
for working-directory resolution, with safe error behavior when task
details are unavailable.
- **Tests**
- Expanded coverage for multi-repository merge workflows and
worktree-based repository/cwd resolution in PR response handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
Repair CLI test mocks and retry-reset expectations for the current store contracts.
- Add the backend store factory to the experiment-finalize mock.
- Provide global settings directory access in backup test stores.
- Assert all manual retry reset fields in task command tests.
Files changed:
.../extension-experiment-finalize.test.ts | 13 ++++++++++
.../commands/__tests__/backup-lock-retry.test.ts | 2 ++
packages/cli/src/commands/__tests__/backup.test.ts | 17 ++++++++++++-
packages/cli/src/commands/__tests__/task.test.ts | 28 +++++++++++++++++-----
4 files changed, 53 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-8222
Fusion-Task-Lineage: 033ce6a6-c699-409c-a59e-2d1f5e041cfc
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Align daemon startup mocks with the model runtime initialization path.
- Add the auth-storage setModelRuntime mock.
- Stub the Fusion model registry factory to use the shared test registry.
Files changed:
packages/cli/src/commands/__tests__/daemon.test.ts | 9 +++++++++
1 file changed, 9 insertions(+)
Fusion-Task-Id: FN-8220
Fusion-Task-Lineage: 6e00980f-2e24-4502-a4d8-e91849df33b9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
An audit of the SQLite→PostgreSQL store migration found data-store paths
still reaching the removed SQLite stub in backend (PG) mode. In backend
mode `store.db`/`getDatabase()` throw the removed-SQLite error, so each
of these either threw on every run or — worse — had the throw swallowed
into a silent wrong result. This PR routes all of them through the
`AsyncDataLayer` (and removes one dead primitive).
## The 6 live bugs fixed
| Fix | Was |
|-----|-----|
| `executor.ts` authoritative assigned-agent fallback now inherits the
TaskStore `asyncLayer` | silently returned `null` → model drift to the
pi built-in (the exact thing its comment guards) |
| `pruneAgentLogFilesAsync` replaces the sync self-healing prune call |
threw `SQLite Database is not available` every maintenance sweep →
agent-log pruning never ran |
| `cleanupOrphanedMaterializedSteps` deletes PG `workflow_steps` rows on
a failed create | swallowed the throw → leaked rows |
| `deleteTaskBackendImpl` now runs the async mission feature/task-link
unlink | PG hard delete left orphaned mission links |
| `getWorkflowSettingsProjectId` returns `rootDir` in backend mode
without touching the stub | swallowed throw for unscoped backend stores
|
| `fn plugin` unregistered-project fallback bootstraps a `CentralCore`
`AsyncDataLayer` | layerless `PluginStore` threw in PG |
## The 4 latent traps, fixed properly
- **`cleanupArchivedTasks`** — real async port (enumerate archived
soft-deleted rows, guarantee cold snapshot, hard-delete project row +
purge selection rows + rm dir).
- **`deleteWorkflowStep`** — real async port (delete `workflow_steps`
via the layer with `.returning()` to preserve the not-found contract).
- **`applyTaskPatch`** — **removed** (zero-caller SQLite column-patch
primitive with no backend analogue; impl + facade + import deleted).
- **`AgentStore.importLegacyFileRuns`** — clean backend no-op (no legacy
SQLite run-files exist in a PG deployment; its only `init()` caller
early-returns in backend mode).
## Symptom Verification
New PG regression suite
`packages/core/src/__tests__/postgres/store-sqlite-residue-fixes.pg.test.ts`
reproduces the original failures against real embedded Postgres and
asserts they're gone:
- orphaned `workflow_steps` are actually deleted (no swallowed throw)
- `pruneAgentLogFilesAsync` resolves and prunes inactive-task log files
- hard delete unlinks the mission feature from the task
- `deleteWorkflowStep` removes the row / reports not-found
- `cleanupArchivedTasks` hard-deletes the project row while retaining
the cold snapshot
## Verification
- `@fusion/core`, `@fusion/engine`, `@runfusion/fusion` typecheck clean
- ~50 existing + 5 new PG tests pass; lint clean; changeset validates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Prevented PostgreSQL backend maintenance from hitting removed legacy
SQLite code paths, avoiding datastore failures and residue cleanup
issues.
* Fixed workflow-step deletion and “not found” behavior in backend mode.
* Ensured backend hard-deletes correctly unlink related mission
feature/task links and clean orphaned materialized steps.
* Prevented legacy file-run imports from incorrectly reporting success
in backend mode.
* **New Features**
* Added async agent-log pruning for inactive tasks and updated
maintenance to use it.
* **Tests**
* Added PostgreSQL regression coverage for residue fixes and
archive/workflow cleanup.
* **Refactor**
* Removed an unused task patch operation and updated task-store cleanup
methods to be async where needed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Restore the CLI package configuration test to the active test lane with current build expectations.
- Allowlist WhatsApp plugin-only tsup externals as non-runtime CLI dependencies.
- Assert the full workspace build command in the verification contract.
- Remove the stale package-config test quarantine.
Files changed:
packages/cli/src/__tests__/package-config.test.ts | 12 +++++++++++-
packages/cli/vitest.config.ts | 9 ++++-----
2 files changed, 15 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-8210
Fusion-Task-Lineage: aa29d866-430f-4097-affa-a89d107474b2
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>