Commit Graph

2359 Commits

Author SHA1 Message Date
gsxdsm
fd43a57a41 FN-8179: align pi SDK versions with ModelRuntime API
Align workspace pi SDK dependencies with the ModelRuntime API required by the engine.

- Pin pi AI and coding-agent packages to 0.80.10 across workspace consumers.
- Keep session option typing compatible with the updated SDK contract.
- Add a patch changeset and regenerate the dependency lockfile.

Files changed:
 .changeset/fn-8179-pi-sdk-align.md                 |   7 +
 packages/cli/package.json                          |   4 +-
 packages/core/package.json                         |   2 +-
 packages/dashboard/package.json                    |   2 +-
 packages/engine/package.json                       |   4 +-
 packages/engine/src/pi.ts                          |   8 +-
 packages/pi-claude-cli/package.json                |   8 +-
 .../src/thinking-config.ts                         |   9 +-
 pnpm-lock.yaml                                     | 947 +++++++++++----------
 pnpm-workspace.yaml                                |   5 +
 10 files changed, 520 insertions(+), 476 deletions(-)

Fusion-Task-Id: FN-8179

Fusion-Task-Lineage: aef45c2e-f353-4014-93de-44be91f43293

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 19:01:03 -07:00
gsxdsm
c449379d00 FN-8172: persist import translations across restarts
Persist GitHub and GitLab import translation caches across application restarts.

- Normalize cache ownership scope for reads, writes, pruning, and PostgreSQL RLS
- Add forward migration 0016 to repair existing cache table partitioning
- Cover durable cache reuse and migration behavior with PostgreSQL and service tests

Files changed:
 .changeset/github-translation-cache-persistence.md |   7 ++
 docs/settings-reference.md                         |   2 +-
 .../postgres/import-translation-cache.pg.test.ts   | 105 +++++++++++++++++++++
 .../src/__tests__/postgres/schema-applier.test.ts  |  84 +++++++++++++++--
 .../migrations/0010_import_translation_cache.sql   |   6 +-
 .../0016_import_translation_cache_scope_fix.sql    |  50 ++++++++++
 packages/core/src/postgres/schema-applier.ts       |  29 +++++-
 packages/core/src/postgres/schema/project.ts       |   8 +-
 packages/core/src/task-store/remaining-ops-8.ts    |  21 +++--
 .../src/__tests__/import-translate-service.test.ts |  32 ++++++-
 packages/dashboard/src/import-translate-service.ts | Bin 10392 -> 11109 bytes
 11 files changed, 323 insertions(+), 21 deletions(-)

Fusion-Task-Id: FN-8172

Fusion-Task-Lineage: b3d18f58-dc0b-47e4-a1bf-acb8b2360869

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 18:46:36 -07:00
gsxdsm
07e1ceb860 FN-8151: add Cobalt, Clay, and Moss themes
Add three synchronized color themes across dashboard and desktop surfaces.

- Register Cobalt, Clay, and Moss theme IDs, token palettes, and light/dark swatches.
- Keep dashboard and desktop bootstrap validators aligned with selector metadata.
- Document and test the cross-surface theme registration invariant.

Files changed:
 .changeset/fn-8151-new-themes.md                   |   7 +
 docs/dashboard-guide.md                            |   3 +-
 packages/core/src/types/execution-and-ui.ts        |   4 +
 .../app/__tests__/new-color-themes.test.ts         | 116 ++++++++++++
 .../dashboard/app/components/ThemeSelector.css     |  42 +++++
 packages/dashboard/app/components/themeOptions.ts  |   3 +
 packages/dashboard/app/index.html                  |   2 +-
 packages/dashboard/app/public/theme-data.css       | 202 +++++++++++++++++++++
 packages/desktop/src/renderer/index.html           |   3 +
 9 files changed, 380 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-8151

Fusion-Task-Lineage: d97973a7-3387-4b98-b150-8ac2e838bfa9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 18:18:35 -07:00
gsxdsm
5a60643c0a FN-8142: migrate auth storage and model runtime to pi SDK
Migrate Fusion's credential and model integrations to pi SDK 0.80.8+.

- Replace legacy AuthStorage initialization with a locked Fusion credential store and ModelRuntime-backed registry.
- Wire asynchronous model initialization and refresh through CLI, desktop, dashboard, executor, and provider paths.
- Update provider, routing, and registry tests for the new SDK contracts.

Files changed:
 packages/cli/src/commands/__tests__/daemon.test.ts |   2 +-
 .../cli/src/commands/__tests__/dashboard.test.ts   |   9 +-
 .../cli/src/commands/__tests__/onboard.test.ts     |   1 +
 packages/cli/src/commands/__tests__/serve.test.ts  |   2 +-
 packages/cli/src/commands/daemon.ts                |  19 +-
 packages/cli/src/commands/dashboard.ts             |  20 +-
 packages/cli/src/commands/onboard.ts               |   6 +-
 packages/cli/src/commands/serve.ts                 |  19 +-
 packages/cli/src/commands/startup-model-sync.ts    |   4 +-
 packages/core/src/__tests__/openai-models.test.ts  |  17 +-
 ...-model-routes-openai-codex-supplemental.test.ts |  17 +-
 ...register-model-routes-zai-real-registry.test.ts |  15 +-
 packages/dashboard/src/routes.ts                   |  12 +-
 .../dashboard/src/routes/register-model-routes.ts  |   2 +-
 packages/desktop/src/local-runtime.ts              |   2 +-
 packages/desktop/src/local-server.ts               |   2 +-
 .../custom-providers-openai-completions.test.ts    |  16 +-
 .../custom-providers-openai-responses.test.ts      |  16 +-
 .../engine/src/__tests__/executor-test-helpers.ts  |   2 +-
 .../src/__tests__/pi-create-fn-agent.test.ts       |   8 +-
 .../engine/src/__tests__/pi-layers-wiring.test.ts  |   2 +-
 packages/engine/src/__tests__/pi.test.ts           |  47 ++---
 .../src/__tests__/provider-registration.test.ts    |  17 +-
 packages/engine/src/auth-storage.ts                | 218 ++++++++++++++++++---
 packages/engine/src/custom-provider-registry.ts    |  14 +-
 packages/engine/src/executor.ts                    |  15 +-
 packages/engine/src/pi.ts                          |  50 +++--
 packages/engine/src/provider-auth.ts               |  58 +++---
 packages/engine/src/provider-registration.ts       |  16 +-
 29 files changed, 421 insertions(+), 207 deletions(-)

Fusion-Task-Id: FN-8142

Fusion-Task-Lineage: 8ae79064-7820-4976-9645-9431b5a3129e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 18:13:37 -07:00
gsxdsm
ca7a5a7106 FN-8144: remove workspace worktrees on archive
Archive workspace task worktrees synchronously and safely across archive entry points.

- Add store-scoped workspace disposal planning, reservations, and quarantine handling.
- Install baseline and executor disposers that remove per-repository worktrees and branches without shell interpolation.
- Cover disposal-plan deduplication and document the archive cleanup behavior.

Files changed:
 .../fn-8144-archive-removes-workspace-worktrees.md |   7 ++
 AGENTS.md                                          |   1 +
 docs/task-management.md                            |   4 +
 .../archive-removes-workspace-worktrees.test.ts    |  59 +++++++++++
 packages/core/src/archive-worktree-disposer.ts     |  52 ++++++++++
 packages/core/src/index.gate.ts                    |   8 ++
 packages/core/src/index.ts                         |   8 ++
 .../core/src/task-store/archive-lifecycle-2.ts     |  29 ++++--
 packages/core/src/task-store/archive-lifecycle.ts  | 114 ++++++++++++++++++++-
 .../src/archive-worktree-disposer-install.ts       |  27 ++++-
 packages/engine/src/executor.ts                    |  25 ++++-
 11 files changed, 319 insertions(+), 15 deletions(-)

Fusion-Task-Id: FN-8144

Fusion-Task-Lineage: 1c4b65f3-a1d2-4a5c-a4b6-c263f9e6f61d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 17:05:29 -07:00
gsxdsm
fd036665ae FN-8123: add agent clarification notifications
Add agent clarification notices with ntfy delivery and mailbox support.

- Add configurable clarification ntfy topics and notification settings
- Route agent clarification requests to ntfy and the planning mailbox
- Update planning dialogs, translations, documentation, and regression tests

Files changed:
 .changeset/fn-8123-agent-clarification.md          |   7 +
 docs/dashboard-guide.md                            |   6 +
 docs/settings-reference.md                         |   1 +
 .../core/src/__tests__/global-settings.test.ts     |  11 +
 packages/core/src/settings-schema.ts               |   2 +
 packages/core/src/types.ts                         |   6 +
 .../app/__tests__/settings-save-split.test.ts      |  13 ++
 packages/dashboard/app/api/legacy.ts               |   3 +-
 .../dashboard/app/components/PlanningModeModal.tsx |  29 ++-
 .../dashboard/app/components/SettingsModal.tsx     |   1 +
 .../__tests__/PlanningModeModal.initial.test.tsx   |  17 +-
 .../app/components/settings/save-split.ts          |   1 +
 .../settings/sections/NotificationsSection.tsx     |  11 +
 .../src/__tests__/routes-planning.test.ts          |   7 +-
 packages/dashboard/src/planning.ts                 | 228 ++++++++++++++++-----
 .../src/routes/register-planning-subtask-routes.ts |  59 +++++-
 packages/i18n/locales/en/app.json                  |   4 +
 packages/i18n/src/resources.d.ts                   |   4 +
 18 files changed, 346 insertions(+), 64 deletions(-)

Fusion-Task-Id: FN-8123

Fusion-Task-Lineage: cf397749-01c6-44d6-8d12-8ce7f5627c29

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 16:51:19 -07:00
gsxdsm
c6be0b158b FN-8129: centralize database backup settings
Move database backup policy and scheduling to shared global configuration.

- Split project memory backups from cluster-wide database backup settings.
- Migrate legacy backup values and routines safely into central global storage.
- Schedule and dispatch one shared PostgreSQL backup routine across project engines.

Files changed:
 .changeset/fn-8129-backup-settings-scope-split.md  |   7 +
 docs/dashboard-guide.md                            |   2 +
 docs/settings-reference.md                         |  10 +-
 packages/cli/src/commands/backup.ts                |   3 +-
 .../__tests__/backup-settings-migration.test.ts    |  50 ++++++
 .../src/__tests__/backup-settings-scope.test.ts    |  27 +++
 packages/core/src/backup-settings-migration.ts     | 188 +++++++++++++++++++++
 packages/core/src/backup.ts                        |  77 +++++----
 packages/core/src/global-routine-store.ts          | 104 ++++++++++++
 packages/core/src/index.gate.ts                    |   6 +-
 packages/core/src/index.ts                         |   6 +-
 .../core/src/postgres/migrations/0000_initial.sql  |  19 +++
 .../postgres/migrations/0015_global_routines.sql   |  19 +++
 packages/core/src/postgres/schema-applier.ts       |  19 ++-
 packages/core/src/postgres/schema/central.ts       |  21 ++-
 packages/core/src/postgres/startup-factory.ts      |  11 ++
 packages/core/src/settings-schema.ts               |  14 +-
 packages/core/src/types.ts                         |  31 +++-
 .../dashboard/app/components/SettingsModal.tsx     |  10 +-
 .../settings/__tests__/section-keys.test.ts        |   1 +
 .../app/components/settings/save-split.ts          |   2 +
 .../search/__tests__/settings-search-index.test.ts |   1 +
 .../settings/search/entries.ts                     |   2 +
 .../app/components/settings/section-keys.ts        |   4 -
 .../settings/sections/BackupsSection.search.ts     |  40 -----
 .../settings/sections/BackupsSection.tsx           | 112 +-----------
 .../sections/DatabaseBackupsSection.search.ts      |  51 ++++++
 .../settings/sections/DatabaseBackupsSection.tsx   | 142 ++++++++++++++++
 .../settings-default-descriptions.test.tsx         |   1 +
 packages/dashboard/src/routes.ts                   |  12 +-
 .../src/routes/register-settings-memory-routes.ts  |  41 ++---
 .../engine/src/__tests__/routine-scheduler.test.ts |  55 +++++-
 packages/engine/src/cron-runner.ts                 |   4 +-
 packages/engine/src/routine-runner.ts              |  67 +++++---
 packages/engine/src/routine-scheduler.ts           |  35 +++-
 35 files changed, 929 insertions(+), 265 deletions(-)

Fusion-Task-Id: FN-8129

Fusion-Task-Lineage: af17f39a-7f1c-40ff-8a4a-cd63895cd532

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 16:34:09 -07:00
gsxdsm
478f226a54 test: green full-suite CI after main drift (#2229)
## Summary
Restores green **Full Suite (non-blocking)** runs on `main`. Recent main
merges left i18n key parity, schema baseline bookkeeping (0011→0012),
heartbeat tool inventory (FN-8058 `fn_task_logs_read`), and merger
whitespace-classification mocks (execFile `git diff -p -w :2: :3:`) out
of date, so all four test shards failed.

## Root causes observed on main
- **Shard 4 / `@fusion/i18n`**: missing `skipConfirmationDialogs*` +
`reviewBudgetExhausted` in non-en locales; orphan
`awaitingApprovalPlanReviewReplanCap`
- **Shard 3 / `@fusion/core`**: `SCHEMA_BASELINE_VERSION` advanced to
`0012` while tests still equated it with
`OWNER_PROJECT_ID_SPLIT_VERSION` (`0011`) and omitted `0012` from
applied-migration lists
- **Shards 1–2 / `@fusion/engine`**: tool count/snapshot drift for
`fn_task_logs_read`; merger tests still mocked `git diff-tree` for
trivial classification after the execFile `:2:`/`:3:` cutover; mock
provider `updateTask` arity drift

## Changes
- Locale catalogs: add missing keys, drop orphan key
- Schema applier tests: immutable 0011 identity + baseline 0012 lists
- Heartbeat + gating snapshots: include `fn_task_logs_read`
- Merger unit mocks: recognize `git diff -p -w :2:path :3:path`
- Mock provider: accept optional third `updateTask` arg

## Test plan
- [x] `pnpm --filter @fusion/i18n exec vitest run` — 23/23
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/postgres/schema-applier.test.ts` (immutable + automation
upgrade) — pass
- [x] `pnpm --filter @fusion/core exec vitest run` project-identity +
satellite-fusiondir — pass
- [x] Engine suites from failed CI shards (file-scoped,
hermes/openclaw/paperclip/grok, reliability post-finalize/mission,
heartbeat, gating, merger recovery/prompt, mock-provider, etc.) — pass
- [ ] Full Suite workflow green on merge to main

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

- **New Features**
  - Improved project data isolation across backend operations.
- Added safer optional toast handling when UI components render outside
the full application shell.
  - Added support for reading task logs during agent heartbeat sessions.

- **Bug Fixes**
- Prevented runtime probes from hanging and avoided scanning large
binary files.
  - Improved path handling for workspaces with missing descendants.
- Corrected task retry state resets and GitHub import/issue-close
behavior.

- **Style**
  - Improved chat, terminal, and settings spacing.
  - Added clearer accessibility labeling for the auto-merge control.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 16:01:29 -07:00
gsxdsm
06a5fd813d refactor: package code organization waves 6–7 (#2166)
## Summary

Waves 6–7 of package code organization (plan:
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`),
after #2148.

### Wave 6
| New module | Parent |
|---|---|
| `merger-autostash-labels.ts` | `merger.ts` |
| `types/agent-state.ts` | `types.ts` |
| `app/api/tasks-lifecycle.ts` | `legacy.ts` |
| `task-store/task-row-mappers.ts` | `remaining-ops-3.ts` (rename) |

### Wave 7
| New module | Parent |
|---|---|
| `self-healing-optional-step-revision.ts` | `self-healing.ts` |
| `self-healing-path-utils.ts` | `self-healing.ts` |
| `merger-git-parse` (+ `quoteArg`, `getBranchChangedFiles`) |
`merger.ts` |
| `app/api/settings.ts` | `legacy.ts` |

Public import paths stay stable via re-exports.

## Test plan
- [x] engine + dashboard typecheck (incl. app)
- [x] eslint on touched modules
- [x] merger-autostash / parse-porcelain / getBranchChanged / api-tasks
- [ ] CI merge gate

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

* **New Features**
* Added a dedicated task lifecycle API client (task promotion, merge,
retry/reset/duplicate, pause/unpause, archive/unarchive, revert, plan
approve/reject) plus branch-group and planner oversight actions.
* Added a settings/config API service (effective task settings, update
check/refresh/install).
* Introduced standardized agent lifecycle states with identity/ephemeral
detection helpers.
* **Bug Fixes**
* Improved autostash label compatibility and NUL-delimited changed-file
detection for branch diffs.
* **Refactor**
* Modularized merger labeling/parsing, self-healing helpers, and
lifecycle/type wiring while keeping behavior consistent.
* **Tests**
  * Updated merger verification tests for `git diff -z` output handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 15:42:22 -07:00
gsxdsm
edc64138e0 feat(worktrees): task-pinned worktrees under worktreeNaming "task-id" (#2233)
## Summary

Adds **task-pinned worktrees** for `worktreeNaming: "task-id"`. Under
task-id naming, a task is pinned to exactly one derivable directory
`<worktreesDir>/<lowercased-task-id>` (e.g. `.worktrees/fn-7996`) for
its entire lifecycle — removing the ambiguity that let stale/foreign
`task.worktree` pointers strand a task (the FN-7996 shape).

`recycleWorktrees` stays fully functional and is **mutually exclusive**
with task-id pinning: the two can't be enabled together.

## Behavior

- **Pinned acquisition (`worktreeNaming: "task-id"`, recycling off):**
`acquireTaskWorktree` runs **derive → validate → reuse-or-recreate** at
the derived path — warm-reuse when the dir is a registered, usable
worktree on the task's own branch; otherwise reclaim-in-place
(`removeWorktree` + recreate at the SAME path, never a sibling name). A
disagreeing `task.worktree` cache self-corrects and emits a new
`worktree:pin-rederived` audit event, without consuming worktree-session
retries. The recycle pool is never consulted in pinned mode.
- **Mutual exclusivity:** enabling both `recycleWorktrees` and
`worktreeNaming: "task-id"` is rejected at the settings-write boundary —
HTTP 400 at `PUT /settings`, and an `Error` backstop in
`store.updateSettings` covering the CLI and every other writer
(`assertWorktreeNamingRecycleExclusive`). The runtime also gates pinned
mode on `!recycleWorktrees`, so a legacy on-disk config carrying both
degrades safely to recycling.
- **Settings UI:** the Settings → Worktrees panel enforces the
exclusivity bidirectionally — the *Recycle worktrees* toggle is disabled
while naming is *Task ID*, and the naming select is disabled while
recycling is on — so the conflicting state is unreachable, with help
text explaining why.
- **Byte-inert for the rest:** `random`/`task-title` naming and the
recycle pool (incl. `merger.ts` release) are unchanged;
worktrunk-managed layouts bypass pinning.

## Acceptance criteria (from the plan)

1. ✅ Pinned task dispatched N times only ever touches
`<worktreesDir>/<task-id>` on its own branch
2. ✅ No code path can hand task A's dir to task B (pool bypassed; path
derived from task id)
3. ✅ FN-7996 stale/foreign `task.worktree` self-corrects at next
dispatch (`worktree:pin-rederived`) without consuming session retries
4. ✅ Non-pinned modes with `recycleWorktrees: true|false` are
byte-identical (existing pool tests pass unchanged)
5. ✅ Stale same-name dir (crash leftover / archive→restore) reclaimed in
place, never suffixed
6. ✅ Docs updated (settings-reference, architecture, `worktreeNaming`
type doc); changeset (`minor`, `feature`); FNXC comments encode the
invariant

## Files

- `packages/engine/src/worktree-pinning.ts` — new pure helpers
(`isTaskPinnedWorktreeNaming`, `pinnedWorktreePathForTask`)
- `packages/engine/src/worktree-acquisition.ts` — pinned branch +
branch-match reclaim-in-place
- `packages/engine/src/run-audit.ts` — `worktree:pin-rederived` audit
type
- `packages/core/src/settings-validation.ts` (+ `index.ts`,
`task-store/settings-ops.ts`) — mutual-exclusion validator + wiring
- `packages/dashboard/src/routes/register-settings-memory-routes.ts` —
400 on conflict
-
`packages/dashboard/app/components/settings/sections/WorktreesSection.tsx`
(+ `packages/i18n/locales/en/app.json`) — bidirectional UI exclusivity
- `packages/core/src/types.ts`, `docs/*`, `.changeset/*`

## Verification

- New tests: engine `worktree-pinning` (5) +
`worktree-acquisition-pinned` (7); core
`worktree-naming-recycle-exclusive` (2); dashboard settings-route 400
(3) + WorktreesSection UI exclusivity (3)
- Regression sweep green: 194 engine
worktree/acquisition/pool/executor/merger-release tests, core settings
tests, dashboard i18n/settings-section tests
- `tsc --noEmit` clean for `@fusion/core` and `@fusion/engine`; changed
source files clean; eslint clean
- `pnpm verify:fast` PASS (build + scoped typecheck + boot smoke)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

## Summary by CodeRabbit

* **New Features**
* Added Task ID worktree naming, providing each task with a stable,
deterministic worktree directory.
* Automatically reuses valid pinned worktrees and recreates stale or
conflicting ones at the same path.
* Added clear settings controls and validation for incompatible Task ID
naming and worktree recycling options.

* **Documentation**
* Updated worktree architecture, settings reference, and in-app guidance
to explain pinned worktrees and configuration constraints.

* **Bug Fixes**
* Improved recovery from stale or incorrect worktree assignments without
consuming session retries.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 15:37:42 -07:00
gsxdsm
f57dfc03b6 FN-8105: remove archived task worktrees safely
Archive task worktrees through a store-scoped, race-safe disposal lifecycle.

- Reserve pinned worktree paths during archive cleanup and successor creation.
- Reconcile quarantined removals before reusing a pinned path.
- Gate PostgreSQL archival before destructive worktree disposal and wire CLI cleanup.

Files changed:
 .changeset/fn-8105-archive-removes-worktree.md     |   7 +
 docs/task-management.md                            |   4 +
 .../extension-experiment-finalize.test.ts          |   1 +
 .../src/__tests__/extension-fn-secret-get.test.ts  |   1 +
 .../extension-gitlab-tracking.test.ts              |   1 +
 .../cli/src/__tests__/extension-web-fetch.test.ts  |   1 +
 .../task-command-github-import-tracking.test.ts    |   1 +
 packages/cli/src/commands/__tests__/task.test.ts   |   1 +
 packages/cli/src/commands/task.ts                  |   8 +-
 packages/cli/src/extension.ts                      |   4 +
 .../__tests__/worktree-path-reservation.test.ts    |  58 ++++++++
 packages/core/src/archive-worktree-disposer.ts     |  21 +++
 packages/core/src/index.gate.ts                    |  13 ++
 packages/core/src/index.ts                         |  13 ++
 .../core/src/task-store/archive-lifecycle-2.ts     |   8 ++
 packages/core/src/task-store/archive-lifecycle.ts  |  37 +++++
 packages/core/src/worktree-path-reservation.ts     | 149 +++++++++++++++++++++
 .../src/archive-worktree-disposer-install.ts       |  18 +++
 packages/engine/src/executor.ts                    |  16 +++
 packages/engine/src/index.ts                       |   2 +
 packages/engine/src/runtimes/in-process-runtime.ts |   1 +
 packages/engine/src/worktree-acquisition.ts        |  27 +++-
 22 files changed, 388 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-8105
Fusion-Task-Lineage: cabb8f52-093f-4986-bfda-2c7601a72579
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 15:27:03 -07:00
gsxdsm
4e4b6be1b4 Plan-approval mailbox notification + triage Plan Review convergence (#2237)
## Summary

Two related changes around the plan-approval flow, plus a fix for
triage's plan-review loop that was stranding tasks at the replan cap.

### 1. Post a mailbox message when a plan needs approval (`feat`)
The ntfy push on `awaiting-approval` already existed end-to-end. This
adds the missing **durable, in-dashboard record**: a `system`-typed
mailbox message linking to the task, written whenever a task enters
`awaiting-approval`.
- Fires **before** the notifications-enabled gate, so a dashboard-only
operator (no ntfy/webhook configured) still gets the record — the whole
point of the mailbox channel.
- `system` type avoids re-triggering the `message:agent-to-user` ntfy
pipeline (no double-notify); idempotent via `sendMessageOnce` (key
`plan-approval:<taskId>`).

### 2. Help triage Plan Review converge before the replan cap (`fix`)
Investigation of three tasks that burned all 8 plan-review replans
without converging (**FN-7996, FN-8105, FN-8108**) found the reviewer
surfaced a *new, deeper* issue each cycle instead of confirming its
prior ones were fixed (goalpost movement), and reviewed specs at
implementation altitude. This addresses the root causes:
- **Feed the spec reviewer its own prior REVISE feedback + the 1-based
replan attempt** so it verifies prior issues rather than moving
goalposts. Gated to `reviewType === "spec"` and `attempt > 1` — **code
review and normal plan review are byte-for-byte unaffected**
(double-verified).
- **Reviewer prompt:** converge-on-re-review rule (don't REVISE for your
own earlier miss), severity ratchet (critical-only at attempt ≥ 3), and
a **Spec Altitude** guard so exact SQL/lock/CAS protocol design is
deferred to code review.
- **Planner prompt:** front-load exhaustive surface enumeration before
writing File Scope, and a storage-architecture ground-truth note
(Postgres-only store, composite PK `(project_id, id)`, `schema-applier`
migrations) to stop the repeated stale-fact REVISE rounds.

## Testing
- `@fusion/core` + `@fusion/engine` typecheck clean.
- Added coverage: reviewer spec-convergence wiring (attempt gating +
code/plan exclusion + severity ratchet), triage prior-feedback
derivation (incl. empty-output→notes fallback), mailbox decoupling
(fires when push disabled) + rejection safety, and assertions for all
new prompt sections.
- Affected suites green: notification-service, reviewer,
triage-plan-review-replan-cap, triage-replan-feedback, agent-prompts.
- `pnpm check:changesets` passes (2 changesets: `@runfusion/fusion`
minor + patch).

## Review
Ran a 6-persona `ce-code-review` (correctness + adversarial on Opus;
maintainability, testing, project-standards, api-contract). Guards
verified unbreakable; no P0/P1 correctness or security issues. Applied
the resulting fixes: decoupled the mailbox write from the push gate
(P2), `??`→`||` in the feedback derivation (P3), de-duplicated the
`specConvergence` ternary (P3), and closed the test-coverage gap the
review flagged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

- **New Features**
- Plans transitioning to **awaiting approval** now write an idempotent,
task-linked **dashboard mailbox** message (approval reason + direct task
link), even if push/notifications are disabled.
- **Bug Fixes**
- Plan Review **replan** behavior now better **converges** on prior
REVISE feedback (including notes fallback) and stops looping at the
replan cap.
- At later attempts (attempt 3+), **REVISE** is applied to **critical**
issues while lower-severity items shift to suggestions.
- **Tests**
- Added/expanded coverage for mailbox messaging and spec-convergence
prompt wiring.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:44:07 -07:00
gsxdsm
d870878a23 FN-7998: add executor alternate model escalation
Add opt-in executor escalation after same-model tool-failure retries are exhausted.

- Persist escalation settings and one-shot task state across SQLite and PostgreSQL stores.
- Retry once on a configured alternate model or scheduler node and audit escalation outcomes.
- Expose escalation controls, documentation, translations, migration, and regression coverage.

Files changed:
 .changeset/fn-7998-executor-escalation.md          |   7 ++
 AGENTS.md                                          |   1 +
 docs/settings-reference.md                         |  13 ++-
 .../core/src/__tests__/settings-defaults.test.ts   |  23 ++++-
 packages/core/src/in-review-stall.ts               |  29 ++++++
 packages/core/src/index.gate.ts                    |   3 +-
 packages/core/src/index.ts                         |   3 +-
 packages/core/src/manual-retry-reset.ts            |   1 +
 .../0014_executor_escalation_attempt.sql           |   2 +
 packages/core/src/postgres/schema-applier.ts       |  17 ++++
 packages/core/src/postgres/schema/project.ts       |   1 +
 packages/core/src/settings-schema.ts               |   4 +
 packages/core/src/store.ts                         |   2 +-
 packages/core/src/task-store/persistence.ts        |   2 +
 packages/core/src/task-store/remaining-ops-2.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-3.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-6.ts    |   2 +-
 packages/core/src/task-store/serialization.ts      |   1 +
 packages/core/src/task-store/task-update.ts        |   2 +
 packages/core/src/types.ts                         |  13 +++
 .../dashboard/app/components/SettingsModal.tsx     |  12 +++
 .../app/components/settings/section-keys.ts        |   4 +
 .../settings/sections/SchedulingSection.search.ts  |  36 +++++++
 .../settings/sections/SchedulingSection.tsx        |   6 ++
 .../settings-default-descriptions.test.tsx         |   4 +
 .../__tests__/executor-tool-failure-retry.test.ts  |  91 +++++++++++++++++-
 packages/engine/src/executor.ts                    | 104 +++++++++++++++++++--
 packages/i18n/locales/en/app.json                  |   8 ++
 28 files changed, 376 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-7998

Fusion-Task-Lineage: bbce767d-c61a-4667-be62-abc0cc54d8be

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 14:31:59 -07:00
gsxdsm
60b6e3e048 FN-7996: add configurable executor tool-failure retries
Add bounded, durable same-model retry handling for qualifying consecutive executor tool errors.
- Persist retry claims, cursors, and audit markers with PostgreSQL migrations.
- Expose project retry count, backoff, and failure threshold settings in the dashboard.
- Cover retry, exhaustion, reset, and stale-run safety behavior with tests.

Files changed:
 .changeset/fn-7996-executor-tool-failure-retry.md  |   7 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |   1 +
 docs/settings-reference.md                         |  10 ++
 .../executor-tool-failure-retry-claim.test.ts      |  17 +++
 .../core/src/__tests__/manual-retry-reset.test.ts  |   3 +
 .../core/src/__tests__/settings-defaults.test.ts   |  15 +-
 packages/core/src/in-review-stall.ts               |  20 +++
 packages/core/src/index.gate.ts                    |   6 +
 packages/core/src/index.ts                         |   6 +
 packages/core/src/manual-retry-reset.ts            |   3 +
 .../0013_executor_tool_failure_retry.sql           |   4 +
 packages/core/src/postgres/schema-applier.ts       |  17 +++
 packages/core/src/postgres/schema/project.ts       |   3 +
 packages/core/src/settings-schema.ts               |   3 +
 packages/core/src/store.ts                         |  10 +-
 packages/core/src/task-store/persistence.ts        |   7 +
 packages/core/src/task-store/remaining-ops-2.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-3.ts    |   2 +-
 packages/core/src/task-store/remaining-ops-6.ts    |  65 ++++++++-
 packages/core/src/task-store/serialization.ts      |   3 +
 packages/core/src/task-store/task-update.ts        |   6 +
 packages/core/src/types.ts                         |  16 +++
 .../dashboard/app/components/SettingsModal.tsx     |  15 ++
 .../app/components/settings/section-keys.ts        |   3 +
 .../settings/sections/SchedulingSection.search.ts  |  27 ++++
 .../settings/sections/SchedulingSection.tsx        |   4 +
 .../settings-default-descriptions.test.tsx         |   3 +
 .../__tests__/executor-tool-failure-retry.test.ts  | 160 +++++++++++++++++++++
 packages/engine/src/executor.ts                    |  87 ++++++++++-
 packages/i18n/locales/en/app.json                  |   6 +
 31 files changed, 523 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-7996
Fusion-Task-Lineage: d1682ef8-534c-410e-b74c-1f2cf176eac2
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 13:41:14 -07:00
gsxdsm
d4914eb8b3 FN-8127: fix embedded PostgreSQL backups
Enable backup managers to resolve active embedded PostgreSQL runtime URLs safely.

- Track embedded backend URLs with generation-aware lifecycle leases.
- Keep backup resolution current through owner shutdown and joiner release.
- Document PostgreSQL client-tool requirements and add regression coverage.

Files changed:
 .changeset/fn-8127-embedded-backup.md              |   7 ++
 docs/settings-reference.md                         |   3 +
 packages/core/src/__tests__/backup.test.ts         | 115 ++++++++++++++++++++
 packages/core/src/backup.ts                        |  17 ++-
 packages/core/src/index.gate.ts                    |   9 ++
 packages/core/src/index.ts                         |   9 ++
 .../core/src/postgres/active-backend-registry.ts   | 119 +++++++++++++++++++++
 packages/core/src/postgres/embedded-lifecycle.ts   |   5 +
 packages/core/src/postgres/index.ts                |   9 ++
 packages/core/src/postgres/startup-factory.ts      | 118 +++++++++++++++++---
 10 files changed, 389 insertions(+), 22 deletions(-)

Fusion-Task-Id: FN-8127

Fusion-Task-Lineage: 6125be5c-d1d5-4228-a6b1-290311de70d9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 13:09:48 -07:00
gsxdsm
0b488523c2 FN-8104: retire legacy SQLite database fallbacks
Retire legacy SQLite database calls from PostgreSQL-only startup and self-healing paths.

- Route plugin schema initialization exclusively through the PostgreSQL executor.
- Delegate soft-delete column repair to the PostgreSQL reconciliation seam.
- Remove temporary getDatabase allowlist entries and add no-SQLite regression coverage.

Files changed:
 .../postgres/store-safe-defaults.pg.test.ts        | 14 +++++-
 packages/core/src/store.ts                         | 24 ++++------
 .../engine/src/__tests__/plugin-runner.test.ts     |  6 ---
 .../self-healing-fake-overlap-seam.test.ts         | 44 +++++++++++++++++++
 packages/engine/src/self-healing.ts                | 51 +++++-----------------
 scripts/lib/getdatabase-allowlist.json             | 17 +-------
 6 files changed, 77 insertions(+), 79 deletions(-)

Fusion-Task-Id: FN-8104

Fusion-Task-Lineage: 88dee027-51c3-4c68-94dc-88191fe20330

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 10:33:47 -07:00
gsxdsm
06d03d4e1f FN-8103: enforce PostgreSQL-only production data access
Require production paths to use PostgreSQL-aware stores and prevent new unrestricted database access.

- Add a checked allowlist that bans production getDatabase() calls by default.
- Route Quality plugin persistence through an async PostgreSQL-aware store and add Drizzle ORM.
- Document backend-safe plugin storage patterns and cover guarded access behavior.

Files changed:
 docs/PLUGIN_AUTHORING.md                           |  20 +++
 package.json                                       |   6 +-
 .../src/__tests__/agent-logs-backend-mode.test.ts  |   7 +
 packages/core/src/store.ts                         |   7 +-
 packages/core/src/task-store/remaining-ops-5.ts    |   8 +-
 plugins/fusion-plugin-quality/package.json         |   1 +
 .../src/__tests__/async-quality-store.pg.test.ts   |  36 +++++
 .../src/__tests__/cancel-and-plans.test.ts         |   8 +-
 .../src/__tests__/experimental-gate.test.ts        |   1 +
 .../src/routes/create-routes.ts                    |  50 +++----
 .../src/runner/command-runner.ts                   |  17 ++-
 .../src/store/async-quality-store.ts               |  34 +++++
 pnpm-lock.yaml                                     |   3 +
 scripts/__tests__/check-no-getdatabase.test.mjs    |  90 ++++++++++++
 scripts/check-no-getdatabase.mjs                   | 159 +++++++++++++++++++++
 scripts/lib/getdatabase-allowlist.json             |  18 +++
 16 files changed, 422 insertions(+), 43 deletions(-)

Fusion-Task-Id: FN-8103
Fusion-Task-Lineage: ff17bcb2-5341-4c6c-a5c4-993580539676
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 10:13:49 -07:00
gsxdsm
291fabcce8 FN-8083: preserve GitLab tracking across archive restore
Preserve GitLab import tracking metadata throughout TaskStore archive and restore flows.

- Store GitLab tracking data in archived task entries and restored tasks
- Hydrate GitLab tracking when serializing archive entries
- Cover live reads, searches, modified-task listings, and archive restoration in PostgreSQL tests
- Add a patch changeset for the tracking persistence fix

Files changed:
 .changeset/fn-8083-gitlab-tracking-hydration.md    |  7 +++
 .../store-gitlab-tracking-hydration.pg.test.ts     | 58 +++++++++++-----------
 .../core/src/task-store/archive-lifecycle-2.ts     |  7 +++
 packages/core/src/task-store/serialization.ts      |  1 +
 4 files changed, 43 insertions(+), 30 deletions(-)

Fusion-Task-Id: FN-8083

Fusion-Task-Lineage: 877f320f-11c9-469d-8ea5-b843e95b78d3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 08:41:34 -07:00
gsxdsm
1a337df5e9 FN-8098: add executor model fallback
Add workflow-specific executor fallback configuration and bounded recovery.

- Add executor fallback provider, model, and thinking settings across core schemas and settings UI.
- Route executor, heartbeat, child, and workflow-step sessions through the executor fallback resolver.
- Retry the primary model after fallback failure before reporting terminal exhaustion.
Files changed:

 .changeset/fn-8098-model-fallback.md               |  7 +++
 docs/settings-reference.md                         |  7 ++-
 .../core/src/__tests__/model-resolution.test.ts    | 13 ++++
 .../core/src/__tests__/settings-parity.test.ts     |  5 ++
 packages/core/src/builtin-workflow-settings.ts     | 24 +++++++
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/model-resolution.ts              | 21 +++++++
 packages/core/src/settings-schema.ts               |  3 +
 packages/core/src/types.ts                         | 11 ++++
 .../app/components/WorkflowSettingsPanel.tsx       |  8 +++
 .../settings/sections/ProjectModelsSection.tsx     | 10 ++-
 packages/engine/src/__tests__/pi.test.ts           | 14 ++++-
 packages/engine/src/agent-session-helpers.ts       |  7 ++-
 packages/engine/src/executor.ts                    | 48 +++++++-------
 packages/engine/src/pi.ts                          | 73 ++++++++--------------
 packages/engine/src/step-session-executor.ts       |  8 ++-
 17 files changed, 180 insertions(+), 81 deletions(-)

Fusion-Task-Id: FN-8098

Fusion-Task-Lineage: 61b3103b-357b-431a-8d58-411e7806b87b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 08:33:57 -07:00
gsxdsm
de1638e262 FN-8090: use mmap shared memory for embedded PostgreSQL
Enable constrained-host embedded PostgreSQL startup without SysV shared-memory exhaustion.

- Default embedded lifecycle flags to mmap-backed shared memory while preserving caller overrides
- Cover normal and elevated Windows launch paths with deterministic flag propagation tests
- Document the 64MB /dev/shm support floor and add a patch changeset

Files changed:
 .changeset/fn-8090-embedded-pg-shm.md              |  7 ++
 docs/postgres-migration-review-2026-07-14.md       |  4 +
 docs/storage.md                                    |  5 ++
 .../__tests__/postgres/embedded-lifecycle.test.ts  | 88 ++++++++++++++++++++++
 .../postgres/embedded-windows-admin.test.ts        | 17 +++++
 packages/core/src/postgres/embedded-lifecycle.ts   | 50 +++++++++++-
 .../core/src/postgres/embedded-windows-admin.ts    |  2 +-
 7 files changed, 168 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-8090

Fusion-Task-Lineage: ac175843-69ba-4c9c-9692-aff095fc351f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 06:00:12 -07:00
gsxdsm
6675cdf696 FN-8094: persist GitLab tracking metadata
Restore shared TaskStore persistence and hydration for GitLab tracking metadata.

- Register GitLab tracking JSONB for task creation, updates, and row hydration.
- Reuse the shared mapper during GitLab reconciliation and cover live/deleted reads.
- Migrate GitLab extension tests to the PostgreSQL harness and add a patch changeset.

Files changed:
 .changeset/fn-8094-gitlab-tracking-mapping.md      |   7 ++
 .../__tests__/extension-gitlab-tracking.test.ts    | 129 ++++++++++-----------
 packages/cli/src/__tests__/pg-extension-harness.ts |   4 -
 .../store-gitlab-tracking-hydration.pg.test.ts     |  85 ++++++++++++++
 .../store-gitlab-tracking-reconcile.test.ts        |  11 +-
 packages/core/src/task-store/persistence.ts        |   9 +-
 packages/core/src/task-store/remaining-ops-2.ts    |  10 +-
 packages/core/src/task-store/serialization.ts      |   1 +
 packages/core/src/task-store/task-creation.ts      |   2 +
 9 files changed, 174 insertions(+), 84 deletions(-)

Fusion-Task-Id: FN-8094

Fusion-Task-Lineage: 5e876856-cf42-4628-a5ef-ab562c1bf501

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 05:52:17 -07:00
gsxdsm
f63818a6e9 FN-8072: add critical-action confirmation skip setting
Add a global operator preference that bypasses centralized critical-action confirmation dialogs.

- Add a global-only skipConfirmationDialogs setting and Settings toggle
- Return primary/default confirmation results without rendering dialogs when enabled
- Route task reset actions through the centralized confirmation seam and add coverage
- Document the setting and add a minor changeset

Files changed:
 .changeset/fn-8072-skip-confirmation-dialogs.md    |  7 +++++
 docs/settings-reference.md                         |  1 +
 .../core/src/__tests__/settings-defaults.test.ts   | 12 ++++++++-
 packages/core/src/settings-schema.ts               |  5 ++++
 packages/core/src/types.ts                         |  5 ++++
 packages/dashboard/app/App.tsx                     | 11 ++++----
 packages/dashboard/app/components/ListView.tsx     | 20 +++++++++++----
 packages/dashboard/app/components/TaskCard.tsx     | 22 +++++++++++-----
 .../dashboard/app/components/TaskDetailModal.tsx   | 26 +++++++++++--------
 .../app/components/__tests__/ListView.test.tsx     | 13 ++++++++++
 .../__tests__/SettingsModal.general.test.tsx       | 19 ++++++++++++++
 .../app/components/__tests__/TaskCard.test.tsx     | 17 ++++++++++++
 .../components/__tests__/TaskDetailModal.test.tsx  | 25 ++++++++++++++++++
 .../app/components/settings/save-split.ts          |  1 +
 .../sections/GlobalGeneralSection.search.ts        | 10 ++++++++
 .../settings/sections/GlobalGeneralSection.tsx     | 10 ++++++++
 .../settings-default-descriptions.test.tsx         |  1 +
 .../app/hooks/__tests__/useConfirm.test.ts         | 30 ++++++++++++++++++++++
 packages/dashboard/app/hooks/useAppSettings.ts     |  4 +++
 packages/dashboard/app/hooks/useConfirm.ts         | 21 ++++++++++++++-
 packages/i18n/locales/en/app.json                  |  4 ++-
 21 files changed, 235 insertions(+), 29 deletions(-)

Fusion-Task-Id: FN-8072

Fusion-Task-Lineage: bab4b8f2-5996-4161-8733-8e03bb6a7024

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 05:42:04 -07:00
gsxdsm
969fce7aa1 FN-8047: migrate AgentStore multi-node tests to PostgreSQL
Migrate multi-node AgentStore coverage to shared PostgreSQL-backed fixtures.

- Make concurrent central claim insertion resolve unique-key races as checkout conflicts.
- Rework claim and owning-node handoff tests to use shared async PostgreSQL layers.
- Restore PostgreSQL-compatible tests from the quarantine ledger.

Files changed:
 packages/core/src/async-central-db.ts              |  9 ++-
 .../cross-node-claim-mutex.integration.test.ts     | 72 ++++++++++---------
 .../distributed-claim-mutex.integration.test.ts    | 27 +++----
 .../owning-node-handoff.integration.test.ts        | 41 +++++------
 .../__tests__/reliability-interactions/_helpers.ts | 83 ++++++++++++++++++++--
 .../multi-node-claim-mutex-interactions.test.ts    | 28 +++-----
 .../owning-node-unavailable-interactions.test.ts   | 36 +++++-----
 packages/engine/vitest.config.ts                   |  8 +--
 scripts/lib/test-quarantine.json                   | 25 -------
 9 files changed, 180 insertions(+), 149 deletions(-)

Fusion-Task-Id: FN-8047

Fusion-Task-Lineage: 3b7ee21e-0190-4364-a0cb-88aac5e2e1a3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 05:33:55 -07:00
gsxdsm
19ab7a9e93 FN-8068: add input-language task definitions
Add an opt-in setting that localizes planner-authored task-definition prose.

- Detect confident Spanish, French, Korean, and Chinese input before instructing triage localization.
- Expose and persist the project-level task-definition language toggle with translations and search support.
- Preserve English structural markers, code, and unsupported or uncertain input for deterministic parsing.

Files changed:
 .../fn-8068-task-definition-input-language.md      |  7 +++
 docs/settings-reference.md                         |  1 +
 packages/core/src/settings-schema.ts               |  7 +++
 packages/core/src/types.ts                         | 10 ++++
 .../app/__tests__/settings-sections.test.tsx       | 18 ++++++
 .../dashboard/app/components/SettingsModal.tsx     |  4 ++
 .../__tests__/SettingsModal.mobileClose.test.tsx   | 12 ++++
 .../__tests__/SettingsModal.models-auth.test.tsx   | 11 ++++
 .../app/components/settings/section-keys.ts        |  1 +
 .../sections/ProjectModelsSection.search.ts        | 10 ++++
 .../settings/sections/ProjectModelsSection.tsx     | 17 ++++++
 .../settings-default-descriptions.test.tsx         |  1 +
 packages/engine/src/__tests__/triage.test.ts       | 64 ++++++++++++++++++++++
 packages/engine/src/triage.ts                      | 29 +++++++++-
 packages/i18n/locales/en/app.json                  |  2 +
 packages/i18n/locales/es/app.json                  |  2 +
 packages/i18n/locales/fr/app.json                  |  2 +
 packages/i18n/locales/ko/app.json                  |  2 +
 packages/i18n/locales/zh-CN/app.json               |  2 +
 packages/i18n/locales/zh-TW/app.json               |  2 +
 packages/i18n/src/resources.d.ts                   |  2 +
 21 files changed, 205 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8068

Fusion-Task-Lineage: fe0102f5-68cb-443c-8512-2ffa5fe0e2f8

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 05:18:30 -07:00
gsxdsm
5b4ec4c08f FN-8042: add merger fallback model lane
Add configurable project fallback models for AI merger retries.

- Resolve complete project merger fallback pairs before the shared global fallback.
- Expose merger fallback model and thinking controls in Project Models with reset-aware persistence.
- Apply the fallback lane across merger, review, PR-response, and recovery sessions with tests and documentation.

Files changed:
 .changeset/fn-8042-merger-fallback-model.md        |  7 +++
 docs/settings-reference.md                         |  4 +-
 .../core/src/__tests__/model-resolution.test.ts    | 24 ++++++++++
 .../core/src/__tests__/settings-parity.test.ts     |  3 ++
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/model-resolution.ts              | 22 +++++++++
 packages/core/src/settings-schema.ts               |  4 ++
 packages/core/src/types.ts                         | 13 ++++++
 .../app/__tests__/settings-save-split.test.ts      | 41 +++++++++++++++++
 .../app/__tests__/settings-sections.test.tsx       | 36 +++++++++++++++
 .../app/components/settings/save-split.ts          |  7 +--
 .../settings/sections/ProjectModelsSection.tsx     | 53 +++++++++++++++++++++-
 .../settings-default-descriptions.test.tsx         |  3 ++
 .../src/__tests__/agent-session-helpers.test.ts    |  1 +
 .../__tests__/mcp-pr-response-forwarding.test.ts   |  1 +
 packages/engine/src/agent-session-helpers.ts       | 10 +++-
 packages/engine/src/merger-ai.ts                   | 17 +++++--
 packages/engine/src/merger.ts                      | 43 +++++++++++++-----
 packages/engine/src/pr-response-run-ops.ts         | 11 +++--
 20 files changed, 276 insertions(+), 26 deletions(-)

Fusion-Task-Id: FN-8042

Fusion-Task-Lineage: 31762a72-461e-438c-a12d-2816580283fd

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 04:29:17 -07:00
gsxdsm
3f133e0b13 FN-8058: add task agent log reader
Expose paginated, filterable persisted agent logs to task-scoped and chat agent sessions.

- Add the read-only fn_task_logs_read tool across engine, dashboard chat/planning, heartbeat, step, and CLI extension surfaces.
- Filter agent-log entries before pagination, report matching totals, and render complete persisted rows for diagnosis.
- Document the tool, add release metadata, regression coverage, and complete affected engine mocks.

Files changed:
 .changeset/fn-8058-task-logs-read.md               |  7 ++
 docs/agents.md                                     |  4 +-
 packages/cli/skill/fusion/SKILL.md                 |  2 +-
 .../cli/skill/fusion/references/extension-tools.md | 11 +++
 .../skill/fusion/references/fusion-capabilities.md |  1 +
 .../extension-experiment-finalize.test.ts          |  2 +
 .../src/__tests__/extension-fn-secret-get.test.ts  |  2 +
 .../__tests__/extension-gitlab-tracking.test.ts    |  2 +
 .../src/__tests__/extension-integration.test.ts    |  1 +
 .../cli/src/__tests__/extension-web-fetch.test.ts  |  2 +
 packages/cli/src/__tests__/extension.test.ts       |  1 +
 packages/cli/src/extension.ts                      | 34 ++++++++
 .../src/__tests__/agent-logs-backend-mode.test.ts  | 28 +++++-
 packages/core/src/store.ts                         | 11 ++-
 packages/core/src/task-store/remaining-ops-7.ts    | 16 +++-
 packages/core/src/types.ts                         |  1 +
 packages/dashboard/src/__tests__/chat.test.ts      |  1 +
 .../planning-answered-question-reemit.test.ts      |  1 +
 .../planning-generation-cancellation.test.ts       |  1 +
 packages/dashboard/src/chat.ts                     |  5 ++
 packages/dashboard/src/planning.ts                 |  3 +
 .../__tests__/agent-task-logs-read-tools.test.ts   | 72 ++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             |  4 +-
 packages/engine/src/agent-tools.ts                 | 99 +++++++++++++++++++++-
 packages/engine/src/executor.ts                    |  6 ++
 packages/engine/src/gating-classifications.ts      |  2 +
 packages/engine/src/index.ts                       |  6 ++
 packages/engine/src/step-session-executor.ts       |  6 +-
 28 files changed, 316 insertions(+), 15 deletions(-)

Fusion-Task-Id: FN-8058
Fusion-Task-Lineage: 74f198b2-f538-4b39-973f-431f22e68f29
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 04:03:43 -07:00
gsxdsm
90ce57f127 FN-8070: preserve task title input language
Ensure auto-generated task titles use the task description's language.

- Detect description language and add a confident language hint to title prompts
- Cover multilingual title generation and stale-model fallback behavior
- Document the language-preserving title-summary setting and add a patch changeset

Files changed:
 .changeset/fn-8070-title-summary-input-language.md |  7 ++
 docs/settings-reference.md                         |  2 +-
 packages/core/src/__tests__/ai-summarize.test.ts   | 87 ++++++++++++++++++----
 packages/core/src/ai-summarize.ts                  | 18 +++++
 4 files changed, 98 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-8070

Fusion-Task-Lineage: e003e2e0-eb0c-487e-83de-8a9b37ee1c02

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 03:23:25 -07:00
gsxdsm
f3b68c9fff FN-8065: preview plans before refinement questions
Show a read-only plan preview at the Planning Mode deepening checkpoint.

- Persist pending plan details on the checkpoint question for fresh and restored sessions.
- Render formatted plan content and deliverables above refinement choices.
- Cover preview behavior and document the checkpoint flow.

Files changed:
 .../fn-8065-planning-checkpoint-plan-preview.md    |  7 +++
 docs/dashboard-guide.md                            |  3 +-
 packages/core/src/types.ts                         | 11 ++++
 .../dashboard/app/components/PlanningModeModal.css | 60 ++++++++++++++++++++++
 .../dashboard/app/components/PlanningModeModal.tsx | 44 ++++++++++++++++
 .../PlanningModeModal.planning-flow.test.tsx       | 24 ++++++++-
 .../planning-interview-formatters.test.ts          | 19 +++++++
 packages/dashboard/src/planning.ts                 | 10 ++++
 8 files changed, 175 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-8065

Fusion-Task-Lineage: 13cd52c3-af82-4723-a6b1-3366775213e1

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 02:54:33 -07:00
gsxdsm
e87b51bd07 FN-8054: add pinned chat conversations
Add durable, scoped pinning for Direct chat conversations.

- Add pinned session persistence, migration coverage, and archive-safe row locking.
- Enforce a three-conversation per-project pin limit through the chat API.
- Add desktop and mobile pin controls, sorting, indicators, and regression tests.

Files changed:
 .changeset/fn-8054-pin-conversations.md            |  7 ++
 docs/dashboard-guide.md                            |  2 +
 .../postgres/satellite-db-injected-stores.test.ts  | 13 ++++
 packages/core/src/async-chat-store.ts              | 27 ++++++++
 packages/core/src/chat-store.ts                    | 63 ++++++++++++++++--
 packages/core/src/chat-types.ts                    |  9 +++
 .../core/src/postgres/migrations/0000_initial.sql  |  1 +
 .../postgres/migrations/0012_chat_session_pins.sql |  8 +++
 packages/core/src/postgres/postgres-health.ts      |  3 +
 packages/core/src/postgres/schema-applier.ts       | 30 ++++++++-
 packages/core/src/postgres/schema/project.ts       |  3 +
 packages/dashboard/app/api/legacy.ts               |  1 +
 packages/dashboard/app/components/ChatView.css     | 32 +++++++++-
 packages/dashboard/app/components/ChatView.tsx     | 74 ++++++++++++++++++++--
 .../dashboard/app/hooks/__tests__/useChat.test.ts  | 21 ++++++
 packages/dashboard/app/hooks/useChat.ts            | 72 +++++++++++++++++----
 .../dashboard/src/routes/register-chat-routes.ts   | 25 +++++++-
 17 files changed, 366 insertions(+), 25 deletions(-)

Fusion-Task-Id: FN-8054

Fusion-Task-Lineage: 088cb01c-582b-4f56-a222-214da90ff356

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 02:35:29 -07:00
gsxdsm
274318aebf FN-8056: enforce task token budgets
Enforce configured task token budgets whenever session usage is persisted.

- Apply soft alerts and hard pauses atomically from all executor persistence paths.
- Exclude cache-read tokens from budget usage and dispatch budget notifications once.
- Document budget semantics and add regression coverage.

Files changed: .changeset/fn-8056-token-budget-enforcement.md     |   7 ++
 docs/settings-reference.md                         |   2 +
 packages/core/src/types.ts                         |   4 +-
 .../src/__tests__/session-token-usage.test.ts      | 101 ++++++++++++++++++++-
 .../src/__tests__/token-budget-enforcer.test.ts    |  81 ++++++++++-------
 packages/engine/src/executor.ts                    |  22 ++++-
 packages/engine/src/session-token-usage.ts         |   8 +-
 packages/engine/src/token-budget-enforcer.ts       |  98 +++++++++++++++++---
 8 files changed, 262 insertions(+), 61 deletions(-)

Fusion-Task-Id: FN-8056

Fusion-Task-Lineage: 5f5ed522-f950-42ce-b4fd-e0b1d45b5815

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 01:52:10 -07:00
gsxdsm
e46ffebde1 FN-7985: surface review budget exhaustion and configure replan cap
Expose exhausted Plan Review replan budgets for operator approval and allow workflows to configure the cap.

- Add validated numeric workflow setting support and a Plan Review replan-cap setting.
- Route configured cap exhaustion with a distinct approval reason and preserve fallback behavior.
- Display the budget-exhaustion state across task cards, lists, and details.
- Add tests, localized copy, documentation, and a minor changeset.

Files changed:
 .changeset/fn-7985-review-budget-approval.md       |  7 ++++
 docs/settings-reference.md                         |  9 +++--
 docs/workflow-steps.md                             |  2 +-
 .../builtin-workflow-settings-triage.test.ts       | 27 +++++++++++++--
 packages/core/src/builtin-workflow-settings.ts     | 17 ++++++++++
 packages/core/src/index.gate.ts                    |  1 +
 packages/core/src/index.ts                         |  1 +
 packages/core/src/workflow-ir-types.ts             |  4 +++
 packages/core/src/workflow-ir.ts                   | 27 +++++++++++++++
 packages/core/src/workflow-settings-resolver.ts    |  1 +
 packages/core/src/workflow-settings.ts             |  6 ++++
 packages/dashboard/app/components/ListView.css     | 19 +++++++++++
 packages/dashboard/app/components/ListView.tsx     | 22 +++++++++---
 packages/dashboard/app/components/TaskCard.css     | 18 ++++++++++
 packages/dashboard/app/components/TaskCard.tsx     |  6 ++--
 .../dashboard/app/components/TaskDetailModal.tsx   |  4 +--
 .../app/components/__tests__/ListView.test.tsx     | 30 +++++++++++++++++
 .../app/components/__tests__/TaskCard.test.tsx     | 17 ++++++++--
 .../app/components/workflow-setting-display.ts     | 11 ++++++
 .../dashboard/app/utils/reviewBudgetApproval.ts    | 11 ++++++
 .../triage-plan-review-replan-cap.test.ts          | 39 +++++++++++++++++++---
 packages/engine/src/triage.ts                      | 21 ++++++++----
 packages/i18n/locales/en/app.json                  |  2 +-
 packages/i18n/src/resources.d.ts                   | 19 ++++++++---
 24 files changed, 288 insertions(+), 33 deletions(-)

Fusion-Task-Id: FN-7985

Fusion-Task-Lineage: 125f101c-caca-45c2-8b40-996b2a31c019

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 01:32:23 -07:00
gsxdsm
375368e147 FN-8051: ensure PostgreSQL schemas initialize before plugin hooks
Ensure required PostgreSQL namespaces exist before plugin initialization on every boot.

- Create project, central, and archive schemas under the schema advisory lock before hooks run
- Cover marker-present databases with a plugin-hook schema availability regression test
- Add a patch changeset for the reliability fix

Files changed:
 .changeset/fn-8051-schema-init.md                  |  7 ++++
 .../src/__tests__/postgres/schema-applier.test.ts  | 43 ++++++++++++++++++++++
 packages/core/src/postgres/schema-applier.ts       | 12 ++++++
 3 files changed, 62 insertions(+)

Fusion-Task-Id: FN-8051

Fusion-Task-Lineage: a3b20683-a742-4a8c-9cfc-fbf316c5649b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 01:26:13 -07:00
gsxdsm
a31c370375 FN-8045: add transactional handoff failure-injection seam
Ensure PostgreSQL review handoffs roll back all dependent writes after an injected late failure.

- Add a test-only failure injector after transactional handoff writes.
- Include workflow work in same-column retry transactions.
- Restore PG-backed handoff atomicity coverage and remove its quarantine.

Files changed:
 packages/core/src/store.ts                         |  24 +++
 packages/core/src/task-store/moves.ts              |  21 ++-
 .../in-review-handoff-atomic.test.ts               | 172 +++++++++++++--------
 packages/engine/vitest.config.ts                   |   1 -
 scripts/lib/test-quarantine.json                   |   5 -
 5 files changed, 151 insertions(+), 72 deletions(-)

Fusion-Task-Id: FN-8045

Fusion-Task-Lineage: 517e3000-9b88-4b0d-9b25-1a585eb8f322

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 00:44:35 -07:00
gsxdsm
261901343e fix(core): split the domain project field from the RLS partition column (#2165)
## Problem

Migration 0006 made `project_id` the RLS isolation partition on every
`project`-schema table — stamped by a BEFORE INSERT trigger from the
`fusion.project_id` session GUC, with every PK/unique/FK rewritten to
composite `(project_id, …)`. Eleven tables **also** carried a
caller-supplied domain `projectId` on their TS types and wrote that
domain value into the same physical column.

When the domain value differs from the session GUC, the parent row lands
in the domain partition while child rows (`research_run_events`,
`experiment_session_records`, `eval_task_results`, …) land in the
session partition — and the composite FK fails with SQLSTATE 23503.
Appending an event to a project-owned research run could not persist.

## Fix

**Decision (operator): separate domain column; `project_id` stays the
partition.**

- **Migration `0011_owner_project_id.sql`** adds a nullable
`owner_project_id` domain column to the 11 conflated tables
(`research_runs`, `experiment_sessions`, `todo_lists`, `eval_runs`,
`chat_sessions`, `chat_rooms`, `ai_sessions`, `chat_token_usage`,
`project_insights`, `project_insight_runs`, `cli_sessions`), backfills
it from `project_id` (identical in production, so exact; the
`__legacy_unscoped__` sentinel backfills to NULL), and indexes it.
Idempotent, `to_regclass`-guarded per the 0007 pattern.
- **Stores** (`async-research-store`, `async-experiment-session-store`,
`async-todo-store`, `async-chat-store`, `async-ai-session-store`,
`async-eval-store`, `async-insight-store`, `cli-session-store`, …) stop
writing `project_id` entirely — the trigger/GUC owns the partition — and
map their domain `projectId` field to `owner_project_id` for both reads
and filters. TS types unchanged.
- **Applier** registers `OWNER_PROJECT_ID_SPLIT_VERSION = "0011"` and
advances `SCHEMA_BASELINE_VERSION`.

## Verification (re-run independently of the implementing agent)

- Core `tsc --noEmit`: exit 0 · `pnpm lint`: exit 0 · `pnpm
check:changesets`: exit 0 · `pnpm test:gate`: 185/185
- Full postgres suite: **5 failed / 807 passed** vs a **7 / 804**
baseline — the two conflation round-trips
(`satellite-db-injected-stores` ResearchStore + ExperimentSessionStore)
go green, zero new failures. The remaining 5 are pre-existing
unbound-harness `__meta`/identity failures, unrelated to this change.

🤖 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**
* Corrected project-scoped persistence and queries across AI sessions,
chats (rooms + token usage), evaluations/experiments, insights,
research, and todos by separating domain ownership from RLS
partitioning.
* Prevented foreign-key and row-level security violations when storing
or retrieving project-scoped data, including legacy records.
* **Database / New Features**
* Added migration 0011 introducing `owner_project_id` and backfilling
existing rows to preserve ownership while improving isolation.
* **Tests**
  * Updated migration-parity coverage to include the new baseline step.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 00:30:59 -07:00
gsxdsm
9a34862586 refactor: package code organization waves 3–5 (#2148)
## Summary

Waves 3–5 of package code organization (plan:
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`).
Behavior-preserving peels after #2139 and #2143.

### Wave 3 — Merger + heartbeat recovery
- **`merger-errors.ts`** — verification/abort error classes
- **`merger-owned-landed.ts`** — ownership classification +
`Fusion-Task-Id` trailer
- **`merger-conflict-resolution.ts`** — conflict classify/auto-resolve
- **`agent-heartbeat-error-recovery.ts`** — durable error-recovery
budget helpers

### Wave 4 — Self-healing + dashboard API
- **`self-healing-constants.ts`** — public timing/budget constants
- **`self-healing-branch.ts`** — `isBranchAheadOfBase`
- **`app/api/client.ts`** — `api` / `ApiRequestError` / `buildApiUrl` /
`proxyApi`
- **`app/api/health.ts`** — health, engine status, updates +
`withProjectId`

### Wave 5 — Types tracking + merger parse + task CRUD
- **`types/task-tracking.ts`** — PR/issue/GitHub/GitLab tracking
contracts
- **`merger-git-parse.ts`** — `parseFailingFilesFromOutput`,
`parsePorcelainZ`, `parseShortstatSummary`
- **`app/api/tasks.ts`** — task list/detail/create/update/move client
surface
- Line-count baselines ratcheted down for `merger.ts`, `types.ts`,
`legacy.ts`

Public import paths stay on parent modules / `legacy.ts` / package
barrels via re-exports.

## Test plan
- [x] core/engine/dashboard typecheck (including `tsconfig.app.json`)
- [x] eslint on touched modules
- [x] `parse-porcelain-z` + merger parseFailing/getBranchChanged tests
- [x] dashboard `api-tasks` + legacy-prinfo/pr-types (69)
- [ ] CI merge gate

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

- **New Features**
- Added dashboard API support for task listing/detail, archiving,
creation, review updates, duplicate detection, bulk model updates,
moving tasks, and overlap repair.
- Added health/engine status and refresh/start controls, plus update
checking.

- **Bug Fixes**
- Improved dashboard API handling for non-JSON/HTML responses with
clearer errors, better URL routing for remote nodes, and project-scoped
queries.
- Strengthened automated recovery for heartbeat error/model-unavailable
scenarios and safer merge-conflict classification/auto-resolution.

- **Tests**
- Updated merge-conflict resolution and lifecycle test mocks to match
the updated git command behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 00:01:17 -07:00
gsxdsm
08c546dc75 test(core): bind the U14 postgres harness to a project, as production does
The usage-events round-trip failed because the harness ran unbound. Production
binds `fusion.project_id` per connection (connection.ts) and only falls back to
`fusion.project_bypass=on` when no projectId is given, so an unbound harness
wrote blank project_ids that the migration-0006 trigger rewrote to
'__legacy_unscoped__' -- and helpers scoping on `layer.projectId ?? ""` then
looked for a literal '' the database never stores.

Unbound is a shape production forbids: AgentStore.backendProjectId throws on it
("Reject unbound backend heartbeat/run access instead of silently reading or
writing the legacy empty-string partition"). The harness was wrong, not the
product -- an earlier attempt to make the product accommodate the unbound
harness was reverted in b51de02a5.

Binds both the layer and the admin connection: the admin connection seeds
fixtures the layer reads back, so it must sit in the same partition or the
layer cannot see its own setup. Three reads that relied on the unbound default
now pass the project id, matching how production callers thread
`layer.projectId` -- getLiveTaskColumn resolves a missing id to the sentinel
partition, so omitting it looked in the wrong place once rows were bound.

No product code changes. 24/24.

The same binding does NOT fit the satellite suites and they are left alone:
satellite-fusiondir has a test asserting the unbound APIs fail closed (binding
defeats its premise) and another that binds two projects itself, so that
harness needs an opt-out parameter rather than a blanket bind.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 23:06:20 -07:00
gsxdsm
b51de02a54 Revert "fix(core): resolve unbound project ids to a real partition or no filter"
This reverts commit a048a619fc.
2026-07-15 22:14:14 -07:00
gsxdsm
a048a619fc fix(core): resolve unbound project ids to a real partition or no filter
Six of the eight postgres-suite failures shared one root cause: writes
normalize project_id, reads did not. The fusion_assign_project_id trigger
(migration 0006) rewrites a blank project_id to the session's fusion.project_id
or '__legacy_unscoped__', but helpers reached as `layer.projectId ?? ""` then
filtered on the literal '' -- a value the database never stores. Every unbound
read missed rows it had just written.

AsyncDataLayer.projectId is optional by design (undefined = project-agnostic),
so `?? ""` is the bug: it turns "no scope" into a scope that matches nothing.

The resolution differs by what the rows are, and conflating them corrupts data:

- Data and analytics reads (usage events, agent runs, research runs) take
  projectScopeFor(): a bound id filters, an unbound one reads across projects.
  This matches the contract taskProjectScope already documents ("when undefined
  the scope filter is a no-op").
- __meta migration guards (project-identity stamps, agent-store markers) take
  projectPartitionId(): an unbound id resolves to the shared sentinel
  partition. projectScopeFor would be wrong here -- dropping the predicate lets
  an unbound getMetaValue return whichever project's marker it finds first, so
  on the shared cluster project A's "migration complete" marker would tell
  project B to skip a migration it never ran. upsertMetaValue already documented
  this: "the empty binding remains the explicit project-agnostic compatibility
  partition". Writing the sentinel explicitly also keeps the partition
  deterministic -- a blank write from a session carrying fusion.project_id would
  otherwise land in that project's stamp.

Names the sentinel (LEGACY_UNSCOPED_PROJECT_ID) instead of open-coding it, and
puts both helpers next to taskProjectScope so the convention has one home.

Fixes taskstore-remaining (24/24), project-identity (6/6), and
satellite-fusiondir-stores (16/16).

The remaining two failures are a different bug and are NOT addressed here: the
child tables research_run_events and experiment_session_records never declared
project_id in schema-as-code, though migration 0006 added the column and
rewrote their FKs to composite (project_id, parent_id). Drizzle therefore cannot
write the parent's partition, the trigger stamps '__legacy_unscoped__', and the
FK fails against a project-owned parent. That needs a schema-as-code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:47:01 -07:00
gsxdsm
a588c38784 fix(core): read usage events across projects when the layer is unbound
An unbound (project-agnostic) data layer read zero usage events it had just
written. AsyncDataLayer.projectId is optional by design -- undefined means a
project-agnostic layer for single-project / global / analytics reads -- but
helpers taking `projectId: string` are called as `layer.projectId ?? ""`, which
turns "no scope" into a literal '' scope.

'' never matches: the fusion_assign_project_id BEFORE INSERT trigger (migration
0006) rewrites a written '' to the session's fusion.project_id or
'__legacy_unscoped__', so a read filtering on '' looks for a value the database
never stores. Writes normalize, reads did not. Proven by probe: the row is
present with project_id '__legacy_unscoped__', emitUsageEvent returns true, and
queryUsageEvents returns [] even with no other filters.

Treat blank as unbound and drop the scope predicate, matching the contract
taskProjectScope already documents ("when undefined the scope filter is a
no-op"). Restricting an unbound reader to '__legacy_unscoped__' rows instead
would make an unscoped analytics read silently partial.

Adds projectScopeFor() next to taskProjectScope so the convention has one home
rather than a third open-coded variant.

Note the write path is already live: remaining-ops-7.ts emits with
`layer.projectId ?? ""` under backendMode, so unscoped events are accumulating
under the sentinel today. The async reader has no production caller yet, which
is why nothing user-facing broke.

Fixes taskstore-remaining.test.ts (24/24). The remaining failures in that suite
share this root cause but not this resolution -- see the follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:37:44 -07:00
gsxdsm
3dcb62f40f FN-8008: normalize plan approval fingerprints
Keep approval recovery idempotent when deterministic prompt hygiene is injected.

- Normalize plan approval fingerprints around Original Description and Frontend UX sections.
- Preserve re-approval for operator-authored plan changes and cover recovery behavior.
- Document the normalization contract and add a patch changeset.

Files changed:
 .changeset/fn-8008-plan-approval-fingerprint.md   |  7 +++
 docs/workflow-steps.md                            |  2 +-
 packages/core/src/__tests__/plan-approval.test.ts | 53 +++++++++++++++-
 packages/core/src/plan-approval.ts                | 73 ++++++++++++++++++++++-
 packages/engine/src/__tests__/triage.test.ts      | 45 ++++++--------
 packages/engine/src/triage.ts                     | 40 ++-----------
 6 files changed, 153 insertions(+), 67 deletions(-)

Fusion-Task-Id: FN-8008

Fusion-Task-Lineage: 9c0f415d-662a-455a-a4bd-b873307e53bc

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 21:33:11 -07:00
gsxdsm
d1bda3683c fix(core): reap the losing wrapper and stop self-joining on a startup race
Two related leaks on the embedded Postgres startup-race join.

The flagged one: the catch dropped `nonAdminHandle` to null without stopping
it, so a wrapper that onLaunched had already published leaked. The obvious fix
-- call handle.stop() first -- is worse than the leak. stop() runs killAll(),
which resolves its target by reading line 1 of the data dir's postmaster.pid.
On this path that file belongs to the process that WON the race, so stop()
would taskkill the instance we are joining. pg.stop() is the same trap via
pg_ctl -D on the shared dir, which is why settleCancelledStart (it calls both)
cannot be reused here. Added NonAdminServerHandle.stopWrapperOnly(), which
kills only our wrapper pid and its children, and called it before the handle is
dropped. A racing winner is another process's child, so /t cannot reach it.

The one found while making that safe: the catch joined on ANY start failure. A
start that took the lock and then failed later (readiness timeout, non-admin
poll error) reads back its OWN postmaster.pid, so isAlreadyRunning hands back
our own port and we "join" ourselves with ownsProcess=false -- nothing ever
stops it, orphaning a live postmaster for the life of the host. The join now
fires only on a lock-collision error, which is the one failure proving our
postgres refused to start and someone else owns the dir. Every other failure
returns to the existing cancellation/cleanup paths, which stop what they
started. That is also what makes the wrapper-only kill provably safe: on this
path our postgres never took the lock.

Tests: a non-lock failure must propagate even with a postmaster.pid present
(fails without the fix -- the old catch swallowed it and joined), and a lock
collision must still join. Both always-on with a mocked ctor.

Pre-existing and unrelated: taskstore-remaining.test.ts fails identically on a
clean tree with these changes stashed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:11:40 -07:00
gsxdsm
08a10bf486 fix(FN-8006): back off and pause Plan Review on provider rate limits
A rate-limited Plan Review re-ran every 30s for hours (~1,900 requests
per 5h window, reviewerFallbackRetryCount observed past 100), which is
the request volume that trips a provider's low-interactivity throttle —
so the retry storm prolonged the very outage it was retrying.

Root cause: runPlanReviewBeforeExecution catches every reviewStep throw
inline to keep triage alive, which converts them all to an UNAVAILABLE
verdict. That laundering had two consequences the earlier fixes missed:
FN-8006 terminalized RetryStormError and the reviewer started throwing
ReviewerProviderError for 429s, but a ReviewerProviderError still landed
in the UNAVAILABLE park — a FIXED 30s nextRecoveryAt with no attempt
counter and no cap. The reviewer's own escalation contract ("escalate so
UsageLimitPauser pauses every lane") held only on the executor path,
because the inline catch hid the error from triage's usage-limit handler
in specifyTask.

- triage: fire usageLimitPauser.onUsageLimitHit for usage-limit reviewer
  failures, so a 429 pauses every lane instead of re-parking one task.
- triage: re-park via computeRecoveryDecision (60s/120s/240s, ±10%
  jitter) and terminalize at MAX_RECOVERY_RETRIES. A reviewer that never
  yields a verdict is a real failure and must surface, not spin.
- triage: clear the borrowed recoveryRetryCount budget on any real
  verdict, so surviving an outage cannot shorten the executor's later
  transient budget.
- core: RetryStormError takes an optional cause, surfaced as
  underlyingError in serializeRetryStormError and folded into the
  message, so a cap no longer masks the real error. recordRetry threads
  it from the reviewer's error path.

Surface enumeration: the park is driven by a thrown provider error, a
thrown generic error, and a plain UNAVAILABLE verdict with no throw.
All three are covered — a repro pinned only to the reported 429 would
leave the other two spinning on the old fixed timer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:53:05 -07:00
gsxdsm
130c70286b fix(core): create the database when joining a racing embedded Postgres
A lifecycle that joins an already-running instance returned a connection URL
before the owner had created the database. The owner calls ensureDatabase()
only after its own start() resolves, but the signals a joiner detects the
instance by -- the runningInstances entry and, decisively, postmaster.pid,
which postgres itself writes -- both appear earlier. A joiner landing in that
window handed back a URL to a database that did not exist and failed at the
caller's first connect.

Reordering the owner's publish does not fix it: isAlreadyRunning falls back to
the pid file, whose timing postgres owns, so the joiner must verify. Both join
paths (preflight and the startup-race catch) now create the database if absent.
Creating from the joiner is safe rather than a second writer -- CREATE DATABASE
is atomic and both sides tolerate the duplicate, so whoever loses treats the
winner's database as its own success.

Verification takes the joined instance's port explicitly. getPort() resolves to
`options.port ?? resolvedPort`, which on a join with an explicitly configured
port is this instance's requested port, not the one being joined.

It is best-effort by contract: isAlreadyRunning joins optimistically without
probing (a stale pid file from a crash still resolves to a port), so a probe
failure logs and returns the URL exactly as before, letting the connection
layer report an unreachable cluster. A hard throw would turn every stale-pid
start into a startup failure.

Duplicate tolerance covers both codes a real cluster produces: 42P04
duplicate_database when the winner committed before our catalog probe, and
23505 unique_violation on pg_database_datname_index when the two CREATEs
collide inside the catalog insert. The concurrent-ensureDatabase test caught
the 23505 arm -- tolerating only 42P04 left the tighter half of the race
throwing.

Tests: a real-process test proving a joiner creates the database the owner has
not (drop-the-database reproduces the window), a real-process concurrent
ensureDatabase race, and an always-on test pinning the best-effort contract for
an unreachable join. All three fail without the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:46:01 -07:00
gsxdsm
e3f98253cc feat: Quality plugin — Task QA tab, preview servers, tests, and suggested cases (#2127)
## Summary

Adds a bundled **Quality** plugin (`fusion-plugin-quality`) that makes
task QA easier and more visual:

- **Task QA tab** (action-first): preview/test server for the task
worktree, allowlisted test runs, report viewer, screenshots CTA,
suggested test cases, CI handoff
- **Quality hub** (left sidebar): project-wide run history and preset
launches
- Host **task-detail slot context** (`taskId`, worktree, `projectId`) so
plugin tabs can scope correctly
- `superviseSpawn` re-exported on the plugin packaging shim for
published plugins
- Plan: `docs/plans/2026-07-14-001-feat-quality-plugin-plan.md`

## Design constraints

- Does **not** replace the merge gate — advisory orchestration only
- Composes Dev Server process patterns and artifact registry (no second
browser stack)
- Never free-form shell; never port 4040
- Full-suite requires explicit confirm

## Test plan

- [x] `pnpm --filter @fusion-plugin-examples/quality test` (15 tests)
- [x] PluginSlot unit tests still pass
- [ ] Enable Quality plugin in dashboard Settings → Built-in Plugins
- [ ] Open Task Detail → **QA** tab with a worktree; start preview, run
verify:fast, generate suggestions
- [ ] Open left sidebar **Quality** hub and list runs
- [ ] Confirm merge gate / PR checks unchanged

## Residual / follow-up (same plan, later units)

- Deeper hub CI (host route)
- Full browser-verification toggle UX + agent QA sessions (U7/U9/U10)
- Richer screenshots gallery wiring to live artifacts API
- Test plans CRUD polish

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

* **New Features**
* Added the Quality plugin with a project Quality hub and task-focused
QA tab.
* Added test runs, reports, preview server controls, suggested test
cases, and run history.
* Added configurable test presets, cancellation, status tracking, and
safe command execution.
* Added experimental-feature controls for enabling Quality
functionality.
* Bundled Quality with the CLI and made it available through the plugin
manager.

* **Documentation**
* Added Quality plugin guidance, terminology, configuration details, and
implementation planning documentation.

* **Bug Fixes**
* Improved process supervision so command failures and shutdown timers
are handled safely.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 20:28:11 -07:00
gsxdsm
e9537c9e85 docs(core): correct the ensureDatabase comment on the postgres join path
The preflight join carried "// Ensure the database exists on the running
instance" above a line that only builds a URL. No ensureDatabase() call has
ever followed it, so the comment described behavior the code does not have.

Replace it with why the call is absent: a joiner has no cluster of its own to
ensure, the owning process creates the database after its own start(), and
ensureDatabase() would throw here anyway because it requires `this.running` --
which the join path leaves false by design so stop() never reaps an instance
we did not start.

Also records the ordering assumption the path rests on: the owner publishes
runningInstances / writes postmaster.pid before its ensureDatabase() resolves,
so a joiner winning that window fails at the connection layer rather than
silently using a missing database.

Comment-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:14:04 -07:00
gsxdsm
8023aa2d08 fix(core): do not rescue a cancelled embedded Postgres start into a success
The startup-race join added in e33039ad0 catches a failed start, re-reads
postmaster.pid, and joins the competing instance. `startServerAsNonAdminUser`
rejects on abort from inside that same try, so a timeout-cancelled non-admin
launch that happened to observe a postmaster.pid would be rescued into a
published joined instance instead of propagating.

That contradicts the cancellation contract the post-start phases enforce a few
lines below (FNXC:PostgresResourceLifecycle 2026-07-14-18:42), which checks the
signal after every delayed phase specifically to stop a late instance before it
can publish running state or registry ownership.

Rethrow when the signal is aborted, restoring the pre-join behavior for that
path. The genuine race (no cancellation) still joins as intended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:10:53 -07:00
gsxdsm
e33039ad0f fix(core): join competing postmaster when embedded Postgres startup races
Starting a second Fusion process could fail with `lock file "postmaster.pid"
already exists`. The singleton preflight check and `pg.start()` are not atomic,
so another process can create the lock in between — the loser surfaced the
collision to the TUI as an error instead of simply joining the live instance.

`EmbeddedPostgresLifecycle.start()` now wraps the start path in a try/catch. On
failure it re-reads `postmaster.pid` via `isAlreadyRunning()`; when a live
instance is found it connects to that port with `ownsProcess=false` (so this
process never stops a server it did not start) and logs the race. Failures with
no live instance rethrow unchanged, so genuine startup errors are unaffected.

Regression test lives outside the real-process `embeddedDescribe` block — it uses
a mocked ctor, and nesting it there would skip it under FUSION_EMBEDDED_TEST_SKIP=1
(the gate/CI default), leaving the fix unprotected.

Verified: 35/35 embedded-lifecycle tests pass, core typecheck clean, lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:07:25 -07:00
gsxdsm
7cec078054 FN-8016: scope task popups to their opening view
Scope task-detail popups to their origin dashboard view by default.

- Default per-view popup scoping while retaining a legacy global-popup opt-out.
- Key popup lifecycle, navigation, and Escape dismissal by task and origin view.
- Update settings copy, documentation, localization, and regression coverage.

Files changed:
 .changeset/fn-8016-task-popup-view-scoping.md      |   7 ++
 docs/dashboard-guide.md                            |   4 +-
 .../core/src/__tests__/settings-defaults.test.ts   |   4 +-
 packages/core/src/settings-schema.ts               |   6 +-
 packages/core/src/types.ts                         |   6 +-
 packages/dashboard/app/App.tsx                     |  67 ++++++-----
 .../app/__tests__/App.keyboard-shortcuts.test.tsx  |  14 ++-
 .../app/__tests__/App.taskPopupViewGating.test.tsx | 125 +++++++--------------
 .../dashboard/app/components/SettingsModal.tsx     |   2 +-
 .../settings/sections/AppearanceSection.tsx        |   6 +-
 .../sections/__tests__/AppearanceSection.test.tsx  |  18 ++-
 .../app/hooks/__tests__/useAppSettings.test.ts     |  15 +++
 .../app/hooks/__tests__/usePoppedOutTasks.test.ts  |  28 ++---
 packages/dashboard/app/hooks/useAppSettings.ts     |   8 +-
 packages/dashboard/app/hooks/usePoppedOutTasks.ts  |  14 +--
 packages/i18n/locales/en/app.json                  |   4 +-
 packages/i18n/src/resources.d.ts                   |   4 +-
 17 files changed, 158 insertions(+), 174 deletions(-)

Fusion-Task-Id: FN-8016

Fusion-Task-Lineage: e33beeae-0ce3-4202-95dc-6fb2d26f9770

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 19:03:44 -07:00
gsxdsm
3f426c3ef0 fix: isolate mDNS node broadcasts (#2155)
## Summary

- Make Fusion mDNS broadcast names node-unique to avoid same-name DNS-SD
collisions.
- Treat asynchronous Bonjour broadcast errors as non-fatal diagnostics
when no listener is registered.
- Add regression coverage for a service-name collision.

## Validation

- `pnpm --filter @fusion/core exec vitest run
src/__tests__/node-discovery.test.ts --silent=passed-only
--reporter=dot`
- `pnpm --filter @fusion/core typecheck`
2026-07-15 18:08:07 -07:00
Phil Larson
514ccd304c Recover malformed agent interview responses (#2146)
## Summary
- preserve valid onboarding JSON returned in Pi thinking-only assistant
blocks
- retry one bounded JSON-only reformat turn when the model returns prose
or malformed output
- keep streamed output as a final extraction fallback instead of
overwriting it with an empty content array

## Verification
- `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/agent-onboarding.test.ts` — 20 passed
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm lint`
- `pnpm check:changesets --strict`
- live local-runtime AI Interview produced a structured
Hermes/computer-use onboarding question after restart

Follow-up to #2142, which fixed the missing planning-model fallback and
runtime-hint prompt.

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

* **Bug Fixes**
* Improved agent onboarding recovery when assistant replies include
thinking-only content or malformed JSON.
* Added a single automatic retry that re-formats invalid output into
valid onboarding JSON.
* Preserved structured “thinking” content as part of valid onboarding
responses.
* Normalized optional onboarding fields so null/empty/whitespace-only
values are treated as missing.
* Tightened Hermes automation so the runtime hint is set exactly to
`hermes`.
* **Tests**
* Added onboarding event synchronization and expanded coverage for
recovery and field normalization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 18:07:37 -07:00