7745c59e6317c87fc9027ab684f8fd3919826041
189 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
72623ec804 |
FN-8837: fix pull-request merge retry recovery
Make pull-request merge failures retry safely and report actionable terminal states. - Persist exponential retry backoff and schedule durable retry wakeups. - Classify policy, transient, and non-retryable GitHub merge failures correctly. - Clear retry state on successful merges and cover notifications and recovery paths. Files changed: .changeset/fn-8837-pr-merge-retries.md | 7 + docs/architecture.md | 1 + .../task-update-awaiting-approval-reason.test.ts | 9 + packages/core/src/store.ts | 2 +- packages/core/src/task-store/task-update.ts | 6 +- packages/core/src/types/task/task-core.ts | 7 +- .../src/__tests__/merge-error-recovery.test.ts | 440 ++++++++++++++++++++- .../engine/src/__tests__/ntfy-provider.test.ts | 14 + .../engine/src/__tests__/webhook-provider.test.ts | 15 + .../__tests__/notification-service.test.ts | 48 +++ .../src/notification/notification-service.ts | 15 +- packages/engine/src/notification/ntfy-provider.ts | 15 +- .../engine/src/notification/webhook-provider.ts | 9 +- packages/engine/src/project-engine.ts | 240 +++++++++-- 14 files changed, 781 insertions(+), 47 deletions(-) Fusion-Task-Id: FN-8837 Fusion-Task-Lineage: a39b3491-d5cb-4509-b450-3e5671e0ba2d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
b3504f01a4 |
FN-8838: refresh automated PR heads before GitHub mutations
Refresh isolated automated PR heads against their target immediately before PR creation or merge. - Rebase and lease-publish task and group heads at every automated GitHub boundary. - Fail closed on concurrent head updates and reconcile retained refresh worktrees. - Add lifecycle coverage and document the configurable integration remote. Files changed: .changeset/fn-8838-refresh-pr-heads.md | 7 + docs/settings-reference.md | 6 + .../task-lifecycle-refresh.integration.test.ts | 438 ++++++++++++++++ .../src/commands/__tests__/task-lifecycle.test.ts | 309 ++++++++++- packages/cli/src/commands/daemon.ts | 4 +- packages/cli/src/commands/dashboard.ts | 4 +- packages/cli/src/commands/serve.ts | 4 +- packages/cli/src/commands/task-lifecycle.ts | 581 +++++++++++++++++++-- .../src/__tests__/group-merge-coordinator.test.ts | 43 ++ .../engine/src/merge/group-merge-coordinator.ts | 34 +- packages/engine/src/merge/pr-nodes.ts | 35 +- packages/engine/src/project-engine.ts | 16 +- 12 files changed, 1425 insertions(+), 56 deletions(-) Fusion-Task-Id: FN-8838 Fusion-Task-Lineage: a9b9e800-7d73-441f-bc1b-2488d244e0b1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
36f20f835e |
FN-8822: prevent worktree capacity leaks
Keep live-task capacity admission visible and consistent across all lifecycle lanes. - Report canonical active holders when worktree or concurrent-task capacity is exhausted. - Persist deduplicated queue reasons for merge, triage, and workflow-continuation admission. - Cover retained worktree behavior and document the capacity model. Files changed: .changeset/fn-8822-worktree-capacity-leak.md | 7 +++ docs/architecture.md | 1 + docs/settings-reference.md | 4 +- .../src/__tests__/agent-heartbeat-worktree.test.ts | 11 ++++- .../engine/src/__tests__/project-engine.test.ts | 4 ++ ...ecutor-no-task-done-vs-worktree-reclaim.test.ts | 55 +++++++++++++++++++--- ...admission-worktree-ledger-renamed-lanes.test.ts | 4 ++ .../workflow-continuation-capacity.test.ts | 8 +++- .../src/__tests__/worktree-acquisition.test.ts | 6 +-- packages/engine/src/concurrency/concurrency.ts | 22 +++++++++ packages/engine/src/project-engine.ts | 34 ++++++++++++- packages/engine/src/runtimes/in-process-runtime.ts | 33 ++++++++++++- packages/engine/src/scheduler.ts | 53 ++++++++++----------- packages/engine/src/triage.ts | 18 +++++++ 14 files changed, 215 insertions(+), 45 deletions(-) Fusion-Task-Id: FN-8822 Fusion-Task-Lineage: 73eda70b-2b74-4b49-a0ff-29d41fd9aab8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
3dd824d04e |
FN-8823: respect shared member auto-merge holds
Honor project and member auto-merge consent consistently throughout shared-branch integration. - Apply project autoMerge settings and explicit member overrides to shared-member hold decisions. - Expose shared-member integration hold state and controls in branch-group dashboard and lifecycle APIs. - Add regression coverage, operator documentation, and a patch changeset. Files changed: .changeset/fn-8823-shared-member-consent.md | 7 ++ docs/dashboard-guide.md | 4 +- packages/core/src/__tests__/task-merge.test.ts | 57 +++++++++++++++ packages/core/src/index.gate.ts | 3 + packages/core/src/index.ts | 3 + packages/core/src/merge/task-merge.ts | 81 ++++++++++++++++++--- .../dashboard/app/api/tasks/tasks-lifecycle.ts | 11 +++ .../dashboard/app/components/BranchGroupCard.css | 84 ++++++++++++++++++++++ .../dashboard/app/components/BranchGroupCard.tsx | 58 ++++++++++++++- packages/dashboard/app/components/ListView.tsx | 14 +++- .../dashboard/app/components/TaskDetailModal.tsx | 20 +++++- .../components/__tests__/BranchGroupCard.test.tsx | 44 +++++++++++- .../components/__tests__/TaskDetailModal.test.tsx | 24 ++++++- .../app/components/dashboard/MainContent.tsx | 2 +- .../app/components/useRightDockController.tsx | 2 +- packages/dashboard/app/hooks/useModalManager.ts | 6 ++ .../src/__tests__/routes-branch-groups.test.ts | 42 ++++++----- .../src/routes/register-branch-groups-routes.ts | 9 ++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 21 +++--- .../src/__tests__/group-merge-coordinator.test.ts | 7 +- .../workflow-graph-executor-handlers.test.ts | 33 ++++++--- packages/engine/src/executor.ts | 27 ++++--- packages/engine/src/project-engine.ts | 9 ++- packages/engine/src/self-healing.ts | 16 ++--- .../src/workflow-node-runners/merge-runner.ts | 13 ++-- 25 files changed, 503 insertions(+), 94 deletions(-) Fusion-Task-Id: FN-8823 Fusion-Task-Lineage: 19a8ed3f-26e1-4c8e-8782-ca366718a3f2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
4f4aef7173 |
FN-8811: preserve explicit shared-member review holds
Keep shared branch-group integration moving unless an operator explicitly holds the task. - Track auto-merge provenance and distinguish explicit user holds from inherited mission policy. - Preserve manual holds across workflow recovery, merge coordination, API updates, and dashboard status. - Add regression coverage, document the behavior, and quarantine the observed flaky test. Files changed: .changeset/fn-8811-shared-member-review-hold.md | 7 ++ docs/architecture.md | 4 +- docs/dashboard-guide.md | 1 + .../mission-store.sync-auto-merge.test.ts | 7 +- .../__tests__/postgres/mission-store.pg.test.ts | 1 + .../__tests__/postgres/store-movement.pg.test.ts | 20 ++++ packages/core/src/__tests__/task-merge.test.ts | 14 +++ .../core/src/async-stores/async-mission-store.ts | 6 +- packages/core/src/index.gate.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/merge/task-merge.ts | 20 +++- packages/core/src/missions/mission-store.ts | 6 +- packages/core/src/task-store/serialization.ts | 2 +- packages/core/src/task-store/task-creation.ts | 8 +- packages/core/src/types/task/task-core.ts | 12 ++- .../components/__tests__/TaskDetailModal.test.tsx | 63 ++++++++++++ .../dashboard/src/__tests__/routes-tasks.test.ts | 47 +++++++++ .../src/routes/register-task-workflow-routes.ts | 15 ++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 87 +++++++++++++++++ .../src/__tests__/group-merge-coordinator.test.ts | 99 ++++++++++++++++++- .../engine/src/__tests__/project-engine.test.ts | 57 ++++++++++- .../self-healing-paused-abort-recovery.test.ts | 52 +++++++++- packages/engine/src/__tests__/self-healing.test.ts | 106 +++++++++++++++++++++ .../workflow-graph-executor-handlers.test.ts | 23 +++++ packages/engine/src/executor.ts | 37 ++++++- packages/engine/src/project-engine.ts | 25 +++-- packages/engine/src/self-healing.ts | 71 ++++++++++++-- .../src/workflow-node-runners/merge-runner.ts | 24 ++++- .../src/workflows/workflow-graph-executor.ts | 4 + .../src/workflows/workflow-graph-task-runner.ts | 6 ++ .../engine/src/workflows/workflow-node-handlers.ts | 5 +- packages/engine/vitest.config.ts | 11 ++- scripts/lib/test-quarantine.json | 5 + 33 files changed, 789 insertions(+), 58 deletions(-) Fusion-Task-Id: FN-8811 Fusion-Task-Lineage: 5c1609bf-3132-4988-a254-fedec6c0e33d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1e4c7faf68 |
FN-8812: fix workspace merge retry timer cleanup
Ensure workspace contention retries are canceled when the project engine stops. - Track workspace busy re-enqueue timers separately from other merge timers - Clear pending workspace retry callbacks during engine shutdown - Strengthen retry ladder and timer cleanup coverage Files changed: .changeset/workspace-busy-timer-cleanup.md | 7 +++ .../engine/src/__tests__/project-engine.test.ts | 66 +++++++++++----------- packages/engine/src/project-engine.ts | 23 +++++++- 3 files changed, 59 insertions(+), 37 deletions(-) Fusion-Task-Id: FN-8812 Fusion-Task-Lineage: dd4c9581-8738-4082-8d6b-d91d4c463a5a Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
26219b8d34 |
FN-8799: suppress alerts during automatic task recovery
Prevent terminal task alerts while persisted automatic recovery owns a failed task. - Classify scheduled and transient merge recovery ownership before wedge notification claims. - Re-read live task state before claims and deferred failure delivery to avoid recovery races. - Add recovery suppression coverage, architecture documentation, and a patch changeset. Files changed: .changeset/fn-8799-recovery-notifications.md | 7 ++ docs/architecture.md | 2 + .../src/errors/transient-merge-error-classifier.ts | 18 +++++ .../__tests__/notification-service.test.ts | 78 ++++++++++++++++++- .../__tests__/task-wedge-notification.test.ts | 26 ++++++- .../src/notification/notification-service.ts | 90 ++++++++++++---------- .../src/notification/task-wedge-notification.ts | 37 +++++++-- packages/engine/src/project-engine.ts | 4 +- 8 files changed, 210 insertions(+), 52 deletions(-) Fusion-Task-Id: FN-8799 Fusion-Task-Lineage: f8296e81-6b84-4697-8b20-63823f09d584 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9e4a0817db |
feat: restart the development engine on source changes (#3329)
## Summary Add an opt-in source-development loop that restarts the dashboard and engine when runtime TypeScript or JSON changes. Use `pnpm dev:watch`; `pnpm dev:hmr` now combines Vite UI HMR with the same supervised API/engine restart path. The watcher filters tests, fixtures, generated declarations, build output, and task state. It coalesces bursts with a two-second maximum wait, waits for the child to acknowledge its IPC listener, and rebuilds runtime dist artifacts before a source-triggered respawn. ## Safety model - Close scheduler, triage, heartbeat, mission, routine, self-healing, and merge admission before checking for active work. - Let already-running agents reach a safe boundary; do not mutate durable pause settings. - Enter the existing graceful exit-code-86 shutdown and supervised respawn path. - Retry failed liveness reads and declined restart requests instead of dropping the pending change. - Keep ordinary `pnpm dev` behavior unchanged; inherited watch state does not break nested non-dashboard development commands. A development restart intentionally replaces the dashboard process, so transient dashboard connections and project dev-server children reconnect or restart with it. Agent work is the protected boundary. ## Validation - `pnpm lint` - `pnpm test:gate` (753 tests passed across engine, core, PostgreSQL gate, and CI-shape suites) - Focused CLI watcher/restart/supervision suites: 40 tests passed - Focused engine drain/manager suites: 52 tests passed - `pnpm --filter @runfusion/fusion typecheck` - `pnpm --filter @fusion/engine typecheck` - `pnpm verify:fast` (13 steps passed, including CLI build and real health boot smoke) - Manual unsupported-command probe confirms explicit `--watch` fails clearly outside the dashboard command ## Post-Deploy Monitoring & Validation - Watch for `[fusion:dev] source changed`, `source restart deferred`, `active work drained`, and `restart requested` logs during the first watched development session. - Healthy behavior is one exit-86 respawn per edit batch, no interrupted active agents, refreshed dist artifacts, and a healthy dashboard after respawn. - Investigate repeated restart loops, watcher attachment warnings, declined restart retries, or liveness-read failures. - Immediate mitigation is to use ordinary `pnpm dev` without `--watch`; no production runtime behavior or durable setting needs rollback. - Validation owner: Fusion maintainers during the first source edit after merge. --- [](https://github.com/EveryInc/compound-engineering-plugin) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added `pnpm dev:watch` to automatically restart development runtime processes when source files change. * Development restarts now wait for active work to finish, preventing new work from starting during the transition. * Enhanced `pnpm dev:hmr` with graceful runtime source restarts while keeping the dashboard available. * Rapid source changes are grouped to avoid unnecessary restarts. * **Documentation** * Updated development setup and contribution guides with the new watch workflow. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
7824150715 |
FN-8769: protect default-branch mission merges
Keep mission group members behind manual release controls when their target is the default branch. - Create deterministic intermediate branches for project-default mission groups. - Gate default-branch group routing and auto-merge exemptions behind the normal manual-release flow. - Cover intermediate and default-branch group behavior with core and engine tests. Files changed: ...fn-8769-default-branch-group-auto-merge-gate.md | 7 ++ docs/architecture.md | 2 +- docs/missions.md | 2 +- .../mission-store.sync-auto-merge.test.ts | 4 +- packages/core/src/__tests__/task-merge.test.ts | 16 ++- .../core/src/async-stores/async-mission-store.ts | 12 ++- packages/core/src/merge/task-merge.ts | 23 +++- packages/core/src/missions/mission-store.ts | 11 +- ...cutor-live-branch-group-auto-merge-hold.test.ts | 16 ++- .../src/__tests__/group-merge-coordinator.test.ts | 118 ++++++++++++++++++++- .../engine/src/__tests__/project-engine.test.ts | 16 ++- packages/engine/src/executor.ts | 4 +- .../engine/src/merge/group-merge-coordinator.ts | 13 +++ packages/engine/src/project-engine.ts | 15 ++- 14 files changed, 235 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-8769 Fusion-Task-Lineage: dff96c8e-ca96-437c-94bf-9691bdf572e9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
cb57093d03 |
refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary Wave 17 organizes Fusion into **domain folders** (stacks on #2397). ### Layout - **core/types/** — board, task, agents, settings, merge, workflow, mesh, … - **core/src/** — agents, ai, async-stores, workflows, tasks, config, db, … - **dashboard/app/api/** — client, tasks, agents, git, missions, planning, … - **engine/src/** — agents, auth, execution, merge, missions, overseer, worktree, … Root keepers retained for large entrypoints (`store.ts`, `executor.ts`, `merger.ts`, …). Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy) stay stable. ## Test plan - [x] `@fusion/core` typecheck - [x] `@fusion/engine` typecheck (pre-existing playwright-core noise only) - [ ] CI merge gate **Stack:** #2394 → #2397 → **this PR** |
||
|
|
e8ca86d5ee |
FN-8705: prioritize review and execution slot admission
Prioritize lifecycle-critical work whenever project capacity becomes available. - Rank admission candidates as review/merge, execution, then planning. - Coordinate scheduler handoffs with project and host capacity reservations. - Cover lane priority and document the updated operator behavior. Files changed: .changeset/fn-8705-slot-priority.md | 7 ++ docs/architecture.md | 2 +- docs/dashboard-guide.md | 2 +- packages/engine/src/__tests__/concurrency.test.ts | 95 +++++++++++----------- .../engine/src/__tests__/project-engine.test.ts | 4 +- .../starved-refinement-x-triage-poll.test.ts | 11 ++- .../__tests__/triage-refinement-routing.test.ts | 16 ++-- .../workflow-continuation-capacity.test.ts | 3 +- packages/engine/src/concurrency.ts | 37 +++++++-- packages/engine/src/project-engine.ts | 10 ++- packages/engine/src/runtimes/in-process-runtime.ts | 3 +- packages/engine/src/scheduler.ts | 40 ++++----- packages/engine/src/triage.ts | 7 +- 13 files changed, 133 insertions(+), 104 deletions(-) Fusion-Task-Id: FN-8705 Fusion-Task-Lineage: ef66360b-e504-4e3f-b25e-b032a719d8c0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
04c2bb4707 |
FN-8654: rotate credential instances after provider limits
Retry provider-limit failures with eligible credential instances before falling back to existing pauses and backoff. - Add a runtime-shared credential rotator with cooldown, exhaustion, and audit handling. - Wire credential rotation into executor and heartbeat retry lanes while preserving user pause controls. - Document the behavior and cover rotation, recovery, and retry paths. Files changed: .changeset/fn-8654-credential-instance-rotation.md | 7 + AGENTS.md | 1 + docs/architecture.md | 2 +- docs/settings-reference.md | 4 + .../__tests__/credential-instance-rotation.test.ts | 88 +++++++++++ .../__tests__/credential-rotation-lanes.test.ts | 20 +++ .../__tests__/credential-rotation-recovery.test.ts | 19 +++ .../__tests__/credential-rotation-wiring.test.ts | 15 ++ .../__tests__/rate-limit-retry-rotation.test.ts | 50 ++++++ .../src/__tests__/usage-limit-detector.test.ts | 14 ++ packages/engine/src/agent-heartbeat.ts | 102 +++++++++++- .../engine/src/credential-instance-rotation.ts | 175 +++++++++++++++++++++ packages/engine/src/executor.ts | 141 +++++++++++++++-- packages/engine/src/index.ts | 7 + packages/engine/src/project-engine.ts | 5 + packages/engine/src/rate-limit-retry.ts | 32 +++- packages/engine/src/runtimes/in-process-runtime.ts | 29 +++- packages/engine/src/usage-limit-detector.ts | 18 ++- 18 files changed, 699 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8654 Fusion-Task-Lineage: 44d63441-270c-4949-8c34-47ec4c9992e4 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9b82ff29e1 |
fix: enforce active capacity and clarify queued cards
Refresh canonical live-task claims at serialized scheduler, planning, and merge admission boundaries, including workflow-step leases and reservation handoffs. Back off capacity-denied merges safely across abort and restart lifecycles. Render queued planning cards in the header badge family with compact reason-specific icons. |
||
|
|
dcf1b921a3 | fix(engine): close active-slot reservation handoff gaps | ||
|
|
5a19d1da6e |
fix: count only actively running tasks against worktree capacity
Retained directories on queued, paused, blocked, or terminal tasks no longer consume scheduler slots. Agent concurrency and worktree capacity now count the same canonical live-task population through one project admission ceiling (resolveActiveTaskCapacityLimit) with an atomic reserveIfAvailable claim, so planning, execute, and merge lanes cannot each observe and claim the final worktree slot independently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6834ba35bd |
fix(engine): hung-pass watchdogs on scheduler, merge queue, and continuation drain
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00769fad7c |
fix(engine): run merges outside the admission drain — every merge froze planning admission
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
126cee7e6d |
engine: finalization parked ALREADY-MERGED work as failed on a renamed board (#2964)
**The worst symptom in this family: the branch landed, and the board says the task failed.** `project-engine`'s merge-confirmed finalization spread the task's **real** column into `getTaskHardMergeBlocker` with no `reviewColumns`, so the identity check ran against the literal `in-review`. On a renamed board it returned `task is in 'signoff', must be in 'in-review'`, and the caller parked the card: ``` status: "failed" error: "Merge confirmed but finalization blocked: task is in 'signoff', must be in 'in-review'" ``` For work that had already merged. ## Its sibling had already solved this `auto-merge-finalization.ts` passes the **review-eligible sentinel** instead of the card's own column, with the reasoning recorded at that site: `getTaskHardMergeBlocker` asks *"is this card blocked by anything other than where it sits?"*, and its callers are recovery paths for landed work that a graph crash can leave resting in any column. `project-engine` simply never got the same treatment. ## One name instead of two spellings Rather than write the sentinel a second time, it is exported once as `REVIEW_ELIGIBLE_SENTINEL_COLUMN` next to the helper whose contract gives it meaning, and both recovery paths use it. **Two sites independently spelling a magic value is how one of them came to be missing it** — that is the actual root cause here, not the literal itself. This also answers the census, which flagged the new literal — correctly. Its guidance (which I wrote, in #2909) is to hoist a deliberate literal into a *declaration*, where a `DELIBERATE-LITERAL` marker actually attaches, instead of leaving it mid-expression where the marker is silently ignored. The shared constant is exactly that, and it lowers `auto-merge-finalization`'s literal count too. ## Revert result | | reverted → | | --- | --- | | sentinel replaced by the card's own renamed column | reproduces the shipped string | The middle test asserts that string deliberately — it is what landed in `task.error`, so a regression reports what the operator would actually have seen. A third case checks the sentinel does **not** suppress genuine blockers: incomplete steps still block finalization in any lane. These drive the helper directly; reaching `project-engine`'s finalization end to end needs a live engine, a merge run and a real repo, while the defect is entirely in *what the blocker is asked*. ## Verification `pnpm test:gate` 161 + 487 + 13 + 71; `project-engine` + `auto-merge-finalization` + the new suite, 207; `tsc` clean on core and engine; lint, census `--strict`, FNXC gate, changesets all clean. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed merge-confirmed tasks being finalized correctly when boards use renamed workflow columns. * Prevented already-merged tasks from being incorrectly marked as failed due to custom review-column names. * Preserved enforcement of genuine incomplete-step blockers. * **Tests** * Added coverage for finalization on renamed lanes and legitimate merge blockers. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
10f9df1600 |
fix(overseer): the whole oversight loop was inert on a renamed board (#2898)
`resolveWatchedStage` keyed on the literals `in-progress`/`in-review`, so on a board that renames either it returned `null` for **every** card. That is three literals with an outsized blast radius. `observeTask` returns early on a null stage, so: - no `OverseerStageObservation` is recorded, - no `overseer:intervention` entry is emitted, - and `PlannerRecoveryController`, which consumes those observations, has nothing to steer, retry or targeted-fix. **The entire oversight loop was inert and silent about it** — the same shape as the self-healing sweeps whose queries returned empty arrays. ## I deferred this myself, on a cost argument that was wrong The audit note I wrote for this site said resolving inside `observeTask` "buys a workflow read per card per poll". Then I read the caller: the poll **already awaits `resolveEffectiveSettings` per task**. It is a per-task async loop regardless, so with an IR cache keyed by workflow the addition is *(distinct workflows)* resolutions, not *(cards)*. Pricing the fix before checking the caller cost a deferral. Worth recording, because "this needs a cost judgement" is the most comfortable place in this program to leave something. ## The review test is the three-trait union, deliberately `isReviewColumnRole` checks only `mergeBlocker || humanReview`. A board whose review lane carries `merge` (**mergeOrchestration**) — the built-in default's own shape — would classify as *not in review* and be skipped. Reaching for the obvious helper would have reintroduced the bug this change removes, through the helper meant to fix it. There is a case asserting exactly that. ## Wiring Both call sites, because either alone leaves a hole: | site | why it matters | |---|---| | the poll (`project-engine.ts`) | per-poll IR cache — a workflow edit is picked up next tick rather than served stale | | the manual nudge | otherwise a renamed board answers `no-active-stage` to an operator pressing the button | `columnFlags` is in the `unwired-lane-parameter` vocabulary, so the wiring cannot silently rot — the guard reports it if a future change drops the argument. Fail-soft throughout: an unresolvable workflow yields `undefined` and the callee falls back to the legacy ids, which is exactly today's behaviour. A v1 IR declares no columns, so it takes the same path. ## Revert proof (measured) Drop the `columnFlags` branch and **exactly the three renamed-lane cases fail**: ``` expected null to be "executor" expected null to be "merger" (mergeOrchestration lane) expected null to be "merger" (humanReview lane) ``` The legacy-id and neither-role cases stay green — the gate must still gate, and watching every column would be its own defect. ## Verification - `pnpm test:gate` — 161 / 487 / 13 / 71 passed - `pnpm lint` — clean - `tsc --noEmit` (`@fusion/engine`) — clean - `planner-overseer.test.ts` + `planner-recovery-controller-human-control.test.ts` — 64 passed - unwired-lane guard — 9/9, no new entries Carries the one-line SQL-baseline re-record (`team-analytics.ts: 6 → 3`) that #2864 left behind, same as my other open branches — main is red on it, and identical changes to that line merge without conflict. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
109204c590 |
fix: the query class — three sweeps that never ran on a renamed board (#2818)
Three sweeps that **never ran at all** on a renamed board, plus the shared answer the rest of the class needs. Consolidated from three handoff branches so the helper appears once. #2811 merged, so this is my only open PR. `#2800` measured this class and shipped evidence deliberately without conversions: `listTasks({ column: "<literal>" })` filters in the store, so on a renamed board the read returns an **empty array** and the sweep it feeds does nothing. The census scores the comparison *inside* the loop, never the query above it. ## What was broken | file | census count | what actually happened on a renamed board | |---|---|---| | `backlog-pressure-reporter.ts` | **0** | both reads empty, ratio computed as 0/0 — **the alert never fired**, on a board that may be under exactly the pressure it reports | | `stale-task-reporter.ts` | **0** | both reads empty — **no stale-task signal ever raised**, where work is most likely sitting unnoticed | | `restart-recovery-coordinator.ts` | flagged | sweep never ran — **an engine restart left interrupted tasks stuck with no requeue** | Two of the three have a census count of **zero**. They contain no lifecycle comparison at all, so they have never appeared in the backlog, in a per-file list, or in any "N → 0" claim — and were completely inert. **A file at zero is not evidence of anything.** ## The shared answer, and what it is not Every existing resolver answers a **per-task** question. A query has no task in hand, so it needs the project-level one: every column any workflow declares for a role, unioned with the legacy ids so a board mid-rename still finds rows under the old ones. The set is never empty, so a caller cannot accidentally query nothing. The header states what it is **not**: answering a per-card question from the union would mark a card as review because some *other* workflow calls its column review — the flat-set mistake this program has made four times. ## The finding that generalises: the query is rarely the whole defect `stale-task-reporter` **still reported zero after the query was fixed** — `getTaskAgeStalenessSignal` defaults to the legacy pair, so a card the query now returned was refused inside the signal. Converting only the query would have looked like a fix and changed nothing. That is a caveat on #2800's approach, offered as refinement rather than correction: **asserting the query ARGUMENT is right when pinning a known defect** (the outcome is 0 either way) **and insufficient when proving a fix**, because the outcome is the only thing that distinguishes a real conversion from a deeper one. All three conversions here assert outcomes. `restart-recovery` had three layers — query, a redundant re-assertion (deleted; a test pins the `paused` guard it did contribute), and a move destination that was **already** resolved but whose warning comment was stale. A stale warning is its own hazard: it told the next reader a defect existed where none did. ## Verification - helper **8 passed** · three reporter/coordinator suites **29 passed** - `pnpm test:gate` **161 / 13 / 487 / 71** · lint clean · `--strict` exits 0 · four `tsc` targets clean - each conversion revert-proven independently; the failing case is named in each test header ## Two mistakes worth recording **The helper's own test caught a bug in it.** My first draft wrapped the definition loop in one `try`, and `parseWorkflowIr` **validates** rather than parses — one malformed row would have returned legacy-only lanes for *every* workflow, indistinguishable from the bug it exists to fix. Now isolated per definition. **I clobbered the core barrel** by taking `index.ts` wholesale from a handoff branch, dropping two exports `main` had added since; three packages stopped compiling. Taking a file from another branch takes its whole contents, including what is now stale — for a barrel that is nearly always wrong. Re-applied as a single edit on top of `main`. ## Not included `self-healing.ts`'s 49 — actively owned and mid-conversion; an outside refactor there produces conflicting halves of one sweep. `project-engine.ts` (7) and `executor.ts` (2) need their own read of what each sweep does with the rows, which these three are the argument for. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6fc98fd6c7 |
the third census-invisible class: 51 hardcoded moveTask destinations, measured — and duplicates never archived on a renamed board (#2808)
A third census-invisible class, measured — plus the two worst instances
fixed.
## The shape
```ts
if (task.column !== "in-review") { … return; } // the census counts THIS
await this.store.moveTask(taskId, "in-progress"); // and cannot see THIS
```
The census is an AST scan for **comparisons**. A `moveTask` destination
is a **call argument**, so no backlog entry ever points at one.
Converting the guard alone is *worse than converting neither*: the
handler starts admitting work on a renamed board and then tries to move
the card into a lane that board may not declare.
This bit twice in one week — #2797 (`branch-worktree` requeued into a
lane that may not exist) and #2807 (a GitHub "changes requested" review
dropped, then a move to a hardcoded `in-progress`). Both times it was
found only because the guard *next to it* happened to be under
conversion. So I went looking.
## Measured
Across `core`/`engine`/`dashboard`/`cli`/`plugins`, excluding
`__tests__`/`*.test.*` and comment lines:
| | count |
| --- | ---: |
| hardcoded `moveTask` destinations in production | **51** |
| …passing `recoveryRehome: true` — **deliberate**, not defects | 22 |
| …plain, rejected on a board that does not declare the target | **29**
|
**The 22 must not be "fixed".** `moves.ts` exempts them on purpose
(#1411): a card stranded in an undeclared column has to stay rescuable
to a legacy safe-landing column, or it can never be recovered at all. A
sweep that converts them deletes the rescue path. That distinction is
the reason this is 29 and not 51, and it is why I measured before
writing.
## Why this got sharper recently
The `workflowHasColumn(workflowIr, toColumn)` rejection used to sit
inside a block gated on `isWorkflowColumnsCompatibilityFlagEnabled` — a
settings key **nothing in production writes** — so it never executed and
the legacy `VALID_TRANSITIONS` table decided instead. U12 hoisted it out
of that dead branch and it is now live, proven on a real store by
`live-move-path-undeclared-target.test.ts`:
```
moveTask(card in "todo" -> "triage") now REJECTS: /Unknown column for this workflow/
```
That changed the failure mode of all 29 from *"silently lands the card
in an undeclared column"* to *"throws"*.
**29 is not a crash count.** Whether a throw surfaces or disappears
depends on whether the caller catches, which is per-site and I did
**not** measure it — the doc says so explicitly rather than letting the
number imply severity it hasn't earned.
## Fixed here: 9 of the 29
`duplicate-intake` and `duplicate-guard` both archive a duplicate. On a
renamed archive lane the move is rejected, so **the duplicate is never
archived and keeps sitting on the operator's board as live work** — and
in `duplicate-guard` the row has already been stamped
`deterministicDuplicateOf`, so it is *marked* a duplicate while
occupying an active lane. Half-applied, which is the same trap as
#2797's branch clear.
Both now resolve the `archived`-trait column from the task's own
workflow through one shared helper, unioned with the legacy id.
**`cli/commands/task-lifecycle`** — `finalizePullRequestMerge` and
`finalizeNoOpMergeTask` both move the card to a hardcoded `"done"`, and
both run `updateTask({ status: null, mergeRetries: 0 })` *first*. On a
rejection the merge has already landed and the bookkeeping is already
cleared while the card never reaches its complete lane: the operator
sees a merged branch, a card still sitting in review, and a reset retry
counter. Same half-applied shape as #2797's branch clear. Both now route
through one resolver so they cannot drift.
**`contamination` / `foreign-only-contamination` (×2) /
`restart-recovery-coordinator`** — four recovery requeues to a hardcoded
`"todo"`, none of them a `recoveryRehome` escape. On a board without
that column the move is rejected and **the recovery never completes** —
the card stays contaminated or stranded, which is precisely the state
these paths exist to clear.
**Consolidation.** `resolveReboundTargetForTask` and
`resolveArchiveTargetForTask` now live beside
`resolveTaskLifecycleColumns` in `workflow-lifecycle-traits`, already
the store-dependent resolution seam. My first pass put the archive
helper inside `duplicate-intake` and had `duplicate-guard` import it
from there — wrong home, and it would have grown a copy per caller as
more sites converted. Seven call sites now share two definitions.
**Plain (non-`recoveryRehome`) destinations: 29 → 21.**
**Coverage on the CLI pair is scoped, and I'd rather say so than imply
more:** the test covers the *resolver*, not the two call sites. Both
enclosing functions are private and reachable only through
`processPullRequest`, which needs a live GitHub surface — exporting them
purely to test wiring is a worse trade than stating what is covered.
Three cases: renamed lane resolves, no-workflow falls back to the legacy
id (which also pins that a default board is byte-identical), and a
throwing lookup falls back.
## Revert result (measured)
| conversion | reverted → |
| --- | --- |
| duplicate archive destination | new case fails — `moveTask` called
with `"archived"` on a board whose archive lane is `boxed` |
| CLI complete-lane resolver | replacing the body with a bare `return
"done"` fails the renamed case |
| both move-target resolvers | replacing either body with a bare return
of its legacy id fails 5 cases across the resolver suite and
`duplicate-guard` |
Each resolver has a **non-vacuous companion** asserting it does *not*
return the legacy id on a renamed board — without it, a resolver
returning any string would pass. The fallback cases are load-bearing
rather than padding: `resolveWorkflowIrForTask` degrades to the built-in
IR rather than throwing, and the built-in rebound/archive lanes *are*
`todo`/`archived`, so those cases also pin that a default board is
byte-identical.
The pre-existing case asserting the legacy `"archived"` passes both
ways, which is exactly why it could not detect this and why the new one
supplies a workflow.
## Ownership note
`packages/core` was `batch-core`'s territory and `packages/cli` was
`batch-cli-plugins`'. Both batches have landed, and this is
newly-discovered work in the class documented here rather than leftover
conversion backlog. Four sites, two shared helpers — happy for either
half to move if those owners would rather carry it.
## Verification
- `pnpm test:gate` — 161 + 487 + 13 + 71, green
- `duplicate-guard` + `duplicate-intake` — 40 passed
- `tsc` on core and engine — clean
- `pnpm lint`, `check:changesets`, census `--strict` — all clean (run
explicitly; a clean `pnpm lint` alone is not evidence the CI Lint check
passes)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Duplicate tasks are now archived to each workflow’s configured archive
lane.
- Completed tasks are moved to the workflow-specific completion lane,
with a safe fallback for older workflows.
- Recovery and requeue actions now use each workflow’s configured
rebound lane instead of assuming a fixed destination.
- **Documentation**
- Added guidance on avoiding failures caused by hardcoded workflow
destinations and incomplete lifecycle conversions.
- **Tests**
- Added coverage for renamed workflow lanes, fallback behavior,
duplicate archiving, and recovery destinations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d86c1f9d29 |
batch-engine: packages/engine lifecycle-column conversions (capacity worker's mega-batch) (#2773)
The engine mega-batch. Folds my four engine PRs and will absorb the remaining `packages/engine` guards as commits on this branch. **Superseded and closed:** #2722, #2741, #2766, #2770. ## Census — files converted so far | file | before | after | |---|---:|---:| | `notification/notification-service.ts` | 9 | **5** | | `runtimes/in-process-runtime.ts` | 6 | **1** | | `eval-followups.ts` | 2 | **0** | | `pr-comment-handler.ts` | 1 | **0** | | `task-revert.ts` | 2 | **0** | The last two are **census-invisible** (`Set.has(task.column)` membership) — the class measured in #2763, which a comparison-based scan cannot count. So the backlog number moves less than the work does, deliberately. ## What each one actually fixed — all silent, none cosmetic - **Notifications stopped entirely.** `handleTaskMovedAsync` compared `data.to` to `in-review`/`done`, so on a renamed board the two notifications operators rely on most were never sent. - **A finished card's plan review could re-enter.** The continuation drain's terminal test matched nothing, so a completed card's planning continuation was handed to the executor. - **The revert route admitted and the service refused.** The route resolved terminal lanes; the service compared to a hardcoded pair. The operator got a dead end from an affordance the UI and route both offered. - **Follow-up dedup blocked new cards forever.** A finished follow-up in a renamed complete lane read as *open*, so the dedup matched it permanently — defeating the intent the code documents in the line above it. - **The mission requeue wrote a column that may not exist**, and its guard never matched. ## Flagged, not fixed — deliberately - **`concurrency.ts` idle semaphore leak recovery** — the last live caller of the running-agent predicate that does not enrich. On a renamed board it under-counts and can reclaim a legitimately-held slot. The enriching variant is async and this is a synchronous repair path whose failure mode is reclaiming live work. - **The archival `task:moved` listener** — runs on every move with no cheap gate ahead of it; converting costs an IR resolution per move to decide most moves are not archival. ## Notes carried from the folded PRs Two conflicts resolved in main's favour because **main's version was better**: `in-process-runtime`'s seam uses `terminalColumns: ReadonlySet` (membership) where mine used `LifecycleColumns` (first-per-role), and the test is rewritten against main's API. That arity trap has now caught me four times, so membership is the default shape in everything new here. Review fixes from the folded PRs are included: the notifier's review set, the second human-review site, the second dedup copy, the workspace revert surface, and the file-content assertions. ## Verification `pnpm test:gate` **GREEN** (158 + 10 + 487 + 71) · **224 passed** across the touched engine suites · engine and dashboard `tsc` clean · `pnpm lint` clean · census `--strict` exits 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3577cb6adf |
fleet: project-engine.ts 12 → 5 — auto-merge silently declined every card on a renamed board (#2706)
**Claim announced before the work** (on #2689, alongside `register-task-workflow-routes.ts`): `packages/engine/src/project-engine.ts`. **12 → 5.** Repo backlog → **685**. ## The failure mode here has no error signature Every merge guard in this file spelled the lane `in-review`. On a renamed board nothing throws, nothing logs a warning — **auto-merge simply declines every card**: | guard | what a renamed board gets | |---|---| | `requestInterpreterMerge` | returns `noOp: true` — *"parked cleanly in review, awaiting human merge"* — for a card that was in review and fully eligible | | the merge-queue snapshot | returns an **empty list** for a queue full of review cards, so the coordinator sees nothing to admit | | the `taskMoved` auto-merge handoff | never fires, so nothing reaches auto-merge in the first place | | the pause-interruption tracker | drops every card from its paused-review set on the next update, so a merge paused mid-flight is never interrupted | The operator sees cards resting in review with auto-merge **on**, and every log line says the system did the right thing. There is no string to search for — which is the argument for the census being a parse rather than a grep over error messages. ## Implementation notes - **Core's `resolveTaskLifecycleColumns` directly** — the canonical helper, so no new abstraction and no fourth local resolver in a file that had none. - **The merge-queue snapshot resolves per task through a shared `irCache`**, because a merge queue can hold cards from *different* workflows. Per-workflow, not per-card: one IR read each. - **The handoff and its post-grace recheck share one snapshot.** They are halves of one decision — "did this card just enter the merge lane, and is it still there?" — and that is exactly the split that produced the defects in `executor.ts`. ## Revert proof **1 of 3 cases reddens** with the literal restored. The suite invokes the real `requestInterpreterMerge` via `.call()` on a minimal `this` (`runtime.getTaskStore`, `allowInReviewMergeProcessing`, `onMerge`) instead of standing up a whole `ProjectEngine` runtime. The body under test is the shipped one, and *reaching* `onMerge` is the assertion. I would rather explain that seam than either skip the proof or spend the test budget booting a runtime. The default-board case is labelled in the file as no-change evidence, not counted as coverage. ## The remaining 5, flagged not guessed All five are `column === "done"` **merge-confirmation reads** — "did the merge land?". That is a different question from any lane role, and it shares its answer with the dependency guards I flagged in `executor.ts` (`12325`) and `register-task-workflow-routes.ts` (`3995`). Three files, one open question: **what does "landed / satisfied" mean on a board whose terminal column is not named `done`, and is it the complete column or the terminal union?** Deciding it once and applying it to all three is right; swapping it three times independently is how the resolver choice ends up inconsistent — which already happened once inside `executor.ts`, where the correct resolver inverts between two guards a few lines apart. ## Verification `pnpm test:gate` **158 / 10 / 487 / 71** · **56/56** across the six auto-merge / project-engine suites · 3/3 in the new suite · `tsc -p packages/engine` clean · `pnpm lint` clean · census `--strict` exit 0. No changeset: `@fusion/engine` is private, and the behaviour change is confined to renamed boards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3bbb6ffc6b |
capacity, part 2: delete the cross-project concurrency cap (enforcement half) (#2509)
Stacked on #2502 — review that first; this branch contains its three commits. Operator: two capacities **per project**. `globalMaxConcurrent` is a machine-wide *third* limiter kept in a separate authority (a central-DB singleton row) that every runtime had to subscribe to and periodically re-reconcile. It goes. This slice removes **enforcement and wiring only**. The setting key, central DB state, API route and Settings UI come out in part 3, so each half lands green and independently revertable. **Deleted:** the shared `AgentSemaphore` instance in `ProjectManager` and `ProjectEngineManager`; the per-project `ScopedAgentSemaphore` in `InProcessRuntime`; the `globalSemaphore` runtime-config field; both `concurrency:changed` subscriptions; ProjectManager’s 30s limit-refresh poll; the residual-slot return on project stop. The scheduler/triage semaphore gate is now simply **absent** — same shape as the worktrees-off gate in #2502. `semaphoreGate?` was already optional, so no gate object is constructed rather than one holding an infinite limit. Absence cannot start binding again by accident. --- ## Two findings that changed the shape of this slice **1. `AgentSemaphore` the class stays — my earlier estimate was wrong and I withdraw it.** I previously told the coordinator that ~75% of `concurrency.ts` (≈662 of 886 lines) was semaphore machinery that could go with this cap. That was line-range arithmetic, and it was wrong. `AgentSemaphore` is a general primitive with four consumers unrelated to the global cap: | Consumer | Governs | |---|---| | `verification-concurrency.ts` | `maxConcurrentVerifications` | | `research-orchestrator.ts` | research `maxConcurrentRuns` | | `experiment-executor.ts` | `maxConcurrentExperiments` — **a knob absent from my original inventory** | | `step-session-executor.ts` | parallel workflow steps | What goes is the global **instance** and its wiring, not the class. I will report the measured `concurrency.ts` delta after part 3 rather than repeat an estimate. **2. `acquireGlobalSlot` / `releaseGlobalSlot` had no production callers — only tests.** So the cross-project cap had *two* mechanisms: the in-memory semaphore (live) and a durable central-DB `currentlyActive` counter (dead — never incremented by real work). Both deleted, along with the tests that pinned the dead passthrough. ## The regression this almost introduced `runWithMergeAdmission` in `project-engine.ts` opened with: ```ts if (!semaphore) return await start(); ``` Unreachable while a global semaphore always existed. With the semaphore gone it would have fired on **every** merge and skipped `projectAdmissionCoordinator.admitOldest` entirely — silently stopping merges from counting against the **per-project** agent count. That is the opposite of the intent: a merge *is* an agent and still consumes one of the project’s slots; it just no longer consumes a machine-wide one. So the early return is **deleted rather than left to fire**. `admitOldest` already declares `semaphore` as optional and enforces `maxConcurrent` independently of it (`claimed() + reservations >= maxConcurrent`), so dropping the argument preserves per-project admission and oldest-first fairness exactly. Worth flagging as a pattern: this is the third time in this unit that a branch which was *unreachable* became *always-taken* once a limiter was removed. The type system caught the worktree one; this one was only visible by reading the branch, because the semaphore was reached through an `any` cast. ## Verification `pnpm lint` clean · engine `tsc` clean · `pnpm test:gate` green (309 + 10 + 71) · project-manager + hybrid-executor + merge-single-flight + scheduler 93/93. Nothing booted. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0e3d2a2265 |
refactor: delete meta-task auto-archive and automated recovery follow-ups (#2461)
Deletes two pieces of automated "meta" machinery that filed and garbage-collected cards restating state already on the task that failed. Net **-1015 lines**. ## Why **Automated recovery follow-ups.** `createAutomatedFollowup` and its dedup engine (289 lines of signature matching, 1h recurrence rate-limiting, 24h supersedes windows) existed to file recovery cards for verification-cap and merge-conflict give-ups. In both cases the parent is *already* parked `failed` with a descriptive `error` and a log entry carrying the failing command, branch, and output — the card was a second copy of that. **Meta-task auto-archive.** The sweeps that garbage-collected those cards were worse than redundant: the regex classifier matched ordinary feature work, and its positional fallback bound cards to unrelated tasks, so **live work could be archived**. They are removed together, because the auto-archive sweeps only existed to clean up after the follow-up engine. ## What changed ### Deleted - `packages/engine/src/verification-followup-dedup.ts` in full — `createAutomatedFollowup`, `decideAutomatedFollowup`, `AutomatedFollowupKind`, `computeVerificationFailureSignature`, `extractFailingTestFiles`. - `findActiveRecoveryFollowUp` — dead code, defined and never called (`tsc` independently flagged it `6133 declared but its value is never read`). - The meta-task auto-archive sweeps `autoArchiveResolvedMetaTasks` / `autoArchiveStalledMetaTasks` and helpers `classifyMetaTask` / `resolveMetaTargetTaskId` / `computeMetaChainDepth` / `archiveMetaTask` / `evaluateMetaAutoArchiveGuards`, plus settings `metaTaskStallAutoCloseMs` and `metaTaskActiveExecutionGraceMs`. - Run-audit types `task:auto-archived-meta-resolved`, `task:auto-archived-meta-stalled`, `task:auto-archive-meta-resolved-skipped`, `task:auto-archive-meta-stalled-skipped`, `verification:followup-created`, `verification:followup-deduped`. The two signature helpers were **deleted rather than relocated** — once the three call sites went they were provably unreachable: `buildVerificationFailureSignature` had exactly one caller, and it was the only caller of `extractFailingTestFiles`. ### Call sites 1 and 2 — park kept, card dropped Verification-cap and merge-conflict give-ups keep their park, audit event, operator comment, and log entry. Site 1's `error` string was reworded off `"See follow-up task for investigation."` (no follow-up will exist) to carry the guidance itself. `autoResolveDisabled` was **kept** — it still drives the outer park guard and the `reason` string; only the inner branch that guarded card creation is gone. ### Call site 3 — autostash orphan, replaced not deleted This one is a genuine data-loss guard, so it keeps a durable trail. A `live`-classified orphan is a merger stash holding **real uncommitted work**, and unlike sites 1–2 there is no parked parent — the parent may already be `done` and merged, so nothing else on the board would ever mention the stash. The card is replaced by a `logEntry` **and** an `addTaskComment` on the parent, preserving every fact the old description carried: the sha, `record.label` (the handle `git stash` recovery needs), `record.detectedByTaskId`, and `sourcePhase`. New truthful run-audit event `task:autostash-orphan-live-detected` replaces the borrowed `verification:followup-*` name, with ids/outcomes-only metadata per AGENTS.md. ### Kept unchanged: the two real product features Eval follow-ups (`eval-followups.ts`) and PR-comment follow-ups (`pr-comment-handler.ts`) only borrowed the shared engine for its dedup pass. Both keep their exact behavior, column, priority, `sourceType`, and log lines, with dedup inlined as a `listTasks` scan on `suggestionId` / `prNumber` respectively. Both fail open (create) if the listing throws, matching the old engine. ## Test changes — read this one Two tests asserted the *deleted* engine's rate-limited `"[verification recurrence]"` logEntry. Those assertions were removed, **not loosened**: both tests still assert no duplicate card is created, and the eval test still asserts the existing id is reported back. No coverage of surviving behavior was weakened. The three `meta-*` test files were deleted along with the sweeps they covered. ## Verification ``` $ pnpm test:gate Test Files 2 passed (2) Tests 10 passed (10) # core Test Files 16 passed (16) Tests 299 passed (299) # engine-core Test Files 1 passed (1) Tests 70 passed (70) # ci-shape GATE_EXIT=0 $ pnpm --filter @fusion/engine --filter @fusion/core exec tsc --noEmit -p tsconfig.json TSC_EXIT=0 (no output) ``` Plus a file-scoped run over the touched surfaces (`eval-followups`, `pr-comment-handler`, `merger-autostash-orphan-surface`, `merger-autostash-cleanup`, `run-audit`, `run-audit-secret-taxonomy`, `project-engine`, `project-engine-manager`): **213/213 passed**. A repo-wide grep confirms no surviving references to any deleted symbol, module, or audit event. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Failed tasks now retain recovery and verification details directly on the original task instead of generating separate follow-up cards. * Live autostash issues now preserve stash information in task comments and activity logs. * Existing evaluation and pull-request follow-ups continue to be reused when appropriate. * **Changes** * Removed automatic archival of meta-tasks. * Removed obsolete meta-task timing settings. * **Documentation** * Updated architecture and settings documentation to reflect these workflow changes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6422cb93a4 |
fix(engine): stop overseer hard-cancel thrash on live step sessions (#2393)
## Summary
Prevents the FN-8471 failure mode where planner overseer `retry_step`
bounced `in-progress → todo` while a live step-execute session was still
coding, hard-cancelling the agent up to three times until recovery
budget exhausted.
Also closes concurrent resume races after plan-review release that
parked `status=failed` on a losing graph while a peer session still
owned work.
### Changes
- **Overseer live gate:** `retryStep` skips the hard-cancel bounce when
`isTaskLiveForOverseerRetry` is true; returns `false` so attempt budget
is not burned; durable skip log is deduped per task/stage.
- **Single-flight graph dispatch:** `executeCore` claims `graphRouting`
before any await; `executeWorkflowGraph({ alreadyClaimed })` owns
release.
- **Single-flight unpause resume:** claim `resumingUnpaused` before
await; treat existing graph claim as already-owned; clear claim before
completed-work recovery.
- **No false park:** execute-family graph endings with a peer live
session no longer stamp `status=failed` (merge-region failures still
park).
### Tests
- `executor-live-overseer-retry-gate.test.ts` — live probe matrix,
execute-family preserve, merge still parks
- `planner-overseer-intervention-wiring.test.ts` — live skip keeps
column in-progress and `getAttemptCount === 0`
## Test plan
- [x] `vitest run` scoped to the two new/updated test files (16 passed)
- [ ] CI gate (lint/typecheck/build/test:gate)
- [ ] Optional manual: fail a raced graph with a live step session and
confirm overseer does not bounce to todo
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved overseer-retry “live session” gating to avoid interrupting
active work, covering more live surfaces and preventing multi-resume
races.
- Updated failure handling so execute-family failures can be preserved
when another live session is still running, while merge-attempt failures
are still marked failed.
- Added deduping for “retry skipped due to live session” logs so they’re
emitted only once per task stage, and ensured the recovery attempt
budget isn’t consumed when intentionally skipped.
- **Tests**
- Added coverage for live-gating, retry-skip/budget behavior, and the
revised failure-parking rules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
a38524dd54 |
fix(engine): do not read TaskStore in ProjectEngine constructor
FN-8453 merge-admission registration called getTaskStore() before runtime.start(), which threw, left the singleton lock held, and made every later engine start fail with blocked-by-lockfile. Use config.projectId instead. |
||
|
|
eef5eb751e |
FN-8453: unify concurrency accounting and indicators
Unify live-agent capacity accounting across engine and dashboard. - Derive Running and Waiting from workflow traits and durable agent liveness. - Apply unified limits to planner, executor, and merge admission while updating dashboard indicators. - Remove duplicate concurrency controls and document the unified operator model. Files changed: .changeset/fn-8453-unified-concurrency.md | 7 + docs/agent-tool-surface-full-loop.md | 4 +- docs/architecture.md | 2 +- docs/dashboard-guide.md | 4 +- docs/settings-reference.md | 4 +- .../skill/fusion/references/fusion-capabilities.md | 4 +- .../core/src/__tests__/live-agent-count.test.ts | 91 ++++---- packages/core/src/index.gate.ts | 6 + packages/core/src/index.ts | 6 + packages/core/src/live-agent-count.ts | 107 ++++++--- packages/dashboard/app/App.tsx | 28 ++- packages/dashboard/app/api/board-workflows.ts | 2 + packages/dashboard/app/components/Column.tsx | 6 +- .../dashboard/app/components/EngineControlMenu.tsx | 26 --- .../dashboard/app/components/ExecutorStatusBar.tsx | 38 ++- .../dashboard/app/components/SettingsModal.tsx | 1 - .../app/components/__tests__/Column.test.tsx | 6 +- .../__tests__/EngineControlMenu.test.tsx | 10 +- .../__tests__/ExecutorStatusBar.test.tsx | 32 ++- .../command-center/CommandCenterControls.tsx | 26 --- .../settings/sections/SchedulingSection.search.ts | 9 - .../settings/sections/SchedulingSection.tsx | 13 -- .../app/hooks/__tests__/useExecutorStats.test.ts | 12 +- packages/dashboard/app/hooks/useExecutorStats.ts | 50 ++-- .../src/__tests__/project-store-resolver.test.ts | 11 +- packages/dashboard/src/project-store-resolver.ts | 14 +- .../register-config-mcp-pi-settings-routes.ts | 3 +- packages/engine/src/__tests__/concurrency.test.ts | 123 +++++++++- .../engine/src/__tests__/project-engine.test.ts | 34 +++ packages/engine/src/__tests__/triage.test.ts | 7 +- packages/engine/src/concurrency.ts | 207 ++++++++++++++++- packages/engine/src/project-engine.ts | 151 ++++++++++-- packages/engine/src/scheduler.ts | 82 ++++++- packages/engine/src/triage.ts | 254 +++++++++++++-------- .../lib/dashboard-browser-safe-core-modules.json | 5 + 35 files changed, 991 insertions(+), 394 deletions(-) Fusion-Task-Id: FN-8453 Fusion-Task-Lineage: 12cfa5df-675d-4fce-b17e-932376544239 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e24f765495 |
FN-8252: rescue quarantined engine tests
Restore non-mechanical engine coverage with PostgreSQL-safe test fixtures and awaited overseer audit writes. - migrate eligible engine tests to shared PostgreSQL harnesses and restore their Vitest coverage - harden mission and advisory reporting paths for async persistence and observable failures - await the production planner-overseer audit callback and verify the start() wiring preserves persistence Files changed: .../__tests__/mission-autopilot-end-to-end.test.ts | 27 ++-- .../engine/src/__tests__/mission-autopilot.test.ts | 4 +- .../planner-overseer-intervention-wiring.test.ts | 39 +++--- .../engine/src/__tests__/project-engine.test.ts | 138 ++++++++++++++++----- .../unlinked-missions-advisory-reporter.pg.test.ts | 51 ++++++++ .../unlinked-missions-advisory-reporter.test.ts | 20 ++- packages/engine/src/mission-execution-loop.ts | 27 ++-- packages/engine/src/project-engine.ts | 41 +++--- .../src/unlinked-missions-advisory-reporter.ts | 23 ++-- packages/engine/vitest.config.ts | 6 +- scripts/lib/test-quarantine.json | 27 +--- 11 files changed, 260 insertions(+), 143 deletions(-) Fusion-Task-Id: FN-8252 Fusion-Task-Lineage: 4f86ce7e-11a2-4704-a5d1-00e0a8c1448e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
62f121e0c9 |
FN-8247: stop session advisor with oversight
Stop now persists session-advisor disablement and reflects inherited advisor state in task details. - Persist an explicit advisor-off override and clear its live runtime when stopping oversight. - Resolve workflow-level advisor defaults for task-detail icons and toggle behavior. - Cover stop cleanup and desktop/mobile oversight state transitions with regression tests. - Document the combined stop contract and add a patch changeset. Files changed: .changeset/fn-8247-session-advisor-stop-and-icon.md | 7 ++ docs/dashboard-guide.md | 6 +- packages/dashboard/app/components/TaskDetailModal.tsx | 86 +++++++++++------ packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-controls.test.tsx | 104 +++++++++++++++++++++ packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-mobile.test.tsx | 62 ++++++++++++ packages/engine/src/__tests__/project-engine-stop-overseer-session-advisor.test.ts | 62 ++++++++++++ packages/engine/src/project-engine.ts | 13 ++- 7 files changed, 310 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8247 Fusion-Task-Lineage: dcb67af7-9a36-4b25-bafa-84722fe158a0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
5d2c3be6a0 |
FN-8221: clear inactive planner overseer state
Clear retained planner overseer state when effective oversight is disabled. - Remove monitor, recovery, advisor, and dedup runtime for oversight-off tasks. - Suppress stale oversight-off Eye badges in task cards. - Cover cleanup and badge behavior with regression tests. - Document the runtime snapshot invariant and add a patch changeset. Files changed: .changeset/fn-8221-overseer-badge-oversight-off.md | 7 ++ docs/architecture.md | 4 + packages/dashboard/app/components/TaskCard.tsx | 9 +- .../app/components/__tests__/TaskCard.test.tsx | 20 ++++ .../__tests__/planner-overseer-off-cleanup.test.ts | 119 +++++++++++++++++++++ packages/engine/src/project-engine.ts | 12 +++ 6 files changed, 170 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-8221 Fusion-Task-Lineage: 01c9d838-fbe4-4d34-8eb5-d735cf35e581 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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>
|
||
|
|
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 --> |
||
|
|
836e53c6c0 |
FN-7975: exclude engine-paused wall-clock from task active timing
Reconcile active task segment anchors on full Global/Engine unpause so stopped-engine wall-clock does not inflate execution time, reusing the FN-7011 downtime path with a transition-captured heartbeat. - Pass optional engineLastActiveAtOverride into reconcileActiveTimingForEngineDowntime so unpause callers freeze the stopped-window proof against racing scheduler heartbeats - Await downtime reconciliation in resumeAfterUnpauseAndSweepInReview before resuming agentic work or sweeping in-review tasks - Fold Global/Engine unpause into the unified pause-lifecycle listener (single reconcile when both clear together; no-op while either pause remains) - Soft-fail reconcile errors so unpause resume still proceeds - Add store and project-engine coverage for override, await-before-resume, dual-source clear, and fail-soft paths; document FN-7975 in AGENTS.md run-audit notes - Add patch changeset for the operator-facing timing fix Files changed: .changeset/fn-7975-engine-pause-active-timing.md | 7 ++ AGENTS.md | 2 +- .../core/src/__tests__/store-active-timing.test.ts | 86 +++++++++++++ packages/core/src/store.ts | 23 ++-- .../project-engine-unpause-active-timing.test.ts | 94 ++++++++++++++ .../engine/src/__tests__/project-engine.test.ts | 139 +++++++++++++++++++++ packages/engine/src/project-engine.ts | 64 +++++----- packages/engine/src/self-healing.ts | 6 +- 8 files changed, 378 insertions(+), 43 deletions(-) Fusion-Task-Id: FN-7975 Fusion-Task-Lineage: 84a46e6f-92bf-452a-ab67-c25ba85cbffb Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a67c2763af |
fix(merge-queue): serialize reclaim and status-aware silence policy
Prevent concurrent orphan merge after abort, protect long merging-phase tools from false reclaim, emit run-audit on wedged reclaim, and race PR merge dispatch the same way as direct AI merge. |
||
|
|
0eb46f2a89 |
fix(engine): reclaim wedged single-flight merge pump automatically
AI-merge review hangs left activeMergeTaskId/mergeRunning set while status=reviewing and overseer logEntry noise kept updatedAt fresh, so self-healing never reclaimed the owner and the board showed no merging badge. Race merge work with abort, force-abort on pause/reclaim, treat reviewing as merge-active, and recover on merger agent silence; also forward PluginRunner into AI merge so grok-cli merger matches chat. |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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. |
||
|
|
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> |
||
|
|
c745990aa2 |
FN-7879: deliver one-time Postgres-migration inbox notice on first 0.59 startup
Adds a best-effort, idempotent dashboard inbox notice announcing the upcoming embedded-Postgres storage migration, delivered once per project on the first engine start under the Fusion 0.59.x release line. - New `deliverPostgresMigrationNoticeIfNeeded` in `@fusion/engine` (`postgres-migration-notice.ts`) builds and sends a `system` -> `user` inbox message via `MessageStore`, gated to version `0.59.x` by `isPostgresMigrationNoticeVersion` - Idempotency via existing inbox message `metadata.kind = "postgres-migration-notice"` marker (no new settings key or table), so restarts never duplicate the notice - Delivery is fully best-effort: any `MessageStore` failure is caught, logged as a warning, and never blocks or fails `ProjectEngine.start()` - `ProjectEngine.start()` invokes the notice after runtime start, using an injected `cliPackageVersion` threaded from the CLI layer through `EngineManagerOptions` / `ProjectEngineOptions` so the engine never imports CLI/dashboard code directly - `daemon.ts`, `dashboard.ts`, and `serve.ts` resolve the published `@runfusion/fusion` version via `getCliPackageVersion` / `isUnresolvedCliPackageVersion` and pass it into `ProjectEngineManager` - Exported new symbols (`POSTGRES_MIGRATION_HELP_URL`, `POSTGRES_MIGRATION_NOTICE_KIND`, `deliverPostgresMigrationNoticeIfNeeded`, `isPostgresMigrationNoticeVersion`, related types) from `@fusion/engine`, and `isUnresolvedCliPackageVersion` from `@fusion/dashboard` - New unit tests covering version matching and single-delivery/idempotency behavior - Docs updated (`docs/agents.md`, `docs/dashboard-guide.md`) to describe the one-time notice and its dedup key - Changeset added for `@runfusion/fusion` (minor, feature) Files changed: .changeset/fn-7879-postgres-migration-inbox-notice.md | 7 ++ docs/agents.md | 1 + docs/dashboard-guide.md | 1 + packages/cli/src/commands/daemon.ts | 6 +- packages/cli/src/commands/dashboard.ts | 5 + packages/cli/src/commands/serve.ts | 6 +- packages/dashboard/src/index.ts | 2 +- packages/engine/src/__tests__/postgres-migration-notice.test.ts | 140 +++++++++++++++++++++ packages/engine/src/index.ts | 9 ++ packages/engine/src/postgres-migration-notice.ts | 107 ++++++++++++++++ packages/engine/src/project-engine-manager.ts | 6 + packages/engine/src/project-engine.ts | 12 ++ 12 files changed, 299 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-7879 Fusion-Task-Lineage: 201877e5-6bdc-4168-a8ac-ae0e50ec8308 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
eb377ba831 |
FN-7750: gate shared-branch-group auto-merge exemption on live groups
Fixes autoMerge=false being bypassed for engine-created branch-group member tasks whose branch group had already dissolved/finalized. - Add isLiveSharedBranchGroupMemberIntegration(task, group) in @fusion/core, requiring the branch group's status be "open" before the shared-branch-member exemption bypasses the global/task autoMerge:false hold. - Export the new helper from packages/core/src/index.ts and index.gate.ts. - Thread the live-group check through packages/engine/src/project-engine.ts (allowInReviewMergeProcessing, enqueueEligibleInReviewTasks, merge-confirmed fast-path branch routing, and merge handoff paths). - Add TaskExecutor.isLiveSharedBranchGroupMember helper in packages/engine/src/executor.ts and use it in retryable pre-merge remediation, no-op finalize, benign pause-abort classification, and merge-processing gates. - Keep self-healing.ts's solo no-op finalize predicate on the pure branchContext-shape check (isSharedBranchGroupMemberIntegration) intentionally, so stale shared-group members stay excluded from solo finalize regardless of group liveness. - Add regression tests covering the executor and project-engine auto-merge-hold behavior for stale/dissolved branch groups. - Add a patch changeset documenting the fix. Files changed: .../fn-7750-automerge-hold-stale-branch-group.md | 7 ++ packages/core/src/__tests__/task-merge.test.ts | 42 +++++++++-- packages/core/src/index.gate.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/task-merge.ts | 13 +++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 85 ++++++++++++++++++++++ .../engine/src/__tests__/project-engine.test.ts | 37 +++++++++- packages/engine/src/executor.ts | 22 ++++-- packages/engine/src/project-engine.ts | 32 +++++--- packages/engine/src/self-healing.ts | 1 + 10 files changed, 214 insertions(+), 27 deletions(-) Fusion-Task-Id: FN-7750 Fusion-Task-Lineage: d61f8847-0b09-49b5-b66a-00018c8738bb Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1fa4a69dde |
FN-7747: derive dashboard authStorage fallback from engine.getAuthStorage()
Fixes desktop provider API keys not persisting when a host wires an engine into createServer() but forgets to pass its own authStorage, which previously caused register-auth-routes.ts to throw "Authentication is not configured". - Add ProjectEngine.getAuthStorage() exposing the OAuth subsystem's raw createFusionAuthStorage() instance - In createServer(), derive options.authStorage from engine.getAuthStorage() when not explicitly provided (mirrors existing engine-derivation pattern for onMerge/automationStore/etc.); explicit authStorage still overrides - Add regression tests covering the fallback-derivation and explicit-override behavior - Add changeset (patch) documenting the fix for @runfusion/fusion Files changed: .changeset/fn-7747-derive-authstorage-from-engine.md | 7 ++ packages/dashboard/src/__tests__/server.test.ts | 119 +++++++++++++++++++++ packages/dashboard/src/server.ts | 28 ++++- packages/engine/src/project-engine.ts | 25 +++++ 4 files changed, 178 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7747 Fusion-Task-Lineage: f8e72b15-d084-4e8d-89db-47453d57b41b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
2ff8e2e13e |
FN-7743: detect and recover stalled in-progress executor tasks in overseer
Fix the planner overseer's executor-stage stall detection so hung in-progress tasks get unstuck instead of being reported as progressing forever. - Add configurable stuck-detection: the executor-stage overseer observation now emits signal: "stuck" once an in-progress task has been inactive past a new plannerOverseerExecutorStuckAfterMs threshold, feeding the existing decidePlannerRecovery -> bounded inject_guidance recovery path. - Register the new plannerOverseerExecutorStuckAfterMs setting in builtin-workflow-settings.ts and export it via core index.ts/index.gate.ts. - Preserve human-control withholds (user-paused / approval-blocked / autoMerge-off) taking precedence over stuck detection. - Add/extend tests covering planner-overseer, planner-recovery-controller, planner-recovery, and builtin-workflow-settings-triage. - Document the new setting in docs/architecture.md and docs/settings-reference.md. - Add changeset fn-7743-overseer-executor-stall.md (patch). Files changed: .changeset/fn-7743-overseer-executor-stall.md | 7 + docs/architecture.md | 31 +++++ docs/settings-reference.md | 3 +- .../builtin-workflow-settings-triage.test.ts | 22 ++++ .../core/src/__tests__/planner-recovery.test.ts | 12 ++ packages/core/src/builtin-workflow-settings.ts | 26 ++++ packages/core/src/index.gate.ts | 1 + packages/core/src/index.ts | 1 + .../engine/src/__tests__/planner-overseer.test.ts | 146 +++++++++++++++++++++ .../__tests__/planner-recovery-controller.test.ts | 52 +++++++- packages/engine/src/planner-overseer.ts | 88 ++++++++++++- packages/engine/src/project-engine.ts | 11 +- 12 files changed, 391 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-7743 Fusion-Task-Lineage: 791852a2-5b77-48de-955a-1b3916616dfa Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
67cc02750c |
FN-7692: fix misleading merger confirmation copy under active auto-merge
Correct the planner-oversight confirmation messaging so it no longer claims a hard block when the active auto-merge policy will actually advance the merge/pull-request stage unattended. - decidePlannerRecovery accepts an additive, messaging-only `autoMergeWillProceed` flag and picks accurate reason wording (advisory vs. genuine human-approval block vs. neutral/unknown) for merger/pull-request await_confirmation decisions - PlannerRecoveryController.tick threads `allowsAutoMergeProcessing(task, settings)` into decidePlannerRecovery as `autoMergeWillProceed` - project-engine's requestConfirmation steering comment prefix changed from "confirmation required" to neutral "merge checkpoint" so it doesn't contradict the now-accurate reason text - added regression tests in planner-recovery.test.ts and planner-overseer-intervention-wiring.test.ts - added changeset and doc note Files changed: .changeset/fn-7692-merger-confirmation-copy.md | 7 +++ docs/architecture.md | 10 +++- packages/core/src/__tests__/planner-recovery.test.ts | 66 ++++++++++++++++++++++ packages/core/src/planner-recovery.ts | 36 +++++++++++- packages/engine/src/__tests__/planner-overseer-intervention-wiring.test.ts | 37 ++++++++++++ packages/engine/src/planner-recovery-controller.ts | 14 ++++- packages/engine/src/project-engine.ts | 11 +++- 7 files changed, 176 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-7692 Fusion-Task-Lineage: 187684b8-1d24-425d-85d4-627587469908 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a8c018f7d4 |
fix: prevent false "OAuth token expired" push on startup
Start OAuthRefreshScheduler before the refresh-blind OAuthExpiryMonitor so a stale-but-refreshable access token is renewed before the monitor's first awaited check() reads `expires`. Previously the monitor fired a false "OAuth token expired" ntfy push on startup, moments before the refresher silently renewed the token. Ordering locked by an invocationCallOrder assertion in project-engine.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
60081fb1f4 |
FN-7610: route workspace-mode tasks around PR-merge auto-merge strategy
Fixes workspace-mode (workspaceWorktrees) tasks failing auto-merge under mergeStrategy=pull-request, where processPullRequestMergeTask threw "could not determine repository" because the workspace root is a container of independent git sub-repos, not itself a git repo. - Hoist an isWorkspaceTask check in ProjectEngine's merge dispatch (project-engine.ts) before the mergeStrategy branch, so workspace tasks always fall through to the existing direct/landWorkspaceTask path regardless of configured mergeStrategy. - Add processPullRequestMergeTask and syncGroupPrCallback defense-in-depth guards (task-lifecycle.ts) that throw the new named WorkspaceTaskMergeError if a workspace task ever reaches the PR-merge path. - Add engine tests covering multi-repo, single-repo, and zero-commit no-op workspace tasks under mergeStrategy=pull-request, plus a non-regression test for the legacy single-worktree PR path. - Add CLI tests asserting the new guards throw WorkspaceTaskMergeError. - Add a patch changeset describing the fix. Files changed: .changeset/fn-7610-workspace-pr-merge-routing.md | 7 ++ .../src/commands/__tests__/task-lifecycle.test.ts | 56 +++++++++ packages/cli/src/commands/task-lifecycle.ts | 33 ++++- .../engine/src/__tests__/project-engine.test.ts | 140 +++++++++++++++++++++ packages/engine/src/project-engine.ts | 18 ++- 5 files changed, 252 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-7610 Fusion-Task-Lineage: 31768b77-d9a9-4a79-a055-bbc6b228a1c4 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8d6c92ac2e | fix: preserve Hermes runtime chat session state | ||
|
|
ce9df297eb |
FN-7574: fix OAuth token expiry detection and add proactive auto-refresh
Unifies OAuth expiry detection so expired Claude subscription logins correctly show as disconnected with a re-login prompt, and adds a proactive engine-side scheduler that refreshes tokens before they expire. - Share expiry-detection logic between OAuthExpiryMonitor and the /api/auth/status route so both agree on when a token is expired. - Add engine-side oauth-refresh-scheduler that proactively refreshes OAuth tokens ahead of expiry, wired into project-engine (guarded by skipNotifier). - Extend auth-storage with the helpers needed for expiry checks/refresh. - Add tests covering routes-auth status detection, auth-storage expiry helpers, and the new refresh scheduler. - Document the new behavior in dashboard-guide.md and settings-reference.md. - Add changeset for the user-facing fix. Files changed: .../fn-7574-oauth-expiry-detection-refresh.md | 7 + docs/dashboard-guide.md | 4 + docs/settings-reference.md | 4 + .../dashboard/src/__tests__/routes-auth.test.ts | 76 +++++++++++ .../dashboard/src/routes/register-auth-routes.ts | 25 +++- packages/engine/src/__tests__/auth-storage.test.ts | 60 +++++++++ packages/engine/src/auth-storage.ts | 14 +- .../__tests__/oauth-refresh-scheduler.test.ts | 141 ++++++++++++++++++++ packages/engine/src/notification/index.ts | 3 + .../src/notification/oauth-refresh-scheduler.ts | 143 +++++++++++++++++++++ packages/engine/src/project-engine.ts | 14 +- 11 files changed, 488 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-7574 Fusion-Task-Lineage: 59996eac-c070-4992-9727-d066c6934b69 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |