## Summary
Fixes all failing tests in the non-blocking full-suite CI run
[28561416741](https://github.com/Runfusion/Fusion/actions/runs/28561416741)
on main.
## Root causes and fixes
### 1. Missing `formatModelMarkerDetails` mock export (5 files, 17
tests)
Production code in `reviewer.ts` and `triage.ts` calls
`formatModelMarkerDetails()` after resolving an agent session to build a
model-marker log line. Five test files mocked `../pi.js` without
exporting this function, so every reviewer/triage/restart path threw
`[vitest] No "formatModelMarkerDetails" export is defined` before
reaching finalization assertions.
**Files:** `reviewer-prompt-single-source`,
`plan-review-unavailable-recovery`, `triage-fast-mode-workflow-variant`,
`triage-stuck-requeue-preserve-draft`, `restart.integration`
### 2. Outdated worktree acquisition assertions (2 files, 5 tests)
A production fix added `git symbolic-ref --short
refs/remotes/origin/HEAD` resolution to ensure fresh task worktrees
never inherit the root checkout's ambient HEAD. The tests didn't account
for this new exec call or the appended start point.
**Files:** `worktree-acquisition-backend`,
`worktree-acquisition-worktrunk`
### 3. Stale workflow compiler tests (1 file, 2 tests)
FN-7360 removed the linear `WorkflowStep` compiler and `/compile`
endpoint, making `parseWorkflowIr` the sole validity gate. Branching
custom workflows are now valid on the graph interpreter. Two dashboard
tests still asserted 422 for branching IR.
**File:** `workflow-routes`
### 4. Graph cutover production fixes + test migrations (from closed PR
#1869)
- `executor.ts`: `safeLogEntry()` wrapper prevents synchronous
`store.logEntry` throws from aborting pause/abort/finalize control flow
- `workflow-authoritative-driver.ts`: built-in auxiliary custom nodes
(completion-summary, optional-groups) pass through as success instead of
throwing
- Test migrations across engine, CLI, dashboard, and desktop for
graph-native runtime paths
## Verification
- All affected tests pass: engine (87 tests across 7 files), dashboard
(53 tests), plus the cherry-picked migration tests
- Merge gate (`pnpm test:gate`): 319 engine-core + 63 CI-shape tests
green
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added support for branching workflow content in more places, including
workflow creation and selection.
* Added a new shared runtime tool available to plan/review agents for
writing task prompts.
* **Bug Fixes**
* Improved task failure handling so retries now end cleanly instead of
bouncing through extra states.
* Made pause, recovery, and worktree-related behavior more reliable
during long-running operations and restarts.
* Updated Android release automation to use JDK 21.
<!-- end of auto-generated comment: release notes by coderabbit.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 CLI bun `--compile --conditions=source` build could not resolve
@fusion-plugin-examples/hermes-runtime and openclaw-runtime (statically imported
by dashboard routes.ts): those plugins lacked a `source` export condition and
fell through to `import`->dist/index.js, absent on the Windows runner. Add
`"source": "./src/index.ts"` (matching core/dashboard/engine/plugin-sdk) so bun
bundles their TS source directly, independent of dist.
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>
Fixes#1863.
The issue conflated **two distinct v0.52.0 regressions** (the reporter's
i18n root-cause guess was only half of it).
## 1. The real triage loop — engine (primary fix)
The `completion-summary` graph node was added to every built-in workflow
in v0.52.0 (FN-7228/FN-7233), wired with a **success-only edge — no
failure edge**. It's a *best-effort* node
(`ensureWorkflowCompletionSummary` already backfills `task.summary`),
but two failure paths bypass its advisory `!blocking → success`
coercion:
- a **thrown handler exception** (e.g. missing worktree) →
`executeNodeWithRetries` returns `value:"exception"`
- a **failed summary projection write** →
`publishTaskProjectionFromResult` returns `value:"projection-error"`
With no failure edge, either **terminates the graph at
`completion-summary`**. `routeGraphFailureToExecutionResume` then treats
the in-review failure as recoverable and moves the task back to `todo`,
which replays the already-passed nodes (0 new tokens) and fails again —
the **infinite `triage → in-progress → todo` loop** in the report (token
usage 0, execution NOT STARTED).
**Fix:** the graph executor now degrades a `completion-summary` node
failure to `success` so the graph always advances, with a
`routeGraphFailureToExecutionResume` backstop that parks `failed`
instead of looping. Shared `isCompletionSummaryNode` predicate exported
from `@fusion/core`.
## 2. The i18n crash the reporter saw — dashboard
`t("taskDetail.executionMode")` resolves to a **nested object** (the
inline mode-toggle copy), so i18next returns *"key
'taskDetail.executionMode (en)' returned an object instead of string"*
and crashes the Stats tab — the exact error in the report. Surface
enumeration found **two more** callers of the same class:
`routing.source` and `nodes.dockerHost`. Added leaf label keys across
all 6 locales and switched the callers.
## Tests
- `workflow-graph-completion-summary-nonfatal.test.ts` — a failing
summary node (both modes) never terminates/loops the graph, a
non-summary node still fails, and all 5 built-ins have no failure edge.
**Fails without the engine fix.**
- `i18n-string-keys-not-objects.test.ts` — invariant guard scanning
every `t("literal")` caller against the real `en/app.json`. **Catches a
reverted caller.**
- `TaskTokenStatsPanel.test.tsx` — reproduces the exact i18next error
against the real bundle.
## Verification
- Merge gate: **317 engine-core + 63 CI-shape pass**
- Core/engine typecheck clean; i18n parity + typecheck pass; changeset
validates
- All touched dashboard/core/engine suites green
🤖 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**
* Fixed an issue where completion-summary failures could cause tasks to
loop indefinitely during resume.
* Made completion-summary behavior best-effort so workflows keep
progressing even when summary handling fails.
* Prevented dashboard crashes by ensuring UI labels use leaf translation
keys (not nested objects).
* **New Features**
* Added missing i18n label keys for Docker host, routing source, and
execution mode across multiple languages.
* **Tests**
* Added regression coverage for completion-summary non-fatal handling
and translation-key validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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>
## Problem
Claude **subscription** (Max/Pro) chats fail with a rotating `404 / 502
/ 429` cascade — the same symptom as #1857, which was reported fixed.
Raw `ANTHROPIC_API_KEY` and explicit Claude CLI usage work; direct OAuth
does not.
## Root cause (proven against git history)
- **v0.51.0 (worked):** `packages/core/src/anthropic-models.ts` didn't
exist. `getApiKey("anthropic")` returned the subscription **OAuth**
access token and pi-ai's built-in `anthropic` provider POSTed it to
`api.anthropic.com/v1` with **Claude Code identity headers** (`Bearer` +
`anthropic-beta: claude-code-20250219,oauth-2025-04-20`). Direct OAuth
worked — **not** via the CLI.
- **The regression chain:** #1857 mis-concluded "0.51.0 uses the CLI /
OAuth is blocked on /v1" (its `grep` missed the runtime-appended `/v1`;
a naive curl repro lacked pi-ai's full impersonation). FN-7291 → FN-7391
→ FN-7396 were built on that wrong premise. FN-7396's
`registerAnthropicSubscriptionProvider` rerouted subscription OAuth to a
new `anthropic-subscription` provider **still pointed at
`api.anthropic.com/v1`** — reintroducing #1857.
## Fix — restore the v0.51.0 path (three independent surfaces, no
rerouting)
- **`auth-storage.ts`** — `getApiKey("anthropic")` resolves
subscription/legacy OAuth again (raw API key still takes precedence), so
the built-in provider receives the token.
- **`pi.ts`** — remove the runtime reroute and the `/v1`-based
`anthropic-subscription` execution provider; `anthropic/*` selections
stay on the built-in OAuth-capable provider.
- **`register-model-routes.ts`** — advertise `anthropic` in the picker
for OAuth users so direct OAuth is selectable.
- Result: **direct OAuth**, **raw `ANTHROPIC_API_KEY`** (precedence),
and **explicit `pi-claude-cli`** all work independently.
## Verification
- `packages/engine`: `auth-storage.test.ts` +
`pi-create-fn-agent.test.ts` — **137 passed** (assertions restored to
the correct invariant; the FN-7391/FN-7396 tests encoded the bug).
- `packages/dashboard`: `routes-auth.test.ts` + `usage.test.ts` — **352
passed**.
- `pnpm --filter @fusion/engine build` clean.
- Docs (`settings-reference.md`, `dashboard-guide.md`) + changeset
updated.
Fixes#1857🤖 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**
* Restored Anthropic Claude subscription chat reliability by reverting
the OAuth execution behavior to the expected direct runtime flow.
* Improved Anthropic credential precedence and provider visibility
across direct API key, subscription OAuth, and Claude CLI modes.
* Reinstated Claude Sonnet 5 in the model picker with correct
catalog/pricing.
* **Documentation**
* Updated the OAuth re-login banner guide and settings reference to
clarify Anthropic credential behavior and precedence.
* **Tests**
* Updated authentication, model discovery, session routing, and pricing
expectations for Anthropic scenarios.
* **Chores**
* Tightened peer dependency version constraints for the Claude CLI
package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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>
Keep Claude Sonnet 5 visible only through eligible provider catalogs without duplicate picker rows.
- Dedupe API model rows by provider/model after registry and supplemental catalog merges.
- Cover Claude CLI Sonnet 5 visibility, disabled-toggle filtering, and duplicate suppression in API tests.
- Add dropdown coverage for stale direct-Anthropic favorite shells when Claude CLI Sonnet 5 is selected.
- Add a patch changeset for the model picker fix.
Files changed:
.changeset/fn-7389-sonnet-5-model-list.md | 7 +++++
.../__tests__/CustomModelDropdown.test.tsx | 33 ++++++++++++++++++++++
.../dashboard/src/__tests__/routes-auth.test.ts | 15 ++++++++--
.../dashboard/src/routes/register-model-routes.ts | 12 ++++++++
4 files changed, 65 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7389
Fusion-Task-Lineage: d41b422e-60f9-46c3-9c6f-d115a86e9e2a
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Keep Anthropic OAuth subscription credentials away from the direct /v1 provider while preserving raw API-key routing.
- Treat direct anthropic auth as raw API-key-only in auth storage and model discovery.
- Re-route persisted anthropic model selections to the Claude CLI provider when no raw Anthropic API key is configured.
- Add regression coverage for OAuth-only routing, raw API-key preservation, and provider list separation.
- Document the distinct raw API key, OAuth subscription, and Claude CLI routes.
Files changed:
.../fn-7391-anthropic-subscription-cli-routing.md | 7 ++
docs/settings-reference.md | 2 +-
.../dashboard/src/__tests__/routes-auth.test.ts | 74 ++++++++++++++
.../dashboard/src/routes/register-model-routes.ts | 27 +++++-
packages/engine/src/__tests__/auth-storage.test.ts | 108 +++++++--------------
.../src/__tests__/pi-create-fn-agent.test.ts | 100 +++++++++++++++++++
packages/engine/src/auth-storage.ts | 45 ++++-----
packages/engine/src/pi.ts | 61 ++++++++++++
8 files changed, 319 insertions(+), 105 deletions(-)
Fusion-Task-Id: FN-7391
Fusion-Task-Lineage: d1fecc2d-7f31-408c-bf65-0e65b7fd88b1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
- add an opt-in `allowAnswerQuestionIdDrift` flag for interactive AI
sessions
- keep strict question-id validation by default
- enable the tolerance only for Compound Engineering recovered sessions
so persisted session rows can answer after dashboard
restarts/non-deterministic rehydration
## Test Plan
- `corepack pnpm --filter @fusion/engine exec vitest run
src/__tests__/interactive-ai-session.test.ts --silent=passed-only
--reporter=dot`
- `corepack pnpm --filter @fusion/engine typecheck`
- `corepack pnpm --filter @fusion-plugin-examples/compound-engineering
build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved interactive session recovery so persisted answers can
continue after dashboard restarts, even if the question ID changes
during rehydration.
* Keeps strict question-ID validation by default; mismatches still fail
unless drift is explicitly allowed.
* **New Features**
* Added `allowAnswerQuestionIdDrift` option to permit accepting the
persisted question ID during recovered session answering.
* **Tests**
* Added/updated coverage for strict mismatch error behavior and the
successful completion path when drift is enabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->