Commit Graph

2376 Commits

Author SHA1 Message Date
gsxdsm
d6f94693b2 FN-8028: widen tablet chat message bubbles
Increase tablet agent-side chat bubble width while preserving responsive behavior.

- Raise the tablet assistant, streaming, and failure bubble cap to 92%.
- Update the responsive CSS contract and dashboard documentation.

Files changed:
 docs/dashboard-guide.md                                              | 2 +-
 packages/dashboard/app/components/ChatView.css                       | 5 ++++-
 packages/dashboard/app/components/__tests__/ChatView.core-contracts.test.tsx        | 4 ++--
 3 files changed, 7 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-8028

Fusion-Task-Lineage: 2158a4c8-f07b-4e96-bcf2-80ebe6b08515

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 23:49:24 -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
402b3a91fa fix(FN-8004): treat heartbeat soft-delete races as benign instead of stranding agents
A task soft-deleted concurrently with a heartbeat-driven moveTask raised
TaskDeletedError from the engine's own board path, leaving the agent in `error`
with a non-empty lastError and requiring a stop/start cycle to recover.

The race is benign by construction: the task is gone, so the move is a no-op.
The heartbeat now classifies it via isConcurrentSoftDeleteRaceError (matching the
canonical message and serialized/typed forms), keeps the agent active, clears
stale error/recovery state, and emits agent:heartbeat-move-skipped-soft-delete
with ids/counts-only metadata. Concurrent operator pauses are preserved.

Squash-merged by hand from fusion/fn-8004. The engine's AI merge approved this
content twice (squash a3a3cc6a8) but could not land it: main advances every ~8
minutes and each merge cycle took ~10, so every attempt lost to a concurrent
advance and rebuilt. Each cycle also burned a corrective pass on a first-pass
review rejection with no stated reason — the issue #1946 class of bug that this
task's own report cites as a sibling.

Reconciled against #2157, which refactored transient-error-detector.ts: the new
classifier coexists with the extracted transient-error-patterns.ts leaf. Verified
on the merged tree — 123 tests green across FN-8004's suites and #2157's,
engine typecheck clean.

Fusion-Task-Id: FN-8004

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:00:45 -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
71dd191c7c FN-8006: terminalize Plan Review retry storms
Plan Review now fails tasks when reviewer fallback retry limits are exceeded.

- Detect RetryStormError from Plan Review workflow execution
- Serialize the terminal retry error, clear recovery scheduling, and preserve workflow results
- Add retry-storm regression coverage, architecture guidance, and a patch changeset

Files changed:
 .changeset/fn-8006-plan-review-retry-storm.md      |  7 ++++
 docs/architecture.md                               |  2 +-
 packages/engine/src/__tests__/triage-plan-review-unavailable-retry.test.ts | 47 +++++++++++++++++++++-
 packages/engine/src/triage.ts                      | 33 +++++++++++++++
 4 files changed, 87 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-8006

Fusion-Task-Lineage: 932e7930-2069-4b0c-9cd1-9db39c2de5a3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 20:37:31 -07:00
gsxdsm
728eb1adaf FN-8003: add planning prompt recovery copy action
Preserve original planning prompts for recovery from stalled or off-track interviews.

- Add Copy prompt actions to active interview and error recovery surfaces.
- Restore original prompts for resumable sessions and provide clipboard feedback.
- Cover prompt copying across active, error, resumed, and absent-prompt states.

Files changed:
 docs/dashboard-guide.md                            |   3 +
 .../dashboard/app/components/PlanningModeModal.css |  33 +++++
 .../dashboard/app/components/PlanningModeModal.tsx | 116 +++++++++++++----
 .../PlanningModeModal.planning-flow.test.tsx       | 139 +++++++++++++++++++++
 4 files changed, 266 insertions(+), 25 deletions(-)

Fusion-Task-Id: FN-8003

Fusion-Task-Lineage: 11c0ec9d-290b-4793-9571-002d4b429c5e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 20:32:19 -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
1b9c7a7ca2 fix(FN-7575): stop double-commenting when a task is both imported and tracked
A task can carry BOTH linkages at once, pointing two services at ONE issue:
- GitHub: maybeCreateTrackingIssue() ADOPTS a github sourceIssue as
  githubTracking.issue (github-tracking.ts, `source_issue_linked`).
- GitLab: buildGitLabTaskProvenance() always returns sourceIssue AND
  gitlabTracking.item for the same item, so on GitLab EVERY imported task with
  gitlabCommentOnDone on was double-commented.

With comment-on-done enabled the issue-comment service and the tracking-comment
service both posted. Reproduced against the real wiring: two comments on
acme/widgets#42 ("✅ Task FN-1 ... resolved." then "✅ Done — ...").

The issue-comment services now suppress themselves when the tracking service
provably posts to the SAME target, and the tracking comment wins — it carries
commit/branch/PR/files/merged plus the release lines.

Identity, never "both linked": the two may legitimately target DIFFERENT issues
(a tracking issue linked separately from the source issue), which is two comments
on two issues and must keep working. GitHub matches on case-insensitive
owner/repo + number; GitLab is identical by construction because
resolveGitLabTarget() prefers the tracked item.

Both guards mirror the tracking services' `from === to` no-op guard: on a
same-column re-emit the tracking service stays silent, so suppressing there would
drop the only comment rather than dedupe it.

The net split is now disjoint: issue-comment owns "imported but not tracked",
tracking owns "tracked". Suppression is logged (once per completion, not the
high-frequency skip-noise FN-8024 removed) because a custom comment template
silently not rendering on a tracked issue is otherwise unexplainable.

Behavior change, documented in settings-reference.md: githubCommentTemplate /
gitlabCommentTemplate no longer render on a tracked issue.

Tests updated where they encoded the double-post path (they exercised the
services in isolation, so the duplicate was invisible). GitLab fixtures now
distinguish tracked vs imported-not-tracked shapes. Also asserts a PRE-EXISTING
gap left unchanged: resolveGitLabTarget() early-returns on an unresolvable item
and never falls back to sourceMetadata, so neither service comments there.

Verified non-vacuous: the 4 suppression tests fail against the pre-fix source;
the "still posts" tests pass either way by design. Gate green (294/122/63).

Fusion-Task-Id: FN-7575

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:24:49 -07:00
gsxdsm
cae7847085 fix(FN-8004): retry ACP provider blips in auto-merge instead of parking failed (#2157)
## What happened

FN-8004's implementation work finished and passed review. The auto-merge
then failed with `Grok ACP turn failed: Internal error` — a ~20 second
provider blip — and the task was parked `status: "failed"` with 8 files
of complete, reviewed work stranded on its branch.

The park is the interesting part: `status: "failed"` is precisely what
tells recovery to stop. So a misclassification here isn't a missed
retry, it's **terminal**. Both recovery paths were disabled by the same
wrong verdict:

- `maybeRetryTransientMerge` (inline, 3 retries w/ backoff) — never
fired once (`mergeTransientRetryCount: 0`).
- `recoverTransientMergeFailures` (self-healing sweep, exists exactly to
rescue parked in-review tasks) — skipped it, gated on the same
classifier.

## Three defects fixed

**1. No AI-provider failure class existed.** The AI merge drives a real
LLM turn, but `classifyTransientMergeError` only modeled git/lease/spawn
faults. Adds `ai-provider-turn-failure`.

**2. ACP dropped the error detail.** `promptAcpSession` rethrew the SDK
error unchanged, discarding the JSON-RPC `code`/`data` — the only
evidence the fault was provider-side. ("Internal error" is just the
standard text for `-32603`.) It now preserves them, keeping the original
as `cause`:

```
Internal error (acp rpc code -32603, retryable)
```

Classification anchors on that envelope, **not** on the bare `"Internal
error"` — matching that unanchored would disguise genuine application
defects as retryable blips. Only provider-fault codes (`-32603`,
`-32000`..`-32003`) are retryable; caller-fault codes
(`-32600`..`-32602`) stay permanent, since retrying just repeats the
failing call.

**3. Sweep/inline asymmetry** (found while tracing; latent and
unreported). The inline gate accepted `isTransientError(msg) ||
classify(msg)`, but the sweep consulted **only** the classifier. So
`ECONNRESET` / `socket hang up` during a merge earned inline retries and
then went **invisible to the sweep** once parked — stranded forever. The
classifier now delegates to `isTransientError`, so both gates agree by
construction.

To keep that delegation from importing the detector's
`usage-limit-detector → logger` chain (the chain FN-5627 split the
classifier out to avoid, which would break
`notification-service.test.ts`'s partial `vi.mock`), the pure predicates
moved to the import-free leaf `transient-error-patterns.ts`, re-exported
from `transient-error-detector.ts`. All 13 exports preserved, verified
programmatically.

## Loosened budgets

Per request, so more self-heals. Both apply **only** to errors already
proven transient; the ceiling and
`merger:transient-failure-budget-exhausted` audit path remain.

| Budget | Before | After |
|---|---|---|
| `MAX_AUTO_MERGE_TRANSIENT_RETRIES` | 3 | 5 (backoff
5s/10s/20s/40s/80s) |
| `MAX_TRANSIENT_MERGE_RECOVERIES` | 2 | 5 |

The bump broke two suites that had hardcoded the old `3`. Rather than
swap in another magic number, both now derive the cap from the constant
so future tuning doesn't re-break them.

## Verification

- `pnpm test:gate` green · `pnpm lint` clean · engine + ACP typecheck
clean · `pnpm verify:fast` PASS (5/5)
- ACP plugin 230 tests green · Grok plugin 64 green · engine
transient/merge suites 136 green
- Regression tests assert the **invariant across every surface** (per
*Fix the Invariant, Not the Repro*), not just the reported Grok string:
both ACP runtime prefixes, all retryable/non-retryable rpc codes, both
SDK error shapes, network delegation, class-ordering, and negative cases
proving bare `"Internal error"` and real defects stay permanent.
- A test caught a genuine bug in my own code mid-review (nested-shape
message shadowing), now fixed.
- `notifier.test.ts > "awaiting approval"` fails — **confirmed
pre-existing on clean main**, unrelated.

## Note

FN-8004's own branch (`fusion/fn-8004`) is still unmerged and its work
looks complete. Once this lands, its merge should be retried separately.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:14:25 -07:00
gsxdsm
adcba0eed2 FN-8017: add imported-item filter to task import
Add a persisted control that declutters Import Tasks by hiding items already on the board.

- Filter imported GitHub issues, pull requests, and GitLab resources while retaining full imported counts.
- Clear hidden selections, show an all-imported empty state, and preserve the preference per project.
- Document the toggle, add styling, coverage, and a minor changeset.

Files changed:
 .changeset/fn-8017-hide-imported-toggle.md         |   7 ++
 docs/dashboard-guide.md                            |   4 +-
 .../dashboard/app/components/GitHubImportModal.css |  21 ++++
 .../dashboard/app/components/GitHubImportModal.tsx |  70 ++++++++++++-
 .../__tests__/GitHubImportModal.test.tsx           | 108 +++++++++++++++++++++
 packages/dashboard/app/hooks/modalPersistence.ts   |   6 ++
 6 files changed, 209 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-8017

Fusion-Task-Lineage: 5f03fe92-6dce-4de7-a745-0c8a46dc2dbb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 19:27:00 -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
66ae82af5b FN-8007: align concurrency current-use markers
Align dashboard and footer concurrency markers with their native range thumbs.

- Map running counts in min-relative slider coordinates and clamp them to the configured cap
- Standardize native slider thumb dimensions and marker geometry across browsers
- Add dashboard coverage and document the marker behavior

Files changed:
 .changeset/fn-8007-concurrency-dot-alignment.md    |   7 +
 docs/dashboard-guide.md                            |   8 +-
 .../dashboard/app/components/EngineControlMenu.css |  20 ++-
 .../dashboard/app/components/EngineControlMenu.tsx |  19 ++-
 .../__tests__/EngineControlMenu.test.tsx           |  96 +++++-------
 .../command-center/CommandCenterControls.css       |  22 ++-
 .../command-center/CommandCenterControls.tsx       |  19 ++-
 .../__tests__/CommandCenterControls.test.tsx       | 164 +++++++++++++++++++++
 8 files changed, 277 insertions(+), 78 deletions(-)

Fusion-Task-Id: FN-8007

Fusion-Task-Lineage: 9ad8ee0b-09da-413e-96bc-530c897cb32e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 18:43:37 -07:00
gsxdsm
22fde62510 FN-8001: open footer planning sessions in Planning view
Navigate Background Tasks footer planning rows into the embedded Planning view so resume actually loads planning mode.

- Call handleChangeTaskView("planning") when opening a background planning session
- Extend App tests for footer planning resume and unchanged non-planning session routes
- Update dashboard-guide planning resume entry-point docs

Files changed:
 docs/dashboard-guide.md                            |  4 +-
 packages/dashboard/app/App.tsx                     |  5 ++
 .../app/components/__tests__/App.test.tsx          | 94 +++++++++++++++++++---
 3 files changed, 91 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-8001

Fusion-Task-Lineage: 402ece21-5f29-4304-a3aa-ef7004a30155

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:58:56 -07:00
gsxdsm
0b06026c74 FN-7992: open GitHub import issue/PR details in FloatingWindow
Show GitHub/GitLab import item details in a draggable FloatingWindow instead of an embedded two-pane preview, simplifying the import modal layout.

- Replace inline list/preview split with FloatingWindow for issue and PR detail
- Remove two-pane resize handle, mobile list/preview switch, and related CSS
- Keep close confirmation when discarding detail-window changes
- Update FloatingWindow styles and dashboard guide for floating import details
- Slim GitHubImportModal tests while restoring core import-modal coverage

Files changed:
 docs/dashboard-guide.md                            |   6 +-
 .../dashboard/app/components/FloatingWindow.css    |  23 +-
 .../dashboard/app/components/GitHubImportModal.css | 359 +-------
 .../dashboard/app/components/GitHubImportModal.tsx | 347 ++------
 .../components/__tests__/FloatingWindow.test.tsx   |   2 +-
 .../__tests__/GitHubImportModal.test.tsx           | 909 ++-------------------
 6 files changed, 139 insertions(+), 1507 deletions(-)

Fusion-Task-Id: FN-7992

Fusion-Task-Lineage: 0991e28c-d793-4a41-9312-6e250e8a09c4

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:51:34 -07:00
gsxdsm
c0bef0bfbe FN-7969: deprecate unused builtin Coding (Ideas) workflow
Hide builtin:coding-ideas from new selection after occupancy preflight, while keeping it resolvable for any existing task selections.

- Add builtin:coding-ideas to DEPRECATED_BUILTIN_WORKFLOW_IDS so it is excluded from defaultEnabledBuiltinWorkflowIds and listWorkflowDefinitions selection listings
- Keep getBuiltinWorkflow / direct resolution working for pre-existing Coding (Ideas) task selections
- Document deprecation and custom-workflow copy path in dashboard-guide and workflow-steps
- Extend builtin-workflows and settings-sections tests for hide-from-selection + management/resolution retention
- Add minor changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7969-deprecate-coding-ideas.md       |  7 +++++++
 docs/dashboard-guide.md                            |  2 +-
 docs/workflow-steps.md                             |  2 +-
 .../core/src/__tests__/builtin-workflows.test.ts   | 28 ++++++++++++++--------
 packages/core/src/builtin-workflows.ts             |  9 +++----
 packages/core/src/types.ts                         |  9 ++++---
 .../app/__tests__/settings-sections.test.tsx       |  2 ++
 7 files changed, 43 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-7969

Fusion-Task-Lineage: 578ae727-e1b6-4ff9-a3a2-d1228c50fba6

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:47:01 -07:00
gsxdsm
214af98591 FN-7977: hold Plan Review provider failures without replan regression
Prevent provider, model, transport, and abort failures from bouncing tasks back to planning after they enter execution.

- Classify non-plan-defect Plan Review failures and skip needs-replan handoff
- Terminate graph traversal with plan-review-provider-failure-hold and retry in place
- Guard triage recovery so advanced column/worktree/step state is never overwritten
- Document planning-recovery no-regression invariant and add regression tests
- Add patch changeset for the operator-facing fix

Files changed:
 .changeset/fn-7977-planning-failure-no-regression.md |   7 ++
 docs/architecture.md                               |   1 +
 docs/workflow-steps.md                             |   2 +-
 packages/engine/src/__tests__/replan-target.test.ts     |  17 +++-
 packages/engine/src/__tests__/transient-error-detector.test.ts |  32 +++++-
 packages/engine/src/__tests__/triage.test.ts       | 110 +++++++++++++++++++++
 packages/engine/src/__tests__/workflow-graph-optional-group.test.ts          |  46 ++++++++-
 packages/engine/src/__tests__/workflow-graph-optional-step-fix.test.ts       |  36 +++++++
 packages/engine/src/executor.ts                    |  62 +++++++++++-
 packages/engine/src/replan-target.ts               |  22 +++++
 packages/engine/src/transient-error-detector.ts    |  37 +++++++
 packages/engine/src/triage.ts                      |  73 +++++++++++---
 packages/engine/src/workflow-graph-executor.ts     |  45 ++++++++-
 13 files changed, 466 insertions(+), 24 deletions(-)

Fusion-Task-Id: FN-7977

Fusion-Task-Lineage: 6d62d3ca-c6f3-4d02-a377-d7fd59f0c0f9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:29:10 -07:00
gsxdsm
1c02e683b7 FN-7970: deprecate unused builtin:brainstorming from new selection
Hide the built-in Brainstorming workflow from new selection after occupancy preflight, while keeping it resolvable for existing tasks.

- Add DEPRECATED_BUILTIN_WORKFLOW_IDS and isBuiltinWorkflowDeprecated helper
- Exclude deprecated built-ins from defaults and selection listings
- Hide deprecated built-ins from Settings workflow enablement toggles
- Update docs/tests and add a minor changeset for the operator-facing change

Files changed:
 .changeset/fn-7970-deprecate-brainstorming.md      |  7 ++++
 docs/workflow-steps.md                             |  2 +-
 .../core/src/__tests__/builtin-workflows.test.ts   | 40 ++++++++++++----------
 packages/core/src/builtin-workflows.ts             | 17 ++++++++-
 packages/core/src/index.gate.ts                    |  2 ++
 packages/core/src/index.ts                         |  2 ++
 packages/core/src/task-store/remaining-ops-8.ts    | 10 ++++--
 packages/core/src/types.ts                         |  9 +++++
 .../app/__tests__/settings-sections.test.tsx       | 28 ++++++++++++++-
 .../settings/sections/GeneralSection.tsx           |  9 +++--
 10 files changed, 101 insertions(+), 25 deletions(-)

Fusion-Task-Id: FN-7970

Fusion-Task-Lineage: 47f9cd6e-d843-4c14-b197-447ff2072e3b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:24:23 -07:00
gsxdsm
363916926d FN-7995: always persist tool_error detail for Activity feed diagnosis
Always persist bounded tool_error detail so the task Activity feed can surface underlying failure messages even when verbose tool-output persistence is off.

- Keep tool args and successful tool_result detail opt-in via persistAgentToolOutput
- Always include bounded tool_error detail in agent-log JSONL rows
- Document diagnostic retention in types, agent-logger, and storage docs
- Cover Activity reveal behavior and logger persistence with unit tests
- Add patch changeset for operator-facing Activity error detail fix

Files changed:
 .changeset/fn-7995-tool-error-detail.md            |  7 ++++
 docs/storage.md                                    |  1 +
 packages/core/src/agent-log-constants.ts           |  4 +++
 packages/core/src/types.ts                         | 10 ++++--
 .../app/components/__tests__/TaskChatTab.test.tsx  | 42 ++++++++++++++++++++++
 packages/engine/src/__tests__/agent-logger.test.ts | 41 ++++++++++++++++++---
 packages/engine/src/agent-logger.ts                |  9 ++---
 7 files changed, 104 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-7995

Fusion-Task-Lineage: 0fa063df-58b1-4991-a0d9-e8a77181d32a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:17:37 -07:00
gsxdsm
e83116a970 FN-7991: mark import-screen items as imported immediately
Mark successful GitHub/GitLab import rows as Imported right away via optimistic local URL state, without waiting for the parent tasks prop round-trip.

- Add optimisticImportedUrls unioned with tasks-derived importedUrls via isUrlImported
- Populate on successful GitHub issue/PR and GitLab imports; clear on modal reset and source change
- Disable re-import and show Imported badge on rows, counts, and import buttons for optimistic URLs
- Cover optimistic import surfaces in GitHubImportModal tests
- Document the behavior in the dashboard guide and add a patch changeset

Files changed:
 .changeset/fn-7991-import-screen-optimistic-imported.md   |  7 +++
 docs/dashboard-guide.md                                    |  2 +-
 packages/dashboard/app/components/GitHubImportModal.tsx    | 57 +++++++++++++++++-----
 packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx | 56 ++++++++++++++++++---
 4 files changed, 103 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-7991

Fusion-Task-Lineage: ddfb249a-e2e8-4723-a86d-7f6edc74305c

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:51:54 -07:00
gsxdsm
5ff7a20738 FN-7976: fix mailbox artifact open and view-task popups
Fix Mailbox/Artifacts media auth and ensure View task always opens a usable popup.

- Add artifactMediaUrlWithToken for authenticated img/video/audio/link loads while keeping artifactMediaUrl token-free for fetch and HTML previews
- Load script-capable HTML artifact previews via Authorization + revocable blob URL so tokens never reach allow-scripts iframes
- Keep non-board/list task popups (Mailbox, Documents) visible even when board/list-only popup gating is enabled
- Upgrade duplicate popOut entries so reopening a task refreshes snapshot and origin
- Document the behavior and add a patch changeset

Files changed:
 .changeset/fn-7976-mailbox-artifact-fixes.md       |  7 +++
 docs/dashboard-guide.md                            |  2 +-
 packages/dashboard/app/App.tsx                     | 15 +++--
 .../app/__tests__/App.taskPopupViewGating.test.tsx | 10 ++-
 .../dashboard/app/__tests__/api-artifacts.test.ts  | 12 +++-
 .../api/__tests__/legacy-artifact-media.test.ts    | 27 ++++++++
 packages/dashboard/app/api/legacy.ts               | 21 +++++--
 .../dashboard/app/components/ArtifactsGallery.tsx  | 72 ++++++++++++++++++----
 .../dashboard/app/components/DocumentsView.tsx     |  4 +-
 .../app/components/MailboxArtifactAttachment.tsx   |  6 +-
 .../dashboard/app/components/TaskDocumentsTab.tsx  |  6 +-
 .../components/__tests__/DocumentsView.test.tsx    | 31 ++++++----
 .../__tests__/MailboxArtifactAttachment.test.tsx   | 24 ++++----
 .../app/components/__tests__/MailboxView.test.tsx  |  8 +--
 .../components/__tests__/TaskDocumentsTab.test.tsx | 16 ++---
 .../app/hooks/__tests__/usePoppedOutTasks.test.ts  |  9 ++-
 packages/dashboard/app/hooks/usePoppedOutTasks.ts  | 17 +++--
 17 files changed, 206 insertions(+), 81 deletions(-)

Fusion-Task-Id: FN-7976

Fusion-Task-Lineage: 4c25b3a6-5836-4629-b33e-647f213e3261

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:30:55 -07:00
gsxdsm
667f4c8a55 FN-7987: expose shared fusion toolset to chat agents and Grok CLI
Give dashboard chat and room responders the same safe coordination/productivity tools as other agent lanes, including via the Grok MCP bridge.

- Export chat coordination tool factories from @fusion/engine for public use
- Assemble createChatFusionToolset with board, delegation, web, goal, memory, and research tools
- Wire the shared toolset into model-loop chat and room-responder sessions
- Exclude destructive agent-lifecycle tools and fn_memory_append from chat
- Cover chat fusion parity and Grok bridge tool preservation with tests
- Document chat Grok tool parity and add a minor changeset

Files changed:
 .changeset/fn-7987-chat-fusion-toolset.md          |  7 ++
 docs/agents.md                                     |  1 +
 docs/grok-cli-contract.md                          |  2 +-
 packages/dashboard/src/__tests__/chat-manager.test.ts | 52 +++++++++++-
 packages/dashboard/src/chat.ts                     | 95 +++++++++++++++++++++-
 packages/engine/src/__tests__/agent-session-helpers.test.ts | 15 ++++
 packages/engine/src/index.ts                       | 26 ++++++
 plugins/fusion-plugin-grok-runtime/src/__tests__/tool-bridge.test.ts | 36 ++++++++
 8 files changed, 230 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7987

Fusion-Task-Lineage: 4d8d3dbc-10b8-4b56-9b63-79fe85a13755

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:25:39 -07:00
gsxdsm
d9843f75bc FN-7983: colocate project summarization model with summarization settings
Move the Project Summarization model lane and title-summarizer fallback next to the AI title/commit summarization controls in Project Models.

- Extract shared project-lane renderer and keep default/merger/import-translate in the general Model Lanes list
- Render summarization + title-summarizer fallback inside the AI summarization section with the same models-available guard
- Add regression tests for colocation and empty-models guard
- Update settings reference docs and add a patch changeset

Files changed:
 .changeset/fn-7983-summarization-lane-colocation.md       |   7 ++
 docs/settings-reference.md                         |   6 +-
 .../app/__tests__/settings-sections.test.tsx       |  61 ++++++++++
 .../settings/sections/ProjectModelsSection.tsx     | 126 ++++++++++++---------
 4 files changed, 142 insertions(+), 58 deletions(-)

Fusion-Task-Id: FN-7983

Fusion-Task-Lineage: c473ba61-f003-401d-bc66-86f2078ba047

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:21:16 -07:00
gsxdsm
10b80453c4 FN-7978: share GitHub import dedup via sourceIssue-first helper
Unify GitHub issue import deduplication so prior imports stay marked after description edits or owner/repo casing changes.

- Extract shared buildGitHubIssueSource and isGitHubIssueAlreadyImported helpers in dashboard github.ts (sourceIssue-first, case-insensitive repo, sourceMetadata + description URL fallbacks)
- Route CLI import paths, extension tools, and dashboard single/batch import through the shared helpers
- Drop local description-URL-regex-only importedUrls dedup; list existing tasks with slim:false for full provenance
- Add regression coverage and changeset for the operator-facing fix

Files changed:
 .changeset/fn-7978-github-import-dedup.md          |  7 ++
 docs/gitlab-parity-inventory.md                    |  2 +-
 packages/cli/src/__tests__/extension.test.ts       | 12 ++--
 .../task-command-github-import-tracking.test.ts    |  6 ++
 packages/cli/src/commands/__tests__/task.test.ts   | 36 +++++++---
 packages/cli/src/commands/task.ts                  | 82 +++++++++-------------
 packages/cli/src/extension.ts                      | 35 ++-------
 packages/dashboard/src/__tests__/github.test.ts    | 22 +++++-
 .../dashboard/src/__tests__/routes-github.test.ts  |  8 +--
 packages/dashboard/src/github.ts                   | 62 +++++++++++++++-
 packages/dashboard/src/index.ts                    |  2 +-
 .../dashboard/src/routes/register-git-github.ts    | 33 +--------
 12 files changed, 174 insertions(+), 133 deletions(-)

Fusion-Task-Id: FN-7978

Fusion-Task-Lineage: 44f3d555-49fb-41e2-87d0-0a722462f132

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:16:19 -07:00
gsxdsm
f9c19f9f3a FN-7967: accept custom triage workflow IDs and honor project default
Allow triageDefaultWorkflowId and triageDecisionOnlyWorkflowId to accept custom workflow IDs so project default workflows are honored at triage time.

- Change triage workflow settings from enum to string; empty triageDefaultWorkflowId inherits config.settings.defaultWorkflowId
- Render triage prompt default from project settings unless an explicit stored override exists
- Only pass stored triageDefaultWorkflowId into triage policy settings so declaration defaults do not clobber project defaults
- Document settings behavior and add core/engine regression coverage
- Add patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7967-triage-default-workflow.md                        |  7 +++++++
 docs/settings-reference.md                                           |  4 ++--
 packages/core/src/__tests__/builtin-workflow-settings-triage.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++--
 packages/core/src/builtin-workflow-settings.ts                       | 35 ++++++++++++++++++++---------------
 packages/engine/src/__tests__/triage.test.ts                         | 41 +++++++++++++++++++++++++++++++++++++++++
 packages/engine/src/triage.ts                                        | 28 ++++++++++++++++++++++++----
 6 files changed, 135 insertions(+), 23 deletions(-)

Fusion-Task-Id: FN-7967

Fusion-Task-Lineage: e42ea061-889c-4bdd-8a9d-f56f34fc0c89

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:43:55 -07:00
gsxdsm
a75b2f4bb8 FN-7980: dismiss mobile task popups on swipe/back without leaving board
Register mobile task popups on the Fusion nav stack so browser Back, iOS edge-swipe, and Android Back close the popup and keep the board/list visible.

- Push a modal nav entry when opening a mobile task popup and clean it up on close
- Route FloatingWindow and shortcut closes through nav-aware popup close
- Add swipe-back tests for board and list popup dismissal
- Document popup Back behavior in the dashboard guide

Files changed:
 docs/dashboard-guide.md                            |  3 +-
 packages/dashboard/app/App.tsx                     | 35 +++++++--
 .../__tests__/TaskDetail.swipe-back.test.tsx       | 84 +++++++++++++++++++++-
 3 files changed, 114 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-7980

Fusion-Task-Lineage: e321a1df-e271-41c0-81af-3560d759f7bb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:23:23 -07:00
gsxdsm
0863c0fb58 feat(dashboard): auto-translate foreign-language GitHub issues on import (#2141)
## Why

The Import Tasks panel routinely lists issues in languages the operator
cannot read. Translation already shipped in #2128, but deliberately
**opt-in and preview-only** — its header comment read *"Translation is
opt-in (never automatic) so import provenance stays faithful until the
operator asks."*

This reverses that decision **behind a default-off setting**, so
operators who never opt in keep byte-faithful import provenance. The
superseded comment is kept and annotated rather than deleted, so the
reason the rule changed stays in the code.

### The structural gap #2128 left

`POST /github/issues/import` accepts only `{owner, repo, issueNumber}`
and **re-fetches the issue server-side**. A translation held in React
state could never reach the created task, and the in-memory cache died
with the modal. That is why the cache here is server-side rather than in
the hook — it's what makes "imported issues carry the translated
version" actually true.

## What operators get

Auto-translate is **off by default**. When enabled:

- The **50 most recent OPEN** foreign-language issues translate on panel
load — **list titles**, not just the preview, so the list reads in your
language before you click anything.
- Translations show **by default**, with a toggle back to the original
(hover a translated list title to see the original).
- Translations **persist until the issue closes**, so re-opening the
panel neither waits nor re-bills.
- **Both single and batch import** carry the translation, so the created
task reads like the preview you approved.
- A **target language** setting (unset = follow the dashboard language)
and a dedicated **model lane**, so you can pin a cheap/fast model
without dragging the summarization lane onto it.

## Notable decisions

| Decision | Why |
|---|---|
| Detect **before** the model | An issue already in the target language
is never sent. Without this, an English repo with the setting on would
bill every issue to return its input unchanged. |
| Detection moved to `@fusion/core` | The panel and the server must not
disagree about which issues are foreign; two copies of a heuristic
drift. |
| Own rate-limit budget | Translation shared a 10/hour budget with
refine/goal-draft. Fanning out per-issue would fail partway **and**
starve refine for the hour. |
| Cache keyed on a **source hash** | An edited issue misses the cache
and re-translates instead of serving stale prose. |
| Import is **cache-read only** | A miss imports the original. Import
must never block on, or fail because of, translation. |
| `project_id` leads the cache PK + full RLS contract | All projects
share one flat `project` schema. `verification_cache`'s PK predates that
discipline; this table does not copy that mistake. |

## Verification

- ✅ `pnpm lint`, `@fusion/core` + `@fusion/dashboard` typecheck
- ✅ `pnpm verify:fast` — build + scoped typecheck + real boot smoke
(`/api/health`)
- ✅ `pnpm test:gate` — 479 tests
- ✅ 19 new tests covering the billing invariants
(off/closed/same-language ⇒ **no model call**), cache hit/miss-on-edit,
the 50 cap, and per-item fail-soft
- ✅ `schema-applier` real-Postgres suite (46 tests) exercises migration
`0010` and its isolation invariant

**Pre-existing failures NOT touched** (confirmed red on `HEAD` before
this branch): `AppearanceSection`'s task-popup test, and two PG-cutover
keys (`sqliteMigrationNotice`, `postgresMigrationInboxMessageSentAt`)
missing description mappings. I left the latter rather than guess an
allowlist entry that could mask a real coverage gap.

## Reviewer notes

- Short Latin-script prose (a one-line Spanish title) rates only
*medium* confidence and won't auto-translate — the existing heuristic is
deliberately conservative so English issues are never billed. CJK
detects regardless of length. The threshold is the knob if you'd rather
bias toward translating.
- The RLS/isolation contract in migration `0010` is the part most worth
a careful look.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:06:42 -07:00
gsxdsm
05151a25db feat: faster dashboard and serve startup (#2132)
## Summary

Speeds up **time-to-HTTP-ready** for `fn dashboard` and `fn serve` after
the PostgreSQL cutover without reintroducing the historical 3s
cwd-engine race that degraded webhooks.

- **Dashboard store share (serve parity):** inject the factory-booted
`TaskStore` as `externalTaskStore` so cwd `ensureEngine` does not open a
second pool; share only when store root matches project working
directory (multi-project safe).
- **Serve multi-project:** stop awaiting `startAll()` before listen;
await only the primary engine; background the rest + reconciliation.
- **Defer non-route-critical engine work:** ordered OAuth (refresh →
monitor), automation schedule syncs, and auto-merge **enqueue** after
the engine handle is returnable.
- **Critical-path merge status clear:** still clear stale
`merging`/`merging-pr` before ready so manual merge is not blocked after
crash.
- **Serve `--paused`:** apply `enginePaused` before
`ensureEngine`/`startAll` (dashboard ordering).
- **Stop safety:** generation counter so deferred tails cannot resume
after `stop()` clears `shuttingDown`.
- **Phase timing:** shared `phaseTime` helper, factory substep logs,
serve time-to-listen.

Plan: `docs/plans/2026-07-14-001-feat-faster-startup-plan.md`

## Test plan

- [x] `packages/engine` — `project-engine-manager.test.ts` (path-matched
external store)
- [x] `packages/engine` — `project-engine-deferred-startup.test.ts`
(status clear, OAuth order, stop generation)
- [x] `packages/cli` — `startup-phase.test.ts`
- [x] `packages/cli` — `serve.test.ts` (60 tests, including `--paused`)
- [ ] Local: warm `fn dashboard` / `fn serve` and compare `startup phase
*` / `time-to-listen` logs
- [ ] `pnpm smoke:boot` (real serve `/api/health` on ephemeral port)

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

* **Performance**
* Improved dashboard and serve startup times, including faster
time-to-listen and time-to-ready.
* Moved non-essential background initialization off the critical startup
path.
  * Parallelized dashboard service initialization where possible.

* **Reliability**
  * Improved multi-project startup handling and project selection.
  * Prevented cross-project task-store sharing.
  * Added safer shutdown behavior for partially completed startup.

* **Diagnostics**
* Added startup phase timing logs to help identify performance
bottlenecks.

* **Tests**
* Expanded coverage for deferred startup, shutdown, project isolation,
and startup timing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:01:08 -07:00
gsxdsm
599a509d22 refactor: package code organization (god-file peels, wave 1) (#2139)
## Summary

First wave of package-internal code organization: split oversized
modules into domain-named files/folders while preserving public import
paths via re-exports, and refresh the line-count ratchet scoreboard.

- **Plan:**
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`
(multi-wave program; this PR lands U1–U4 + first U3/U6 slices)
- **Core types:** peel `types.ts` into
`types/{board,merge-queue,execution-and-ui,merge-policy,workflow-steps}.ts`
with browser-safe Vite alias preserved
- **Core TaskStore:** rename `remaining-ops-9` →
`task-commit-associations` (domain-named, not ordinal dump)
- **Engine executor:** peel pure helpers into
`executor/{browser-probe,requeue-loop,pseudo-pause,workflow-step-failures}.ts`
- **Engine heartbeat:** peel system prompts/procedures into
`agent-heartbeat-prompts.ts`
- **Ratchet:** one-time baseline truth-up + ratchet-down for touched
files

### Deferred to follow-up PRs (plan U5, U7–U9 + remaining waves)
- Self-healing folder split
- Further remaining-ops domain peels
- Dashboard `legacy.ts` / routes / UI monofiles
- CLI extension + TUI peels

## Test plan

- [x] `pnpm --filter @fusion/core exec tsc --noEmit`
- [x] `pnpm --filter @fusion/engine exec tsc --noEmit`
- [x] Focused vitest: `detect-pseudo-pause`,
`executor-browser-verification`, `clear-terminal-workflow-step-failures`
- [x] `node scripts/check-file-line-count.mjs` clean against updated
baseline
- [ ] CI merge gate (lint/typecheck/build/gate)
- [ ] Browser smoke: N/A for this PR (no dashboard UI route changes)

## Residual Review Findings

None. Review autofix applied dual-home wiring for
`clearTerminalWorkflowStepFailures` only.

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

* **New Features**
* Added configurable heartbeat procedures for task and no-task scenarios
(including patrol-aware rendering).
* Improved agent-browser availability verification with clearer
availability/status reporting.
  * Added detection for pseudo-pauses and review-handoff requests.
* Expanded core configuration/contract options for
execution/UI/localization, merges, merge queues, and workflow steps.
* **Bug Fixes**
* Improved handling of transient execute-requeue and workflow-step
retry/cleanup behavior, including better Windows path support.
  * Preserved existing public interfaces during internal restructuring.
* **Documentation**
  * Added a multi-phase roadmap for future package reorganization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:34:30 -07:00
gsxdsm
85f8b1f909 feat: shared Postgres multi-node — retire mesh data-plane replication (#2130)
## Summary

- Treat **shared PostgreSQL** (`DATABASE_URL`) as the multi-node durable
data plane; mesh HTTP is membership + optional auth, not task/settings
replication.
- **Peer exchange**: under Postgres backend mode, write queue is
**topology/auth-only**; non-topology pending rows fail rather than
replaying multi-leader task/settings payloads.
- **Mesh routes**: task-ID reserve/commit/abort always hit local shared
allocator rows (ignore remote `coordinatorNodeId`); mesh sync ignores
settings and only exchanges `authMaterial`.
- **Docs**: rewrite multi-project runbook, shared cluster protocol, and
architecture mesh sections for shared-Postgres + claims/leases.

## Context

Follows the SQLite→Postgres cutover. Multiple Fusion nodes can share one
external Postgres while keeping **per-node execution** (worktrees,
processes, claims via `central.task_claims`). Explicit non-goals remain:
scheduler failover and live process migration.

Plan:
`docs/plans/2026-07-15-001-refactor-mesh-shared-postgres-multinode-plan.md`

## Test plan

- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/peer-exchange-service.test.ts`
- [x] `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/mesh-routes.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/shared-mesh-state.test.ts`
- [ ] CI gate (lint/typecheck/build/gate)
- [ ] Manual (optional): two processes, same `DATABASE_URL`, create task
on A visible on B; settings change without mesh settings sync; claim
exclusivity

## Operator note

Multi-node shared board requires **external** `DATABASE_URL` on every
node. Default embedded Postgres is still single-host.

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

* **New Features**
* Improved multi-node deployments using shared PostgreSQL as the durable
source of execution state.
* Task ID reservation/commit/abort now run locally (no remote
coordinator forwarding).
* Mesh syncing now prioritizes topology visibility and authentication
material; settings replication is disabled in shared-Postgres mode.
* **Bug Fixes**
* Prevented task/settings replication over mesh HTTP in shared-Postgres
deployments.
* Refined lease ownership, recovery, and reconciliation to converge via
shared-database primitives.
* **Documentation**
* Updated architecture and shared-mesh protocol guidance, including
multi-node setup and lease/task-ID allocation behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:32:33 -07:00
gsxdsm
3e978e1540 fix: quiet per-poll scheduler hold-release and routing log spam
Both lines fired on every scheduler poll while nothing changed: a held
card re-attempts release each sweep, and every dispatch candidate logged
its resolved node. On a busy board that filled the operator log pane with
"Hold release for FN-XXXX deferred" and "routed to node=local" within
seconds, burying real scheduler events.

Add a Logger.debug() level, off by default and opted into per subsystem
via FUSION_DEBUG, and demote both lines to it. Routing to a remote node
stays at info since it explains where work actually went; only the local
default is demoted. Lines reporting a real transition (capacity
rejection, racing sweep, release failure) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:49:43 -07:00
gsxdsm
d893a026df FN-7971: hide GitLab import tab when GitLab is disabled
Hide the Import Tasks GitLab provider affordance when gitlabEnabled is off, coerce restored GitLab state to GitHub, and document the behavior.

- Gate GitLab provider tab visibility on effective gitlabEnabled and wait for settings before replaying persisted GitLab auto-load
- Coerce disabled GitLab provider preference to GitHub without firing GitLab fetch/import requests
- Cover hide/show/coerce paths in GitHubImportModal tests and update dashboard guide copy
- Add patch changeset for the published package

Files changed:
 .changeset/fn-7971-hide-gitlab-when-disabled.md    |  7 +++
 docs/dashboard-guide.md                            |  4 +-
 .../dashboard/app/components/GitHubImportModal.tsx | 27 ++++++++--
 .../__tests__/GitHubImportModal.test.tsx           | 62 +++++++++++++++++++---
 4 files changed, 88 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-7971

Fusion-Task-Lineage: e645a4a7-85e0-4635-8dad-f5839390e5c5

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:47:14 -07:00
gsxdsm
6e3a338cac FN-7968: defer slow cleanup off task deletion critical path
Make soft-delete return after the DB mutation while branch and agent cleanup run in the background.

- Schedule cleanupBranchForTask after the soft-delete transaction instead of awaiting it under withTaskLock
- Persist cleaned-branch log entries on the deleted row asynchronously; warn on deferred failures
- Respond from DELETE /tasks/:id after deleteTask and schedule execution-agent binding release off the HTTP path
- Add core and dashboard regression tests for non-blocking delete cleanup
- Document the fast-path contract in architecture.md and add a patch changeset

Files changed:
 .changeset/fn-7968-task-delete-latency.md          |   7 +
 docs/architecture.md                               |   1 +
 .../task-delete-nonblocking-cleanup.test.ts        | 160 +++++++++++++++++++++
 packages/core/src/task-store/archive-lifecycle.ts  |  57 +++++++-
 .../routes-task-delete-nonblocking.test.ts         | 139 ++++++++++++++++++
 .../src/routes/register-task-workflow-routes.ts    |  19 ++-
 6 files changed, 370 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-7968

Fusion-Task-Lineage: f218a91e-aee3-46c9-a80f-182751b3ccc4

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:44:30 -07:00
gsxdsm
de25e32eac FN-7963: add plannerHeartbeatPatrolEnabled to gate idle heartbeat task creation
Add a workflow setting that disables idle/no-task heartbeat proactive task creation without turning off planner overseer stuck-task recovery.

- Declare plannerHeartbeatPatrolEnabled (default true) in BUILTIN_OVERSIGHT_SETTINGS
- Resolve the flag via resolveEffectivePlannerHeartbeatPatrolEnabled and wire it into agent-heartbeat/triage prompts
- Render patrol-off instruction when disabled; keep FN-7962 outage backoff lines when patrol stays enabled
- Cover setting defaults, prompt builders, and heartbeat executor paths with tests
- Document the setting in settings-reference and add a changeset

Files changed:
 .changeset/fn-7963-planner-heartbeat-patrol.md     |   7 ++
 docs/settings-reference.md                         |  11 +-
 packages/core/src/__tests__/agent-prompts.test.ts  |  29 +++++
 .../builtin-workflow-settings-triage.test.ts       |  21 ++++
 .../plannerHeartbeatPatrolEnabled-default.test.ts  |  64 ++++++++++
 packages/core/src/agent-prompts.ts                 |  55 +++++++--
 packages/core/src/builtin-workflow-settings.ts     |  14 +++
 packages/core/src/index.gate.ts                    |   5 +
 packages/core/src/index.ts                         |   5 +
 packages/core/src/workflow-settings-resolver.ts    |  15 ++-
 .../src/__tests__/heartbeat-executor.test.ts       |  59 ++++++++-
 packages/engine/src/agent-heartbeat.ts             | 135 +++++++++++++++++++--
 packages/engine/src/triage.ts                      |   8 +-
 13 files changed, 402 insertions(+), 26 deletions(-)

Fusion-Task-Id: FN-7963

Fusion-Task-Lineage: c5e7a382-52c1-4cc1-8b21-aba7dc7d2b97

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:27:15 -07:00
gsxdsm
7a4a9c8229 fix(engine): auto-recover false-positive heartbeat-model-unavailable parks
Admit under-budget paused/heartbeat-model-unavailable agents to the shared
heartbeatErrorRecovery budget so timer, self-healing, and startup paths
retry without a manual Retry. Keep the pause reason when the budget is
exhausted so operators still see credential guidance.
2026-07-15 08:58:33 -07:00
gsxdsm
e9f14bf024 perf: speed up local pnpm build and cap stacked verifications (#2134)
## Summary

- Extend the workspace content-hash skip cache to **all** packages (not
just plugins), with `--force` / `--full` flags
- Default local CLI packaging to a **fast mode** (bin/extension +
migrations only); full desktop/plugin/DTS staging runs on CI or `pnpm
build:full`
- Enable TypeScript `incremental` builds for warm recompiles
- Add `maxConcurrentVerifications` (default **1**) so concurrent tasks
cannot stack monorepo typecheck/build and peg CPU

Warm `pnpm build` measured ~**126s → ~0.8s** when nothing changed.

## Test plan

- [x] `node --test scripts/__tests__/build-workspace.test.mjs` (12 pass)
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/verification-concurrency.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/settings-parity.test.ts`
- [x] Local: first `pnpm build` rebuilds as needed; second warm `pnpm
build` skips all packages (~0.8s)
- [x] Fast CLI packaging logs skip of desktop/plugin staging without
`FUSION_CLI_FULL_PACKAGE`
- [ ] CI: `pnpm build` still full-packages under `CI=true` (plugin
staging / release surfaces)

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

* **New Features**
* Added a Scheduling setting to limit concurrent verification tasks from
1–8, with a default of 1.
* Verification tasks now support cancellation while waiting or running.
  * Added options for forced and full workspace builds.

* **Performance**
* Local builds can skip unchanged packages and use incremental
compilation for faster rebuilds.
* Local CLI packaging is faster by default, while full packaging remains
available when needed.

* **Documentation**
* Updated the settings reference with the new verification concurrency
option.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 08:44:11 -07:00
gsxdsm
bc2d22df6e feat(dashboard): offer AI translation in Import Tasks preview (#2128)
## Summary

Import Tasks can now offer on-demand AI translation when a selected
GitHub or GitLab issue/PR title and body appear to be in a different
language than the active dashboard locale.

- Detect foreign-language content with a conservative client heuristic
(Unicode scripts + Latin stopwords)
- Show an opt-in banner: **Translate**, then **Show original / Show
translation**, plus **Dismiss**
- Call new `POST /api/ai/translate-text` (shared AI-helper rate limit
with refine/draft)
- Translation is **display-only** in the preview; imported task text
stays the original source language

## Why

Operators working in a non-English dashboard (or reading
non-dashboard-language issues) needed a way to understand import
candidates without leaving the preview or changing what gets imported.

## Test plan

- [x] Unit tests for language detection (`detectContentLanguage`)
- [x] Unit tests for translate request validation, response parsing, and
AI agent path
- [x] GitHub import modal: French content shows translate controls;
English content does not
- [x] Dashboard typecheck clean for app + server packages
- [ ] Manual: open Import Tasks with dashboard language English, select
a French/Korean issue, translate and toggle original
- [ ] Manual: confirm Import still creates the task with original
title/body
- [ ] Manual: dismiss banner for a selection and confirm it stays
dismissed for that item

## Notes

- Comments are not translated (title + body only)
- zh-CN / zh-TW share a CJK family so Chinese content does not prompt
translation when the UI is either Chinese locale
- Secondary locale catalogs have empty placeholders for the new
`git.translate*` keys (runtime falls back to English)
2026-07-15 02:18:43 -07:00
gsxdsm
a242f1b449 fix(FN-7952): migrate bundled plugins to PostgreSQL (#2111)
## Summary

Bundled plugins now persist shared runtime state in project-scoped
PostgreSQL tables instead of maintaining independent SQLite authority.
Reports, CLI Printing Press, Compound Engineering, Roadmap, Even
Realities, and WhatsApp all follow the same ownership and startup
contract as Fusion core.

## Design decisions

- Plugin schema hooks run through the host’s PostgreSQL owner and
enforce project isolation.
- The SDK exposes the host contract needed by bundled plugins without
importing engine internals.
- Legacy Roadmap ownership fixtures use the supported empty-owner
sentinel, preserving current composite primary/foreign keys while
exercising backfill behavior.
- The lockfile travels with the Even Realities PostgreSQL dependency so
packaged installs remain reproducible.

## Validation

- All six affected plugin builds pass.
- Affected plugin suites pass: 773 tests across Printing Press, Compound
Engineering, Even Realities, Reports, Roadmap, and WhatsApp.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 40 files.

## Stack

- Depends on #2110 → #2109 → #2108.
- The documentation/release PR completes the stack.

Related: #2105


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

## Summary by CodeRabbit

* **Breaking Changes**
* PostgreSQL is now required for runtime storage; SQLite files are used
only as one-time migration inputs.
  * The legacy `FUSION_NO_EMBEDDED_PG` fallback has been removed.

* **New Features**
* Added project-isolated PostgreSQL storage for plugins, reports, tasks,
notifications, and other plugin data.
  * Added agent tools for reports and CLI service drafts.
  * Added PostgreSQL schema initialization support for plugin authors.

* **Bug Fixes**
  * Improved migration and recovery of legacy plugin state.
* Prevented cross-project data access and strengthened transactional
schema updates.

* **Documentation**
* Updated storage, migration, deployment, plugin authoring, CLI, and
dashboard guidance for PostgreSQL.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 00:27:59 -07:00
gsxdsm
4f037679ad feat: planner overseer session advisor (OMP advisor parity) (#2082)
## Summary

Adds a **session advisor** to the planner overseer so Fusion can review
live executor transcripts the way [oh-my-pi’s
advisor](https://github.com/can1357/oh-my-pi/tree/main/packages/coding-agent/src/advisor)
does — without replacing the existing lifecycle supervisor (stage watch,
retry, merge confirmation, human-control withhold).

### What ships

- **Emission guard** (`OverseerEmissionGuard`) — content-free phrase
filter, session dedupe with severity-rank escalation, one accept per
advisor update
- **Session delta runtime** — queues agent-log deltas, drains through an
advisor agent, drops backlog after 3 failures
- **Session advisor service** — model gate, level matrix (`observe` /
`steer` / `autonomous`), human-control re-check at inject,
`[session-advisor]` steering comments
- **OVERSEER.md / WATCHDOG.md** discovery for project review priorities
- **AgentLogger `onEntriesFlushed`** + poll-backed agent-log cursor for
durable deltas
- Workflow settings: `plannerOverseerAdvisorProvider` +
`plannerOverseerAdvisorModelId` (both required; empty = soft-disabled
for cost safety)
- Docs + changeset

### What does not ship (deferred)

- Multi-advisor YAML roster, mutating advisor tools, reviewer/merger
shadowing, true tool-abort interrupt

### Plan

`docs/plans/2026-07-13-001-feat-overseer-advisor-parity-plan.md`

## Enablement

1. Set workflow **Session advisor model provider** + **Session advisor
model id**
2. Oversight level `observe` (log only), `steer`, or `autonomous`
(inject)
3. Optional: add `OVERSEER.md` or `WATCHDOG.md` in the project

## Test plan

- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/overseer-emission-guard.test.ts`
- [x] `pnpm --filter @fusion/engine exec vitest run` overseer-* unit
tests (21 tests)
- [x] Related planner-overseer / intervention regression tests
- [x] `@fusion/engine` + `@fusion/core` typecheck
- [ ] Manual: configure advisor model, run an executor task, confirm
`[session-advisor]` inject + timeline metadata when concern is raised

## Residual Review Findings

None from autofix pass (log-cursor ordering fix already committed).


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

* **New Features**
* Added an off-by-default “session advisor” that can review live
execution activity and provide severity-based guidance.
* Added project and per-task controls to enable it, including a default
enable switch and Quick Add / Task Detail toggles.
* Enhanced advisor prompting by discovering and incorporating
`OVERSEER.md`/`WATCHDOG.md` review files.
* **Documentation**
* Added architecture and settings documentation for the new
session-advisor parity behavior.
* **Bug Fixes**
* Improved fail-soft handling so advisor behavior won’t disrupt
execution.
  * Fixed concurrent PostgreSQL migration startup failures.
* **Tests**
* Added coverage for advice parsing, emission guarding, runtime
behavior, and watchdog discovery.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 20:27:35 -07:00
gsxdsm
9bdbdc5f16 FN-7955: stage bundled plugin skills
Ensure bundled Compound Engineering skills are present in published CLI packages.

- Copy plugin src/skills directories into dist/plugins/<id>/skills during CLI packaging.
- Add bundle-output coverage that verifies Compound Engineering SKILL.md files stage and resolve from the plugin root.
- Document runtime-read bundled plugin asset staging and add a patch changeset for @runfusion/fusion.

Files changed:
 .changeset/fn-7955-ce-skills-published.md        |  7 ++++
 docs/PLUGIN_AUTHORING.md                         |  3 ++
 packages/cli/src/__tests__/bundle-output.test.ts | 51 ++++++++++++++++++++++++
 packages/cli/tsup.config.ts                      | 14 +++++++
 4 files changed, 75 insertions(+)

Fusion-Task-Id: FN-7955

Fusion-Task-Lineage: 32c4ad31-4f3a-478b-996f-ce6bcafd1e27

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-14 17:29:16 -07:00
gsxdsm
d0ce7829c0 FN-7953: fix mobile OAuth code submit taps
Submit Anthropic OAuth manual codes on the first mobile tap instead of requiring keyboard dismissal first.

- Add a reusable touch action gesture hook that handles touch/pointer activation before synthetic clicks.
- Wire the OAuth manual code Submit button to invoke submission on the first touch while preventing duplicate click handling.
- Cover the mobile double-tap regression and document the UI bug pattern for future fixes.

Files changed:
 .../oauth-manual-code-mobile-double-tap-submit.md  |  60 +++++++++++
 .../app/components/OAuthManualCodeForm.tsx         |  31 +++++-
 .../__tests__/OAuthManualCodeForm.test.tsx         | 110 +++++++++++++++++++++
 .../hooks/__tests__/useTouchActionGesture.test.ts  | 110 +++++++++++++++++++++
 .../dashboard/app/hooks/useTouchActionGesture.ts   |  89 +++++++++++++++++
 5 files changed, 399 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7953

Fusion-Task-Lineage: d387cdbd-25a7-4b7d-add6-27a1ded5cbea

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-14 17:18:06 -07:00
gsxdsm
dff864e098 feat: harden permanent-agent heartbeat instructions (#2081)
## Summary

Hardens permanent-agent operating law while keeping the
heartbeat/executor split:

- **Critical Rules** in task-scoped and no-task heartbeat system prompts
(survive custom `HEARTBEAT.md`)
- Stronger default procedures: disposition checklist, scoped-wake,
blocked dedup, progress note style
- **Wake Delta multi-assign inventory** (ranked, cap 8,
coordination-only framing) + `checkout_conflict` regression test
- Standing instructions six-section template for blank custom create /
empty detail insert
- Onboarding interview guidance to prefer structured `instructionsText`
- Playbooks, CONCEPTS, agents.md accuracy; remove stale agent
gap-analysis doc

Plan:
`docs/plans/2026-07-12-001-feat-permanent-agent-heartbeat-instructions-plan.md`

## Test plan

- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/assigned-task-ranking.test.ts`
- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/agent-heartbeat-procedures.test.ts
src/__tests__/heartbeat-executor.test.ts -u`
- [x] `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/standing-instructions-template.test.ts`
- [ ] CI gate green on PR

## Residual Review Findings

None recorded at open (inline review; no residual sink).

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

## Summary by CodeRabbit

* **New Features**
* Added ranked multi-assignment context to agent heartbeat wake-ups,
including task status, ownership, and lease details.
* Added standing-instructions templates for creating and editing
permanent agents.
* Improved onboarding guidance with a consistent six-section instruction
structure.
* Added clearer heartbeat handling for blocked tasks, no-task runs, and
checkout conflicts.

* **Documentation**
* Added permanent-agent heartbeat playbooks and expanded coordination
glossary entries.
  * Updated documentation indexes and heartbeat behavior guidance.

* **Tests**
* Added coverage for task ranking, instruction templates, wake-up
context, and conflict handling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 08:23:11 -07:00
gsxdsm
b563b12662 feat: add Oh My Pi (omp) ACP runtime plugin (#2083)
## Summary

- Add `fusion-plugin-omp-runtime` so Fusion agents can run through
operator-installed **Oh My Pi (`omp`)** over the [Agent Client
Protocol](https://omp.sh/docs/acp) (`omp acp`).
- Wire staged/bundled install, Settings → Authentication card (enable +
binary path), model discovery (`omp models` → `omp-cli/*`), and MCP
eligibility for runtime id `omp`.
- Forward Fusion `systemPrompt` via ACP `session/new`
`_meta.systemPromptOverride`.

## How operators use it

1. Install/auth `omp` (credentials under `~/.omp`).
2. Enable **Oh My Pi — via omp ACP** in Settings → Authentication
(optional binary path).
3. Set agent **Runtime Source → OMP Runtime** (`runtimeHint: "omp"`), or
pick an `omp-cli/*` model when enabled.

## Known v1 gaps

- No Grok-style Fusion `fn_*` loopback tool bridge yet (operator MCP is
forwarded; in-process custom tools are not).
- Model is fixed at spawn (`omp --model … acp`); no mid-session Fusion
model switch.

## Test plan

- [x] `pnpm --filter @fusion-plugin-examples/omp-runtime test` (unit +
live ACP when `omp` is on PATH)
- [x] Auth routes: `POST /api/auth/omp-cli`, `GET
/api/providers/omp-cli/status`
- [x] Engine `runtimeSupportsMcp("omp")`
- [ ] Manual: enable card in dashboard, select OMP runtime on an agent,
run a short chat turn

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

* **New Features**
* Added Oh My Pi (OMP) CLI support as an ACP-backed runtime and model
provider, including model discovery and probing.
* Added dashboard auth/status controls to enable OMP, check readiness,
and configure the local binary path (with validation).
* Exposed OMP custom `fn_*` tools via an MCP loopback bridge, plus
optional filesystem capabilities and stricter tool permission gating.
* **Documentation**
* Added/expanded OMP runtime contract and integration docs (including
the ACP session/handshake flow).
* **Tests**
* Added Vitest coverage for settings wiring, provider status, model
discovery, runtime sessions, permissions, MCP bridging, and live
connectivity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 08:18:52 -07:00
gsxdsm
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>
2026-07-14 00:12:22 -07:00
gsxdsm
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>
2026-07-13 19:07:58 -07:00
gsxdsm
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>
2026-07-13 13:10:53 -07:00
gsxdsm
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>
2026-07-13 10:14:22 -07:00
gsxdsm
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>
2026-07-13 09:50:48 -07:00
gsxdsm
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>
2026-07-13 09:27:29 -07:00
gsxdsm
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>
2026-07-13 08:58:51 -07:00
gsxdsm
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>
2026-07-13 08:15:25 -07:00