d7e072a03cbf2223cb2bbc5c745d29e48651f73b
11297 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7e072a03c |
fix: add psql binary guard + delete expired quarantine tests (ratchet) (#2090)
## Summary Follow-up to PR #2086 addressing two Greptile review findings. ## P2 — Missing `psql` binary guard (Greptile P2) `hasPg` in `_helpers.ts` previously checked only TCP connectivity to PostgreSQL. But `adminExecAsync()` shells out to the `psql` CLI for DDL (`CREATE/DROP DATABASE`). On a runner where Postgres is reachable but `psql` isn't installed, tests would fail with `spawn psql ENOENT` instead of skipping cleanly. **Fix**: Added `hasPsql = spawnSync("psql", ["--version"]).status === 0` to the `hasPg` guard, so tests skip when either Postgres is unreachable OR `psql` is missing. ## P1 — Expired quarantine entries (Greptile P1) The 16 dashboard test files quarantined on 2026-06-25 were past the 14-day deletion ratchet (AGENTS.md: "DELETED after 14 days unless rescued"). Per the ratchet, the test files were deleted and all references removed: - **Deleted 16 test files** (CSS drift, mock drift, mobile-render regressions) - **Removed 16 entries** from `scripts/lib/test-quarantine.json` (only the CLI entry remains) - **Emptied `quarantinedDashboardTests` array** in `packages/dashboard/vitest.config.ts` ## Verification | Check | Result | |---|---| | Merge gate (`pnpm test:gate`) | ✅ 294 + 99 + 63 = 456 passed | | Dashboard curated-gate | ✅ passes (891 files, 892 executed, 1 skip-listed, 1 quarantined) | | Typecheck (engine) | ✅ clean | | Lint | ✅ exit 0 | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Removed multiple outdated dashboard UI, CSS/token, theme contrast, and API/route test suites. * Updated dashboard test configuration to stop excluding quarantined tests and to prune the quality shard to the current set. * Updated the Vitest split/config guard to match the new test fixture set. * Improved PostgreSQL test detection by requiring the `psql` CLI before running database checks. * Adjusted quarantine tracking by adding a new CLI extension distribution ledger entry and removing obsolete dashboard quarantine entries. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
d8f0b1a268 |
Restore PostgreSQL integration parity (#2089)
## Summary - add asynchronous PostgreSQL parity to research commands and engine execution paths - persist Roadmap, Compound Engineering sessions, and WhatsApp state in PostgreSQL - harden cancellation, concurrency, reconnect, replay-claim, and detached-promise behavior - bundle the PostgreSQL-backed integration implementations in the published CLI This is PR 2 of 2 and is intentionally stacked on #2088. It contains 44 changed files; merge #2088 first, then retarget this PR to `main` if GitHub does not do so automatically. ## Verification - `pnpm check:changesets --strict` - `pnpm lint` - `pnpm test:gate`: 463 tests passed - Compound Engineering plugin: 299 tests passed - Roadmap plugin: 144 tests passed - WhatsApp plugin: 27 tests passed - research CLI: 18 tests passed - `pnpm verify:fast`: all scoped typechecks, builds, CLI build, and boot smoke passed ## Post-Deploy Monitoring & Validation - deploy only after #2088 and verify schema migration `0002` is present - monitor research cancellation, automation claims, agent execution, plugin schema initialization, and unhandled rejections - validate Roadmap ownership, Compound Engineering session recovery, and WhatsApp reconnect/replay deduplication - compare per-project plugin and workflow counts after cutover - restore the pre-deploy backup for data rollback; avoid an in-place schema downgrade |
||
|
|
c25f8b796d |
Harden PostgreSQL migration foundation (#2088)
## Summary - make SQLite-to-PostgreSQL cutover retryable, fail-closed, versioned, and transactionally serialized - isolate migration sessions from runtime traffic and apply schema upgrades through `0002` - enforce tenant ownership across automations, analytics, activity, usage, agent runs, evals, and todos - replace expired SQLite-only coverage with PostgreSQL parity and concurrency coverage This is PR 1 of 2. The stacked follow-up restores PostgreSQL parity for CLI, engine, dashboard, and bundled integrations. ## Verification - `pnpm check:changesets --strict` - `pnpm --filter @fusion/core typecheck` - migration schema, connection, and SQLite cutover suite: 57 tests passed - `pnpm test:gate`: 463 tests passed ## Post-Deploy Monitoring & Validation - take a restorable PostgreSQL backup before deploy - confirm `fusion_schema_migrations` contains `0002` - confirm each expected project has a complete `fusion_sqlite_migrations` row - verify no null or empty tenant ownership in automations, activity logs, agent runs, and usage events - monitor for ownership inference failures, cutover verification failures, and migration session errors - restore the backup for data rollback; do not downgrade the tenant-isolation schema in place <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * PostgreSQL-backed analytics and live dashboard metrics are now project-scoped (activity, tools, monitor, signals, and live snapshots). * Evaluation runs and scheduled eval batches received lifecycle improvements (ordering, updates, and execution flow). * Todo list changes now emit events; WhatsApp persistence and project-scoped roadmap data are supported. * **Bug Fixes** * SQLite-to-PostgreSQL cutovers now fail safely with stronger verification, serialized cutover handling, and safer project ownership. * PostgreSQL backend writes and reads are now strictly project-isolated and fail closed when project context is missing. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
3fce53640e |
fix(dashboard): keep completed Planning Mode sessions in history after multi-task creation (#2079)
## Problem
In the dashboard **Planning Mode** screen, a planning session that runs
to completion **and creates multiple tasks** disappears from the "saved
sessions" history panel ("No saved sessions yet").
## Root cause
The multi-task route `POST /api/planning/create-tasks` called
`cleanupSession(planningSessionId)` → `unpersistSession` →
`_aiSessionStore.delete`, **deleting the persisted `ai_sessions` row**.
The single-task route `POST /api/planning/create-task` deliberately uses
`releaseSession` instead — it releases the in-memory runtime but
**keeps** the persisted completed row, which is what the history list
reads (`listAll` includes completed sessions). So multi-task creation
erased its own history entry.
## Fix
Switch the multi-task route to `releaseSession`, matching the
single-task path. The completed `type: "planning"` session row now
survives task creation and appears in history.
## Tests
Adds a regression test in `routes-planning.test.ts` asserting the
persisted planning row survives multi-task creation (verified it fails
against the old `cleanupSession` behavior). Merge gate green locally;
changeset included.
Made with Claude (see `Co-Authored-By` trailer).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed an issue where Planning Mode multi-task sessions could be
removed from planning history after task creation.
* Completed multi-task planning sessions are now reliably retained with
their completed status.
* **Tests**
* Added a regression test for the multi-task Planning Mode flow to
confirm all tasks are created and the planning session remains persisted
in history.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
bc348345a4 |
fix(engine): break Plan Review REVISE replan loop (feedback + bounded cap) (#2078)
## Problem A task whose Plan Review step returns verdict `REVISE` can loop forever: plan → plan-review REVISE → `needs-replan` → re-plan → near-identical plan → REVISE → repeat. The triage **pre-execution** Plan Review gate (`runPlanReviewBeforeExecution`) sets `status: "needs-replan"` on REVISE with **no cap and no escape to `awaiting-approval`** — unlike the executor graph path, which already has `PLAN_REVIEW_REPLAN_HARD_CAP`. Under `planApprovalMode: require-all` there is also no human exit, because the task never reaches `awaiting-approval`. Separately, replan feedback (`triage.ts`) was derived only from `task.log` comment actions + the latest user comment; it never consulted the plan-review verdict stored in `task.workflowStepResults`. ## Fix 1. **Thread plan-review feedback into replan** — when re-planning with no comment-derived feedback, seed `buildSpecificationPrompt` from the most recent `plan-review` REVISE `output` in `workflowStepResults` (existing user/AI-comment precedence preserved). 2. **Bounded cap** — new `planReviewReplanCount` counter (`types.ts`, `store.ts` column + updateTask, `db.ts` migration 146, `manual-retry-reset.ts`). After `PLAN_REVIEW_GATE_REPLAN_CAP = 3` consecutive REVISE replans the task escalates to `awaiting-approval` (`awaitingApprovalReason: "plan-review-replan-cap"`) instead of replanning. Counter resets on APPROVE. ## Tests Adds `triage-replan-feedback-from-plan-review.test.ts` and `triage-plan-review-replan-cap.test.ts`. Merge gate green locally (`verify:fast`, `test:gate` 337+63, `lint`); changeset included. Made with Claude (see `Co-Authored-By` trailer). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Prevented Plan Review “REVISE” from looping indefinitely by enforcing a bounded replan cap. * After repeated Plan Review replans, tasks now escalate to an approval-hold state with a dedicated reason. * Improved replan feedback by seeding from the latest Plan Review output when no explicit feedback is available; the counter clears when Plan Review approves. * Manual retries now reset the Plan Review replan cap counter. * **Documentation** * Added release notes describing the Plan Review replan safeguards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> |
||
|
|
03966ecb79 |
Fix multi-project branch-group route store scoping (#2085)
## Summary Conflict resolution for closed [#2074](https://github.com/Runfusion/Fusion/pull/2074) (FN-001 multi-project branch-group store scoping), rebased onto current `main`. #2074 closed when its fork head was briefly reset to `main` during a ref update; maintainer write access to the fork head only works while the PR is open, so that PR could not be reopened without new fork commits. This branch carries the same fix: - Request-scoped `TaskStore` for branch-group list/read/assign/promote/abandon - Integrated reconcile/close uses the request store for cwd + persistence - Compatible with async branch-group store APIs and main’s CentralProjectIdentity (`projectId` trim) - Postgres durable FN-7438 tests + padded `projectId` regression ## Verification - `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest run --project dashboard-api src/__tests__/routes-branch-groups.test.ts src/__tests__/integrated-routers-group-pr-token.test.ts src/__tests__/routes-context-project-identity.test.ts --silent=passed-only --reporter=dot` — 3 files, 41 tests passed. --------- Co-authored-by: Tchorizo <295840812+Tchorizo@users.noreply.github.com> Co-authored-by: Fusion <noreply@runfusion.ai> |
||
|
|
8de0fbcd01 |
fix: repair full-suite failures after SQLite-to-PostgreSQL cutover (#2086)
## Summary
Fixes all deterministic full-suite (non-blocking) CI failures on `main`
caused by the SQLite-to-PostgreSQL cutover (VAL-REMOVAL-005).
## Changes
### i18n Key Parity (5 locale files)
- Added missing `taskPopupsBoardListOnly` +
`taskPopupsBoardListOnlyHelp` keys (empty strings per convention) to
zh-CN, zh-TW, fr, es, ko `app.json`
### Dashboard Curated-Gate Guard (`scripts/lib/test-quarantine.json`)
- Repaired "mirror drift": 16 dashboard test files were quarantined in
`vitest.config.ts` but never added to the quarantine ledger. Added all
16 with failing run URLs and `quarantinedAt` dates.
### Line-Count Audit CI Cache (`.github/workflows/full-suite.yml`)
- Removed `skip-install: "true"` from `line-count-audit` job —
`setup-node@v5` with `cache: pnpm` failed post-step because no
`node_modules` existed to cache.
### Engine Slow Tier — Full PG Migration
- **CI**: Added PostgreSQL service container to `test-slow` job (same
config as `test-shards`)
- **`_helpers.ts`**: Migrated `makeReliabilityFixture()` from removed
SQLite `Database.init()` to PG-backed `TaskStore`:
- Added `probeTcpReachable()` (TCP probe, copied from shared harness)
- Added `hasPg` export (uses TCP probe, not env-var guess)
- Added `adminExecAsync()` (`Promise.withResolvers`, psql via
`PG_TEST_URL_BASE`)
- Added `createPgLayer()` (fresh PG database + schema baseline +
`AsyncDataLayer`)
- Updated cleanup: `await store.close()`, close layer, drop database
- **Slow test**: Migrated 24 sync SQLite API calls to async PG APIs:
- `store.getRunAuditEvents()` → `await auditEvents(store, ...)` via
exported `queryRunAuditEvents`
- `store.getDatabase().prepare(...)` → Drizzle queries via
`store.getAsyncLayer()!.db`
- **Core exports**: Added `queryRunAuditEvents` from `async-audit.ts`
and `eq as drizzleEq` from `drizzle-orm`
- **22 reliability test files**: Added `hasPg` guards so tests skip
locally when PG is unavailable
### Shard 3 — PG Test Auth Bug (18 postgres test files)
- Replaced `psql -U ${process.env.USER ?? "postgres"}` with `psql
"${PG_TEST_URL_BASE}/postgres"` connection string. On GitHub Actions,
`process.env.USER` is `'runner'`, not `'postgres'`, causing auth
failure.
### Shard 3 — Removed Function Tests (`mesh-task-replication.test.ts`)
- Deleted 3 tests for functions intentionally removed in PostgresCutover
(`buildMeshReplicatedTaskCreatePayload`, `toReplicatedCreateInput`,
`taskMatchesReplicatedCreate`). Kept `buildBootstrapPrompt` test.
### Shard 3 — Store Thinking Levels (`store-thinking-levels.test.ts`)
- Migrated from removed SQLite path to PG-backed
`createTaskStoreForTest` + `pgDescribe`.
## Verification
| Check | Result |
|---|---|
| Merge gate (`pnpm test:gate`) | ✅ 294 + 99 + 63 = 456 passed |
| Engine slow tier (22 tests) | ✅ 22/22 passed |
| i18n parity tests | ✅ 7 passed |
| mesh-task-replication | ✅ 1 passed |
| PG data-layer | ✅ 14 passed |
| PG taskstore-lifecycle | ✅ 16 passed |
| store-thinking-levels | ✅ 1 passed |
| Dashboard curated-gate | ✅ passes |
| Typecheck (engine + core) | ✅ clean |
| Lint | ✅ exit 0 |
## Parked (not in scope)
- **Shards 1/2 timeout**: Engine test suite exceeds CI time budget.
Pre-existing, unrelated to these fixes.
- **2 latent PG files** (`chat-store-content-search-edit`,
`satellite-db-injected-stores`): Surface a separate pre-existing schema
baseline gap. Out of scope.
|
||
|
|
b5c76af700 |
fix(core): preserve jsonb defaults during PostgreSQL migration (#2080)
## Summary - preserve target defaults when legacy SQLite rows contain `NULL` or empty strings for `NOT NULL` jsonb columns - derive the fallback from PostgreSQL column metadata instead of hard-coding table or column names - keep migration checksum conversion aligned with inserted values - add regression coverage for legacy null JSON fields ## Test plan - `corepack pnpm@10.33.0 --filter @fusion/core typecheck` - `FUSION_PG_TEST_SKIP=1 corepack pnpm@10.33.0 --filter @fusion/core exec vitest run src/__tests__/postgres/sqlite-migrator.test.ts` - `corepack pnpm@10.33.0 --filter @fusion/core build` The PostgreSQL-backed integration suite requires `psql`, which is unavailable in this environment; CI should exercise the added migration case against PostgreSQL. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved SQLite-to-PostgreSQL migration for legacy rows containing `NULL` or empty JSON values. * For eligible `NOT NULL` `jsonb` columns, the migrator now preserves/apply compatible PostgreSQL column defaults instead of writing SQL `NULL`. * Migration verification now aligns with the final values inserted into PostgreSQL to prevent checksum mismatches. * **Tests** * Added an end-to-end legacy migration case to confirm `jsonb` fields materialize as empty defaults (e.g., `[]`) rather than staying `NULL`. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
9aa2852033 |
fix: resolve an explicit central-registry project id for all dashboard API requests
Implements the explicit-project-identity directive at the route layer: a request's store is resolved from request projectId -> the daemon's registered launch project id -> only for unregistered launch directories, the raw launch-dir store (one-time warn). Resolution funnels through a single seam (routes/context.ts resolveRequestProjectId + resolveStoreForProjectId); the server.ts realtime resolveScopedStore delegates to the same function instead of mirroring it. Scattered 'projectId ? getOrCreateProjectStore : store' ternaries in todo/goals/mission/insights/research/evals routes now use the shared seam. Code-review fixes folded in (multi-agent ce-code-review, 10 reviewers): - mission interview drafts list/discard resolve the same project id the start endpoint stamps (write/read no longer split namespaces) - chat stream-attach guard treats legacy null-projectId sessions as launch-owned instead of 404ing; planner-chat dedup retries unscoped to reuse legacy sessions instead of duplicating them - getProjectIdFromRequest trims and rejects whitespace-only ids - evals/research middleware forwards store-resolution failures to Express (previously rethrew inside a detached promise chain and hung the request) - one-time launch-dir fallback warning routes through runtimeLogger - seam + delegation + whitespace + engine-fallthrough covered in routes-context-project-identity.test.ts (10 cases) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f7d29dd1da |
chore: archive pre-0.60 changelog notes and distill corrupted 0.47–0.59 entries
Raise the durable archive cutoff to 0.60.0, keep only the current release in CHANGELOG.md, and rewrite labeled summary/category/dev package aggregates for 0.47–0.59 into operator-facing Highlights/New/Fixed notes. |
||
|
|
8e4514e585 |
fix: key workflow settings by the central project id and stamp all partitioned tables on both migration paths
Closes the remaining PG-cutover partitioning gaps: - getWorkflowSettingsProjectId resolves the bound AsyncDataLayer's central- registry id first. In backend mode the SQLite stub's getProjectIdentity() throws, so the old fallback ALWAYS keyed workflow_settings / workflow_prompt_overrides by the rootDir path string — a namespace nothing else reads, making workflow settings appear reset after cutover. - Stamping is extracted into core stampMigratedProjectRows (tasks/archived NULL->id, config ''->id, workflow_settings + workflow_prompt_overrides rootDir-key->id, all guarded against clobbering per-project rows), shared by startup-factory Step 5.5 and 'fn db migrate', which now resolves the registered project by path after the copy and warns when unregistered. - The task-id allocator and merge_queue are verified safe WITHOUT project partitioning: task ids are a global PK, the per-prefix sequence scans are intentionally global (only the per-project config floor can raise them), so two projects sharing a prefix cannot mint duplicate ids. FNXC comments lock the invariant; a cross-project PG regression test proves it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ccc9f96e6 |
fix: bind rootDir boots to the central project registry and re-key the migrated config row
Implements the central-project-identity architecture: cwd/rootDir is ONLY a
lookup key into central.projects; project identity (the partition key for
every task/config read and write) comes from the registry.
- createTaskStoreForBackend resolves the registered project id by path for
rootDir-only boots and binds the AsyncDataLayer to it. Previously
'fn dashboard' / 'fn serve' / desktop booted their main store UNBOUND, so
unscoped API requests wrote NULL-project_id rows the projectId-bound engine
could never see, and unbound config reads (id = 1) were indeterminate once
multiple per-project rows existed. The engine already worked registry-first
(resolveLocalProjectWorkingDirectory); this brings the store boots in line.
- Step 5.5 auto-migration now also re-keys the migrated legacy config row
('' -> project id, guarded against clobbering an existing per-project row).
configScope() has no bound->'' fallback, so the migrated project settings,
workflowSteps, taskPrefix, and nextId counters were silently invisible to
bound readers right after a successful migration.
- Unregistered paths resolve to undefined and boot unbound, preserving legacy
single-project behavior with unfiltered readers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0f3a3d3f49 |
fix: stamp migrated task rows with the central-registry project id on rootDir-only boots
The SQLite -> PostgreSQL auto-migration leaves project_id NULL and Step 5.5 only stamped rows when options.projectId was bound — but 'fn dashboard' in the project directory (the main cutover path) boots with rootDir only, so every migrated row stayed NULL, project-bound readers (engine InProcessRuntime, dashboard project-store-resolver) filtered them all out, and the board showed no tasks right after a successful migration. The stamping id is now resolved from the freshly-migrated central registry by matching the registered project path to rootDir; projects never registered centrally keep NULL rows, matching their unbound readers. Integration test covers the rootDir-only stamp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7aa969892a |
fix: count actually-inserted rows in the SQLite -> PostgreSQL migrator via RETURNING
insertBatch read the driver wrapper's count (result.count ?? result.rowCount ?? rows.length), which reported 0 through drizzle's execute even when every row landed — migration reports showed 'inserted 0' for fully-migrated tables and the startup banner's migratedRows total was wrong. ON CONFLICT DO NOTHING RETURNING 1 yields exactly one row per row actually inserted, making the count driver-agnostic and correctly excluding conflict-skipped rows. Idempotency test now asserts first-run insertedRows == sourceRows and re-run insertedRows == 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fbcd00204a |
fix: snake_case legacy SQLite table names in the PG migrator and keep PG-mode boots from touching SQLite
Two post-cutover fixes: 1. The SQLite -> PostgreSQL migrator matched table names verbatim while only column names were snake_cased, so all 22 legacy camelCase tables (activityLog, runAuditEvents, mergeQueue, taskClaims, projectNodePathMappings, ...) resolved zero PostgreSQL columns and were silently skipped as 'no PostgreSQL counterpart'. First observed as 'Project/node path mapping not found' on engine start because central.project_node_path_mappings was never populated. TablePlan now carries a snake_cased pgTable used for every PostgreSQL-side operation; regression test migrates a camelCase activityLog into project.activity_log. 2. The first-boot auto-migration guard opened .fusion/fusion.db with a read-write DatabaseSync on every boot (isValidSqliteDatabaseFile), which performs WAL recovery + checkpoint — writing the legacy file on each PG boot. The PG emptiness count now runs before the SQLite probe, so steady-state PG boots never open the legacy SQLite files at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8596035159 |
fix: bind fallback CentralCore to the async layer so projectId-only boots resolve projects on PostgreSQL
getOrCreateForProjectImpl constructed its fallback CentralCore without the caller's AsyncDataLayer. Post-cutover a layer-less CentralCore has no database at all (the SQLite CentralDatabase path is deleted and init() degrades to a no-op), so project lookups returned empty and every projectId-only boot through the startup factory (engine InProcessRuntime, dashboard project-store-resolver) failed with 'Project "<id>" not found' even though central.projects had the row — dashboard UI came up but the engine never connected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eb5c81cc59 |
fix: widen ce_sessions.last_activity_at to bigint so PG first-boot migration survives epoch-ms values
project.ce_sessions.last_activity_at stores Date.now() epoch milliseconds but was declared integer in both the Drizzle shape and the CE plugin schema-hook DDL, overflowing PG int4 during the SQLite -> PostgreSQL first-boot auto-migration and blocking startup at task-store init. Now bigint in both sites, with an idempotent ALTER for datadirs that already materialized the integer column, plus a schema-wide invariant test that no numeric *_at/*_time/*_timestamp column is 32-bit integer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c15c78feeb |
feat: migrate storage from SQLite to PostgreSQL (#1793)
# Migrate storage from SQLite to PostgreSQL — full dashboard cutover Migrates Fusion's storage layer to the embedded PostgreSQL `AsyncDataLayer` (the default backend) and **completes the satellite-store + feature cutover** so every dashboard and Command Center surface works in PG mode. ## Status — every surface works in embedded-PG mode Verified live against a running embedded-Postgres dashboard (all **200**, zero 5xx) and gate-tested (**23 files / 99 tests** on embedded PG, plus engine-core 294 and ci-shape 63 in the blocking merge gate; core/engine/cli/dashboard typecheck clean). | Area | Surfaces | State | |---|---|---| | Satellite stores | workflows, todos, insights, research, missions, goals, mailbox | ✅ | | Views | artifacts, documents, evals | ✅ | | Command Center | activity, productivity, team, tokens, tools, **workflows**, **github**, **signals**, **plugin-activations**, **live** (all 10) | ✅ | | Run execution | insight generation, research run execution | ✅ (store-path; AI step needs a provider) | | Live updates | SSE push for mission/research/insight events | ✅ | | Workflow editing | create / update / delete / select (+ id counter) | ✅ | | Engine | mission autopilot, incident-signal ingestion, regression storm-guard, agent wake-on-message | ✅ | | Core | tasks, agents, secrets, automations, memory, chat, usage, PRs, git | ✅ | ## Approach Each satellite store gets an `Async<Store>` wrapper exposing the sync store's method names over the existing `async-*-store.ts` helpers; `get<Store>Store()` returns a `Sync | Async` union; consumers `await` (harmless on sync), and engine/CLI paths that can't convert use `instanceof Sync` graceful fallback. Analytics aggregators branch on `"ping" in dbOrLayer` to run schema-qualified raw SQL over `project.*` (snake_case) in PG. Executors/orchestrators/autopilot are await-converted to drive the union store; the async store wrappers extend `EventEmitter` so SSE live-push fires in both backends. Not-yet-ported capabilities degrade gracefully (never 500) and are individually called out in commits. ## Sync with main The branch is kept continuously merged with `main` (currently through FN-7845, 2026-07-12); the earlier "final rebase deferred" note no longer applies. Use **Create a merge commit** (or squash) to land it — GitHub's rebase-merge cannot replay a merge-maintained branch. ## Residual Review Findings Multi-agent code review of the PostgreSQL satellite-store ports (U1–U5) applied 3 safe fixes (see `fix(review): apply autofix feedback`). The following are **real but gated** — recorded here as follow-up work rather than auto-applied. All are SQLite→PostgreSQL **concurrency/atomicity regressions**: the sync stores were immune only by SQLite's single-writer, single-threaded-handler execution; the async ports open multi-await read-modify-write windows. **Reachability is low today** because the execution engines that generate concurrent same-run mutations (insight run executor, research orchestrator/dispatcher) are `instanceof`-gated to sync mode in PG. No process-crash class survived (all engine fallbacks correctly guard the sync store). - **[P1] Research `appendResearchEvent` dual-write is non-atomic** (`packages/core/src/async-research-store.ts`, corroborated: adversarial + reliability). The `research_run_events` insert (own transaction) and the `run.events` jsonb update are separate writes — a crash between them, or two concurrent appends, splits the table count from the jsonb array. **Fix:** perform the seq-insert and the jsonb update in one `layer.transactionImmediate`. - **[P1] Research run terminal-reversion via stale full-row persist** (`async-research-store.ts` `persistResearchRun`/`updateResearchStatus`). Concurrent `PATCH /runs/:id/status` + `POST /runs/:id/events` can revert a terminal run to `running` by overwriting the whole row, bypassing the transition guard. **Fix:** scoped column `UPDATE`s with a `WHERE status …` guard, or optimistic version column. - **[P2] `updateResearchRun`/`updateInsightRun` read-then-write TOCTOU** — concurrent PATCHes last-writer-wins on the lifecycle merge. **Fix:** `SELECT … FOR UPDATE` / enclosing transaction. - **[P2] `upsertRun`/`createRunOrThrowConflict` check-then-create race** (`async-insight-store.ts`) — two callers can each create an "active" run. **Fix:** partial unique index on `(projectId, trigger) WHERE status IN ('pending','running')`. - **[P3] `createResearchRetryRun` return-value divergence** — sync returns the pre-update `queued` snapshot; async returns the reloaded `retry_waiting` run (persisted state is identical). Pick one side for cross-backend parity. - **[P2/perf] Mission `getMissionWithHierarchy`/`getMissionHealth` N+1 fan-out** — O(milestones×slices) sequential round-trips hold one pool slot per request; can starve the pool for large hierarchies. **Fix:** batched/joined reads. - **Testing gaps:** no PG-mode concurrency tests (interleaved status/event mutations), no sync↔async parity assertion for the lifecycle-error codes, and no mission status/health rollup parity test vs the sync `MissionStore`. ~~Out of scope (deferred): AI run *execution* (insight/research) + mission autopilot + live SSE mission events remain sync-gated/degraded in PG mode.~~ **Since ported** — insight/research run execution, mission autopilot, and SSE live push all run on the async layer now, which also makes the concurrency findings above genuinely reachable; they remain open follow-ups. --- ## Update — 2026-07-12: production-readiness hardening & live acceptance Everything below landed on this branch since the description above was written: **Production blockers from review — fixed** - `recoverStaleTransitionPending` ported to the async layer (backend moves write + clear the crash-safe marker; startup/maintenance sweeps no longer throw). - Lost-update class fixed: `atomicWriteTaskJson`/`WithAudit` write changed columns only (full-row upserts silently resurrected stale fields across concurrent store instances — the "task stuck unplanned forever" bug). - First-boot **auto-migration**: booting the PG backend over a project with a legacy `fusion.db` migrates it automatically (loud failure, SQLite kept as backup), and the dashboard shows a one-time **"your data was migrated" banner** with the backup paths and a Need-help Discord link. - `pg_dump`/`pg_restore` discovered from common install locations for embedded-mode backups. - The PG suite is part of the blocking merge gate (`test:pg-gate`). **Multi-project isolation (PR #2007, merged into this branch)** - `project_id` partition key on tasks / archived tasks / config, `taskProjectScope` threaded through every scan/claim/count, per-project config rows, layer bound to the project at startup. - Review P1 follow-up: the shared cold-storage `archive.archived_tasks` table is also partitioned and all archived-board reads/counts/searches are scoped. - Schema drift self-heal generalized to schema-qualified columns so existing databases upgrade in place. **Other changes** - Node settings sync **removed** in PG mode (409 `settings-sync-disabled-postgres`) — nodes share state by connecting to the same database; auth sync kept (per-machine file). - Perf (review findings): `listTasks` pushes column filter + ORDER BY + LIMIT/OFFSET into SQL; `getConversation` capped to the most recent 200 messages. - Fixed a false "operator action required" pause-abort log fired on every successfully auto-merged task. **Live acceptance — PASSED (2026-07-12)** A sandboxed instance (isolated HOME, embedded PG, real Opus executor) ran a task through the complete cycle: create → triage (AI spec) → execute → in-review → AI squash-merge landed on the project's `main` → done. A write+read sweep of every data surface (settings, comments, documents, attachments + artifact bridge + artifact edit, chat with real generation, goals, missions, agent mail, secrets, workflows, memory, CC analytics) was green on embedded PG. **Known remaining work** - The per-project `config` PK re-key has no upgrade path for pre-isolation embedded-PG databases (needs a real `DROP CONSTRAINT`/re-key migration; fresh databases are fine). - `pg_dump`/`pg_restore` binaries are not yet bundled in release artifacts (PATH/common-location discovery only). - The satellite-store concurrency findings listed above. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Phil Larson <hello@phillarson.xyz> Co-authored-by: fusion-merge <fusion-merge@local> |
||
|
|
9a43aa1d24 |
FN-7951: harden runGenerationWithTimeout abort cancellation across planning surfaces
Ensures aborted AI generation (timeout, user-stop, displacement, retries) actually tears down the in-flight agent session instead of only rejecting the Promise.race waiter, since provider SDKs may ignore AbortSignal. - Add a once-only onAbort teardown hook to GenerationGuard, invoked for timeout, user-stop, and displaced abort causes so consumers can dispose their in-flight session exactly once. - Give planning's local generation runner (runGenerationWithTimeout) the same guaranteed once-only abortTeardown for timeout, user-stop, displacement, stuck, and loop aborts, replacing the ad hoc dispose-on-timeout-only logic. - Forward the AbortSignal into planning's history-replay prompt, turn prompts, and JSON-parse-retry prompts, and short-circuit with createAbortError() when the signal is already aborted before/after each prompt call. - Wire subtask-breakdown's onTimeout/onUserStop handlers to the new onAbort hook instead of disposing the agent directly, keeping teardown centralized in the guard. - Add GenerationInProgressError / TargetGenerationInProgressError handling in mission-routes to return 409 Conflict instead of a generic 500 when a generation is already running. - Extend mission-interview and milestone-slice-interview generation paths with matching abort-forwarding and teardown behavior, plus new/expanded tests covering cancellation across timeout, user-stop, displacement, and retry paths. - Add a patch changeset documenting the fix for @runfusion/fusion. Files changed: .changeset/harden-generation-abort.md | 7 ++ .../src/__tests__/ai-session-timeout.test.ts | 41 +++++-- .../__tests__/milestone-slice-interview.test.ts | 72 ++++++++++++- .../src/__tests__/mission-interview.test.ts | 64 ++++++++++- .../planning-generation-cancellation.test.ts | 82 ++++++++++++++ .../src/__tests__/subtask-breakdown.test.ts | 21 +++- packages/dashboard/src/ai-session-timeout.ts | 33 +++++- .../dashboard/src/milestone-slice-interview.ts | 120 +++++++++++++++++++-- packages/dashboard/src/mission-interview.ts | 119 ++++++++++++++++++-- packages/dashboard/src/mission-routes.ts | 12 +++ packages/dashboard/src/planning.ts | 70 +++++++++--- packages/dashboard/src/subtask-breakdown.ts | 10 +- 12 files changed, 589 insertions(+), 62 deletions(-) Fusion-Task-Id: FN-7951 Fusion-Task-Lineage: debcd6a9-f54e-4ef3-87e1-4f06be0b5f64 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6e0fde860c |
FN-7949: fix deleted planning-mode session resurrecting after in-flight generation completes
Adds a bounded-TTL delete tombstone to AiSessionStore so a straggling post-delete generation write can never resurrect a session the user explicitly deleted. - AiSessionStore now records a 10-minute delete tombstone (id -> deletion timestamp) in delete(), deleteByIdAndType(), and bulk cleanup paths (cleanupOld/cleanupStaleSessions/emitDeletedSessions). - upsert() checks the tombstone first and drops (no-ops) any write for a tombstoned id without touching SQLite or emitting ai_session:updated, fixing the root cause once in the shared store rather than per-producer (planning.ts, subtask-breakdown.ts, mission-interview.ts, milestone-slice-interview.ts). - Tombstone entries are pruned lazily on check and piggyback on the existing cleanupStaleSessions() cadence so the in-memory map cannot grow unbounded. - Adds a changeset (patch) documenting the user-facing fix. - Updates docs/architecture.md and docs/storage.md with the new "AI session delete tombstones" behavior. - Adds regression tests covering the tombstone guard in ai-session-store.test.ts and routes-planning.test.ts. Files changed: .changeset/fn-7949-ai-session-delete-tombstone.md | 7 + docs/architecture.md | 2 +- docs/storage.md | 12 +- packages/dashboard/src/__tests__/ai-session-store.test.ts | 145 +++++++++++++++ packages/dashboard/src/__tests__/routes-planning.test.ts | 200 ++++++++++++++++++++- packages/dashboard/src/ai-session-store.ts | 83 +++++++++ 6 files changed, 446 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-7949 Fusion-Task-Lineage: 8e509dae-0cc5-46cd-9c4b-9048cfda56d3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1ff83a2735 |
chore(release): v0.60.0
Version bump via changesets. |
||
|
|
4e7e013d6f |
FN-7947: add Plan action to context menu for pre-execution task cards
Adds a Plan action to Board/List task context menus so triage/hold/intake cards can jump straight into Planning Mode without duplicating a task. - Add `onPlan` handler and `isPreExecutionHoldColumn` gate to `TaskContextMenu` so Plan only appears for pre-execution (triage/intake/hold) columns, and only when a host wires the handler - Wire the Plan action through `Board.tsx`, `Column.tsx`, `ListView.tsx`, and `WorktreeGroup.tsx` so both board and list views expose the new menu item - Surface the Plan entry point on `TaskCard.tsx` - Add test coverage in `TaskContextMenu.test.tsx`, `TaskCard.test.tsx`, and `ListView.test.tsx` for the new gating/wiring behavior - Document the new action in `docs/dashboard-guide.md` - Add a minor changeset for `@runfusion/fusion` Files changed: .changeset/fn-7947-plan-context-menu-action.md | 7 ++ docs/dashboard-guide.md | 10 ++- packages/dashboard/app/components/Board.tsx | 10 ++- packages/dashboard/app/components/Column.tsx | 4 + packages/dashboard/app/components/ListView.tsx | 15 +++- packages/dashboard/app/components/TaskCard.tsx | 24 +++++- packages/dashboard/app/components/TaskContextMenu.tsx | 18 ++++ packages/dashboard/app/components/WorktreeGroup.tsx | 9 ++ packages/dashboard/app/components/__tests__/ListView.test.tsx | 21 +++++ packages/dashboard/app/components/__tests__/TaskCard.test.tsx | 96 ++++++++++++++++++++++ packages/dashboard/app/components/__tests__/TaskContextMenu.test.tsx | 32 ++++++++ 11 files changed, 236 insertions(+), 10 deletions(-) Fusion-Task-Id: FN-7947 Fusion-Task-Lineage: 41c759a2-e76b-4771-9421-c9805c4596e5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7cc622bed2 |
FN-7946: auto-retry stuck Planning Mode AI generation up to 3 times
Planning Mode now automatically retries a stuck or terminally-errored AI generation session up to three times before falling back to the permanent Retry/Dismiss error panel, reducing manual retries for transient failures. - Add a bounded (MAX_PLANNING_AUTO_RETRIES = 3) client-side auto-retry that reuses the existing /planning/:id/retry endpoint whenever the SSE stream's onError, a session reload, or the stuck-session poll observes a terminal "error" status. - Track the retry budget in refs (planningAutoRetryAttemptRef, planningAutoRetryInFlightRef) so async SSE/poll/loadSession handlers share a single in-flight guard, with the current attempt mirrored into state (isAutoRetrying/autoRetryAttempt) for the UI. - Reset the retry budget whenever the session makes real progress (reaches a new question or a completed summary), and surface the permanent Retry/Dismiss error view once the budget is exhausted. - Show a "Retrying... (attempt N of 3)" loading message while an automatic retry is in flight, distinct from the manual Retry button state. - Fix a stuck-poll edge case where a terminal error discovered only by the poll (missed SSE event) after the auto-retry budget was exhausted left the modal spinning on "Generating next question..." forever instead of showing the error view. - Document the new auto-retry behavior in docs/dashboard-guide.md and add a minor changeset for @runfusion/fusion. - Extend PlanningModeModal.planning-flow.test.tsx with coverage for the auto-retry budget, single-flight behavior, and the poll-discovered terminal-error fallback. Files changed: .changeset/fn-7946-planning-auto-retry.md | 7 + docs/dashboard-guide.md | 3 + .../dashboard/app/components/PlanningModeModal.tsx | 339 ++++++++++++++------ .../PlanningModeModal.planning-flow.test.tsx | 353 ++++++++++++++++++--- 4 files changed, 567 insertions(+), 135 deletions(-) Fusion-Task-Id: FN-7946 Fusion-Task-Lineage: 42e911dc-9639-46ab-bb4f-bc9060413140 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
f0888d43c3 |
FN-7945: route List-view task opens through the movable popup when Open tasks as popups is on
Extends the existing board/right-dock "Open tasks as popups" routing so ordinary List row/card and keyboard opens use the same shared movable/resizable FloatingWindow instead of the docked split-pane/mobile detail. - Add openMobileTasksInPopup prop to ListView, threaded through App -> MainContent -> ListView (dashboard/types.ts) - handleRowClick routes to onPopOut (popOutTaskDetail) when the setting is on, on both desktop split-pane and mobile/tablet single-pane; docked behavior is preserved when the setting is off - Restore Enter/Space keyboard activation on list rows to invoke the same handleRowClick path, alongside existing context-menu key handling - Update docs/dashboard-guide.md and docs/settings-reference.md to describe List row/card opens as part of the popup routing surface, and refresh the Appearance settings help copy/FNXC comment accordingly - Add changeset (.changeset/fn-7945-list-view-task-popup.md, minor) describing the user-facing behavior - Extend ListView.test.tsx coverage for the new popup routing and restored keyboard activation Files changed: .changeset/fn-7945-list-view-task-popup.md | 7 ++ docs/dashboard-guide.md | 4 +- docs/settings-reference.md | 2 +- packages/dashboard/app/App.tsx | 1 + packages/dashboard/app/components/ListView.tsx | 48 +++++++++---- .../app/components/__tests__/ListView.test.tsx | 80 +++++++++++++++++++++- .../app/components/dashboard/MainContent.tsx | 2 + .../dashboard/app/components/dashboard/types.ts | 1 + .../settings/sections/AppearanceSection.tsx | 4 +- 9 files changed, 127 insertions(+), 22 deletions(-) Fusion-Task-Id: FN-7945 Fusion-Task-Lineage: 784cb4ee-c493-4ace-bf8b-0e3dbaaef9a3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7246df22f6 |
FN-7944: add setting to keep task popups attached to their Board/List view
Adds an opt-in project setting so open task-detail popups stay attached to the Board or List view where they were opened, instead of floating over every main-content view. - New project setting taskPopupsBoardListOnly (default: off) in settings-schema.ts and ProjectSettings type, with default preserved via settings-defaults tests. - usePoppedOutTasks now stores each popup's originating TaskView alongside its task snapshot (PoppedOutTaskEntry), keeping legacy tasks output for existing callers. - App.tsx adds isTaskPopupVisibleForView() gating helper and filters popped-out entries to the current view for rendering/keyboard-close handling, while hidden popups remain mounted in hook state (not cleared) so switching back to the originating view restores them with shared persisted geometry. - Settings -> Appearance gets a new "Keep task popups on their Board/List view" checkbox (AppearanceSection.tsx) with i18n strings and updated settings search text in SettingsModal. - Documentation updated in docs/dashboard-guide.md and docs/settings-reference.md to describe the render-only hide/restore behavior. - New/updated tests: App.taskPopupViewGating.test.tsx, usePoppedOutTasks.test.ts, AppearanceSection.test.tsx, settings-default-descriptions.test.tsx, settings-defaults.test.ts. Files changed: docs/dashboard-guide.md | 5 +- docs/settings-reference.md | 1 + .../core/src/__tests__/settings-defaults.test.ts | 13 +++ packages/core/src/settings-schema.ts | 5 + packages/core/src/types.ts | 7 ++ packages/dashboard/app/App.tsx | 49 +++++++-- .../app/__tests__/App.taskPopupViewGating.test.tsx | 113 +++++++++++++++++++++ .../dashboard/app/components/SettingsModal.tsx | 3 +- .../settings/sections/AppearanceSection.tsx | 8 ++ .../sections/__tests__/AppearanceSection.test.tsx | 21 ++++ .../settings-default-descriptions.test.tsx | 1 + .../app/hooks/__tests__/usePoppedOutTasks.test.ts | 14 +++ packages/dashboard/app/hooks/useAppSettings.ts | 4 + packages/dashboard/app/hooks/usePoppedOutTasks.ts | 27 +++-- packages/i18n/locales/en/app.json | 2 + 15 files changed, 255 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-7944 Fusion-Task-Lineage: 4b8ced0e-1853-429f-8482-163821a35ae6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6b78633b07 |
FN-7943: keep Quick Chat open when portaled model/thinking-level dropdowns are clicked
Quick Chat's outside-pointer dismissal now recognizes body-portaled dropdown menus (model, thinking-level, agent, dependency, node, priority) as part of the panel instead of treating them as outside clicks. - Extend FloatingWindow's outside-pointerdown safe-surface selector to include the portaled dropdown classes used by model combobox, model nested menu, dependency, node picker, agent picker, and priority picker menus - Add regression tests covering pointerdown on each portaled dropdown surface and on a child element inside a portaled dropdown, asserting onClose is not called - Update dashboard-guide docs to describe that these portal dropdowns are treated as part of the Quick Chat panel for outside-click purposes Files changed: docs/dashboard-guide.md | 2 +- .../dashboard/app/components/FloatingWindow.tsx | 19 +++++++- .../components/__tests__/FloatingWindow.test.tsx | 50 ++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-7943 Fusion-Task-Lineage: fa91bd43-241c-48b0-8858-16521f383784 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
c3c3861efa |
fix: stale TRANSITIVE_EXTERNALS + dashboard dist/client clean + cross-worker build lock (round 12) (#2065)
## Summary
Fixes shard 3 failures from runs 29258546612 + 29259574946 (FN-7936
drift).
## Fixes
### `package-config.test.ts` — stale TRANSITIVE_EXTERNALS entry
FN-7936 aliased `@fusion/core` to a runtime shim in bundled plugin
outputs; it's no longer a tsup external. Removed the stale allowlist
entry.
### `bundle-output.test.ts` — stale dashboard client hash ENOENT
**Root cause:** Two test files (`bundle-output.test.ts` +
`extension-integration.test.ts`) call
`buildCliWithRealDashboardAssets()` which triggers concurrent vite/tsup
builds. Vitest runs them in parallel (`pool: "forks"`, `fileParallelism:
true`). Without coordination, two builds clean and write `dist/client`
simultaneously, causing `ENOENT` on content-hashed chunk files.
**Fix (3 parts):**
1. **`workspace-tools.ts buildDashboardClient`** — `rm dist/client`
before vite build. Prevents stale content-hash references from previous
builds.
2. **`bundle-output-helpers.ts`** — atomic `mkdirSync` file lock around
`buildCliWithRealDashboardAssets()`. Winner builds; losers poll with
`Atomics.wait`, then re-check `hasBuiltDashboardAssets()`. On timeout,
**throws** (never builds without owning the lock).
3. Lock uses `Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0,
0, 500)` for sync sleep — no child process spawning.
## Verification
- Gate: exit 0 ✅
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved dashboard asset builds by removing stale files before
rebuilding.
* Prevented concurrent builds from producing incomplete or corrupted
dashboard assets.
* Added safeguards to detect stalled asset builds and fail with clearer
errors.
* **Tests**
* Updated package validation checks to reflect current runtime bundling
behavior.
* Improved reliability of CLI build-related test execution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
d4001ab0ee |
feat: make merger AI model configurable under Global and Project Models
Add a dedicated merger model lane (project + global provider/model/thinking) so merge-agent sessions no longer share only the default model, without inheriting executor/planner/reviewer lanes. |
||
|
|
e35620c9aa |
FN-7939: supervise heartbeat timer-audit interval and bound non-advancing zombie re-arms
Fixes agents silently going stale for hours even though the heartbeat repair audit process was running. - HeartbeatTriggerScheduler now runs an independent watchdog (armTimerAuditWatchdog/checkTimerAuditLiveness) that tracks the audit loop's last-run timestamp and re-arms + immediately re-runs the 60s audit interval if it goes stale beyond a bounded multiple of the cadence, so a silently dropped audit driver self-heals instead of leaving active agents unrepaired for hours. - Tracks consecutive non-advancing zombie-timer re-arms per agent (nonAdvancingRearmState) and escalates once the count crosses a threshold, recording consecutiveNonAdvancingRearms/nonAdvancingEscalated in agent.metadata.heartbeatTimerRepair and logging reason=heartbeat-rearm-nonadvancing-escalated instead of silently churning the same zombie-timer-rearmed repair forever. - Clears non-advancing rearm state on unregister, non-eligible agents, paused settings, and stale-run-reap skip paths so tracking never leaks stale per-agent counters. - Watchdog and its interval handle are armed in start() and cleared in stop() alongside the existing audit interval. - Adds a changeset (patch) describing the fix, and updates docs/agents.md and docs/architecture.md to document the FN-7939 audit watchdog and non-advancing escalation behavior. - Adds heartbeat-scheduler.test.ts coverage for watchdog re-arm/liveness and non-advancing escalation. Files changed: .changeset/fn-7939-heartbeat-audit-supervision.md | 7 + docs/agents.md | 8 +- docs/architecture.md | 1 + .../src/__tests__/heartbeat-scheduler.test.ts | 209 +++++++++++++++++++++ packages/engine/src/agent-heartbeat.ts | 128 ++++++++++++- 5 files changed, 341 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7939 Fusion-Task-Lineage: 9fa90240-4333-4588-b595-aef3811b1524 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
316d4fa034 |
FN-7941: anchor execute-requeue loop guard to monotonic terminal-step progress
Hardens the FN-7863 execute-node self-requeue loop guard so residual execute_loop_stall cases (#2043/#2045/#2046/#2047) can no longer reset the loop counter forever via non-terminal signature drift. - Change buildExecuteRequeueLoopSignature to track terminal step count (done/skipped) plus total step count instead of raw currentStep + every step status, so pending/in-progress oscillation no longer produces a "new" signature each cycle. - Add buildExecuteRequeueLoopHighWaterSignature, which derives current terminal-step progress via the shared signature parser (parseExecuteRequeueLoopProgressSignature) and only resets the streak on monotonic forward progress, keeping a high-water mark across cycles so decreases/oscillation below the high-water still count toward exhaustion. - Update executor.ts's execute self-requeue dispatch path to use the new high-water helper when deciding whether to reset (1) or increment executeRequeueLoopCount, replacing the previous raw signature-equality check. - Extend execute-requeue-loop-guard.test.ts with regression coverage: a drifting-signature case that oscillates step order/status with no terminal progress (still terminalizes at MAX_EXECUTE_REQUEUE_LOOP_CYCLES), a done/in-progress oscillation case bounded after the high-water stops increasing, and an updated "real progress never terminalizes" case driven by genuine monotonic done-step advancement. - Update docs/architecture.md's FN-7863/FN-7926 self-healing notes to describe the new terminal-step high-water signature and cross-reference FN-7941. Files changed: docs/architecture.md | 4 +- .../execute-requeue-loop-guard.test.ts | 83 +++++++++++++++++++++- packages/engine/src/executor.ts | 54 ++++++++++++-- 3 files changed, 130 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-7941 Fusion-Task-Lineage: cbf1e536-d29b-40da-bdd8-8c34d8d6b1ca Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
cf1b33bf5e |
FN-7942: index Project Models chat-default settings in Settings search
Extends the shared Settings search index so queries like "chat" surface the Project Models section's Direct-chat default fields, which previously had no matching searchable terms. - Add chat-related searchableText keywords (chat, new chat, chat default, chat model, chat agent, etc.) to the project-models SETTINGS_SECTIONS entry - Add searchableKeys covering the i18n chat-default labels (chatHeading, chatDescription, chatNewSessionMode*, chatDefaultKind/Model/Agent) so translated labels are also indexed - Export SettingsSection type plus normalizeSettingsSearchText/sectionMatchesSettingsSearch/filterSettingsSectionsForSearch/SETTINGS_SECTIONS from SettingsModal.tsx for direct unit testing - Add SettingsModal.search.test.ts covering chat-query matches and a negative case (unrelated Remote Access term does not match) - Add changeset fn-7942-settings-search-chat.md (patch, fix category) Files changed: .changeset/fn-7942-settings-search-chat.md | 7 +++ .../dashboard/app/components/SettingsModal.tsx | 50 +++++++++++++++++++--- .../__tests__/SettingsModal.search.test.ts | 41 ++++++++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-7942 Fusion-Task-Lineage: fa044908-7ada-4334-a789-6d2bf08afdb8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
281bb05161 |
FN-7936: alias @fusion/core to a runtime shim in bundled plugin outputs
Fix bundled example plugins (dependency-graph, grok-runtime, roadmap, acp-runtime, compound-engineering) crashing on enable with "Cannot find package '@fusion/core'" by aliasing the private import to a self-contained runtime shim during CLI bundling. - packages/cli/tsup.config.ts: drop @fusion/core from bundlePluginEntry's external list and alias it to the existing pluginSdkCoreRuntimeShim so bundled.js no longer references the private workspace package at runtime - packages/cli/src/__tests__/bundle-output.test.ts: add a regression test asserting every staged bundled plugin's bundled.js contains no bare @fusion/core import/reference - docs/PLUGIN_AUTHORING.md: document that bundled.js outputs must be self-contained and must not leak private @fusion/* workspace imports - .changeset/fn-7936-bundled-plugin-fusion-core-external.md: add a patch changeset for @runfusion/fusion describing the fix Files changed: .changeset/fn-7936-bundled-plugin-fusion-core-external.md | 7 +++++ docs/PLUGIN_AUTHORING.md | 3 +++ packages/cli/src/__tests__/bundle-output.test.ts | 30 ++++++++++++++++++++++ packages/cli/tsup.config.ts | 9 +++++-- 4 files changed, 47 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-7936 Fusion-Task-Lineage: a8a391b2-9441-4a7c-92bc-f1675e1a8a0d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a81486559e |
fix: dashboard lucide/CSS/SettingsModal/mcp-helper fixes + expand mock-completeness guard to dashboard (round 11) (#2049)
## Summary Fixes all remaining full-suite failures from run 29231108919 (FN-7923–7931 drift) + expands the structural mock-completeness guard to cover dashboard tests. ## Fixes ### Dashboard (shard 4) — 4 files - **`TaskCard.cli-states.test.tsx`** — Proxy lucide-react mock needed `has`/`getOwnPropertyDescriptor` traps; TaskCard now imports `priorityIndicator.tsx` which reads `ArrowDown` at module-init. Vitest validates ESM named exports via `in`/descriptor, not `get`. Also added `useToast` mock. - **`SettingsModalNodeRouting.test.tsx`** — Pass `initialSection="node-routing"` (it's in `ADVANCED_SETTINGS_SECTION_IDS`, nav hidden by default). - **`styles-css-rgba-tokenization.test.ts`** — Removed stale `.settings-sidebar` color-mix expectation (FN-7825 made it structural-only). - **`mcp-helper-forwarding.test.ts`** — Added `resolvePlanningThinkingLevel` to `@fusion/engine` mock (insight extraction calls it before MCP forwarding). ### Structural guard expansion - **`.tsx` blind spot fixed** — `collectTs` and test file filter now include `.tsx` files - **Shorthand property extraction** — key extractor now matches both `key: value` and `key,` (shorthand) - **Convention mapping** — `.test.tsx → .tsx` source resolution added - **Dashboard test coverage** — `@fusion/engine` barrel check now scans `packages/dashboard/src/__tests__/` - **7 latent mock gaps completed** — `pr-conflict-resolver`, `project-pause-resume-routes`, `routes-approval-sandbox-provisioning`, `routes-approval`, `routes-worktrunk`, `session-reconnect`, `setup-routes` ### Engine (shards 1+2) — zero real failures All 3 failing files are local-only (`@agentclientprotocol/sdk` + pi-ai staleness). CI resolves them from lockfile. ## Verification - Gate (with expanded guard): exit 0 ✅ - Dashboard (5 non-local files): 36/36 passed ✅ - 3 files skipped locally (`@agentclientprotocol/sdk`) — CI will verify <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved test reliability by completing module mocks across dashboard and routing scenarios. * Updated settings and task card test coverage to reflect current UI behavior. * Enhanced mock validation to cover additional test files, TypeScript React files, and shorthand exports. * Prevented failures related to missing providers, engine helpers, and planning configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
502c4c132f |
chore(release): v0.59.0
Version bump via changesets. |
||
|
|
edfe57c8e0 |
feat: AI Highlights and engagement X draft on release
Claude-author Highlights plus an engagement-oriented tweet (≤280) during changelog distillation, with scheme-free links, Fusion version openers that drop .0 patch, and a soft deterministic fallback when Claude is offline. |
||
|
|
b487f4542d |
fix: raise TaskCard Actions control onto header centerline
Nudge the text+chevron Actions/Send-back chip up by a quarter space-xs so it no longer sits optically below the ⋯ menu and size badge on the locked chip-height row. |
||
|
|
0b60fe9548 |
fix: align TaskCard right header cluster with task-id baseline
Lock .card-id and .card-header-actions to the chip height so the mobile 28px ⋯ touch target cannot stretch the row and sink Actions/size below FN-####. Cancel residual menu layout growth and extend badge-wrap regression coverage for the shared locked-row contract. |
||
|
|
cf58d7234d |
FN-7933: align mobile task-card header controls to one centerline
Follow-up to FN-7928: fix vertical alignment of the Send-back/Actions trigger, menu button, and size badge in the mobile task-card header actions. - Set line-height: 1 on .card-menu-btn, .card-size-badge, and .card-send-back-btn so their text/icon baselines match instead of drifting from default line-height. - Add align-items: center to .card-header-actions at the mobile breakpoint so Send back, menu, and size badge share one optical vertical centerline. - Add padding-block to .card-size-badge to keep the badge's rendered height consistent with the neighboring controls after the line-height fix. - Add regression tests (TaskCard.badge-wrap.test.tsx) asserting the mobile header-actions rule set (min-height, align-items, gap) and per-control line-height/padding declarations, plus an awaiting-user-input coverage case exercising the send-back/menu/size-badge centerline together. Files changed: packages/dashboard/app/components/TaskCard.css | 16 ++++ .../__tests__/TaskCard.badge-wrap.test.tsx | 92 ++++++++++++++++++++++ 2 files changed, 108 insertions(+) Fusion-Task-Id: FN-7933 Fusion-Task-Lineage: e3d52b36-2446-4cdf-8faf-5c94b0f52786 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1f9dcea4b6 |
FN-7935: route mailbox artifact View task to the popped-out task-detail window
Mailbox artifact "View task" now opens the producing task in the same shared, movable/resizable floating task-detail window used elsewhere in the dashboard, instead of the docked task-detail modal. - MainContent's MailboxView onOpenTask handler now calls popOutTaskDetail(task) after fetchTaskDetail resolves, instead of openDetailTask(task), matching DocumentsView's artifact-task open path - add regression test verifying mailbox artifact "View task" clicks resolve the task and route to popOutTaskDetail (not openDetailTask) - update docs/dashboard-guide.md to describe the shared movable/resizable task-detail window behavior - add changeset (patch) documenting the fix for @runfusion/fusion Files changed: .changeset/fn-7935-mailbox-artifact-view-task-popout.md | 7 ++ docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/dashboard/MainContent.tsx | 8 ++- .../MainContent.mailbox-view-task.test.tsx | 83 ++++++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-7935 Fusion-Task-Lineage: 51374962-aa36-4390-a6b5-b519e7fc2bf2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
29560021d3 |
FN-7934: fix chat brain popup clipping in narrow floating windows
Fixes the in-chat model/thinking (Brain) popup being cut off inside a narrow floating Chat window or compact dock on a wide desktop viewport. - Key the popover's viewport-fitting inset layout on ChatView's .chat-view--narrow class (chat surface width) instead of only the @media (max-width: 768px) browser-viewport query, so narrow floating/docked chat surfaces get the fitted layout too. - Add narrow-surface CSS rules for .chat-thinking-level-root, .chat-thinking-popover, .chat-thinking-agent-list, and .chat-thinking-popover-list to constrain position/width/max-height to the chat surface. - Add a CSS-contract regression test asserting both the desktop popover sizing and the new narrow-surface rules stay in sync. - Update docs/dashboard-guide.md to describe the popup staying fitted to the chat surface for narrow floating Chat windows/compact docks, not just mobile/tablet viewports. - Add a patch changeset for @runfusion/fusion documenting the fix. Files changed: .changeset/fn-7934-chat-narrow-model-popup.md | 7 +++++ docs/dashboard-guide.md | 3 +- packages/dashboard/app/components/ChatView.css | 22 +++++++++++++++ .../__tests__/ChatThinkingLevelControl.test.tsx | 32 ++++++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7934 Fusion-Task-Lineage: 30c461c5-a153-4005-8a8c-24f02916a934 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9ba8a2e575 |
FN-7932: add per-lane Reviewer and Planning thinking-level overrides
Adds validatorThinkingLevel and planningThinkingLevel task fields so the Reviewer and Planning AI lanes can override reasoning effort independently of the shared task thinkingLevel, with dashboard UI, storage, and runtime fallback wiring. - Add validatorThinkingLevel and planningThinkingLevel to Task/TaskCreateInput types (packages/core/src/types.ts) - Persist the new fields in the SQLite schema and store read/write/replication paths (packages/core/src/db.ts, store.ts, mesh-task-replication.ts) - Wire executor and triage lanes to fall back per-lane thinking level -> task.thinkingLevel -> existing settings/lane fallback (packages/engine/src/executor.ts, triage.ts) - Add per-lane thinking-level selectors to the ModelSelectorTab UI, alongside the existing thinking-level control (packages/dashboard/app/components/ModelSelectorTab.tsx) - Expose the new fields through the legacy task API and task-workflow routes (packages/dashboard/app/api/legacy.ts, packages/dashboard/src/routes/register-task-workflow-routes.ts) - Document the new settings in dashboard-guide.md and settings-reference.md - Add a minor changeset and unit/integration test coverage for store persistence, routes, UI, and agent-session helpers Files changed: .changeset/per-lane-task-thinking.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 2 +- .../src/__tests__/store-thinking-levels.test.ts | 43 +++++++ packages/core/src/db.ts | 15 ++- packages/core/src/mesh-task-replication.ts | 4 + packages/core/src/store.ts | 24 +++- packages/core/src/types.ts | 12 ++ packages/dashboard/app/api/legacy.ts | 2 + .../dashboard/app/components/ModelSelectorTab.tsx | 126 ++++++++++++++++++++- .../components/__tests__/ModelSelectorTab.test.tsx | 50 +++++++- .../src/__tests__/routes-tasks-ops.test.ts | 74 ++++++++++++ .../src/routes/register-task-workflow-routes.ts | 19 +++- .../src/__tests__/agent-session-helpers.test.ts | 15 +++ packages/engine/src/executor.ts | 16 ++- packages/engine/src/triage.ts | 8 +- 16 files changed, 395 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-7932 Fusion-Task-Lineage: 4202f774-aab9-41d2-86a0-f5277dd0f848 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
5f432971db |
FN-7931: fix terminal workspace picker rendering behind floating terminal modal
Fixes the portaled terminal workspace picker menu appearing invisible behind the floating terminal modal by z-layering it above the panel's floatingZ and hiding it until it is positioned. - Compute terminalWorkspaceMenuFloatingZ one layer above the floating modal's floatingZ (min 5000) so the portaled listbox always renders above the floating terminal stack. - Position the workspace picker menu synchronously via useLayoutEffect before paint, instead of relying only on the async rAF-driven position update. - Keep the menu invisible and non-interactive (visibility: hidden, pointer-events: none) until computed trigger-relative coordinates are applied, avoiding a flash at stale/fallback CSS coordinates. - Add regression tests covering floating-mode z-index layering/pre-rAF positioning and docked/below/embedded/mobile workspace-picker positioning. - Add a patch changeset describing the fix. Files changed: .changeset/terminal-workspace-picker-floating.md | 7 ++ .../dashboard/app/components/TerminalModal.tsx | 37 +++++-- .../components/__tests__/TerminalModal.test.tsx | 120 +++++++++++++++++++++ 3 files changed, 156 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-7931 Fusion-Task-Lineage: 41736804-3f99-4cb7-a0fb-f755a86ffc62 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
27110ed893 |
FN-7930: remove terminal footer 'Connected' label and shortcut help text
Declutters the terminal footer by dropping steady-state noise: the redundant 'Connected' status text (the header status dot already conveys connection state) and the persistent zoom/shortcuts/escape help copy. - Remove .terminal-shortcuts / .terminal-shortcuts--header CSS rules (including responsive breakpoint overrides) as orphaned styling - Stop rendering the 'Connected' text and the Ctrl++/- zoom / Shortcuts panel / Esc close help span in TerminalModal's footer - Drop the now-unused terminal.helpText locale key from all 6 locales (en, es, fr, ko, zh-CN, zh-TW) and regenerate resources.d.ts - Update TerminalModal tests to match the trimmed footer markup and add a regression test asserting the connected-status text and shortcut help are omitted - Add a patch changeset documenting the fix Files changed: .changeset/remove-terminal-footer-noise.md | 7 +++++ .../dashboard/app/components/TerminalModal.css | 34 --------------------- .../dashboard/app/components/TerminalModal.tsx | 7 +++-- .../components/__tests__/TerminalModal.test.tsx | 35 +++++++++++++++++----- packages/i18n/locales/en/app.json | 1 - packages/i18n/locales/es/app.json | 1 - packages/i18n/locales/fr/app.json | 1 - packages/i18n/locales/ko/app.json | 1 - packages/i18n/locales/zh-CN/app.json | 1 - packages/i18n/locales/zh-TW/app.json | 1 - packages/i18n/src/resources.d.ts | 1 - 11 files changed, 40 insertions(+), 50 deletions(-) Fusion-Task-Id: FN-7930 Fusion-Task-Lineage: 2b8acb5d-c4d4-4766-ba84-b2691a7eb5e8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
da919fddea |
FN-7928: align TaskCard header-actions controls on a shared centerline
Fixes vertical baseline drift among the Send-back button, ⋯ menu button, and size badge inside .card-header-actions by normalizing their line-height, plus adds regression coverage. - Set line-height: 1 on .card-menu-btn and .card-send-back-btn so their inline-flex content no longer drifts off-center relative to the size badge - Add FNXC:TaskCardLayout comment documenting the FN-7928 requirement and its coexistence with prior FN-7889/FN-7862/FN-7837/FN-4351 header rules - Add a badge-wrap regression test asserting Send-back/menu/size controls share one optical centerline across in-progress, done, triage, no-menu, and no-size card states Files changed: packages/dashboard/app/components/TaskCard.css | 6 + packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx | 136 +++++++++++++++++++++ 2 files changed, 142 insertions(+) Fusion-Task-Id: FN-7928 Fusion-Task-Lineage: 1b4ad644-8495-4ea1-8687-0849d6a3319b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
967f3dd900 |
FN-7923: align task-card cost badge bottom-right with other footer chips
Narrative: Reworked TaskCard footer/meta layout so the cost badge (and its sibling footer-right chips) render inline at the bottom-right of the card-meta row when the footer has no leading content, instead of always sitting in a separate footer row beside the time badge. - Extracted the footer-right chip cluster (cost, time, retry, near-duplicate, undo-of, GitHub tracking) into a shared `footerRightCluster` render, computed once instead of duplicated inline. - Added `footerHasLeadingContent`/`footerRightHasContent`/`placeFooterRightInMeta` derivations so the cluster moves into `.card-meta` (bottom-right, inline with other tags) when there's no files-changed button or GitHub-import leading content, and the meta row is visible; otherwise it keeps the existing `.card-footer-row` placement for in-progress/tracked cards. - Updated dashboard-guide.md wording to describe the cost badge as appearing 'with the card's other footer/meta chips' rather than 'beside the execution-time badge'. - Extended TaskCard.test.tsx coverage for the new placement behavior. - Desktop local-runtime.ts: kept the previously-unused `reason` parameter on `requestRestart` explicitly referenced (void reason) for API parity/lint cleanliness, unrelated cosmetic cleanup carried in the same branch. Files changed: docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/TaskCard.tsx | 242 +++++++++++---------- .../app/components/__tests__/TaskCard.test.tsx | 96 +++++++- packages/desktop/src/local-runtime.ts | 4 +- 4 files changed, 222 insertions(+), 122 deletions(-) Fusion-Task-Id: FN-7923 Fusion-Task-Lineage: 7e4c3109-f39e-45c0-af13-358ce54f945c Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
30d2e3660d |
FN-7927: fix Refine feedback modal self-dismissing immediately after opening
The task-detail Refine overlay used a raw onClick backdrop handler with a stopPropagation-wrapped inner modal, so the same click/touch sequence that opened Refine could bubble into the backdrop handler and close it right away; route it through the shared useOverlayDismiss contract instead so it behaves like every other dashboard modal. - Compute refineOverlayDismissProps via useOverlayDismiss(handleCloseRefineModal) and spread it onto the refine overlay instead of a plain onClick handler - Drop the redundant stopPropagation-only onClick from the inner .detail-refine-modal div now that the overlay itself no longer misfires on the opening interaction - Update docs/dashboard-guide.md to document that the Refine modal (Board and List entry points) stays open until an explicit close or an enabled backdrop dismissal - Add TaskDetailModal.refine.test.tsx regression coverage for the modal staying open across the opening interaction and honoring the dismiss-preference gate - Add a patch changeset summarizing the fix for @runfusion/fusion release notes Files changed: .changeset/fn-7927-refine-modal.md | 7 + docs/dashboard-guide.md | 8 +- .../dashboard/app/components/TaskDetailModal.tsx | 12 +- .../__tests__/TaskDetailModal.refine.test.tsx | 182 +++++++++++++++++++++ 4 files changed, 201 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-7927 Fusion-Task-Lineage: c7e7cd4a-d103-47e6-93ce-6577147b4795 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
f7e942e6f4 |
fix: resolve all full-suite failures + add structural mock-completeness gate check (round 10) (#2040)
## Summary
Fixes ALL failing shards from the latest full-suite run (29225946428)
AND adds a structural gate check to prevent the recurring mock-export
drift pattern that has caused every full-suite failure across rounds
1–9.
## What broke (run 29225946428, commit
|
||
|
|
6dcecb0c34 |
FN-7926: park completed-but-blocked tasks instead of looping execute-requeue
Stops the execute → pause-abort → re-queue-to-todo infinite loop for tasks whose implementation work is done but a dependency/blockedBy blocker is still live, by diverting them into a dedicated parked state instead of feeding the FN-7863 no-progress backstop or looping forever. - Add TaskExecutor.parkCompletedBlockedTask(): when work is complete but getTaskCompletionBlocker() still reports a blocker, park the task in todo with pausedReason:"completed-work-blocked", status:"queued", preserved worktree/branch/steps, and a cleared execute-requeue signature. - Replace shouldFinalizeCompletedTask's boolean with getCompletedTaskFinalizationDecision() returning "finalize" | "blocked" | "incomplete" so both the paused-after-completion and finalization call sites can react to the new "blocked" outcome without re-entering execution. - Divert completed-but-blocked tasks before the FN-7863 execute-requeue-loop counter increments, so waiting-on-dependency states are no longer misclassified as EXECUTION_DISPATCH_LOOP_EXHAUSTED. - Add SelfHealingManager.reconcileCompletedBlockedTasks(): a bounded sweep (wired into both startup/maintenance and periodic self-healing passes) that clears the park and advances the task to review once getTaskCompletionBlockerForStore() resolves, guarded by auto-merge eligibility, user-pause, and live-execution checks; failed advances re-park rather than strand the row. - Add run-audit mutation types task:completed-blocked-parked and task:completed-blocked-advanced (ids/counts/outcomes-only metadata) plus AGENTS.md/docs/architecture.md entries documenting the new lifecycle. - Extend execute-requeue-loop-guard.test.ts with coverage for the park/advance flow, including the zero-step task edge case. Files changed: AGENTS.md | 1 + docs/architecture.md | 2 + .../execute-requeue-loop-guard.test.ts | 256 ++++++++++++++++++++- packages/engine/src/executor.ts | 85 ++++++- packages/engine/src/run-audit.ts | 4 + packages/engine/src/self-healing.ts | 95 ++++++++ 6 files changed, 432 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-7926 Fusion-Task-Lineage: e47945f4-a816-447e-9ea1-7c13105d0ba9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e84fda936a |
FN-7918: make chat go-to-top contextual and inline edit pencil compact
Reworks chat message footer affordances: the scroll-to-top control now only becomes visible once a message's top is actually clipped above the visible thread viewport, and the edit pencil moves from a standalone action row into the timestamp footer beside user messages. - ChatView measures assistant message tops on scroll/message changes (rAF-scheduled) and tracks which message IDs are currently clipped above the `.chat-messages` container edge - StandardChatMessageItem accepts a new `isTopClipped` prop; the go-to-top button stays DOM-mounted (for tests/a11y) but is visually hidden via CSS until clipped - Merged the assistant thinking/copy/scroll-to-top actions into a single collapsible footer row instead of separate action rows - Moved the user-message edit pencil into an inline `chat-message-time-row` next to the relative timestamp instead of a standalone action row above it - Updated ChatView.css for the new inline layout, collapsed-row state, and hidden/visible scroll-to-top button states - Updated message-edit and scroll-to-top tests to cover the new inline placement and clipped-visibility behavior - Added changeset and docs/dashboard-guide.md note describing the new behavior Files changed: .changeset/fn-7918-chat-inline-icons.md | 7 ++ docs/dashboard-guide.md | 6 +- packages/dashboard/app/components/ChatView.css | 80 +++++++++++++++------- packages/dashboard/app/components/ChatView.tsx | 52 +++++++++++++- .../app/components/StandardChatSurface.tsx | 33 +++++++-- .../__tests__/ChatView.message-edit.test.tsx | 34 ++++++++- .../__tests__/ChatView.scroll-to-top.test.tsx | 75 +++++++++++++++++++- 7 files changed, 253 insertions(+), 34 deletions(-) Fusion-Task-Id: FN-7918 Fusion-Task-Lineage: 76206cd2-94a8-47be-b282-94943e184d01 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
c8999369c3 |
feat: add gate check for CLI dashboard mock completeness — prevents recurring full-suite barrel-export drift (#2035)
## Summary
**Structural fix** for the recurring full-suite failure pattern where a
new `@fusion/dashboard` barrel export is imported by CLI source code but
missing from the hardcoded `vi.mock("@fusion/dashboard")` factory in CLI
tests.
## What's new
### Gate check script:
`scripts/check-cli-dashboard-mock-completeness.mjs`
Added to the merge gate (`pnpm test:gate`). Statically validates that
every hardcoded `vi.mock("@fusion/dashboard")` factory in CLI tests
includes all `@fusion/dashboard` exports that the corresponding source
files import.
- Pure static analysis (regex + depth-aware brace tracking) — no module
evaluation, <0.1s
- Handles named imports (`import { foo } from "@fusion/dashboard"`) AND
namespace imports (`import * as dashboard from "@fusion/dashboard"` →
scans `dashboard.X` usages)
- Filters against the real barrel exports to avoid false positives from
typos
- Resolves test→source mapping by parsing static/dynamic imports in the
test file (not just naming convention)
**Result:** the next time someone adds `export { newFunc } from
"./mod.js"` to `dashboard/src/index.ts` and `cli/src/commands/daemon.ts`
imports it, the gate catches the missing mock before merge instead of
the full-suite failing on main.
### Completed all 9 incomplete CLI dashboard mocks
Added the missing exports identified by the check:
| File | Missing exports added |
|---|---|
| `daemon.test.ts` | `registerGithubTrackingHook` |
| `serve.test.ts` | `registerGithubTrackingHook` |
| `dashboard.test.ts` | `AttachTicketStore`, `CliInputAttributionLog`,
`CliConfirmAdvanceRegistry`, `CliRelaunchRegistry`,
`registerGithubTrackingHook` |
| `task.test.ts` | `registerGithubTrackingHook`, `GitLabClient`,
`resolveGitlabAuth`, `buildGitLabTaskProvenance`,
`isGitLabAlreadyImported`, `buildGitLabTaskDescription` |
| `extension-*.test.ts` (×4) | `GitLabClient`, `resolveGitlabAuth`,
`buildGitLabTaskProvenance`, `isGitLabAlreadyImported`,
`buildGitLabTaskDescription` |
| `task-command-github-import-tracking.test.ts` | Same GitLab exports |
These were latent issues — the mocks were incomplete but tests passed
because the missing exports weren't called during test execution. Any
test change that exercises those code paths would have broken.
## Why not `importActual` spread?
Tried converting daemon.test.ts to `vi.mock("@fusion/dashboard", async
(importOriginal) => { ... })` — fails because the barrel's `export *
from "./plugins/index.js"` transitively imports
`@agentclientprotocol/sdk` which isn't available at test evaluation
time. The static check approach avoids this entirely.
## Verification
- `pnpm test:gate`: exit 0 (includes new check)
- `pnpm lint`: exit 0
- CLI tests: daemon 21/21, serve 58/58, dashboard 91/91, task 149/149 ✅
- Gate script: `✅ CLI dashboard mock completeness: all hardcoded mocks
cover source imports.`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Tests**
- Added automated validation to ensure CLI test mocks remain aligned
with available dashboard functionality.
- Updated test coverage setup so GitHub, GitLab, daemon, dashboard,
server, and task scenarios use complete dashboard mocks.
- Test verification now reports missing mocked functionality and blocks
the release gate when inconsistencies are detected.
- **Chores**
- Improved reliability and maintainability of automated verification for
CLI and dashboard integrations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|