Commit Graph

6360 Commits

Author SHA1 Message Date
Phil Larson
ab228551e9 fix(dashboard): use active node registry authority (#2145)
## Summary

- reuse the dashboard server's initialized `centralCore` for all
node-management routes
- preserve the legacy fallback only when no shared central authority is
provided
- never close the server-owned central authority from an individual
request
- cover node list and registration with regression tests that fail if a
route constructs its own `CentralCore`

## Problem

With the dashboard running on the PostgreSQL backend, `/api/nodes` and
`POST /api/nodes` bypassed the server's initialized PostgreSQL-backed
`centralCore` and constructed a separate legacy `CentralCore()`. Reads
could hit the wrong registry, while writes failed with a null SQLite
handle (`Cannot read properties of null (reading 'prepare')`). The same
pattern affected node detail, health, path-mapping, version,
plugin-sync, and Docker-config endpoints.

## Verification

- targeted regression: 2 tests passed
- dashboard typecheck passed
- ESLint passed for changed TypeScript files
- strict changeset validation passed
- dashboard production build passed
- `pnpm test:gate` passed against an isolated PostgreSQL 16 cluster:
  - engine core: 294 tests
  - PostgreSQL gate: 122 tests
  - CLI CI-shape: 63 tests

## Changeset

Patch release for `@runfusion/fusion`.


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved multi-node management by keeping node operations connected to
the active PostgreSQL registry.
* Updated node listing, registration, configuration, health, version,
plugin, path, and Docker configuration routes for more consistent
behavior.
* Preserved Docker configuration validation and safe response handling.

* **Tests**
* Added coverage for retrieving and registering nodes through the active
registry.
* Verified successful node creation responses and handling of optional
configuration fields.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:45:06 -07:00
Phil Larson
329fc1f664 fix(dashboard): authenticate artifact media URLs (#2144)
## Summary
- append the existing same-origin daemon token fallback to artifact
media URLs used by image, video, and link navigation
- preserve project scoping and artifact ID encoding
- add a focused regression test and patch changeset

## Root cause
Artifact metadata loads through authenticated `fetch`, but previews and
links use raw browser navigation (`<img src>`, `<video src>`, and
anchors), which cannot attach the dashboard bearer header. The media
endpoint therefore returned `401 Valid bearer token required` even
though the dashboard itself was authenticated.

## Verification
- `pnpm --filter @fusion/dashboard exec vitest run --project
dashboard-app-quality-foundation-api app/__tests__/api-artifacts.test.ts
--reporter=dot`
- `pnpm lint`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm check:changesets --strict`
- `pnpm build`
- `FUSION_PG_TEST_URL_BASE=postgresql://plarson@127.0.0.1:55432
VITEST_MAX_WORKERS=1 nix shell nixpkgs#postgresql --command pnpm
test:gate`

The broader dashboard foundation API shard was also attempted but
aborted in Node after repeated unmanaged-file-descriptor warnings; the
focused regression and canonical merge gate both pass.

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

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed protected artifact images and links so they load correctly in
authenticated dashboards.
* Added authentication tokens to generated artifact media URLs for
reliable previews and navigation.

* **Tests**
* Added coverage verifying authenticated artifact media URLs include the
expected token and parameters.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:40:38 -07:00
gsxdsm
e24980ae46 FN-7997: keep task-card agent badge text visible on mobile and narrow cards
Stop hiding agent badge labels as icon-only pills on mobile and narrow task cards; rely on existing ellipsis truncation instead.

- Remove @container and max-width media rules that set .card-agent-badge-text { display: none }
- Document the no-icon-only-label requirement with an FNXC comment
- Add regression coverage that label text stays visible with ellipsis and that CSS no longer hides it

Files changed:
 packages/dashboard/app/components/TaskCard.css     | 15 +++-------
 .../__tests__/TaskCard.badge-wrap.test.tsx         | 33 ++++++++++++++++++++++
 2 files changed, 37 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7997

Fusion-Task-Lineage: 3add9a91-adef-4299-a638-d71257d26633

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:31:21 -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
d74018ff81 FN-7974: collapse chat thinking blocks by default
Collapse Thinking reasoning blocks by default so chat transcripts stay scannable without manually closing each block.

- Remove the open attribute from TaskChatTab thinking details so blocks start collapsed
- Strengthen ChatView and TaskChatTab tests for collapsed-by-default and expand-on-click across persisted, streaming, and Task Detail surfaces
- Add a patch changeset for the operator-facing transcript UX fix

Files changed:
 .changeset/fn-7974-collapse-thinking.md            |  7 ++++++
 packages/dashboard/app/components/TaskChatTab.tsx  |  6 ++++-
 .../__tests__/ChatView.core-interactions.test.tsx  | 26 +++++++++++++++++-----
 .../app/components/__tests__/TaskChatTab.test.tsx  | 13 +++++++----
 4 files changed, 41 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7974

Fusion-Task-Lineage: 7cd9009f-3483-444c-8024-ed6b1cec3b89

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:15:50 -07:00
gsxdsm
2179a61db9 FN-7973: fix mobile concurrency sliders with touch-action none
Restore horizontal concurrency thumb drags on mobile by opting range inputs out of the pan-y ancestor lock.

- Set touch-action:none on Engine Control menu and Command Center concurrency range inputs
- Update geometry/touch contract test to assert none and reject pan-y
- Add patch changeset for the mobile slider fix

Files changed:
 .changeset/fn-7973-mobile-concurrency-sliders.md              |  7 +++++++
 packages/dashboard/app/components/EngineControlMenu.css       |  5 ++++-
 .../app/components/__tests__/EngineControlMenu.test.tsx       | 11 ++++++++---
 .../app/components/command-center/CommandCenterControls.css   |  5 ++++-
 4 files changed, 23 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7973

Fusion-Task-Lineage: bf36e544-c24f-49ae-beae-b11707be9c79

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:12:43 -07:00
gsxdsm
aa0d8635fa FN-7972: fix agents controls panel overlapping content
Elevate the Agents controls popover stacking so it layers above agent cards and token usage on desktop and mobile.

- Add `.agents-view-primary-actions--controls-open` with z-index 50 when the controls panel is open
- Toggle the elevated class from AgentsView when the panel opens
- Extend AgentsView tests for open-state class and stacking CSS invariant
- Add patch changeset for the operator-facing fix

Files changed:
 .changeset/fn-7972-agents-controls-overlap.md           |  7 +++++++
 packages/dashboard/app/components/AgentsView.css        |  8 ++++++++
 packages/dashboard/app/components/AgentsView.tsx        |  2 +-
 .../app/components/__tests__/AgentsView.test.tsx        | 17 ++++++++++++-----
 4 files changed, 28 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-7972

Fusion-Task-Lineage: e6cca457-1cd1-4577-83a7-d7de7e484580

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:10:28 -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
Phil Larson
883f38d68f Fix agent AI interview model routing (#2142)
## Summary
- resolve the configured planning model when agent onboarding requests
omit an explicit override
- align the onboarding prompt with supported runtime/model hint fields,
allowing AI-created agents to select runtimes such as Hermes
- refresh the generated GitHub issue import limits required by the
repository sync gate

## Root cause
The agent onboarding route loaded project settings but passed only
request-body model fields. The AI Interview UI omits those fields, so
`createFnAgent` was called with `provider=undefined, model=undefined`;
the session returned no usable assistant JSON. The prompt catalog also
prohibited `runtimeHint` despite the parser and form already supporting
it.

## Verification
- targeted agent onboarding tests: 22 passed
- `pnpm --filter @fusion/core typecheck`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm lint`
- `pnpm build`
- `pnpm smoke:boot`
- engine merge-gate subset: 294 passed

Full `pnpm test` reached the PostgreSQL gate but this host has no `psql`
binary, so 23 PostgreSQL suites could not start; this is an environment
prerequisite failure, not a test assertion failure.

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

* **Bug Fixes**
* Agent onboarding interviews now use the configured planning model when
no override is provided.
* Runtime suggestions and runtime-hint guidance are preserved during
onboarding and reflected in generated configurations.
* On onboarding start streaming, planning provider/model resolution now
comes from settings with stricter override validation, and test mode
continues to take priority.

* **Documentation**
* Updated onboarding prompt guidance to support additional configuration
fields and optional runtime draft hints.
  * Reduced the maximum GitHub issue import/browse limit from 100 to 50.

* **Tests**
* Added coverage for runtime-hints prompting and planning-model override
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:57:15 -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
2b8df56cb8 fix: escalate reviewer provider errors instead of looping on them
A rate-limited reviewer filled a task's Chat tab with 14 identical
"Reviewer using model: ..." markers and no review text, hammering an
already-limited provider.

Root cause: the reviewer was the only AI lane that never classified
provider errors, so a 429 became an UNAVAILABLE verdict. With no
validator fallback configured the fallback ladder re-ran the SAME model
instantly, and fn_review_step answered with "code review remains
blocking; retry once" — bounding the loop with prompt text rather than
code. The tool's catch-all also swallowed the error into tool output, so
withRateLimitRetry, UsageLimitPauser and RetryStormError never fired.

- reviewer: throw ReviewerProviderError for usage-limit/transient errors
  instead of laundering them into UNAVAILABLE, and never spend the
  fallback budget (which bounds bad reviews) on an outage.
- reviewer: absorb flaky-network blips in-lane via withRetry with
  jittered backoff; rate limits still escalate immediately.
- executor: re-raise the fatal after the prompt via
  throwDeferredReviewerFatal — pi-agent-core converts tool throws into
  tool_error results, so a tool cannot throw out of session.prompt().
- executor: give code review a real MAX_CODE_REVIEW_UNAVAILABLE_RETRIES
  counter, mirroring the plan/spec limiter.
- reviewer: dedupe the model marker on text, so same-model retries stay
  silent while a genuine model switch still emits.

Also fixes the run-on rendering: AgentLogType gains `status` for complete
engine messages. `text` means "streamed delta" and is re-glued with
join(""), which is why N standalone markers rendered as one string. The
split is at the type, not a separator — a separator would reintroduce the
FN-5787/5789/5803 streamed-spacing regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:54:16 -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
a1672726d6 FN-7984: keep chat agents from switching checkout branch unless asked
Add branch-stickiness guardrails to the chat system prompt so agents do not switch the live checkout unless the user explicitly requests it.

- Extend CHAT_SYSTEM_PROMPT to forbid git checkout/switch unless asked; allow read-only Git inspection
- Add regression coverage for the branch-stickiness clause
- Add patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7984-chat-branch-guardrail.md                 | 7 +++++++
 packages/dashboard/src/__tests__/chat-system-prompt.test.ts | 9 +++++++++
 packages/dashboard/src/chat.ts                              | 7 +++++--
 3 files changed, 21 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7984

Fusion-Task-Lineage: b093b1da-f45c-49fc-918b-50b06f99d7f6

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:38:02 -07:00
gsxdsm
a8b6387420 FN-7964: fix Project Models workflow model lane persistence
Keep pending Project Models workflow lane overrides registered across section navigation so primary Settings Save still flushes them.

- Stop clearing the workflow-lane saver ref on Project Models unmount
- Add regression coverage for save-after-section-nav and reload override display
- Add patch changeset for the dashboard fix

Files changed:
 .changeset/fn-7964-project-models-workflow-lanes.md                            |  7 +++++++
 packages/dashboard/app/components/SettingsModal.tsx                            |  8 +++++++-
 packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7964

Fusion-Task-Lineage: ed4e6268-d5b7-44ba-bbcc-2d44b301403e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:33:06 -07:00
gsxdsm
b2977d1a7a FN-7982: clear stale planner chat streaming on fresh reply
Fix task chat showing a previous agent message while a new reply is generating by clearing streaming carriers on fresh generations.

- Clear streamingThinking and the streaming-assistant row when starting a generation without an in-flight snapshot
- Preserve text/thinking/tool restore only when attaching to a live in-flight generation
- Add regression coverage for consecutive replies that must not reuse prior stream content
- Add patch changeset for the planner chat stale-message fix

Files changed:
 .changeset/fn-7982-planner-chat-stale-message.md   |  7 ++++
 .../app/components/TaskPlannerChatTab.tsx          | 19 ++++++++--
 .../__tests__/TaskPlannerChatTab.test.tsx          | 44 ++++++++++++++++++++++
 3 files changed, 67 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7982

Fusion-Task-Lineage: 71daaa80-6d66-4940-a656-a2c42a6b03bb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:22:33 -07:00
gsxdsm
37db66965c FN-7958: loosen mobile agent header and overview hero spacing
Ease cramped mobile agent detail identity and Overview hero rows while preserving the non-overlap header grid.
- Increase mobile header padding, column/row gaps, and identity gap for breathing room
- Add badge wrap gaps and keep lifecycle controls on the FN-6865 non-overlap grid
- Give Overview hero heading/meta/skills deliberate row gaps and wrap long health text
- Nudge summary-card padding and hero gaps further under 480px
- Extend mobile scroll and core tests for spacing invariants

Files changed:
 packages/dashboard/app/components/AgentDetailView.css   |  51 +++++-
 .../__tests__/AgentDetailView.core.test.tsx        |   4 +
 .../AgentDetailView.mobile-scroll.test.tsx         | 182 ++++++++++++++++++++-
 3 files changed, 230 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-7958

Fusion-Task-Lineage: c555811f-9dce-4725-81b5-2705aeaaa1db

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 12:12:53 -07:00
gsxdsm
335b6a4dc2 fix: raise Plan Review replan cap to 8 and explain approval holds
Give planner/reviewer pairs more room to converge before escalating, and surface why a task is parked for plan approval—especially plan-review-replan-cap non-convergence—on cards, detail, and notifications.
2026-07-15 11:14:17 -07:00
gsxdsm
e172b13612 fix(dashboard): never show raw landing/reviewing on task status badges
Share one badge label mapper for board and list so AI-merge pipeline
statuses always display as Merging… instead of engine strings.
2026-07-15 10:39:57 -07:00
gsxdsm
49a459a869 FN-7954: fix plugin skill toggle keys for custom skillFiles paths
Align plugin skill enable/disable reads with the resolved skillFiles path so Skills-view toggles persist and sessions honor them.

- Accept optional skillRelativePath in resolvePluginSkillEnabled for custom skillFiles keys
- Pass resolved relativePath from dashboard skills adapter when merging plugin skills
- Reuse resolved body path in session-skill-context for enable checks and additionalSkillPaths
- Add unit coverage for custom-path round-trip, enable, and disable behavior
- Ship patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7954-plugin-skill-toggle-key-fix.md  |  7 +++
 packages/core/src/__tests__/skill-settings.test.ts | 15 ++++++
 packages/core/src/skill-settings.ts                |  8 +++-
 .../dashboard/src/__tests__/skills-adapter.test.ts | 55 ++++++++++++++++++++++
 packages/dashboard/src/skills-adapter.ts           |  1 +
 .../src/__tests__/session-skill-context.test.ts    | 45 ++++++++++++++++++
 packages/engine/src/session-skill-context.ts       | 29 +++++++-----
 7 files changed, 147 insertions(+), 13 deletions(-)

Fusion-Task-Id: FN-7954

Fusion-Task-Lineage: 3399186b-7325-4ed7-8900-85eb2ef98c7e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 10:38:04 -07:00
gsxdsm
49114a27ad fix(dashboard): show Merging badge during AI-merge reviewing/landing
AI merge spends most of its time in reviewing and landing, not merging.
Treat the full merge pipeline as active so cards, workflow switcher, and
stall suppression show Merging… while the pump owns a task.
2026-07-15 10:36:10 -07:00
gsxdsm
16b0109300 FN-7959: focus Planning compose textarea on New session
Focus the Planning compose textarea when New session is pressed, even if blank compose is already active, and preserve in-progress draft text.

- Add a click-driven newSessionFocusSignal so New session always re-focuses the compose textarea after rAF (mobile detail pane visibility)
- Preserve initialPlan when starting a new session from the already-active blank compose view
- Cover focus, caret placement, draft preservation, and mobile show-detail surfaces in PlanningModeModal tests
- Add patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7959-planning-new-session-focus.md   |   6 +
 .../dashboard/app/components/PlanningModeModal.tsx |  37 ++++-
 .../__tests__/PlanningModeModal.initial.test.tsx   | 174 +++++++++++++++++++++
 3 files changed, 213 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7959

Fusion-Task-Lineage: ad71ad28-3e22-454a-907f-3423475180f9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 10:28:38 -07:00
gsxdsm
3676586460 fix(cli): pass engine PluginRunner into hosts, not PluginLoader
Stop publishing the bare PluginLoader as createServer.pluginRunner so Grok CLI
routing can resolve getRuntimeById. Dashboard engine mode relies on engine.onMerge;
UI-only/bare CLI omit the runner (dual-remediation). Conflict resolver drops
non-capable runners instead of casting them.
2026-07-15 10:24:30 -07:00
gsxdsm
30f9cac46a fix(cli): forward PluginRunner into UI/CLI merge and PR conflict doors
Thread a real engine PluginRunner (getRuntimeById) into runAiMerge,
landWorkspaceTask, and create-PR conflict resolution so grok-cli/no-key
sessions resolve the Grok runtime. Bare fn task merge keeps pluginRunner
undefined rather than inventing a bootstrap.
2026-07-15 10:07:21 -07:00
gsxdsm
4d78f9641b feat(dashboard): build/link local fn and npm restore from System panel
Add Command Center System controls for source/dev hosts to build the
standalone fn binary and install it as the default PATH binary, switch
back to the global npm install, and force-check for published updates.
Build jobs stream into the shared job log and scroll that view into focus.
2026-07-15 10:07:20 -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
c63637ff44 fix(dashboard): make title auto-summarize settings searchable
Index Project Models for summarize/auto-summarize phrases and control labels so Settings search surfaces autoSummarizeTitles.
2026-07-15 00:29:24 -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
Phil Larson
7261083781 fix(dashboard): isolate MCP settings scopes (#2116)
## Summary

- bind Global and Project MCP editors to their raw values from
`/api/settings/scopes`
- keep edits isolated in the owning scope instead of mutating the merged
project-effective form
- persist changed MCP scopes independently of which Settings section is
visible when Save is pressed
- add unit and SettingsModal regressions for opposite scope values and
edit → navigate → save

## Root cause

`SettingsModal` passed the merged project-effective `form.mcpServers` to
both MCP sections. A project override could therefore appear as the
Global MCP value. Saving the apparent global change could then be
dropped as a no-op when compared with the actual global-scoped value.

The first fix still tied save routing to the active section. The
follow-up carries both raw scoped MCP values through
`splitSettingsSave`, where changed-only comparisons persist each owning
scope even after navigation.

## Testing

- `pnpm exec vitest run --project dashboard-app
app/__tests__/settings-save-split.test.ts` (32 passed on PR branch)
- `pnpm exec vitest run --project dashboard-app-quality-settings
app/components/__tests__/SettingsModal.general.test.tsx` (84 passed)
- `pnpm run typecheck`
- `pnpm exec eslint app/components/SettingsModal.tsx
app/components/settings/save-split.ts
app/__tests__/settings-save-split.test.ts
app/components/__tests__/SettingsModal.general.test.tsx`
- `pnpm run build`


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

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed MCP settings so global and project configurations remain
correctly separated.
* Prevented inherited global settings from appearing as project
overrides.
  * Preserved MCP edits when navigating between Settings sections.
  * Ensured unchanged settings are not unnecessarily saved.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 22:58:59 -07:00
gsxdsm
ba1e82381e fix(FN-7952): cut runtime services over to PostgreSQL (#2109)
## Summary

Engine and dashboard traffic now stays on the authoritative PostgreSQL
layer across execution, recovery, project discovery, planning sessions,
analytics, and shutdown. The dashboard no longer presents a migration
notice for a cutover that is already mandatory.

## Design decisions

- Runtime composition requires an async data layer instead of
constructing a hidden SQLite fallback.
- Engine workflow, mission, claim, and self-healing reads await their
PostgreSQL-backed store contracts.
- Project-scoped dashboard stores retain and close their backend owner
exactly once.
- The dashboard test quarantine entry remains paired with its Vitest
exclusion, preserving the repository’s deletion-ratchet policy.

## Validation

- Core, Engine, Dashboard, CLI, and Desktop typechecks pass on the
stacked branch.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 62 files.

## Stack

- Depends on #2108.
- CLI/desktop/ops, plugins, and docs/release follow in later PRs.

Related: #2105


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

## Summary by CodeRabbit

* **New Features**
* Project discovery now recognizes projects using the
`.fusion/project.json` marker.
* Knowledge indexing and search are more reliable across project-scoped
storage.
* **Bug Fixes**
* Improved session, audit timeline, approval, monitoring, and analytics
data consistency.
* Prevented stale planning-session updates and project-store shutdown
races.
* Ensured chat usage and CLI session status are saved before continuing.
* **UI Changes**
* Removed the storage migration notice banner now that the PostgreSQL
transition is complete.
* **Reliability**
* Improved shutdown handling, workflow execution, and worktree behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 22:16:16 -07:00
gsxdsm
2e4fcfcaea fix(FN-7952): establish PostgreSQL core authority (#2108)
## Summary

Fusion’s core runtime now treats PostgreSQL as the authoritative
metadata store without leaving current CLI, dashboard, desktop, or
engine composition roots uncompilable between stack layers. This is the
99-file foundation for the larger cutover: subsequent PRs migrate the
remaining consumers, plugins, and operator surfaces.

## Design decisions

- Runtime store construction fails closed when an asynchronous
PostgreSQL layer is unavailable; SQLite remains readable only at
explicit migration and identity-recovery boundaries.
- Project ownership is enforced across active, archived, workflow,
mission, analytics, and plugin-schema data.
- The small set of cross-package files in this layer are
compatibility-critical call sites required for a green intermediate
commit, not the complete consumer migration.
- Schema migration 0008 remains assigned to session-advisor state from
current `main`; mission lineage idempotency advances to 0009 so neither
invariant can be skipped.

## Validation

- All affected package typechecks pass: Core, Engine, Dashboard, CLI,
and Desktop.
- `pnpm test:gate` passes: 478 tests across the engine gate, PostgreSQL
core gate, and CLI workflow shape.
- The PR changes exactly 99 files.

## Stack

This is the base PR. Engine/dashboard, CLI/desktop/ops, plugins, and
docs/release follow as stacked PRs, each below 100 changed files.

Related: #2105


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

## Summary by CodeRabbit

* **New Features**
* PostgreSQL is now the standard runtime backend, with embedded
PostgreSQL enabled by default.
* Added project-scoped storage for tasks, archives, chat sessions,
missions, knowledge pages, and operational data.
* Improved archived-task search, filtering, pagination, and restoration.
* Added safer plugin schema initialization with validation and project
isolation.
* Added PostgreSQL-backed workflow, mission, validator, and dashboard
capabilities.

* **Bug Fixes**
  * Improved startup timeout cancellation and resource cleanup.
* Prevented cross-project data access and phantom reservation cleanup
errors.
* Ensured archived tasks remain read-only and asynchronous writes
complete reliably.
  * Retired SQLite opt-out settings with clear startup errors.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 22:13:30 -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
cdf67c1d98 fix(dashboard): stop Planning Mode retry loop, make AI sessions multi-tab (#2101)
## Problem

Reported: planning gets stuck in a cycle of retrying and regenerating
after a response was already supplied.

After the user answers a planning question, `submitResponse` pushed the
answer to history but left `session.currentQuestion` pointing at the
just-answered question for the whole next generation. The planning SSE
route's catch-up path re-emits `currentQuestion` to every fresh
connection — and each FN-7946 auto-retry (#2073) opens a fresh
connection. So after any generation error:

1. Auto-retry connects a fresh stream → the server re-emits the
**already-answered** question.
2. The client treats any question event as progress: it **resets the
3-attempt auto-retry budget** and re-shows the answered question.
3. The retry regenerates; if it errors again the cycle repeats with a
fresh budget — an unbounded retry/regenerate loop. Re-answering the
stale question also 409-collided with the in-flight generation, feeding
the same loop.

## Fix

Invariant: `currentQuestion` is only set while the session is genuinely
awaiting user input.

- `submitResponse` clears it the moment an answer is accepted (normal
turns and the deepening checkpoint), while preserving the legacy 200
respond contract on generation failure (the modal ignores the body and
lets the SSE error drive recovery).
- `retrySession` scrubs stale questions persisted by pre-fix builds
before regenerating.
- `buildSessionFromRow` only restores a question when the persisted row
is `awaiting_input`.
- `didSubmitSameAnswer` now compares against the last history entry so
the duplicate-submit 409 message survives.
- Agent onboarding gets the same fix (its SSE route also re-emits
`currentQuestion` on connect); retry now asks the next question instead
of re-asking the answered one.

Surface enumeration: mission and milestone interviews keep questions the
same way but their SSE routes never re-emit on connect, and the
auto-retry budget machinery is Planning-Mode-only — planning +
onboarding were the two affected surfaces.

## Symptom Verification

- **Original symptom:** after answering a question, Planning Mode loops
between "Retrying…" and regenerating, re-showing the already-answered
question, with the auto-retry budget never exhausting.
- **Exact reproduction:** answer a question, have the next generation
fail (stuck watchdog/provider error), let the client auto-retry open a
fresh SSE connection.
- **Assertion it is gone:** new regression suite
`planning-answered-question-reemit.test.ts` asserts `currentQuestion` is
cleared mid-generation, on generation failure, on retry, and on restore
from non-`awaiting_input` rows — so the SSE catch-up path has nothing
stale to re-emit. All 5 tests fail against pre-fix code and pass with
the fix; an onboarding regression test covers the sibling surface.

## Verification

- New regression tests: 5/5 fail on pre-fix code, pass with the fix
(plus 1 onboarding test).
- Existing suites: 137 planning server tests pass (3 failures in
`routes-planning.test.ts` fail identically without this change —
pre-existing on the branch); all 69 `PlanningModeModal.planning-flow`
client tests pass; `tsc --noEmit` clean; `pnpm check:changesets` passes.

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

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

* **New Features**
* Made Planning Mode (and related planning controls) lock-free and
multi-tab—no more take-over/active-in-another-tab lock overlays.

* **Bug Fixes**
* Fixed Planning Mode retry/generation flows where already-answered
questions could reappear.
* Ensured answered questions clear immediately and aren’t re-emitted
during session recovery/SSE catch-up.
* Improved session restoration and preserved legacy recovery behavior
when generation fails after an answer.

* **Tests**
* Added regression coverage for the answered-question invariant and
updated existing tests to reflect lock-free behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---

## Follow-up: Planning Mode is now multi-tab via DB state (lock-free)

Second commit removes all cross-tab coordination from planning — the
persisted session row is the single source of truth and multiple tabs
can read and interact with the same session:

- **Server:** `/planning/*` routes no longer run `checkSessionLock` or
parse `tabId`; a stale `tabId` from an older client is ignored instead
of 409'd. Subtask/mission interview routes keep their existing lock
behavior.
- **Client:** `PlanningModeModal` drops `useSessionLock`, the
`useAiSessionSync` BroadcastChannel broadcasts,
`sessionTabId`/`lockSessionId` state, and the "Take Control" overlay.
Tabs stay current via the per-session SSE stream plus the global
`ai_session:updated` events `useBackgroundSessions` already consumes;
concurrent writes resolve via the server's generation-in-progress guard
(409).
- **API client:** planning functions lose their `tabId` params.
- **Fix uncovered by the refactor:** the 8s stuck-poll now resolves the
session id inside each tick — the removed lock state was what previously
re-armed the poll after Start Planning resolved the session id.
- Also fixes a pre-existing PG-cutover break in
`planning-generation-cancellation.test.ts` (`getSession` is async).

Verification: 144 client planning tests and 137 server planning tests
pass (the 3 remaining `routes-planning.test.ts` failures are
pre-existing on the branch and fail identically without these changes);
`tsc --noEmit` and eslint clean on changed files; `pnpm
check:changesets` passes. Lock-conflict route tests were rewritten to
assert lock-free semantics, plus a new modal test proving a session
stays fully interactive with no lock acquisition even when another tab
is active.


---

## Follow-up 2: the per-tab session lock is gone entirely

Third commit extends the multi-tab model from planning to **every** AI
interview surface (planning, subtask breakdown, mission interview,
milestone/slice interview) and deletes the lock machinery root and
branch.

**Server**
- Deleted the `/ai-sessions/:id/lock`, `/lock/force`, and `/lock/beacon`
routes.
- Dropped `checkSessionLock` from every
planning/subtask/mission/milestone route (both copies — `routes.ts` and
`mission-routes.ts`). A `tabId` from an older client is ignored, never
409'd; all `tabId` body parsing is gone.
- Dropped `acquireLock` / `releaseLock` / `forceAcquireLock` /
`getLockHolder` / `releaseStaleLocks` from `AiSessionStore`, plus the
`@fusion/core` async helpers (`acquireAiSessionLock` et al) and core's
re-exports.
- Removed `lockedByTab`/`lockedAt` from
`AiSessionRow`/`AiSessionSummary`, the upsert SQL, and all four session
producers.

**Client**
- Deleted `useSessionLock` and the now-orphaned `getSessionTabId` util.
- Removed the Take Control overlay, the "active in another tab" banners,
and `BackgroundTasksIndicator`'s active-elsewhere gate (the confirm
prompt and lock badge — sessions now just open).
- Reduced `useAiSessionSync` to what its own comments already called it
— a low-latency *status* supplement to SSE: no `activeTabMap`,
`broadcastLock/Unlock/Heartbeat`, `owningTabId`, `tab:*` messages, or
stale-heartbeat sweep.
- Dropped `tabId` from every session API client function; removed the
lock CSS.

**Deliberately kept: the two DB columns.** `ai_sessions.locked_by_tab` /
`locked_at` remain as dead, always-NULL columns with a deprecation note.
Dropping them is an irreversible migration, and released binaries still
name those columns explicitly in their upsert — an older install pointed
at the same database would fail every session write. They can be dropped
once no such binary can reach it. No code reads or writes them.

**Verification**: 397 client tests and 137 server planning tests pass
(the same 3 `routes-planning.test.ts` failures are pre-existing —
verified identical on a clean stash); `tsc --noEmit` clean for
`@fusion/core` and `@fusion/dashboard`; eslint clean on all changed
files; the 30 PG `schema-applier` tests pass (they exercise the retained
columns); `pnpm check:changesets` passes. The lock-conflict route tests
and both modal lock tests were rewritten to assert the inverse: routes
and modals stay fully interactive while another tab "holds" a lock, and
the lock API is never called.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 18:47:53 -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
2d61976df0 fix(FN-7952): restore runtime state after PostgreSQL migration
Route workflow selections, model lanes, goals, skills, and reliability reads through project-scoped async stores. Recover heartbeat agents parked against an unrelated project model and preserve workflow JSONB patches atomically.
2026-07-14 17:02:47 -07:00
gsxdsm
278ede9dfa fix(FN-7952): recover provider failures without retry loops
Preserve authenticated CLI usage after migration, surface OAuth remediation, and use a single distinct model fallback before parking permanent failures. Keep transient credential errors retryable and confirm each OAuth expiry notification independently.

Fusion-Task-Id: FN-7952
2026-07-14 15:54:44 -07:00
gsxdsm
79d4299be2 fix: preserve provider and workflow behavior after migration
Use canonical Anthropic OAuth refresh, keep CLI-backed providers out of API-key auth rows, parse Grok's omitted zero usage, and carry board workflow context into task creation.
2026-07-14 15:07:26 -07:00
gsxdsm
945d629e3b fix(core): make SQLite cutover lossless and project-local
Preserve legacy-only tables, recover partial migration ownership, and enforce project-local keys, relationships, agents, merge queues, task IDs, archives, and monitor state with PostgreSQL RLS.

Report successful cutovers once in the dashboard and system inbox with retained SQLite paths and Discord support details.
2026-07-14 12:41:10 -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
d7e072a03c fix: add psql binary guard + delete expired quarantine tests (ratchet) (#2090)
## Summary

Follow-up to PR #2086 addressing two Greptile review findings.

## P2 — Missing `psql` binary guard (Greptile P2)

`hasPg` in `_helpers.ts` previously checked only TCP connectivity to
PostgreSQL. But `adminExecAsync()` shells out to the `psql` CLI for DDL
(`CREATE/DROP DATABASE`). On a runner where Postgres is reachable but
`psql` isn't installed, tests would fail with `spawn psql ENOENT`
instead of skipping cleanly.

**Fix**: Added `hasPsql = spawnSync("psql", ["--version"]).status === 0`
to the `hasPg` guard, so tests skip when either Postgres is unreachable
OR `psql` is missing.

## P1 — Expired quarantine entries (Greptile P1)

The 16 dashboard test files quarantined on 2026-06-25 were past the
14-day deletion ratchet (AGENTS.md: "DELETED after 14 days unless
rescued"). Per the ratchet, the test files were deleted and all
references removed:

- **Deleted 16 test files** (CSS drift, mock drift, mobile-render
regressions)
- **Removed 16 entries** from `scripts/lib/test-quarantine.json` (only
the CLI entry remains)
- **Emptied `quarantinedDashboardTests` array** in
`packages/dashboard/vitest.config.ts`

## Verification

| Check | Result |
|---|---|
| Merge gate (`pnpm test:gate`) | ✅ 294 + 99 + 63 = 456 passed |
| Dashboard curated-gate | ✅ passes (891 files, 892 executed, 1
skip-listed, 1 quarantined) |
| Typecheck (engine) | ✅ clean |
| Lint | ✅ exit 0 |

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

* **Tests**
* Removed multiple outdated dashboard UI, CSS/token, theme contrast, and
API/route test suites.
* Updated dashboard test configuration to stop excluding quarantined
tests and to prune the quality shard to the current set.
* Updated the Vitest split/config guard to match the new test fixture
set.
* Improved PostgreSQL test detection by requiring the `psql` CLI before
running database checks.
* Adjusted quarantine tracking by adding a new CLI extension
distribution ledger entry and removing obsolete dashboard quarantine
entries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 08:18:10 -07:00
gsxdsm
d8f0b1a268 Restore PostgreSQL integration parity (#2089)
## Summary

- add asynchronous PostgreSQL parity to research commands and engine
execution paths
- persist Roadmap, Compound Engineering sessions, and WhatsApp state in
PostgreSQL
- harden cancellation, concurrency, reconnect, replay-claim, and
detached-promise behavior
- bundle the PostgreSQL-backed integration implementations in the
published CLI

This is PR 2 of 2 and is intentionally stacked on #2088. It contains 44
changed files; merge #2088 first, then retarget this PR to `main` if
GitHub does not do so automatically.

## Verification

- `pnpm check:changesets --strict`
- `pnpm lint`
- `pnpm test:gate`: 463 tests passed
- Compound Engineering plugin: 299 tests passed
- Roadmap plugin: 144 tests passed
- WhatsApp plugin: 27 tests passed
- research CLI: 18 tests passed
- `pnpm verify:fast`: all scoped typechecks, builds, CLI build, and boot
smoke passed

## Post-Deploy Monitoring & Validation

- deploy only after #2088 and verify schema migration `0002` is present
- monitor research cancellation, automation claims, agent execution,
plugin schema initialization, and unhandled rejections
- validate Roadmap ownership, Compound Engineering session recovery, and
WhatsApp reconnect/replay deduplication
- compare per-project plugin and workflow counts after cutover
- restore the pre-deploy backup for data rollback; avoid an in-place
schema downgrade
2026-07-14 08:17:36 -07:00
gsxdsm
c25f8b796d Harden PostgreSQL migration foundation (#2088)
## Summary

- make SQLite-to-PostgreSQL cutover retryable, fail-closed, versioned,
and transactionally serialized
- isolate migration sessions from runtime traffic and apply schema
upgrades through `0002`
- enforce tenant ownership across automations, analytics, activity,
usage, agent runs, evals, and todos
- replace expired SQLite-only coverage with PostgreSQL parity and
concurrency coverage

This is PR 1 of 2. The stacked follow-up restores PostgreSQL parity for
CLI, engine, dashboard, and bundled integrations.

## Verification

- `pnpm check:changesets --strict`
- `pnpm --filter @fusion/core typecheck`
- migration schema, connection, and SQLite cutover suite: 57 tests
passed
- `pnpm test:gate`: 463 tests passed

## Post-Deploy Monitoring & Validation

- take a restorable PostgreSQL backup before deploy
- confirm `fusion_schema_migrations` contains `0002`
- confirm each expected project has a complete
`fusion_sqlite_migrations` row
- verify no null or empty tenant ownership in automations, activity
logs, agent runs, and usage events
- monitor for ownership inference failures, cutover verification
failures, and migration session errors
- restore the backup for data rollback; do not downgrade the
tenant-isolation schema in place

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

* **New Features**
* PostgreSQL-backed analytics and live dashboard metrics are now
project-scoped (activity, tools, monitor, signals, and live snapshots).
* Evaluation runs and scheduled eval batches received lifecycle
improvements (ordering, updates, and execution flow).
* Todo list changes now emit events; WhatsApp persistence and
project-scoped roadmap data are supported.

* **Bug Fixes**
* SQLite-to-PostgreSQL cutovers now fail safely with stronger
verification, serialized cutover handling, and safer project ownership.
* PostgreSQL backend writes and reads are now strictly project-isolated
and fail closed when project context is missing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 08:16:42 -07:00
Victor Canô
3fce53640e fix(dashboard): keep completed Planning Mode sessions in history after multi-task creation (#2079)
## Problem
In the dashboard **Planning Mode** screen, a planning session that runs
to completion **and creates multiple tasks** disappears from the "saved
sessions" history panel ("No saved sessions yet").

## Root cause
The multi-task route `POST /api/planning/create-tasks` called
`cleanupSession(planningSessionId)` → `unpersistSession` →
`_aiSessionStore.delete`, **deleting the persisted `ai_sessions` row**.
The single-task route `POST /api/planning/create-task` deliberately uses
`releaseSession` instead — it releases the in-memory runtime but
**keeps** the persisted completed row, which is what the history list
reads (`listAll` includes completed sessions). So multi-task creation
erased its own history entry.

## Fix
Switch the multi-task route to `releaseSession`, matching the
single-task path. The completed `type: "planning"` session row now
survives task creation and appears in history.

## Tests
Adds a regression test in `routes-planning.test.ts` asserting the
persisted planning row survives multi-task creation (verified it fails
against the old `cleanupSession` behavior). Merge gate green locally;
changeset included.

Made with Claude (see `Co-Authored-By` trailer).

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

* **Bug Fixes**
* Fixed an issue where Planning Mode multi-task sessions could be
removed from planning history after task creation.
* Completed multi-task planning sessions are now reliably retained with
their completed status.
* **Tests**
* Added a regression test for the multi-task Planning Mode flow to
confirm all tasks are created and the planning session remains persisted
in history.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-14 08:15:57 -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
9aa2852033 fix: resolve an explicit central-registry project id for all dashboard API requests
Implements the explicit-project-identity directive at the route layer: a
request's store is resolved from request projectId -> the daemon's registered
launch project id -> only for unregistered launch directories, the raw
launch-dir store (one-time warn). Resolution funnels through a single seam
(routes/context.ts resolveRequestProjectId + resolveStoreForProjectId); the
server.ts realtime resolveScopedStore delegates to the same function instead
of mirroring it. Scattered 'projectId ? getOrCreateProjectStore : store'
ternaries in todo/goals/mission/insights/research/evals routes now use the
shared seam.

Code-review fixes folded in (multi-agent ce-code-review, 10 reviewers):
- mission interview drafts list/discard resolve the same project id the
  start endpoint stamps (write/read no longer split namespaces)
- chat stream-attach guard treats legacy null-projectId sessions as
  launch-owned instead of 404ing; planner-chat dedup retries unscoped to
  reuse legacy sessions instead of duplicating them
- getProjectIdFromRequest trims and rejects whitespace-only ids
- evals/research middleware forwards store-resolution failures to Express
  (previously rethrew inside a detached promise chain and hung the request)
- one-time launch-dir fallback warning routes through runtimeLogger
- seam + delegation + whitespace + engine-fallthrough covered in
  routes-context-project-identity.test.ts (10 cases)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 23:16:36 -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
9a43aa1d24 FN-7951: harden runGenerationWithTimeout abort cancellation across planning surfaces
Ensures aborted AI generation (timeout, user-stop, displacement, retries) actually tears down the in-flight agent session instead of only rejecting the Promise.race waiter, since provider SDKs may ignore AbortSignal.

- Add a once-only onAbort teardown hook to GenerationGuard, invoked for timeout, user-stop, and displaced abort causes so consumers can dispose their in-flight session exactly once.
- Give planning's local generation runner (runGenerationWithTimeout) the same guaranteed once-only abortTeardown for timeout, user-stop, displacement, stuck, and loop aborts, replacing the ad hoc dispose-on-timeout-only logic.
- Forward the AbortSignal into planning's history-replay prompt, turn prompts, and JSON-parse-retry prompts, and short-circuit with createAbortError() when the signal is already aborted before/after each prompt call.
- Wire subtask-breakdown's onTimeout/onUserStop handlers to the new onAbort hook instead of disposing the agent directly, keeping teardown centralized in the guard.
- Add GenerationInProgressError / TargetGenerationInProgressError handling in mission-routes to return 409 Conflict instead of a generic 500 when a generation is already running.
- Extend mission-interview and milestone-slice-interview generation paths with matching abort-forwarding and teardown behavior, plus new/expanded tests covering cancellation across timeout, user-stop, displacement, and retry paths.
- Add a patch changeset documenting the fix for @runfusion/fusion.

Files changed:
 .changeset/harden-generation-abort.md              |   7 ++
 .../src/__tests__/ai-session-timeout.test.ts       |  41 +++++--
 .../__tests__/milestone-slice-interview.test.ts    |  72 ++++++++++++-
 .../src/__tests__/mission-interview.test.ts        |  64 ++++++++++-
 .../planning-generation-cancellation.test.ts       |  82 ++++++++++++++
 .../src/__tests__/subtask-breakdown.test.ts        |  21 +++-
 packages/dashboard/src/ai-session-timeout.ts       |  33 +++++-
 .../dashboard/src/milestone-slice-interview.ts     | 120 +++++++++++++++++++--
 packages/dashboard/src/mission-interview.ts        | 119 ++++++++++++++++++--
 packages/dashboard/src/mission-routes.ts           |  12 +++
 packages/dashboard/src/planning.ts                 |  70 +++++++++---
 packages/dashboard/src/subtask-breakdown.ts        |  10 +-
 12 files changed, 589 insertions(+), 62 deletions(-)

Fusion-Task-Id: FN-7951
Fusion-Task-Lineage: debcd6a9-f54e-4ef3-87e1-4f06be0b5f64
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-13 13:58:35 -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