## Problem
A transient error at merge time can flag an in-review card `failed` even
when its PR actually squash-merged on the remote (human merge,
merge-train, etc.). `recoverAlreadyMergedReviewTasks` runs the
already-merged **evidence detector** only against the **local** base
ref. If this process never fetched the merge, the owned commit is absent
locally → the detector returns `null` → `landed` is null → the card
never finalizes and **holds its file-scope lease forever**, wedging
every other task that touches the same files.
## Fix — fetch-then-prove
When a `failed` in-review candidate has a recorded PR
(`getPrimaryPrInfo`) and the local base yields no owned commit:
1. best-effort `git fetch origin <base>` (new `refreshRemoteBaseRef`
helper), then
2. re-run the **same** evidence detector against `origin/<base>`.
The detector's owned-commit proof and every foreign-ownership guard
inside it remain the **sole** finalize gate, so this only un-wedges a
genuinely-merged task — it never phantom-finalizes on unproven state.
**Safety:**
- Gated on a recorded PR — no PR ⇒ nothing could have merged remotely ⇒
no fetch.
- Fail-closed — a fetch error (offline / auth / no remote) is swallowed;
if `origin/<base>` can't be resolved the card is left untouched.
- No new dependency, no github client seam — direct git only.
## Tests
Two real-git tests in `self-healing-already-merged.real-git.test.ts`,
both verified to **fail without the prod change**:
- **fetch-then-prove positive:** a PR squash-merges on a bare remote
while the local base stays stale → recovery fetches, proves the owned
SHA against `origin/main`, and finalizes the card to `done`
(`mergeConfirmed: true`, worktree removed).
- **phantom-finalize guard:** remote base advances with a commit owned
by a *different* task → the fetch still runs, but the detector proves
nothing → the card is left `failed`/`in-review`, never healed.
Full self-heal suite: 594 passed. Engine typecheck clean.
🤖 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**
* Improved recovery for review tasks that appear already merged when the
local base branch is stale.
* The app now refreshes the remote base branch before re-checking merge
status, helping finalize tasks correctly and clean up completed
worktrees.
* Added coverage for cases where the remote base has moved forward with
either the merged commit or unrelated changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Use the project-scoped plugin runner for project chat routes so
runtime hints resolve plugin runtimes correctly.
- Expose the runtime plugin runner through
ProjectEngine/InProcessRuntime.
- Preserve Hermes runtime session message state and accept session_id
emitted on stderr without mixing stderr into the assistant body.
## Test Plan
- corepack pnpm --filter @fusion-plugin-examples/hermes-runtime test
- corepack pnpm --filter @fusion-plugin-examples/hermes-runtime
typecheck
- corepack pnpm --filter @fusion/engine typecheck
- corepack pnpm --filter @fusion/dashboard typecheck
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Improved project-scoped chat routing so plugins resolve more
consistently with the engine’s runtime.
* Hermes chat sessions now refresh cached plugin-runner usage when
needed and preserve conversation history (user and assistant messages).
* **Bug Fixes**
* More robust Hermes output parsing: session IDs are extracted reliably
even when emitted on stderr, and stderr is no longer treated as
assistant text.
* Hermes session state now retains an error message when the CLI fails,
improving troubleshooting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes TaskDetailModal so manual PR affordances stay visible based on the live global auto-merge setting rather than the per-task effective override, and repairs a pre-existing test regression from the FN-7510 oversight default change.
- isManualPrFlow now checks mergeStrategy === "pull-request" && !autoMergeEnabled (live global setting) instead of the per-task effective auto-merge override, fixing a regression from FN-7255 that stranded users without manual PR controls when a task's auto-merge override was true but global auto-merge was off.
- Pinned plannerOversightLevel: "off" on the Chat-first default-routing test fixture so the FN-7510 autonomous-oversight default doesn't add an extra Activity-view option and break the test's actual intent (asserting Chat-first tab routing).
- Added changeset documenting the fix.
Files changed:
.changeset/fn-7607-manual-pr-flow.md | 7 +++++++
packages/dashboard/app/components/TaskDetailModal.tsx | 14 +++++++++++++-
.../TaskDetailModal.attachments-and-tabs.test.tsx | 12 +++++++++++-
3 files changed, 31 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7607
Fusion-Task-Lineage: f0b077d4-792f-4e43-8e40-43d325920be5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Unifies the previously mobile-only Oversight overflow menu into a single, always-present dropdown that replaces the scattered desktop oversight buttons and the separate mobile affordance.
- Replace discrete desktop oversight action buttons in TaskDetailModal's footer with one universal "Oversight actions" dropdown trigger, reusing the menu across desktop and mobile breakpoints.
- Simplify TaskDetailModal.tsx footer rendering logic, removing now-redundant responsive branching for oversight controls.
- Update TaskDetailModal.css to drop the old mobile-only oversight-overflow styles and support the unified dropdown across breakpoints.
- Update definition-actions, oversight-controls, oversight-mobile, rendering, and responsive-and-dependencies tests to assert the single dropdown behavior and disambiguate the exact "Actions" button query from the new "Oversight actions" aria-label.
- Refresh docs/dashboard-guide.md to describe the unified oversight dropdown UX.
Files changed:
docs/dashboard-guide.md | 14 +-
packages/dashboard/app/components/TaskDetailModal.css | 89 +++++------
packages/dashboard/app/components/TaskDetailModal.tsx | 177 +++------------------
packages/dashboard/app/components/__tests__/TaskDetailModal.definition-actions.test.tsx | 70 ++++----
packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-controls.test.tsx | 130 ++++++++++-----
packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-mobile.test.tsx | 42 +++--
packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx | 27 ++--
packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 57 ++++---
8 files changed, 278 insertions(+), 328 deletions(-)
Fusion-Task-Id: FN-7604
Fusion-Task-Lineage: afbb7573-d654-48db-a9ac-aecbd8e22e46
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Give the TaskDetailModal priority chip distinct tinted borders/backgrounds per level so low/high/urgent/normal are visually distinguishable at a glance.
- Add per-level border-color and stronger background overrides for .detail-priority-chip.card-priority-badge--{low,high,urgent}, using the matching semantic color token (info/warning/error) with higher specificity than the shared base rule.
- Leave the FN-7585 shared base chip rule and FN-7597 neutral 'normal' treatment untouched; scope changes strictly to .detail-priority-chip so read-only TaskCard badge tints are unaffected.
- Add a regression test asserting each level has distinct, non-var(--border) border-colors and backgrounds, mutually distinct across levels, while the read-only TaskCard badge selectors remain unchanged.
Files changed:
.../dashboard/app/components/TaskDetailModal.css | 33 ++++++++++
...etailModal.responsive-and-dependencies.test.tsx | 71 ++++++++++++++++++++++
2 files changed, 104 insertions(+)
Fusion-Task-Id: FN-7601
Fusion-Task-Lineage: 1e2fc574-835c-43b0-8689-02884ad5c2d6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Moving a card out of a non-legacy workflow column — e.g. Coding (Ideas)
"ideas" → "todo" — was rejected with "Invalid transition: 'ideas' → 'todo'.
Valid targets: none".
Workflow columns graduated to always-on but moveTaskInternal's compat-flag
legacy branch (the default path, since no experimental flag is emitted)
validated every move against the legacy VALID_TRANSITIONS table, which is
keyed only by the built-in column ids. Default-workflow moves survived by
coincidence (its ids ARE the legacy ids); a task in a custom column had no
key so every move was rejected.
The legacy branch now resolves a non-legacy source column's targets from the
task's own workflow adjacency (resolveAllowedColumns), while keeping the
legacy bare-Error contract intact for legacy columns (transition-parity /
characterization suites unchanged). Adds a regression test covering the
ideas -> todo -> in-progress -> in-review chain and non-adjacent rejection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A present, unexpired Anthropic subscription OAuth token that lacks an
inference scope (e.g. a profile-only grant) authenticates identity but
403s on every model call. /auth/status previously validated only token
presence + expiry, so it reported such a token as connected while all
inference failed. It now treats an inference-incapable Anthropic OAuth
token as not-connected (authenticated:false, expired:true so the
re-login banner fires) with a scope-specific loginError. Gated to
Anthropic providers only; tokens with no recorded scopes are treated as
usable to avoid false negatives on fresh logins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes overlapping Record and Clear buttons on the Keyboard Shortcuts settings rows by replacing the icon-only button class with a text button class and locking layout with flex-shrink.
- Swap ShortcutCaptureInput Record/Clear buttons off the icon-only `btn-icon` class (which forced line-height:0 and a 36px mobile square, clipping labels) onto a text-button class
- Add `.shortcut-capture` row CSS with `flex-shrink:0` on controls so the input and buttons never overlap and stack cleanly on mobile
- Add regression tests covering the Keyboard Shortcuts section layout
- Add changeset documenting the fix
Files changed:
.changeset/fn-7602-shortcut-row-layout.md | 7 ++
.../dashboard/app/components/SettingsModal.css | 17 ++++
.../settings/sections/ShortcutCaptureInput.tsx | 14 +++-
.../__tests__/KeyboardShortcutsSection.test.tsx | 95 ++++++++++++++++++++++
4 files changed, 131 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7602
Fusion-Task-Lineage: 50cf6975-f0fb-42dd-87b0-50578977a0f4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The Anthropic OAuth refresh request sent `scope: user:profile`, which under
RFC 6749 §6 re-issues the access token with exactly that scope — stripping
`user:inference` and 403-ing every model call while the account still read
as "logged in via OAuth". Stop sending `scope` on refresh (Anthropic then
preserves the originally-granted scopes, matching pi-ai), and widen
ANTHROPIC_DEFAULT_SCOPES to mirror pi-ai's full granted Claude Code scope
set so any fallback describes a usable token.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Attach the transient plannerOverseerState snapshot to the single-task detail route so the Nudge control reflects live overseer observation instead of always showing the periodic-observation message.
- GET /api/tasks/:id now best-effort attaches plannerOverseerState (mirrors the list route), never throwing on enrichment failure.
- TaskDetailModal reads overseerSnapshot from workingTask (merged full-detail object) instead of the raw task prop, so detail refetches via fetchTaskDetail (dependency chips, Documents view, logs, post-open refetch) no longer drop the snapshot.
- Added regression tests for the detail-route enrichment and the modal's Nudge-availability behavior.
- Added a patch changeset documenting the fix.
Files changed:
.changeset/fn-7600-oversight-nudge-detail-snapshot.md | 7 ++
packages/dashboard/app/components/TaskDetailModal.tsx | 14 ++-
.../TaskDetailModal.oversight-controls.test.tsx | 131 +++++++++++++++++++++
.../__tests__/tasks-planner-overseer-state.test.ts | 95 +++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 25 +++-
5 files changed, 269 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7600
Fusion-Task-Lineage: 500614d0-091a-461c-8e7b-329a7b791502
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Aligns the task-detail Priority dropdown's size, border, and typography with the Oversight dropdown so both controls read as one consistent style.
- Give the untinted `normal` priority level a neutral, token-based chip background (scoped to `.detail-priority-chip.card-priority-badge--normal`) instead of an empty bordered shell, matching the Oversight `--off` chip treatment.
- Remove the Priority-only forced uppercase text-transform on the select/option so it relies on the ancestor label's uppercase transform like the Oversight select does.
- Add regression coverage asserting shared box-size/border tokens across the Priority chip, Oversight chip, and mobile Oversight overflow trigger, no duplicated text-transform overrides, preserved low/high/urgent semantic tints, and unaffected --saving state.
- Add a patch changeset documenting the fix.
Files changed:
.changeset/fn-7597-priority-dropdown-matches-oversight.md | 7 +++
packages/dashboard/app/components/TaskDetailModal.css | 27 ++++++++++--
packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 51 ++++++++++++++++++++++
3 files changed, 82 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7597
Fusion-Task-Lineage: d703e59a-35d8-4788-9ad2-1462d6f3c588
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Docs-only change adding front-door discovery for the already-shipped planner-oversight feature (FN-7508 → FN-7583), which previously had no entry point outside internal reference docs.
- Add a README.md feature table row and a new "Planner oversight" section describing oversight levels (off/observe/steer/autonomous) and the always-on human-confirmation gate for merge/PR and destructive actions, linking to Settings Reference and Dashboard Guide
- Add a README.md capabilities bullet cross-linking the new section
- Add a docs/README.md hub row pointing to Settings Reference, Dashboard Guide, and Architecture for planner oversight, and extend the 'power user' reading path
- Add a one-line pointer in docs/getting-started.md workflow section noting per-task/workflow oversight controls
Files changed:
README.md | 11 +++++++++++
docs/README.md | 9 ++++++---
docs/getting-started.md | 3 +++
3 files changed, 20 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7598
Fusion-Task-Lineage: 8141b44c-f007-45c0-a057-f4eeb34ae8d4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Tasks added to a workflow whose intake column differs from the default
(e.g. Coding (Ideas) -> "ideas") disappeared from the board until a manual
reload. The board resolves a card's lane from the board-workflows
taskWorkflowIds map, which only refetches on mount/focus/workflow-CRUD SSE
-- never on task creation. A freshly created card was absent from that map,
fell back to the default workflow (no "ideas" column), and was dropped from
every lane.
- Board.tsx: force one board-workflows refetch (deferred a tick,
signature-guarded) whenever a rendered task is missing from taskWorkflowIds,
so its real workflow + intake column resolve for any create surface.
- Board.tsx: re-home a selected-workflow task whose column the workflow no
longer declares into the intake lane instead of a phantom bucket.
- useBoardWorkflows.ts: widen refreshBoardWorkflows type to accept forceFresh.
- Add regression tests for tasks arriving via the tasks prop (SSE / non-board
create surfaces) and the orphan-column safety net.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reorders task-definition prompt templates so the Before -> After Transformation section appears before other sections, making the expected change visible first.
- Move the Before -> After Transformation section ahead of other sections in agent-prompts.ts task-definition templates
- Update docs/task-management.md to reflect the new section order
- Add/extend tests in agent-prompts.test.ts and triage.test.ts covering the new ordering
- Add changeset fn-7593-before-after-top.md documenting the change
Files changed:
.changeset/fn-7593-before-after-top.md | 7 +++++++
docs/task-management.md | 2 +-
packages/core/src/__tests__/agent-prompts.test.ts | 20 ++++++++++++++++++++
packages/core/src/agent-prompts.ts | 20 +++++++++++++-------
packages/engine/src/__tests__/triage.test.ts | 17 +++++++++++++++++
5 files changed, 58 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7593
Fusion-Task-Lineage: d0d5eb4d-2fe0-456c-b061-5c078b78911b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Swaps the planner-overseer status badge from an uppercase text pill to a compact icon glyph, keeping accessibility text on aria-label/title.
- Render a small lucide-react Eye icon instead of the state-label text inside the overseer badge
- Keep the readable state name on aria-label and the composed tooltip on title for accessibility
- Add per-state coloring (watching/steering/recovering/awaiting-confirmation) keyed off the data-planner-overseer-state attribute in TaskCard.css, sized tightly around the icon
- Update TaskCard tests to assert the icon renders and the accessible name moved to aria-label instead of textContent
Files changed:
packages/dashboard/app/components/TaskCard.css | 40 ++++++++++++++++++++++
packages/dashboard/app/components/TaskCard.tsx | 14 ++++++--
.../app/components/__tests__/TaskCard.test.tsx | 25 +++++++++++---
3 files changed, 72 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7592
Fusion-Task-Lineage: d7ca93a6-9236-4fa0-a829-80f5b99dbd5f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a presentation-only enter animation for mobile task-detail surfaces (modal and board main-panel), layered on top of the existing FN-7583/FN-7586 dismissal routing, without altering close/back timing.
- Gate a new `.task-detail-modal--mobile-transition` class in TaskDetailModal.tsx via a local resize listener at the 768px breakpoint, mirroring the existing OVERSIGHT_MENU_MOBILE_BREAKPOINT pattern
- Add matching `.task-detail-main-panel--mobile-transition` modifier in MainContent.tsx gated by the existing isMobile prop
- Add slide/fade keyframe animations in TaskDetailModal.css and styles.css, both honoring prefers-reduced-motion
- Add regression tests covering the modal and board-panel mobile transition behavior
- Document the Capacitor WebView limitation preventing a true interactive predictive-back in packages/mobile/README.md
Files changed:
.../dashboard/app/components/TaskDetailModal.css | 33 ++
.../dashboard/app/components/TaskDetailModal.tsx | 31 +-
...skDetail.mobile-transition.board-panel.test.tsx | 333 +++++++++++++++++++++
.../TaskDetail.mobile-transition.test.tsx | 156 ++++++++++
.../app/components/dashboard/MainContent.tsx | 10 +-
packages/dashboard/app/styles.css | 35 +++
packages/mobile/README.md | 30 ++
7 files changed, 626 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7587
Fusion-Task-Lineage: cc5f08df-4aaf-447d-9c30-237b32191d3f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Enables the WKWebView interactive-pop (edge-swipe-back) gesture on iOS, matching the Android predictive-back parity already in place, by patching the generated AppDelegate.swift during cap sync.
- Add packages/mobile/scripts/patch-ios-webview.ts: patches AppDelegate.swift to cast the root view controller to CAPBridgeViewController and set webView?.allowsBackForwardNavigationGestures = true before the didFinishLaunchingWithOptions return, is idempotent, and no-ops safely when no ios/ project exists yet
- Wire capacitor:sync:after to run both patch-android-manifest.ts and patch-ios-webview.ts so cap sync keeps both native back-gesture opt-ins in sync; add patch:ios-webview script
- Add unit tests covering patch, idempotency, already-patched, missing-project, and source-preservation cases for the new iOS webview patch, alongside the existing Android manifest patch tests
- Add TaskDetail.swipe-back.test.tsx coverage proving the shared popstate-driven nav-history dismissal stack (no iOS-specific native-back emitter needed) already satisfies the gesture's dismissal contract
- Add mobile-scripts.test.ts dashboard coverage and update MOBILE.md / packages/mobile/README.md documenting the new iOS gesture opt-in
Files changed:
MOBILE.md | 12 ++
.../dashboard/app/__tests__/mobile-scripts.test.ts | 31 +++++
.../__tests__/TaskDetail.swipe-back.test.tsx | 131 +++++++++++++++++++++
packages/mobile/README.md | 36 ++++++
packages/mobile/package.json | 3 +-
packages/mobile/scripts/patch-ios-webview.ts | 119 +++++++++++++++++++
packages/mobile/src/__tests__/native-shell.test.ts | 122 +++++++++++++++++++
7 files changed, 453 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7586
Fusion-Task-Lineage: 248092a1-de1b-49b6-94ef-70675a7b93a1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the Android predictive back-gesture (edge swipe) not returning from a task detail view to the board, even though the hardware/legacy Back button worked correctly.
- Patch the generated Android manifest post-sync to set android:enableOnBackInvokedCallback="true" on the <application> tag, opting into AndroidX's OnBackPressedDispatcher for predictive-back gesture completion (mitigating ionic-team/capacitor-plugins#2418 via the disableBackButtonHandler toggle shipped in @capacitor/app@7.1.0).
- Wire the patch script into Capacitor's capacitor:sync:after npm-script hook so it runs automatically after every cap sync (and therefore after cap run android / build:mobile).
- Ensure both the back gesture and the hardware Back button funnel through the same AndroidBackButtonManager backButton listener, dispatching the shared fusion:native-back event consumed by the dashboard's nav-history stack.
- Add regression coverage: a unit test for the manifest patch script's idempotent opt-in behavior, and a dashboard test asserting the task detail view returns to the board on fusion:native-back.
- Document the Android manifest patch rationale and the unchanged dashboard-side invariant in packages/mobile/README.md.
Files changed:
.../__tests__/TaskDetail.swipe-back.test.tsx | 13 +++
packages/mobile/README.md | 31 +++++++
packages/mobile/capacitor.config.ts | 13 +++
packages/mobile/package.json | 2 +
packages/mobile/scripts/patch-android-manifest.ts | 90 ++++++++++++++++++
packages/mobile/src/__tests__/native-shell.test.ts | 102 +++++++++++++++++++++
6 files changed, 251 insertions(+)
Fusion-Task-Id: FN-7583
Fusion-Task-Lineage: ad0cb02c-6e49-448a-8c93-607cd5ae657b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Unifies the visual styling of the task-detail modal's Priority, Execution-mode, and Oversight quick-control chips so the cluster reads as one consistent control group.
- Add a shared --detail-control-border-radius token (resolving to --radius-md) alongside the existing --detail-priority-control-min-height token
- Override .detail-priority-chip's inherited transparent border with a visible --btn-border-width/--border pairing so the "normal" priority level renders as a bordered box instead of borderless text
- Pin the same border-width/color/radius trio on .detail-execution-mode-toggle so a future change to .btn defaults can't desync the cluster
- Apply the same trio to .detail-oversight-chip, overriding .card-oversight-badge's transparent border (covers the neutral "off" tint too)
- Apply the same trio to the mobile .detail-oversight-menu-trigger swap-in so the mobile overflow-trigger variant matches the desktop chip
- Add a changeset (patch) documenting the fix for @runfusion/fusion
- Extend TaskDetailModal.responsive-and-dependencies.test.tsx coverage for the unified styling
Files changed:
.changeset/FN-7585-unify-task-detail-quick-control-styling.md | 7 +++
packages/dashboard/app/components/TaskDetailModal.css | 55 ++++++++++++++++++++++
packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 27 +++++++++++
3 files changed, 89 insertions(+)
Fusion-Task-Id: FN-7585
Fusion-Task-Lineage: 0cca8c82-c9fb-4410-af48-51861a743f96
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
A transient error at merge time can flag an in-review card `failed` even
when its PR actually squash-merged on the remote. `recoverAlreadyMergedReviewTasks`
only ran the already-merged evidence detector against the LOCAL base ref, so
when this process never fetched the merge, the owned commit was absent locally,
the detector returned null, and the card held its file-scope lease forever.
Fetch-then-prove: when a failed candidate has a recorded PR and the local base
yields no owned commit, best-effort `git fetch origin <base>` and re-run the
SAME evidence detector against `origin/<base>`. The owned-commit proof and every
foreign-ownership guard inside the detector remain the sole finalize gate, so
this only un-wedges a genuinely-merged task — it never phantom-finalizes on
unproven state. Gated on a recorded PR (no PR ⇒ nothing merged remotely ⇒ no
fetch); fail-closed on fetch error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Plan Review pre-merge gate could loop a task through triage↔plan-review
indefinitely (FN-7525 ran 13+ replans overnight with no operator visibility),
and its reviewer frequently produced "no PROMPT.md found / data lives in a DB"
non-verdicts that fed the loop.
Root cause of the non-verdicts: the reviewer runs readonly with cwd set to the
task worktree, but the spec lives at project-root .fusion/tasks/<id>/PROMPT.md —
outside the worktree — so telling it to "Read PROMPT.md" had it search the wrong
tree and give up. Four fixes:
1. Inject the PROMPT.md content (via readTaskArtifact, store-backed) directly
into the Plan Review reviewer prompt so the verdict never depends on the
agent locating the file.
2. Self-retry a malformed reviewer response once on the primary model when no
fallback model is configured, so a single fumbled response gets a second
chance instead of feeding the replan loop.
3. A malformed (advisory_failure, no parsed verdict) plan-review result can
never trigger a triage replan — it is an infra failure, not a plan defect.
4. Cap the unbounded plan-review replan default at 15 attempts; past the cap it
emits a loud halting log entry and leaves the task for a human instead of
looping forever. Explicit numeric operator budgets are unchanged.
Tests: cap halts at 15 / still replans at 14 / malformed never replans. Existing
Plan Review replan and malformed-verdict-gate tests still pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reword the disabled-Nudge tooltip/helper text so it no longer reads as an overseer fault, and split it into two distinct reasons.
- Add taskDetail.oversight.nudgeSuppressedTitle for the human-control-suppressed case (user-paused, done/archived, autoMerge:false human-review terminal), naming manual control as the cause
- Reword taskDetail.oversight.nudgeDisabledTitle to a reassuring periodic-poll framing for the no-observation-yet case instead of implying the overseer is idle
- Compute the shared nudgeDisabledReason once and reuse it at all four render sites (mobile menu + desktop inline title/helper) so the two copies can't drift
- Add a changeset (patch) documenting the operator-facing copy fix
- Extend TaskDetailModal.oversight-controls tests and test-helpers to cover the new suppressed-vs-disabled copy branching
Files changed:
.changeset/fn-7582-oversight-guideline-copy.md | 7 ++
.../dashboard/app/components/TaskDetailModal.tsx | 37 +++++++++--
.../TaskDetailModal.oversight-controls.test.tsx | 76 +++++++++++++++++++++-
.../__tests__/TaskDetailModal.test-helpers.ts | 7 ++
4 files changed, 121 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7582
Fusion-Task-Lineage: bf2ceab4-2fb0-4686-a9e1-9e015502a521
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a discoverable built-in Brainstorming workflow composing the ask-user + exit-gate reach-out loop ahead of the standard coding plan/execute/review/merge spine, plus a WorkflowNodeEditor fix so clearing the ask-user question textarea deletes the config key instead of persisting an empty string.
- Add packages/core/src/builtin-brainstorming-workflow-ir.ts registering builtin:brainstorming (non-default, default-enabled): ask-user -> refine prompt -> exit-gate-on-approval ahead of the unmodified Coding plan/execute/review/merge spine
- Wire the new builtin into packages/core/src/builtin-workflows.ts and extend the builtin-workflows parity test suite
- Add builtin-brainstorming-workflow-ir.test.ts covering the new workflow's IR shape and validation
- Fix WorkflowNodeEditor.tsx ask-user question textarea onChange to delete the config.question key when cleared to empty (validateAskUserAndExitGateNodes rejects present-but-empty question; only an absent key falls back to the engine default)
- Update docs/workflow-steps.md to document builtin:brainstorming as a selectable built-in composition
- Add .changeset/fn-7584-brainstorming-builtin.md (minor, feature)
Files changed:
.changeset/fn-7584-brainstorming-builtin.md | 7 ++
docs/workflow-steps.md | 2 +-
.../builtin-brainstorming-workflow-ir.test.ts | 79 ++++++++++++++++
.../core/src/__tests__/builtin-workflows.test.ts | 66 +++++++++++++
.../core/src/builtin-brainstorming-workflow-ir.ts | 103 +++++++++++++++++++++
packages/core/src/builtin-workflows.ts | 46 +++++++++
.../app/components/WorkflowNodeEditor.tsx | 18 +++-
7 files changed, 319 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7584
Fusion-Task-Lineage: 2c0258c2-9a35-403a-8688-ee49393a7231
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Stop the Interventions Activity segment from reserving padding for an overlay toggle button it never renders, which was insetting the FN-7519 timeline from the right edge (worse on mobile).
- Add `.detail-activity--interventions` CSS modifier that zeroes `padding-inline-end` for the Interventions segment, both base and mobile breakpoints, while leaving Live/Feed/Raw's reserved padding untouched
- Apply the new modifier class to the Interventions `.detail-activity` container in TaskDetailModal.tsx
- Add a regression test asserting the Interventions container carries `detail-activity--interventions` while the Feed container (which still renders the overlay toggle) does not
Files changed:
.../dashboard/app/components/TaskDetailModal.css | 18 ++++++++++
.../dashboard/app/components/TaskDetailModal.tsx | 2 +-
.../TaskDetailModal.oversight-controls.test.tsx | 38 ++++++++++++++++++++++
3 files changed, 57 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7581
Fusion-Task-Lineage: d5c55773-7df3-4eb4-aa70-01f6e0a622c6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add workflow nodes for mid-flow user reach-out and early exit from a workflow run.
- Add `ask-user` IR node kind that reuses the await-input park/resume mechanism and surfaces the question in the task chat for brainstorming/clarification.
- Add `exit-gate` IR node kind that terminates the workflow early, with an optional condition.
- Wire both node kinds through the engine executor and workflow-node-handlers, including a new exit-gate-runner.
- Update the WorkflowNodeEditor palette, node summaries, and node help text for the two new node types.
- Extend workflow-flow-mapping to support the new node kinds.
- Keep `prompt`+`awaitInput` as a back-compat alias.
- Add core/engine/dashboard tests covering the new node kinds.
- Document the new nodes in docs/workflow-steps.md.
- Add changeset for the new minor feature.
Files changed:
.changeset/fn-7579-ask-user-exit-gate-nodes.md | 7 +
docs/workflow-steps.md | 28 ++++
packages/core/src/__tests__/workflow-ir.test.ts | 120 ++++++++++++++
packages/core/src/workflow-ir-types.ts | 12 +-
packages/core/src/workflow-ir.ts | 47 ++++++
.../app/components/WorkflowNodeEditor.tsx | 181 ++++++++++++++++++++-
.../app/components/__tests__/node-summary.test.ts | 43 +++++
.../__tests__/workflow-flow-mapping.test.ts | 49 ++++++
.../app/components/nodes/WorkflowNodeTypes.tsx | 14 +-
.../dashboard/app/components/nodes/node-help.ts | 24 +++
.../dashboard/app/components/nodes/node-summary.ts | 28 ++++
.../app/components/workflow-flow-mapping.ts | 4 +
.../workflow-graph-executor-handlers.test.ts | 115 +++++++++++++
.../src/__tests__/workflow-node-handlers.test.ts | 66 ++++++++
packages/engine/src/executor.ts | 23 ++-
packages/engine/src/workflow-node-handlers.ts | 18 +-
.../src/workflow-node-runners/exit-gate-runner.ts | 81 +++++++++
17 files changed, 849 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7579
Fusion-Task-Lineage: 9a89ff49-200d-4a6c-b97c-15d219349ee5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Extends FN-7554's single-repo PR revert path to workspace (multi-repo) tasks: when autoMerge is disabled, the revert route now opens one dedicated fusion/revert-<id> PR per sub-repo instead of refusing workspace tasks outright.
- Add prepareWorkspaceRevertPrBranches (packages/engine/src/task-revert.ts): classifies every sub-repo first and only prepares a per-sub-repo fusion/revert-<id> branch when all sub-repos are clean/already-reverted (all-or-nothing at branch-prep phase); never force-writes any sub-repo integration branch.
- Export the new helper from packages/engine/src/index.ts.
- Extend POST /api/tasks/:id/revert (register-task-workflow-routes.ts) to resolve owner/repo and check the GitHub rate limiter for every sub-repo before pushing/creating any PR, opening one PR per sub-repo and returning an additive { mode: "pr", clean: true, workspace: { repos: [...] } } result; degrades the whole task to needsHuman if GitHub is unconfigured or any sub-repo is rate-limited, rather than opening a partial subset of PRs.
- Leave existing { mode: "git" | "ai" | "pr" } shapes, the autoMerge:true workspace path, and FN-7554's single-repo PR path unchanged.
- Add engine real-git coverage (task-revert-workspace-pr.real-git.test.ts) and extend dashboard route tests (task-revert-route.test.ts) for the new workspace PR path.
- Add changeset (.changeset/fn-7577-workspace-pr-revert.md, minor) and update docs/task-management.md.
Files changed:
.changeset/fn-7577-workspace-pr-revert.md | 7 +
docs/task-management.md | 3 +-
.../src/__tests__/task-revert-route.test.ts | 313 ++++++++++++++++-
.../src/routes/register-task-workflow-routes.ts | 181 +++++++++-
.../task-revert-workspace-pr.real-git.test.ts | 371 +++++++++++++++++++++
packages/engine/src/index.ts | 4 +
packages/engine/src/task-revert.ts | 295 ++++++++++++++++
7 files changed, 1166 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7577
Fusion-Task-Lineage: bedbfab7-5804-485f-9b40-64531edfc64a
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
decidePlannerRecovery fell through to inject_guidance for any non-failed
executor/workflow-gate signal, including the healthy `progressing` signal.
Under autonomous oversight this dispatched steering into the live agent of
every healthy task — flipping the card badge to "recovering", burning a
bounded-attempt slot, and consuming AI usage for no reason.
- Only problem signals (`stuck`/`blocked`, plus the existing `failed` path)
now trigger autonomous steering; healthy (`progressing`/`complete`) and
human-wait (`awaiting-human`) signals return `none`.
- PlannerRecoveryController.tick clears stale attempt/last-action records for
a (taskId, stage) once its signal is healthy, so a recovered task drops
from "recovering" back to "watching" and a later problem gets a fresh budget.
- PlannerOverseerMonitor dedupes the activity-feed heartbeat: an unchanged
(stage, signal, reason) observation logs once per change, not every tick.
Invariant tests added across all signals for both fall-through stages.
Fusion-Task-Id: FN-7577
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends GitHubIssueCommentService so task-close comments on issues in the runfusion/fusion repo itself append both a current-version and target-next-minor-release line, while comments on all other linked repos stay byte-for-byte unchanged.
- Add isFusionSelfRepo() and computeNextMinorVersion() helpers to github-issue-comment.ts
- Append "Current version: v{current}" and "Target release: v{next-minor}" lines only when the linked source issue's repo is runfusion/fusion (case-insensitive)
- Fall back silently (no version lines) when the resolved version is unparseable or the unresolved 0.0.0 sentinel
- Add changeset (minor) documenting the new behavior
- Update docs/settings-reference.md and docs/gitlab-parity-inventory.md
- Expand github-issue-comment.test.ts coverage for self-repo vs other-repo behavior and version edge cases
Files changed:
.changeset/fn-7575-release-version-comment.md | 7 +
docs/gitlab-parity-inventory.md | 2 +-
docs/settings-reference.md | 2 +-
packages/dashboard/src/__tests__/github-issue-comment.test.ts | 142 ++++++++++++++++++++-
packages/dashboard/src/github-issue-comment.ts | 69 +++++++++-
5 files changed, 212 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-7575
Fusion-Task-Lineage: b7cf7e6f-8d96-4442-8595-5d54ea911481
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
GitHub tracking-issue creation reused old/closed issues via dedup:
- searched state:"all", so a resolved issue from an unrelated task could be reused
- accepted keyword-only matches (generic shared identifiers) with no file overlap
Dedup now only reuses OPEN issues and requires a File-Scope path overlap;
without an open path-overlapping issue a fresh tracking issue is always created.
Adds two regression tests asserting closed and keyword-only matches are not reused.
Fusion-Task-Id: FN-7579
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a PR-based revert path for done/archived tasks in autoMerge:false projects instead of refusing outright.
- New engine export `prepareRevertPrBranch` (packages/engine/src/task-revert.ts) prepares a dedicated `fusion/revert-<id>` branch off the base branch's HEAD and applies the revert commit(s) there, never mutating the base branch itself.
- `POST /api/tasks/:id/revert` route gains an additive `{ mode: "pr", clean: true, prUrl, prNumber, revertBranch, existingPr? }` result for clean single-repo reverts under autoMerge:false, reusing GitHubClient.createPr, findPrForBranch idempotency, and the manual:true PR handoff.
- Existing `{ mode: "git" | "ai", ... }` result shapes and the autoMerge:true path are unchanged.
- Workspace (multi-repo) tasks are explicitly refused for PR-based revert (out of scope; single PR cannot represent a multi-repo revert).
- Adds real-git integration tests for the new branch-prep/apply/commit flow and expands the dashboard route test coverage.
- Adds a changeset for @runfusion/fusion (minor) and a small task-management doc update.
Files changed:
.changeset/fn-7554-pr-based-revert.md | 7 +
docs/task-management.md | 2 +-
.../src/__tests__/task-revert-route.test.ts | 196 +++++++++++++++++++-
.../src/routes/register-task-workflow-routes.ts | 182 ++++++++++++++++++
.../src/__tests__/task-revert-pr.real-git.test.ts | 206 +++++++++++++++++++++
packages/engine/src/index.ts | 3 +
packages/engine/src/task-revert.ts | 154 +++++++++++++++
7 files changed, 747 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7554
Fusion-Task-Lineage: 9b1bfd82-2428-4cf7-9b36-77afe3517a14
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
isPhantomExecutorBinding's liveness gate (heartbeat/checkout/runAudit) is
structurally blind to ephemeral executor agents, leaving only the
age>graceMs*3 (~30 min) threshold, so any ephemeral-executor task running
longer than ~30 min was reclaimed to `todo` mid-flight and its worktree
destroyed. Add the in-process live-session veto (activeSessionRegistry path /
executingTaskLock / isTaskActive), mirroring the isWorkspaceTaskLive/sessionDead
predicate, and honor clearPhantomExecutorBinding's live-session refusal in
reclaimSelfOwnedBranchConflicts. Legitimate FN-6736 leaked-binding recovery is
preserved (empty registry / no lock / inactive task still reads as phantom).
Fusion-Task-Id: FN-7566
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Subscription OAuth is aliased across the legacy `anthropic` id (where login
persists the credential) and `anthropic-subscription` (where the settings card
and status read are keyed). After an in-session logout, re-login wrote only
`anthropic` and never cleared the in-memory `anthropic-subscription` logged-out
flag, so the card reported "Login did not complete" despite a valid stored
credential until the process restarted.
auth-storage's proxy now clears the logged-out suppression on both aliases when
either is re-authenticated (new `login` trap + hardened `set` trap via
clearReauthenticatedLogoutState); raw api_key writes stay scoped to their own
card. Also surface previously-swallowed background OAuth login failures on
GET /auth/status (`loginError`) plus server logs and a settings toast, so real
paste-callback failures are diagnosable instead of a generic error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The release-authorization classifier substring-matched release signals
(notably `scripts/release.mjs`) even inside disclaimer clauses that
explicitly say the task performs NO release/publish. AI-authored specs
routinely append such disclaimers, so revert/undo/UI tasks (FN-7525,
FN-7554, FN-7556) were parked in awaiting-release-authorization with no
in-band exit — their non-user sources (agent_heartbeat/api) make the
authorization marker inert.
classifyReleaseTask now strips negated release-disclaimer clauses before
signal matching. Genuine "run pnpm release"/"publish @runfusion/fusion"
intent lives in a non-negated clause and still trips the gate. Tests
cover all three real repro shapes plus every documented signal in both
its negated (not release-class) and actionable (still release-class) form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reads for the anthropic-subscription provider now delegate to the real engine authStorage so expired OAuth tokens are refreshed instead of silently failing.
- mergeAuthStorageReads getApiKey("anthropic-subscription") now calls target.getApiKey(providerId) directly, triggering the engine's refresh-token HTTP round trip, instead of a local static Date.now() >= credential.expires check
- Falls back to the read-only fallback storages' local resolution only when the primary engine call yields no key
- Added regression tests exercising the wrapper directly against the engine delegation and fallback behavior
- Added a patch changeset documenting the fix for @runfusion/fusion
Files changed:
.changeset/fn-7576-cli-wrapper-subscription-getapikey-delegation.md | 7 +
packages/cli/src/commands/__tests__/provider-auth.test.ts | 148 +++++++++++++++++++++
packages/cli/src/commands/provider-auth.ts | 12 +-
3 files changed, 166 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7576
Fusion-Task-Lineage: 31e495ec-8487-468b-9b80-36e8d0f53806
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>