Fixes the onboarding/settings GitHub step so it never offers a dashboard OAuth login for github (no github OAuth provider is ever registered; pi only ships anthropic/github-copilot/openai-codex), replacing the broken Connect OAuth button with gh CLI guidance and a clearer server-side error.
- Remove the "Connect OAuth (optional)" button and its login-instructions panel from the onboarding branch that runs when hasGithubProvider is false (ModelOnboardingModal.tsx), since it always called handleLogin("github") against a non-existent provider
- Update ModelOnboardingModal tests to cover the new gh-CLI-only flow
- Make POST /api/auth/login return a clear, actionable 400 naming the requested provider, the registered dashboard OAuth providers, and that GitHub integration uses gh CLI/token auth instead of a generic "Unknown provider" / model-not-found error
- Add routes-auth.test.ts coverage for the improved unknown-provider error message
- Add a patch changeset documenting the fix
Files changed:
.changeset/fn-7624-github-onboarding-auth.md | 7 +++++
packages/dashboard/app/components/ModelOnboardingModal.tsx | 32 +++++++---------------
packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx | 32 +++++++++++++++++++++-
packages/dashboard/src/__tests__/routes-auth.test.ts | 22 +++++++++++++++
packages/dashboard/src/routes/register-auth-routes.ts | 14 +++++++++-
5 files changed, 83 insertions(+), 24 deletions(-)
Fusion-Task-Id: FN-7624
Fusion-Task-Lineage: e7be1b0a-6e50-479d-b56e-1284e8a94b7f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a close affordance to the embedded Settings header on mobile, since only a bottom nav bar (no sidebar) is available to exit there.
- Render a mobile-only `modal-close` button in the embedded Settings header when `isEmbedded && viewportMode === "mobile"`, wired to the existing `onClose` prop.
- Leave desktop/tablet embedded and the standalone modal presentation unchanged.
- Add regression tests covering the new mobile close button.
- Add a patch changeset documenting the fix.
Files changed:
.changeset/fn-7627-mobile-settings-close.md | 7 ++
.../dashboard/app/components/SettingsModal.css | 16 +++
.../dashboard/app/components/SettingsModal.tsx | 17 +++
.../__tests__/SettingsModal.mobileClose.test.tsx | 125 +++++++++++++++++++++
4 files changed, 165 insertions(+)
Fusion-Task-Id: FN-7627
Fusion-Task-Lineage: b39c7172-bce3-4fb2-9095-58ae376c43ef
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Root-caused and fixed the third recurrence of the mobile terminal shortcut bar not scrolling horizontally: styles.css's mobile lockdown resets touch-action to pan-y across ancestors, and touch-action's used value is the intersection of the touched element's and every ancestor's value, so the leaf .terminal-shortcut-panel's pan-x was silently defeated even though it was already correct.
- Opt the terminal overlay and modal ancestors (.modal-overlay.terminal-modal-overlay, .modal.terminal-modal--mobile, plain-media-query mobile modal, and the shortcut/status footer) into touch-action: pan-x pan-y so descendant leaf touch-action values can take effect
- Add FNXC:Terminal comments documenting the ancestor-intersection root cause and recurrence history (FN-7550/FN-7560)
- Add a documented solution note under docs/solutions/ui-bugs/ for the ancestor-intersection touch-action pattern
- Add regression tests asserting the modal/overlay/footer ancestors carry the pan-x pan-y opt-in
- Add a changeset for the fix
Files changed:
.../fn-7621-mobile-terminal-shortcut-scroll.md | 7 ++
...on-ancestor-intersection-defeats-leaf-scroll.md | 57 +++++++++++
.../dashboard/app/components/TerminalModal.css | 38 ++++++++
.../components/__tests__/TerminalModal.test.tsx | 106 +++++++++++++++++++++
4 files changed, 208 insertions(+)
Fusion-Task-Id: FN-7621
Fusion-Task-Lineage: 771fd79e-e193-43b0-908b-0e8fe2fc2c70
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the mobile dashboard terminal sometimes rendering completely blank on open by having TerminalModal recover from a zero/collapsed container box.
- TerminalModal now attaches a persistent ResizeObserver directly on the xterm container, mirroring SessionTerminal's existing pattern
- When the container reports a zero/collapsed box on the first post-open fit, it now re-fits once the real box settles instead of staying stuck at FitAddon's degenerate 2x1-cell floor
- Added regression tests covering TerminalModal and SessionTerminal zero-geometry recovery
- Documented the root cause and fix in docs/solutions/ui-bugs/mobile-terminal-blank-render-zero-geometry-container.md
- Added a patch changeset for @runfusion/fusion
Files changed:
.changeset/fn-7620-mobile-terminal-blank-render.md | 7 +
docs/solutions/ui-bugs/mobile-terminal-blank-render-zero-geometry-container.md | 112 +++++++
packages/dashboard/app/components/TerminalModal.tsx | 49 +++
packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx | 66 ++++
packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 358 +++++++++++++++++++++
5 files changed, 592 insertions(+)
Fusion-Task-Id: FN-7620
Fusion-Task-Lineage: 103f5b17-9a6e-4e9a-ab61-65ccb2203a8d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Fixes the failing **full-suite** CI run on `main` ([run
28874651861](https://github.com/Runfusion/Fusion/actions/runs/28874651861))
— all 4 test shards were red. ~32 test files failing across engine +
dashboard (src + app), rooted in ~13 distinct causes from recent main
commits. All resolved; the merge gate and full engine/dashboard suites
are green locally.
## Root causes & fixes
### Engine (shards 1 & 2)
- **`appendAgentLog` 6th timing arg (FN-7503, `2797803c0`)** —
`agent-logger.ts` now passes an optional
`{durationMs,timeToFirstTokenMs}` 6th arg; many
executor/heartbeat/merger tests asserted the old 5-arg form. Added a
shared timing-tolerant helper `agent-log-assertions.ts` (asserts
`taskId/text/type`, tolerant of the timing object) and applied it across
affected files — so future timing fields won't re-break every executor
test.
- **`reconcileSupersededGeneratedFixFeatures` (mission)** —
`mission-execution-loop.ts` calls a method the test's missionStore mock
lacked; added a no-op stub (the real `MissionStore` already implements
it).
- **`ModelFallbackExhaustedError` / `proseSignalsClearApproval` /
`extractJsonObjectCandidates` missing from `vi.mock`** — converted stale
hand-written mocks (`../pi.js`, `../reviewer.js` in
`executor-test-helpers.ts`) to `importOriginal`-spread so real exports
carry through.
- **Workspace product fixes (2):**
- `merger-ai.ts` — `landWorkspaceTask` now recovers the integration-tip
sha as `landedSha` when the A1 trailer-fallback proved a sub-repo landed
but its sha was never persisted, so `finalizeWorkspaceTask` can build
merge proof (was stranding partial-land retries in-review).
- `worktree-acquisition.ts` — `acquireWorkspaceRepoWorktree` strips the
shared project `integrationBranch/baseBranch` overrides before
forwarding to `acquireTaskWorktree` (FN-7360's `freshStartPoint` was
resolving an absent shared branch).
- **FN-7360 extra `git symbolic-ref` exec** — updated worktree
exec-count assertions for the new `resolveIntegrationBranch` call.
- **Planner-overseer / stepwise-workflow / workflow-graph /
workflow-prompt / executor-step-session / liveness-gate / checkout /
ce-workflow / triage-split** — test-alignments for intentional behavior
changes (FN-7229 retry-cap, FN-7265 review-node removal, FN-7335
pause-abort logging, FN-7577 recovery-budget, FN-7577 overseer denial
loop, specifyTask single promptWithFallback call, FN-4944
already-on-main noop log, FN-7486 ownership short-circuit).
### Dashboard API (shard 3)
- **`store.on('task:moved')` (FN-7337)** — `createServer` now registers
the listener; backed the 4 affected MockStores with EventEmitter (shared
root cause across chat-routes.rooms, register-git-github,
routes-run-cited-goals, routes-sandbox-audit).
- **`routes-agent-import`** — core mock converted to
`importOriginal`-spread (was missing FN-7444 planning-deepening
constants).
- **`session-resume-history`** — engine mock missing
`resolveMcpServersForStore`.
- **`task-create-workflow-route`** — `builtin:legacy-coding`
defaultSteps now include `plan-review` (FN-7224/7226).
- **GitLab parity** — added the missing `[GitLab Parity Inventory]`
cross-link in `docs/signals-connectors.md`.
### Dashboard app (shard 4)
- Test-alignments for intentional product changes: FN-7057 (workflow
selection preservation), FN-7340 (footer concurrency geometry), FN-7156
(Missions overview default), FN-7342/FN-6825 (board scroll + workflow
switcher), FN-7352 (openDetailTask 3rd arg), FN-7261 (backdrop dismiss
default-off), FN-7234 (non-authoritative fetch failures), plus a missing
`fetchWorkflowOptionalSteps` mock.
### MCP coverage
- `mcp-surface-coverage` forwarding needle updated for FN-7446's
`resolvePlanningMcpServers` helper.
## Approach notes
- Each fix is the **minimal** change at the correct source (test-update
where a recent commit intentionally changed behavior; product-fix for
the 2 real regressions). No assertion was loosened/deleted to force a
pass; no timeout appeasement.
- Coordination: work was partitioned by package across parallel
subagents (engine / dashboard-src / dashboard-app) with Main as the sole
git committer (path-scoped commits) after an early shared-index reset
wiped in-progress edits — process was tightened mid-flight.
## Verification
- **Full engine suite**: green (9231 passed; the lone local-only
`custom-providers-openai-completions` import error is stale local
`pi-ai@0.79.9` vs the lockfile's `0.80.3` — CI's fresh install resolves
`/compat`; it passed in the original CI run).
- **Dashboard API** (`dashboard-api-quality-backfill`): 242 files / 3185
tests / 0 failures.
- **Dashboard app** (`dashboard-app-quality-backfill`): all targeted
files green (37 + 95 tests).
- **Merge gate** (`pnpm test:gate`): engine-core 326 + ci-shape 63, plus
nohup/4040/appeasement/changeset-format checks — all pass.
- 2 changesets added for the published-`@runfusion/fusion` behavior
fixes (workspace landedSha, sub-repo worktree branch-strip).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved reliability for partial workspace land retries by recovering
the exact proven landed commit so durable merge proofs can complete.
* Fixed per-sub-repo worktree creation by removing invalid branch
override settings, preventing worktree-add failures.
* Dashboard stability updates: preserve mobile board scroll during
stabilization/restore, correct task filtering when workflows are
missing, ensure the Chat tab appears for done tasks, and refine
modal-dismiss and responsive popover behavior.
* **Documentation**
* Expanded the GitLab connector section with GitLab parity context and a
GitLab Parity Inventory reference.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- In direct-merge mode with `pushAfterMerge: true`,
`pushToRemoteAfterMerge` previously did one preemptive `pull --rebase`
before the first push, then allowed exactly **one** additional retry
when the push failed as non-fast-forward.
- On a busy repo, origin can move again during that single retry's
pull/push window, so the retry itself can also lose the race — leaving
the merge unpushed with no further attempt.
- This generalizes the single retry into a bounded loop
(`PUSH_NON_FF_MAX_RETRIES = 3`, backoff `2s/5s/10s`), re-running
`pullWithRebaseAndResolveConflicts` before each attempt, and breaking
out early if a retry's failure is no longer classified as
non-fast-forward (so unrelated errors surface immediately instead of
being retried needlessly).
- Abort-signal checks (`throwIfAborted`) and merge-abort rethrow
(`rethrowIfMergeAborted`) are preserved at every step of the loop.
## Test plan
- [x] Existing test `"retries push once after non-fast-forward
rejection"` still passes unmodified (loop returns on first successful
retry, same attempt counts as before).
- [x] New test `"retries push multiple times across repeated
non-fast-forward rejections"` — 2 consecutive non-ff failures then
success on the 3rd push attempt, proving the loop goes beyond the old
single-retry ceiling.
- [x] New test `"gives up after exhausting non-fast-forward retries"` —
all attempts fail as non-ff, proving retries are bounded (`pushed:
false` after 1 initial + 3 retries) rather than looping forever.
- [x] `packages/engine` full test suite: 37/37 passing (`npx vitest run
src/__tests__/merger-prompt-and-utils.test.ts`).
- [x] `npx tsc --noEmit -p .` 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 reliability when sending merged changes to a remote by
retrying after non-fast-forward push failures.
* Added configurable backoff and retry limits to recover via pull +
rebase and provide a clear failure when retries are exhausted.
* **Tests**
* Added coverage for repeated non-fast-forward retries, including the
case where the retry limit is reached and the operation ultimately
fails.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
findProvenLandedCommit now keeps --grep as a prefilter but verifies each
candidate carries an actual 'Fusion-Task-Id: <taskId>' trailer line via
git show -s --format=%B, so a later commit that merely mentions the trailer
text in its body cannot be selected. Regression covers a body-mention
intervening commit.
findProvenLandedCommit returns the task's own trailer commit (or recorded
landedSha when still an ancestor) instead of rev-parse on the integration
tip, so an intervening sub-repo land can't attribute a later unrelated
commit. Regression: intervening commit after lost persist recovers tipAfterFirst.
Update five dashboard app test files whose assertions drifted from
intentional product changes that landed on main without updating them:
- graph-workflow-header: FN-7057 treats stale/missing workflow ids as the
default workflow, so FN-unknown now shows under the default selection.
- EngineControlMenu.css: FN-7340 added a 768px range-thumb touch-target
block; narrow the popover-breakpoint assertion to that selector.
- MissionManager.delete-confirm: FN-7156 removed first-mission auto-select;
explicitly select the mission before the detail-delete flow.
- board-mobile-initial-render: FN-7342 preserves board column scroll during
stabilization; FN-6825 renders the workflow toolbar on options, not callbacks.
- workflow-auto-layout: FN-7265 removed the stepwise review node (per-step
review lives in the foreach); the connected run ends at completion-summary.
No assertion was loosened or deleted to force a pass; each change cites the
breaking commit via an FNXC comment. packages/dashboard is private (no changeset).
recoverActiveMissions (mission-execution-loop.ts:263) calls
missionStore.reconcileSupersededGeneratedFixFeatures per slice; the 5
MissionExecutionLoop-backed mocks here omitted it, so recovery threw
(TypeError) at the slice loop and aborted before processTaskOutcome /
ensureFeatureAssertionLinked / startValidatorRun ran — 4 tests failed.
Add a no-op stub (matches mission-execution-loop.test.ts reference) with
an FNXC:MissionReconcile note. No-op is correct: supersession is not
exercised by these tests.
## Summary
In direct-merge mode (`mergeStrategy: direct` + `pushAfterMerge: true`),
when the post-merge `git push` to origin fails or throws, `aiMergeTask`
in `packages/engine/src/merger.ts` only logged the failure to the
process-wide `mergerLog` and set a transient field
(`result.pushedToRemote` / `result.pushError`) on the local
`MergeResult` object — neither of which is persisted anywhere. The task
was still unconditionally marked done/merged.
Because nothing durable recorded the failure, a diverging local `main`
could go unnoticed indefinitely. This is exactly the mechanism that let
a local `main` drift 162 commits behind `origin/main` in our deployment
before it was caught by hand.
## Fix
Both push-failure branches (the non-throwing `else` and the throwing
`catch`) now also:
- call `audit.git({ type: "push:origin", ... })`, activating the
existing-but-previously-unused `"push:origin"` `GitMutationType` in the
run-audit schema (populated with `remote`, `integrationBranch`,
`outcome: "failed"`, `stderrPreview`)
- call `store.logEntry(taskId, ..., "PushToRemoteFailed")` so the
failure is visible on the task itself, not just in an ephemeral process
log
Both calls are wrapped in `.catch(() => undefined)` — they're
best-effort recording, not new failure paths, so a problem persisting
the failure record can't abort the outer merge flow. Task-completion
behavior (mark done even when the push failed) is intentionally
unchanged; this fix is about visibility, not about blocking completion.
## Test plan
- [x] Added assertions to the existing `merger-prompt-and-utils.test.ts`
test `"records push error but still completes merge when push fails"`,
asserting `store.recordRunAuditEvent` and `store.logEntry` are called
with the expected shape
- [x] `npx vitest run src/__tests__/merger-prompt-and-utils.test.ts` —
35 passed
- [x] `npx tsc --noEmit` — 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 handling of merge completion when pushing changes to the
remote repository fails.
* Failures are now consistently recorded and surfaced with clearer error
logging, making it easier to identify when a push did not succeed after
a merge.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### Problem
In **Project Settings → Plugins**, the **Browse registry** panel fails
to load with:
```
Failed to load registry: Plugin "registry" not found
```
The registry listing never appears, so one-click plugin
discovery/install is unusable.
### Root cause
In `createApiRoutes` (`packages/dashboard/src/routes.ts`), the generic
project-scoped route **`GET /plugins/:id`** is registered **before** the
plugin sub-router (`createPluginRouter`) — which owns **`GET
/plugins/registry`** — is mounted (`router.use("/plugins",
createPluginRouter(...))`).
Express matches routes in registration order, so a request to
`/api/plugins/registry` matches `:id` with `id === "registry"`, calls
`pluginStore.getPlugin("registry")`, and throws `Plugin "registry" not
found`. The real registry handler (which builds the curated registry
manifest) is never reached.
### Why not just reorder the mount?
The two handlers are **not** equivalent:
- the inline `GET /plugins/:id` is **project-scoped** via
`getProjectContext(req)`,
- the sub-router uses the **global** plugin store.
Mounting the sub-router first would shadow the project-scoped routes and
change scoping semantics for other endpoints. So reordering is the wrong
fix.
### Fix
Let the reserved static path fall through. In the inline `GET
/plugins/:id` handler, when `id === "registry"`, call `next()` so the
later-mounted sub-router serves the registry listing (it already handles
the `projectId` query param). Minimal and targeted — `"registry"` is the
only static GET path under `/plugins` that collides with the
single-segment `:id` pattern.
```ts
router.get("/plugins/:id", async (req, res, next) => {
if (req.params.id === "registry") { next(); return; } // fall through to the registry sub-route
...
});
```
### Tests
Adds a regression test in `plugin-routes.routes.test.ts` (using the
existing `createApiRoutes` harness that reproduces the real mount order)
asserting `GET /api/plugins/registry`:
- returns **200** with a `plugins` array, and
- **never** calls `pluginStore.getPlugin("registry")` (i.e. is no longer
shadowed by `:id`).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed the `/plugins/registry` endpoint so it responds correctly
instead of being treated like a plugin ID.
* Improved route handling to ensure the registry page/API is reached
even when generic plugin routes are registered first.
* **Tests**
* Added regression coverage for the `/plugins/registry` route to verify
the correct response and prevent the generic plugin lookup from
intercepting it.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Fixes#1939.
Adds the single missing `COPY
plugins/fusion-plugin-linear-import/package.json
./plugins/fusion-plugin-linear-import/package.json` line to the
Dockerfile's pre-install COPY block.
## Root cause
Commit dfb6e5270d added
`plugins/fusion-plugin-linear-import` as a new pnpm workspace member,
but didn't add a matching Dockerfile COPY line for its package.json. As
a result that member's devDependencies (`@types/node`, `@types/react`)
are never installed inside the image, and the later `pnpm build` step
fails with TS2688 once the compiler reaches that plugin.
## Fix
One line, alphabetically placed alongside the other
`plugins/fusion-plugin-*` COPY lines.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated the container build setup to include an additional package
manifest needed during image creation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### Problem
Two settings that already exist in the `Settings` schema and are honored
by the engine have **no control anywhere in the dashboard UI**:
- `buildTimeoutMs` — max time for build/verification commands (default
`300000` = 5 min)
- `insightExtractionEnabled` (+ `insightExtractionSchedule`) — periodic
extraction of durable insights from completed tasks into memory
Because there's no UI, the only way to change them is to hand-edit
`config.json` — but that has to be done with the app fully closed, since
the running app rewrites `config.json` from its database on
reload/shutdown and silently clobbers live file edits. That's a
confusing footgun (edits appear to "not stick"), and 5 minutes is too
low a build timeout for large monorepo / Docker builds.
### Change
Expose both via controls that mirror the existing patterns in the same
sections — no schema, route, or persistence changes needed (the keys
already flow through `form`/`setForm` → `updateSettings`).
- **Scheduling section:** "Build/Verification Timeout (minutes)", placed
next to the existing "Stuck Task Timeout (minutes)", using the same
minutes↔ms conversion.
- **Memory section:** "Enable Insight Extraction" checkbox + conditional
cron "Schedule" field, placed next to the existing Auto-Summarize
controls (matching how `insightExtractionSchedule` is already documented
in `types.ts`).
Both use the existing `t(...)` i18n fallback style used throughout these
sections.
### Scope note (feedback welcome)
Both live in their existing **project-scoped** sections (Scheduling,
Memory), which is the natural home. If maintainers want these settable
as **global/user defaults** too, I'm happy to add a global-defaults
counterpart in a follow-up — just wanted to keep this PR focused and
non-duplicative.
### Testing
- Both keys are pre-existing fields on the `Settings` type
(`buildTimeoutMs?: number` at `packages/core/src/types.ts`;
`insightExtractionEnabled?`/`insightExtractionSchedule?` likewise), so
the additions are type-safe.
- Controls follow the exact JSX/handler pattern of adjacent fields
already in each section.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added an **Insight Extraction** option in memory settings, including
an enable/disable toggle and a schedule field that appears when enabled.
* Added a **Build/Verification Timeout** setting in scheduling, with
minute-based input and helpful guidance text.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### Problem
`docker build` (and `docker compose up --build`) fails during the `RUN
pnpm build` layer while compiling `plugins/fusion-plugin-linear-import`:
```
error TS2688: Cannot find type definition file for 'node'.
error TS2688: Cannot find type definition file for 'react'.
```
even though the prior `RUN pnpm install --frozen-lockfile` layer reports
success.
### Root cause
The builder stage pre-copies each workspace package's `package.json`
**before** `RUN pnpm install --frozen-lockfile` (a layer-caching
optimization), then does `COPY . .` afterward. `pnpm-workspace.yaml`
lists `plugins/fusion-plugin-linear-import` as a workspace member, but
the Dockerfile's pre-copy list has a `COPY plugins/<name>/package.json`
line for **every plugin except** `fusion-plugin-linear-import`.
Because that manifest is missing during the frozen-install layer, pnpm
never links the plugin's devDependencies (`@types/node`, `@types/react`)
into its resolution scope. When `pnpm build` later runs `tsc` for that
package (its `tsconfig.json` uses `types: ["node", "react"]`),
TypeScript can't find the type definitions — the `TS2688` failure above.
### Fix
Add the single missing pre-copy line so `fusion-plugin-linear-import`'s
manifest is present during the install layer, exactly like every other
workspace plugin:
```dockerfile
COPY plugins/fusion-plugin-linear-import/package.json ./plugins/fusion-plugin-linear-import/package.json
```
One-line, additive change; no behavior change beyond making the image
build.
### Verification
- Confirmed the omission is present on `main` and at the latest release
tag `v0.56.1`.
- With the line added, the pre-copied manifest set matches
`pnpm-workspace.yaml`, so `pnpm install --frozen-lockfile` links
`@types/node`/`@types/react` for the plugin and the `pnpm build` `tsc`
step no longer reports `TS2688`.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Updated the build setup to include an additional package manifest
during installation, helping ensure the container build completes
consistently.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
A single retry can still lose the race on busy repos if origin moves again
in the pull-rebase/push window. Generalize the one-shot retry into a bounded
loop (3 attempts, 2s/5s/10s backoff), re-pulling+rebasing before each push
attempt and bailing early once the failure is no longer non-fast-forward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
When pushAfterMerge fails (or throws) in direct-merge mode, the daemon
only wrote to the process-wide mergerLog and a transient MergeResult
field, then unconditionally marked the task done. There was no durable
record on the task or in the audit trail, so a diverged local main
could go unnoticed indefinitely — this is how our local main drifted
162 commits from origin before it was caught by hand.
Record the failure through the two channels merger.ts already has for
this: the dormant "push:origin" GitMutationType via audit.git(), and a
task log entry via store.logEntry(). Both calls are best-effort
(.catch(() => undefined)) so a logging failure can't abort the merge
flow itself — task completion behavior is unchanged, the failure is
just no longer invisible.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Dockerfile's pre-install COPY block was missing a line for
plugins/fusion-plugin-linear-import, which was added as a workspace
member in dfb6e5270d but never got a
matching COPY line. Without it, that plugin's package.json (and its
@types/node / @types/react devDependencies) is never staged before
'pnpm install --frozen-lockfile', causing the later 'pnpm build' step
to fail with TS2688.
Fixes#1939
The Plugins settings 'Browse registry' feature failed with
'Failed to load registry: Plugin "registry" not found'.
Root cause: in createApiRoutes (routes.ts), the generic project-scoped
'GET /plugins/:id' route is registered BEFORE the plugin sub-router
(createPluginRouter) that owns 'GET /plugins/registry' is mounted. Express
matches in registration order, so a request to /api/plugins/registry matched
':id' with id='registry', called pluginStore.getPlugin('registry') and threw
'Plugin "registry" not found' — the real registry handler was never reached.
Reordering the mount is unsafe: the inline ':id' route is project-scoped via
getProjectContext, while the sub-router uses the global plugin store, so
moving it would change scoping semantics. Instead, let the reserved static
path fall through: when id === 'registry', call next() so the mounted
sub-router serves the registry listing (which already handles projectId).
Adds a regression test asserting GET /api/plugins/registry returns 200 with a
plugins array and never calls getPlugin('registry').
Both settings already exist in the Settings schema and are honored by the
engine, but neither had a control in the dashboard, so users could only
change them by hand-editing config.json while the app was closed (the
running app rewrites config.json from its DB, clobbering live file edits).
- Add 'Build/Verification Timeout (minutes)' to the Scheduling section,
next to the existing stuck-task timeout (same minutes<->ms conversion).
- Add 'Enable Insight Extraction' + cron schedule to the Memory section,
next to the existing auto-summarize controls.
Both are placed in their existing project-scoped sections. Follow-up: a
global-defaults counterpart could be added if maintainers want these
settable at the user/global level too.
Add a path-containment check to fn_task_attach so it can no longer read files outside the task's worktree via traversal or absolute paths.
- Resolve the requested path and confine it to ctx.cwd (the task worktree) before any readFile call, rejecting "../" traversal, absolute paths, and other boundary-escaping inputs
- Add regression tests in extension.test.ts covering traversal/absolute-path attack vectors
- Add changeset (patch, category: security) documenting the fix
Files changed:
.changeset/fn-7619-attach-boundary.md | 7 ++
packages/cli/src/__tests__/extension.test.ts | 133 ++++++++++++++++++++++++++-
packages/cli/src/extension.ts | 23 ++++-
3 files changed, 161 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7619
Fusion-Task-Lineage: d35d9218-d678-4989-945d-6c1e1a322c5c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Executors could previously treat a pending approval as a normal turn end and go hunt for ungated workarounds instead of stopping. This change makes wait-for-approval a hard suspend point.
- wait-for-approval now suspends the in-flight executor session via awaitAbortInFlightTaskWork
- Dedupe identical pending approvals so repeated waits don't pile up
- Executor prompts now carve out awaiting-approval as a legitimate turn end (agent-prompts.ts)
- Extend provisioning-gate and agent-action-gate coverage for the new suspend/carveout behavior
- Add changeset (patch) documenting the fix for release notes
- Update docs/agents.md and docs/architecture.md to describe the new blocking behavior
Files changed:
.changeset/fn-7608-awaiting-approval-blocking.md | 7 ++
docs/agents.md | 1 +
docs/architecture.md | 1 +
packages/core/src/agent-prompts.ts | 5 +
.../engine/src/__tests__/agent-action-gate.test.ts | 82 +++++++++++++
.../executor-approval-gate-suspend.test.ts | 128 +++++++++++++++++++++
.../executor-approval-prompt-carveout.test.ts | 61 ++++++++++
packages/engine/src/agent-heartbeat.ts | 13 +++
packages/engine/src/executor.ts | 28 +++++
packages/engine/src/pi.ts | 22 +++-
.../sandbox/__tests__/provisioning-gate.test.ts | 29 +++++
packages/engine/src/sandbox/provisioning-gate.ts | 11 ++
12 files changed, 384 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7608
Fusion-Task-Lineage: 9e42d8ee-bda7-4ef1-b159-46c2100bbc48
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Task creation surfaces stopped hardcoding column:"triage", so new tasks now land in the selected-or-default workflow's resolved intake column instead of always jumping to Planning/triage.
- Removed hardcoded column:"triage" override in engine's createTaskCreateTool (fn_task_create), letting TaskStore.createTask resolve the landing column from the workflow's intake-trait column.
- Removed the equivalent hardcoded override in the pi extension's fn_task_create, and updated its response text to echo the actual landing column instead of a fixed "Column: triage" string.
- Fixed signal-route, GitHub-import, and planning-subtask-route task creation to stop forcing column when no workflowId is given (or, for planning subtask routes, even when one is provided).
- Custom workflows with a non-triage intake column (e.g. Inbox) now correctly capture new cards inert until released, while the default builtin:coding workflow still resolves to "triage" byte-identically.
- Added regression coverage (agent-tools-intake-column.test.ts, extension-workflow-tools.test.ts) and a patch changeset documenting the fix.
Files changed:
.changeset/fn-7611-intake-column.md | 7 ++
.../src/__tests__/extension-workflow-tools.test.ts | 70 +++++++++++
packages/cli/src/extension.ts | 10 +-
.../src/__tests__/register-signal-routes.test.ts | 8 +-
.../dashboard/src/__tests__/routes-github.test.ts | 2 -
.../dashboard/src/routes/register-git-github.ts | 16 ++-
.../src/routes/register-planning-subtask-routes.ts | 24 +++-
.../dashboard/src/routes/register-signal-routes.ts | 8 +-
.../__tests__/agent-tools-intake-column.test.ts | 138 +++++++++++++++++++++
packages/engine/src/__tests__/agent-tools.test.ts | 1 -
packages/engine/src/agent-tools.ts | 21 +++-
11 files changed, 288 insertions(+), 17 deletions(-)
Fusion-Task-Id: FN-7611
Fusion-Task-Lineage: daf7f755-b1c7-4859-b74f-f15593d5e79e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Replace the fixed generic "Would you like to go deeper?" theme buckets with AI-proposed, plan-specific topics, falling back to the existing regex-derived themes when the AI supplies none.
- Add optional PlanningSummary.deepeningThemes (id/label/description) to the completion payload contract in @fusion/core.
- Instruct the planning AI prompt to propose 2-5 concrete, plan-aligned deepening themes tied to the plan's actual title/description/deliverables.
- Add normalizeDeepeningThemes to validate/sanitize untrusted AI-supplied theme data (dedupe, cap at 6, trim, drop malformed entries) and omit the field entirely when nothing valid remains.
- Update buildDeepeningCheckpointOptions to prefer AI-supplied deepeningThemes over the generic CHECKPOINT_THEME_CANDIDATES regex fallback, keeping the reserved 'Proceed to final plan' option first and deterministic in both branches.
- Update docs/dashboard-guide.md to describe the new plan-specific deepening behavior and its generic fallback.
- Add unit test coverage for the new normalization and checkpoint-option-building logic.
- Add a minor changeset for @runfusion/fusion.
Files changed:
.changeset/fn-7616-planning-deepening-themes.md | 7 +
docs/dashboard-guide.md | 3 +-
packages/core/src/types.ts | 11 ++
.../planning-interview-formatters.test.ts | 176 +++++++++++++++++++++
.../src/__tests__/routes-planning.test.ts | 61 +++++++
packages/dashboard/src/planning.ts | 86 +++++++++-
6 files changed, 341 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7616
Fusion-Task-Lineage: f6c1d0d7-f0ca-45c9-85cd-d57958ad94c7
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the Task Detail Oversight dropdown trigger rendering shorter than the Priority and Execution-mode controls on non-mobile viewports.
- Made `.detail-oversight-menu-dropdown` an `inline-flex` with `align-items: stretch` so the popover-positioning wrapper participates in `.detail-meta-inline-controls`'s stretch behavior instead of only sizing to its own content.
- Added `align-self: stretch` to `.detail-oversight-menu-trigger` so it fills the now-stretched wrapper, matching Priority/Execution-mode's direct-child stretch.
- Added a regression test asserting the wrapper/trigger stretch declarations exist, apply at every viewport, and don't leak into the absolutely-positioned popover.
- Added a patch changeset documenting the fix.
Files changed:
.changeset/fn-7618-oversight-trigger-height.md | 7 ++++
.../dashboard/app/components/TaskDetailModal.css | 26 ++++++++++++
...etailModal.responsive-and-dependencies.test.tsx | 48 ++++++++++++++++++++++
3 files changed, 81 insertions(+)
Fusion-Task-Id: FN-7618
Fusion-Task-Lineage: f918c2aa-5677-446c-a7dc-17c72adb9c27
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix Planning Mode's Back button incorrectly rendering the AI generation/loading view instead of returning directly to the previous question.
- handleBack no longer sets view to "loading" during the deterministic rewindPlanningSession call; that view is reserved for real model-generation turns.
- Added isBackPending state to drive a lightweight inline pending indicator on the Back button (spinner icon, disabled Back/Continue) while the rewind request is in flight, keeping the QuestionForm mounted throughout.
- On success, the rewound history/question is applied the same as before; on failure, the error message is surfaced while remaining on the question view (never loading).
- Added a changeset for the fix.
- Expanded PlanningModeModal.planning-flow.test.tsx coverage for the Back button's success/error/pending behavior.
Files changed:
.changeset/FN-7615-planning-back-no-generation.md | 7 +
.../dashboard/app/components/PlanningModeModal.tsx | 34 +++-
.../PlanningModeModal.planning-flow.test.tsx | 182 ++++++++++++++++++++-
3 files changed, 217 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7615
Fusion-Task-Lineage: 27e9c541-ce2f-42de-b83a-777af1e56858
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds bounded internal retry to the desktop embedded-start path so a transient first-attempt failure self-heals before the operator ever sees the "Couldn't start local Fusion" error screen.
- LocalRuntimeManager.startEmbedded() now retries startEmbeddedAttempt() up to startupRetries (default 3) total attempts with a startupRetryDelayMs (default 150ms) delay between attempts, both overridable via constructor options for deterministic zero-delay tests.
- status.state stays "starting" across retried attempts; only the final attempt's real error sets state "error" and is thrown/surfaced, so genuine failures still report their real message unchanged.
- Only affects the embedded-start path (never external-cli or already-running paths).
- Adds regression tests covering retry-then-success, exhausted-retries-surfaces-final-error, and status transitions across attempts.
Files changed:
.../desktop/src/__tests__/local-runtime.test.ts | 158 ++++++++++++++++++++-
packages/desktop/src/local-runtime.ts | 76 +++++++++-
2 files changed, 228 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7617
Fusion-Task-Lineage: 2baf90f0-052c-42ff-a5b1-bc0b802d0ddc
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Introduces a single canonical step-number helper so the task-dialog step indicators agree with the Activity tab for the same underlying step.
- Add `getCanonicalStepNumber()` in `packages/dashboard/app/lib/step-display.ts`, returning the raw 0-based, PROMPT.md-numbered step index (Step 0 = Preflight), clamped to a valid range.
- Update `ActiveAgentsPanel` to derive its step/total-steps display from the new helper instead of adding its own +1 to `task.currentStep`.
- Update `TaskTokenStatsPanel`'s step-progress row to use the same canonical helper instead of its own +1 math.
- Add regression tests (`step-number-alignment.test.tsx`) asserting the task-dialog and Activity-tab step numbers stay in sync across surfaces.
Files changed:
.../dashboard/app/components/ActiveAgentsPanel.tsx | 8 +-
.../app/components/TaskTokenStatsPanel.tsx | 7 +-
.../__tests__/step-number-alignment.test.tsx | 162 +++++++++++++++++++++
packages/dashboard/app/lib/step-display.ts | 57 ++++++++
4 files changed, 229 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7612
Fusion-Task-Lineage: a33703f5-32cb-4d10-9153-399821517ea3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>