When a task adds/removes a dependency without regenerating the lockfile,
the inferred `pnpm install --frozen-lockfile` (and yarn/bun equivalents)
in the AI-merge clean room fails with ERR_PNPM_OUTDATED_LOCKFILE, dead-
ending the merge. Detect that specific frozen-refusal and retry once
non-frozen (pnpm gets explicit --no-frozen-lockfile to override any CI
default), regenerating the lockfile and recomputing the install marker.
A configured worktreeInitCommand keeps its authoritative frozen intent
and still hard-fails. Surfaced via the merge:ai-deps-sync run-audit event.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prevent duplicate Refine Further activations from interrupting an active completed-summary refinement stream.
- Add a synchronous single-flight guard and loading state for summary refinement.
- Preserve the connected planning stream when a same-turn generation-in-progress response is reported.
- Cover rapid desktop/mobile activation and active-stream conflict behavior with regression tests.
- Add a patch changeset for the published Fusion package.
Files changed:
.../fn-7431-planning-refine-single-flight.md | 7 +
.../dashboard/app/components/PlanningModeModal.tsx | 41 +++++-
.../PlanningModeModal.planning-flow.test.tsx | 156 +++++++++++++++++++++
3 files changed, 201 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7431
Fusion-Task-Lineage: fc876022-da86-409e-818d-8c4798b49aa5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Keep dashboard project selection in the URL so refreshing or sharing the page retains the active project.
- Hydrate current project state from the existing ?project= query parameter before falling back to cached or global defaults.
- Update project selection, setup completion, overview, and unregister flows to preserve or clear the project URL parameter without dropping other URL state.
- Cover URL-driven project persistence and query/hash preservation with hook tests.
- Document the refresh-safe project URL behavior and add a patch changeset.
Files changed:
.changeset/fn-7416-project-url-persistence.md | 7 +++
docs/dashboard-guide.md | 2 +
packages/dashboard/app/App.tsx | 2 +-
.../app/hooks/__tests__/useCurrentProject.test.ts | 64 ++++++++++++++++++++++
.../app/hooks/__tests__/useProjectActions.test.ts | 40 +++++++++++++-
packages/dashboard/app/hooks/useCurrentProject.ts | 54 ++++++++++++++++--
packages/dashboard/app/hooks/useProjectActions.ts | 5 ++
packages/dashboard/app/utils/projectUrlState.ts | 24 ++++++++
8 files changed, 190 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7416
Fusion-Task-Lineage: 2d640ae6-629f-4632-9014-986320acfef5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Missing mock export (5 files, 17 tests):
- reviewer-prompt-single-source, plan-review-unavailable-recovery,
triage-fast-mode-workflow-variant, triage-stuck-requeue-preserve-draft,
restart.integration: add formatModelMarkerDetails to the ../pi.js mock.
Production code calls this after resolving an agent session, but the test
mocks were missing the export, causing all reviewer/triage/restart paths
to throw before reaching finalization assertions.
Outdated worktree assertions (2 files, 5 tests):
- worktree-acquisition-backend, worktree-acquisition-worktrunk: update
assertions for the new git symbolic-ref origin/HEAD resolution call and
the appended start point in git worktree add (worktree isolation fix).
Stale workflow compiler tests (1 file, 2 tests):
- workflow-routes: FN-7360 removed the linear compiler /compile endpoint
and made parseWorkflowIr the sole validity gate. Branching custom
workflows are now valid on the graph interpreter. Updated the two stale
tests that asserted 422 for branching IR to assert 201/200 instead.
Restore the board/list search affordance after users dismiss an active desktop search.
- Keep the non-mobile open-search button available once a closed search has an empty query.
- Cover empty-close and parent-cleared populated search flows in Header tests.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/restore-board-search-trigger.md | 7 ++++
packages/dashboard/app/components/Header.tsx | 13 +++++---
.../app/components/__tests__/Header.test.tsx | 38 ++++++++++++++++++----
3 files changed, 46 insertions(+), 12 deletions(-)
Fusion-Task-Id: FN-7409
Fusion-Task-Lineage: 9cb4ff80-8490-43be-8d18-a3f292603ecb
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Prevent task-bound callers from soft-deleting the task they are currently executing.
- Add a TaskSelfDeleteError guard in TaskStore.deleteTask before mutation or audit emission.
- Pass the current task id through the fn_task_delete audit context so CLI tool calls inherit the store invariant.
- Cover self-delete rejection and cross-task deletion allowance in core and CLI tests.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-7411-self-delete-guard.md | 7 +++
.../task-delete-allow-resurrection.test.ts | 48 ++++++++++++++++-
packages/cli/src/extension.ts | 2 +
.../src/__tests__/store-self-delete-guard.test.ts | 60 ++++++++++++++++++++++
packages/core/src/store.ts | 22 +++++++-
5 files changed, 136 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7411
Fusion-Task-Lineage: 50028769-5396-4435-84fb-2ae182315e81
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Reviews no longer fail on formatting. Three changes to how reviewer/gate
verdicts are parsed and how retries reset state:
- Approval leniency: a review that clearly approves in prose passes even
without a structured verdict (proseSignalsClearApproval, with a
revise/reject/negated-approval guard so a rejection is never flipped). Any
APPROVE*/APPROVAL verdict token classifies as approved. Shared by the
reviewer/plan-review parser and the code-review/browser-verification gate.
- Prose + trailing JSON: extractJsonObjectCandidates does a string-aware
balanced-brace scan and prefers the last object, so a model that emits
reasoning prose then a trailing {"verdict":...} payload parses correctly.
An explicit "Verdict:" heading/line still takes precedence over an
incidental/example JSON object.
- Malformed handling: executeWorkflowStep retries the fallback model on
malformed output (not just timeout); malformed gate output becomes a
non-blocking advisory (a genuine parsed REVISE still blocks).
- Retry clears prior terminal step failures (incl. optional gate nodes like
code-review) after the task leaves the mergeable in-review column, so a
retry starts clean without an auto-merge race.
Fail-closed merge / PR-review / mission-verification gates are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The initial-plan textarea gated duplicate createPlanningDraft calls only
on draftSessionIdRef, which is populated after the create round-trip
resolves. Keystrokes arriving while a create was in flight each passed the
guard and spawned a fresh draft. Add a synchronous draftCreateInFlightRef
sentinel that suppresses concurrent creates and clears on failure so a
later keystroke can retry. Includes an in-flight-concurrency regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add TaskExecutor.blockOuterDispatchWhenEphemeralDisabled, gating all three
workflow dispatch paths (graph / authoritative / work-engine) on
ephemeralAgentsEnabled at the top of execute(). Previously the toggle was
enforced only on the legacy scheduler/EphemeralWorkerManager path — whose
onTaskStart spawn refusal is a fire-and-forget callback that runs after
execution begins — so unassigned tasks reaching execute() off a non-scheduler
path still ran. Unassigned tasks are now re-queued for permanent-agent
assignment; permanent-agent-bound tasks still run. Adds regression coverage
across all three entry points.
Also includes the ephemeralAgentsCanCreateTasks project setting (default on)
gating fn_task_create for ephemeral callers in both the pi extension and the
executor task-worker tool.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A pre-merge-remediation/plan-replan node (e.g. code-review-remediation) is a
fire-and-forget async scheduler with no failure out-edge. When its schedule call
can't re-arm (missing rehydrated failureContext after restart,
remediation-not-scheduled, or an exhausted rework budget), the failure bubbled
out as the terminal graph outcome and handleGraphFailure stamped status:"failed"
— surfacing a spurious "Task Failed" even while the previously-scheduled
fix/reviewer session was still live.
Guard the terminal sink: skip the failed park when the failed node is a
remediation node AND a live agent session surface is still registered for the
task. Scoped via isRemediationGraphNode (IR workflowAction + built-in node-id
fallback) and hasLiveTaskSessionSurface; genuine execute/merge failures and
remediation failures with no live session still park failed unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Activity view menu (Live/Feed/Raw) is position:fixed and portaled to
<body>, so it is anchored to the layout viewport. It was clamped using
window.visualViewport width/offset, which under pinch-zoom or an open mobile
keyboard diverges from the layout viewport and shoved the popup to the left of
the modal. Position it purely from the layout viewport
(document.documentElement.clientWidth/clientHeight) with no visual-viewport
offset so it stays under the "Activity" trigger.
Adds a regression test asserting the menu anchors under the trigger even when
the visual viewport diverges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Desktop "Local" mode crashed with ERR_MODULE_NOT_FOUND because electron-builder's
pnpm collector drops `deduped` subtrees, so engine's transitive closure
(@modelcontextprotocol/sdk, pi-ai provider SDKs, etc.) was never packed. Stage the
complete flat prod closure with `pnpm deploy --legacy --config.node-linker=hoisted`
and package it via `electron-builder --projectDir deploy`, bypassing the lossy
collector entirely.
Also make the dashboard-imported example plugins loadable under plain Node (the
Electron main runtime): cursor/droid/roadmap now expose compiled `dist` on the
`import` condition (keeping `source`→src for the bun CLI) and are built during the
desktop build. Add `source` conditions to paperclip/agent-browser/even-cards/
even-realities-glasses/whatsapp-chat so the bun `--conditions=source` Windows CLI
compile resolves them from source.
Validated on macOS: @fusion/core|engine|dashboard import cleanly from the staged
deploy; packing yields a complete 705-package asar; bun-windows-x64 cross-compiles
with all plugin dist removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The packaged desktop "Local" runtime dynamically imports @fusion/engine, whose
tsc dist is gitignored. desktop-windows.yml built only `@fusion/desktop build`
(no root `pnpm build`), so it packaged an empty engine/dist and the app crashed
on Local mode with ERR_MODULE_NOT_FOUND for app.asar/node_modules/@fusion/engine.
Make the desktop build self-contained (build core then engine before packaging)
and add the parity `pnpm build` step to desktop-windows.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the pnpm-lock.yaml conflict. Main's #1865 (review-checkout routing)
auto-merged cleanly with the completion-summary backstop in executor.ts.
Main independently pinned pi-claude-cli's pi-ai/pi-coding-agent to ^0.80.3
(e15489259) but kept the top-level `getModels` import, which 0.80.3 removed —
this branch's migration to `getBuiltinModels` from `/providers/all` is retained
as the working fix. Lockfile regenerated against the merged package.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Typecheck CI gate was failing because pi-claude-cli declared pi-ai and
pi-coding-agent as unpinned "*" peers. pi-ai 0.80 was published and, via
hoisting/non-frozen resolution, the bare `@earendil-works/pi-ai` import floated
to 0.80.3 — which moved the top-level `getModels` export to the deprecated
`/compat` shim ("has no exported member named 'getModels'").
Migrate forward to the latest, consistent with cli/engine which already pin
^0.80.3:
- Pin pi-ai and pi-coding-agent to ^0.80.3 (peer + dev) so the whole extension
resolves one pi-ai version; pinning pi-coding-agent too avoids the
AssistantMessageEventStream type skew that a pi-ai-only bump reintroduced.
- Import the canonical `getBuiltinModels` from
`@earendil-works/pi-ai/providers/all` (identical signature to the old
`getModels`; the top-level export is now the deprecated compat alias).
- Update the provider test mock to the new subpath.
Behavior-preserving: getBuiltinModels === getModels. Typecheck, the full
recursive typecheck, and all 347 pi-claude-cli tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
- Add explicit `sourceMetadata.externalReviewCheckout` review routing
with fail-closed validation.
- Log the selected review checkout so operator/runtime tasks can verify
where review ran.
- Cover metadata extraction, invalid metadata fallback,
`fn_review_step`, and workflow `stepReview` routing.
## Test Plan
- `cd packages/engine && corepack pnpm exec vitest run
src/__tests__/review-checkout.test.ts
src/__tests__/reviewer-workspace.test.ts`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Review routing now supports explicit external checkout overrides via
review checkout metadata, using the resolved external checkout as the
review working directory when valid.
* Logging now highlights the selected review routing target and warns
when external checkout metadata is present but invalid.
* **Bug Fixes**
* Missing/blank/invalid/relative/nonexistent/non-git paths now fail
closed to the task worktree instead of falling back.
* Metadata priority is enforced strictly (custom fields → branch context
→ source metadata → root), preventing lower-priority fallback when
higher-priority data is invalid.
* **Tests**
* Added/expanded automated coverage for override and fail-closed
behavior, including workspace-mode routing semantics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Two distinct v0.52.0 regressions reported in issue #1863.
1. Triage loop (engine): the best-effort completion-summary graph node is
wired into every built-in workflow with a success-only edge. A thrown
handler exception or a failed summary projection write bypassed the
advisory `!blocking -> success` coercion, terminated the graph at
'completion-summary', and routeGraphFailureToExecutionResume bounced the
in-review task back to todo forever (token usage 0, execution NOT STARTED).
The graph executor now degrades a completion-summary node failure to
success (ensureWorkflowCompletionSummary still backfills task.summary), with
a routeGraphFailureToExecutionResume backstop. Shared isCompletionSummaryNode
predicate exported from @fusion/core.
2. i18n object-key crashes (dashboard): three views called t() with keys that
resolve to nested objects (taskDetail.executionMode, routing.source,
nodes.dockerHost), so i18next returned "returned an object instead of
string" and crashed the render. Added leaf label keys across all locales and
switched the callers.
Tests: engine non-fatal completion-summary regression (fails without the fix),
dashboard invariant guard scanning t("literal") callers against real en/app.json,
and a Stats-panel reproduction against the real bundle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The vendored pi-claude-cli extension declared `@earendil-works/pi-ai` and
`pi-coding-agent` as `*` peers, so its dev/workspace resolution drifted to
0.77.0 while the rest of the workspace is on ^0.80.3. Pin both peers to
^0.80.3 so the extension's `getModels("anthropic")` catalog (which feeds the
pi-claude-cli picker rows) matches the engine/cli — 0.80.3 ships
claude-sonnet-5 natively. Behavior-preserving; the bundled CLI already
provided 0.80.3 at runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sonnet 5 had disappeared from every surface: pi-ai 0.79.9 (the installed
version) lacks it, and FN-7374 removed the static row expecting the live
registry to carry it. Live-verified that claude-sonnet-5 returns 200 on
api.anthropic.com/v1 with a raw ANTHROPIC_API_KEY and runs via the Claude
CLI (it 403s on subscription-OAuth /v1 — scope-gated; runtime fallback
applies). Note: pi-ai 0.80.3 ships sonnet-5 natively, so this SUPPLEMENTAL
row dedupes once the install catches up.
- core: re-add claude-sonnet-5 to SUPPLEMENTAL_ANTHROPIC_PROVIDER_REGISTRATION
and restore its static pricing (revert FN-7374); update pricing tests.
- engine/dashboard tests: flip the FN-7374 "withheld" assertions to the
restored "advertised" behavior.
PR feedback:
- Trim the two FNXC comments (auth-storage.ts, pi.ts) to concise
requirement prose per coding guidelines (CodeRabbit).
- Replace the now-inert getApiKey mock in two subscription routing tests
with a clarifying note (Greptile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude subscription (Max/Pro) chats regressed to 404/502/429 because
FN-7396 rerouted subscription OAuth to a /v1-based `anthropic-subscription`
runtime provider — reintroducing issue #1857 that FN-7391 had fixed. Both
routed the OAuth token to api.anthropic.com/v1, the surface that broke.
Proven in code that v0.51.0 (working) sent subscription OAuth directly to
/v1 via pi-ai's built-in `anthropic` provider (Claude Code impersonation:
Bearer + anthropic-beta oauth headers), NOT through the CLI. Restore that:
- auth-storage: getApiKey("anthropic") resolves subscription/legacy OAuth
again (raw API key still wins), so the built-in provider gets the token.
- pi.ts: remove the runtime reroute and the /v1 `anthropic-subscription`
execution provider so anthropic/* selections stay on the built-in provider.
- register-model-routes: advertise `anthropic` for OAuth users so direct
OAuth is selectable in the picker.
Three independent surfaces, no rerouting: direct OAuth, raw ANTHROPIC_API_KEY
(precedence), and explicit pi-claude-cli.
Fixes#1857
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>