Add in-app preflight remediation so Create PR can push task branches without leaving Fusion.
- add a dashboard API route and client helper to push the task branch to origin and recompute PR preflight state
- update the Create Pull Request modal, styles, and tests to surface push-branch remediation alongside AI conflict resolution
- document the flow and add a published CLI changeset plus server coverage for the new push-branch endpoint
Files changed:
.changeset/fn-5950-pr-push-branch.md | 5 +
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/api/legacy.ts | 19 +++
packages/dashboard/app/components/PrCreateModal.css | 4 +-
packages/dashboard/app/components/PrCreateModal.tsx | 48 +++++-
packages/dashboard/app/components/__tests__/PrCreateModal.test.tsx | 44 ++++++
packages/dashboard/src/__tests__/register-git-github.pr-push-branch.test.ts | 176 +++++++++++++++++++++
packages/dashboard/src/routes/register-git-github.ts | 68 ++++++++
8 files changed, 362 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-5950
Fusion-Task-Lineage: 5a6c5c6a-8f99-44c0-8f54-f0a6ff537ed0
Re-entering a chat flushed the restored queued message based on the
client's stale isGenerating flag (a route-level enrichment the
chat:session:updated SSE payload lacks), firing a send that aborted the
live generation server-side and could lose the message entirely. The
restore path in useChat and useQuickChat now asks the server first:
attach and defer the flush while generating, send immediately only when
no generation is in flight, and keep the bubble on a failed check.
FixesRunfusion/Fusion#1279
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spinners, status-dot pulses, and entrance animations across the dashboard
rendered frozen: transition tokens (--transition-slow: 0.3s ease) bundle a
duration AND an easing, and 15 animation declarations reused them as bare
durations. Substituting "0.3s ease" next to an explicit easing (linear,
ease-in-out, ease-out) — or inside calc() — makes the declaration invalid at
computed-value time, which per spec resolves the entire property to
animation: none with no console error.
- add duration-only tokens (--duration-instant/fast/normal/slow) and derive
the --transition-* tokens from them so the two cannot drift
- switch all 15 broken animation declarations across 14 CSS files to the
duration tokens, preserving effective durations
- add animation-duration-tokens.css.test.ts: sweeps every app CSS file and
fails on transition-token-as-duration, calc() over a transition token, and
transition token in animation-duration
Verified in a real browser against the production build: previously frozen
.status-dot--connecting and calc-based NodesView spinners now report running
animations; transition shorthands still resolve to "0.15s / ease".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Importing a catalog ("company") agent assigns role "custom", which the
scheduler never auto-assigns mission/queue work to. Combined with a
model/provider that rejects the "developer" system role, this surfaced as
an invisible, repeating failure loop (GitHub #1261).
- pi.ts: treat an unsupported message-role rejection as a model-selection
error so a configured fallback model is tried once (single-swap guarded)
before the task is marked failed.
- mission-autopilot.ts: block a mission feature immediately on an
operator-actionable failure instead of burning the retry budget
re-running the same cryptic error.
- mission-routes.ts: preflight mission start — when ephemeral agents are
disabled and no eligible executor exists, fail fast with an actionable
message instead of queueing tasks forever.
- agent import route + AgentImportModal: warn when only custom-role agents
are imported and no executor exists.
- agent-assignment.ts: extract shared listEligibleExecutorAgents helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Main independently landed the same pausedReason persistence fix (column in
the task INSERT/upsert, TaskRow type, rowToTask mapping) with the column
ordered between paused and userPaused; adopt main's ordering and drop this
branch's duplicate entries so the values array stays aligned. Union the
dashboard package.json deps (main's compound-engineering plugin alongside
this branch's sorted pi-coding-agent/cli-printing-press/cursor-runtime).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both sides bootstrapped CONCEPTS.md — kept main's broader structure (Missions,
Merge lifecycle, CE sessions) and folded the branch-group vocabulary into its
Merge-lifecycle cluster (enriched Shared branch group + Self-healing sweep;
added Branch assignment mode, Landed, Group promotion). Union-merged core
index/store imports; took main's AGENTS.md discoverability wording. Also adds
execFile to the two child_process test mocks that only stubbed exec (the
coordinator's argv-based git calls now require it).
Add dashboard support for resolving task-branch PR conflicts with AI before PR creation.
- add a dashboard PR conflict resolver that merges the selected base into the task branch, invokes an AI merge session on conflicted files, verifies markers are removed, then commits and pushes the updated branch
- expose a POST /tasks/:id/pr/resolve-conflicts route plus client API/export wiring, docs updates, and a changeset for the published CLI package
- update the Create PR modal UI, styling, and tests to surface preflight conflicts, trigger AI resolution, and refresh preflight state after success
- add API coverage for successful and unresolved conflict-resolution paths and include the new route test in the dashboard API quality shard
Files changed:
.changeset/fn-5949-pr-conflict-resolution.md | 5 +
docs/dashboard-guide.md | 1 +
docs/task-management.md | 1 +
packages/dashboard/README.md | 1 +
packages/dashboard/app/api/legacy.ts | 20 ++
.../dashboard/app/components/PrCreateModal.css | 32 ++-
.../dashboard/app/components/PrCreateModal.tsx | 46 ++++
.../components/__tests__/PrCreateModal.test.tsx | 33 +++
...egister-git-github.pr-resolve-conflicts.test.ts | 186 +++++++++++++++
packages/dashboard/src/index.ts | 5 +
packages/dashboard/src/pr-conflict-resolver.ts | 258 +++++++++++++++++++++
.../dashboard/src/routes/register-git-github.ts | 225 ++++++++++++------
packages/dashboard/vitest.config.ts | 2 +-
13 files changed, 739 insertions(+), 76 deletions(-)
Fusion-Task-Id: FN-5949
Fusion-Task-Lineage: fea35fbf-6254-415c-83cc-0bc24abc911e
In-review tasks showed other tasks' files in their "files changed"
list. Task branches fork from local main, but the base capture
measured merge-base(HEAD, origin/main) — when local main carried
merged-but-unpushed task commits, the recorded base rewound past
them, and after the post-merge rebase-and-push rewrote those SHAs,
baseCommitSha..HEAD permanently swept the predecessors' files into
the new task's diff.
Extract the capture into base-commit-capture.ts, measure local main
first (origin/main fallback) to match the contamination-base sites,
and add a real-git regression suite covering local-ahead-of-origin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #1363 review findings:
- core: pausedReason was written in-memory and read by SELECT but never
persisted by the task upsert (missing column/value) nor mapped back in
rowToTask — so it was lost on every reload. Add it to both. This is the
root cause behind the workflow CLI-approval / await-input pause cycle and
also fixes token-budget / worktrunk pause reasons silently vanishing.
- dashboard: approve-cli now derives the approved command exclusively from
the task's pausedReason; a caller-supplied body.command is ignored, closing
a trust-on-first-use bypass.
- engine: await-input nodes resume only when THIS node paused the task (its
marker on pausedReason), not on any pre-existing steering comment.
- engine: write-capable custom nodes (coding/script/CLI) are refused until a
task worktree exists, so they never mutate the shared repo root before the
execute seam.
- engine: document cliSkipApproval as an intentional workflow-author-only
escape hatch; scriptName is now const (ESLint).
- tests: pausedReason round-trip coverage in store-persistence; approve-cli
body-command-ignored + no-pending-command coverage; built-in-aware list
assertion in workflow-routes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Users can now watch everything the agent does while a CE stage works, steer
it mid-stage, and read the whole conversation as a proper chat surface.
Live output:
- New host capability: CreateInteractiveAiSessionOptions.onProgress — the
engine adapter streams thinking/text deltas + tool start/end markers from
the pi agent hooks (any plugin can use this).
- Orchestrator buffers per-session live activity (merged deltas, discrete
tool lines, capped), emits throttled progress events over SSE, and
GET /sessions/:id attaches it as liveActivity for the polling fallback.
- Routes detach turn execution: start/answer/resume return immediately
(status active) and clients converge via push/poll — the turn is watchable
instead of hidden inside a blocking POST.
- Turn timeout is now INACTIVITY-based: an actively-working long turn is
never killed; a quiet one interrupts with its working trace preserved.
- On settle the trace persists into history as a condensed record.
Steering:
- Stage protocol: responses may be a direct answer, {value, comment}
(answer + guidance), or {feedback} (guidance without answering); the
system prompt instructs agents to treat steering as first-class input.
- CeFlow: guidance textarea alongside selectable questions — attach to the
clicked answer, or "Send guidance" on its own.
Q&A UI:
- Transcript no longer hides control records: past questions/answers render
as chat bubbles (option ids → labels), steering turns marked, working
traces as collapsible "Agent work" blocks, completion marker.
- Live working pane (pulse + streaming thinking/tool lines) while a turn runs.
Tests: 130 plugin tests green (14 new: live buffer/flush ordering, inactivity
watchdog survives active work, detached convergence, steering payload shapes,
transcript rendering, live pane). Engine seam tests green; plugin/core/
engine/dashboard tsc clean. Core full suite OOMs locally (known orchestrator-
shell issue) — covered by CI shards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Node inspector: CLI executor toggles between a raw command textarea and a named
script. Task modal workflow tab: the await-input banner is now interactive
(reply textarea + Submit & resume), and a new awaiting-cli-approval banner shows
the pending command with an Approve & run action.
The store/orchestrator were already multi-session (independent rows + live
handles per session); this surfaces it end to end:
- Sessions panel in the dashboard view: lists every session with stage,
status badge ("needs your input" for awaiting_input), and last activity;
stays visible while a flow is open so switching is one click. Closing a
flow returns to the overview without stopping the session.
- useCeSession.open(): adopt an existing session (pins its projectId for
answer/resume/poll); useCeSessions list hook with push-event refresh and
poll fallback while any session is mid-turn.
- DELETE /sessions/:id + orchestrator.discard(): dispose the live handle
before deleting the row (pipeline-link rows kept for task provenance);
Discard affordance on settled sessions.
- Tests: cross-session independence through one orchestrator, store delete,
route list/delete, hook open/list/remove/push/poll, view panel
open/switch/discard. 116 tests green; plugin + dashboard tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- runFeatureValidation now lazy-ensures a linked assertion (FN-5902) instead
of the removed zero-assertion auto-pass, for both task-completion and the
stranded-feature recovery path
- CONCEPTS.md: union of main's Merge-lifecycle cluster and this branch's
Missions clusters; Contract Assertion entry updated for FN-5902 semantics
- AGENTS.md: take main's docs/solutions + CONCEPTS.md pointer wording
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inspector for prompt nodes: executor picker (model via CustomModelDropdown,
agent, skill from discovered skills, CLI named script), auto-approve toggle,
per-node max retries, and a wait-for-user-input mode with a User input palette
preset. Task card shows a 'Needs input' badge for awaiting-user-input status;
the task modal workflow tab shows the node's question as a banner.
Two compounding bugs made the memory-pressure vitest auto-kill a
30-second SIGKILL sweep of anything mentioning vitest:
1. False pressure: getAvailableMemory probed os.availableMemory, which
does not exist, and silently fell back to os.freemem() — on macOS
that reads ~99% used on an idle 256GB machine, permanently above the
90% threshold. Now reads process.availableMemory() (Node 22+) and
refuses to auto-kill when only the unreliable freemem fallback is
available.
2. Overbroad targeting: pgrep -f vitest matches full command lines, so
the sweep also killed wrapper shells (zsh -c '... npx vitest run'),
monitor loops, and anything else whose argv mentions vitest —
stranding exit handlers and taking out unrelated process trees.
New shared findVitestProcessIds (@fusion/core) filters matches to
actual node executables.
Surface enumeration (all vitest-process kill/count surfaces):
- TUI memory-pressure auto-kill (controller.killVitestProcesses)
- TUI manual kill-vitest command (same method)
- dashboard POST /api/kill-vitest
- dashboard GET /api/system-stats vitestProcessCount (display)
All four now route through findVitestProcessIds.
Extend BranchGroupCard/GroupTaskModal with an Abandon action (open PRs) and
terminal merged/closed badges; promote stays completion-gated. New
fn branch-group list|show|promote (alias fn bg) reaching the same coordinator
path with createGroupPrCallback wired — agent-native parity with the dashboard
promote flow, same completion-gate rejection.
Push the single group PR's body (member checklist, x/N landed) on each member
landing via an injected SyncGroupPrFn — new updatePr/closePr GitHubClient
helpers (gh CLI + API parity); refreshPrInBackground is task-scoped/wrong
direction and intentionally not reused. Sync failures are non-fatal+retryable;
out-of-band closed/merged PRs reconcile prState instead of erroring. New
POST /branch-groups/:id/abandon closes the PR best-effort and marks the group
abandoned. Also fixes the U5-introduced stub-context regression in the U4
dashboard bridge test (missing options).
Tasks with autoMerge explicitly enabled never auto-merged when the
project-level setting was disabled: the merge enqueue gate
(allowInReviewMergeProcessing) and all 19 in-review self-healing sweeps
checked only settings.autoMerge, and the board stall-signal hydration
passed the raw global into the diagnostic gates.
Introduce allowsAutoMergeProcessing(task, settings) in core — additive
relative to the global setting so configs with global auto-merge ON are
unchanged (explicit autoMerge:false tasks still flow to the merger's
manual-required parking) — and use it at the enqueue gate, every
self-healing sweep, and the store's stall/stalled signal contexts.
Group promotion in PR mode previously flipped prState to 'open' without ever
calling GitHub — prNumber/prUrl were never populated. Add an injected
CreateGroupPrFn (mirrors the processPullRequestMerge seam, no engine→dashboard
import): coordinator creates-or-reuses exactly one PR per group, persists
prNumber/prUrl/prState, and leaves state untouched on GitHub failure so
re-promotion retries. Idempotent via persisted prNumber +
getBranchGroupByBranchName. Wired at all three CLI engine-construction sites
(daemon/dashboard/serve).
Add a React Flow (@xyflow/react) based WorkflowNodeEditor: a lazy-loaded modal
with a workflow list, a node palette (prompt/script/gate/merge-boundary),
drag-to-connect edges, a per-node inspector, and save with compile-validation
that surfaces non-linear graphs as a banner. Pure irToFlow/flowToIr mapping
round-trips the v1 IR plus editor layout. Reachable via a Graph editor button in
the Workflow Steps manager; mounted in AppModals behind a new modal-manager
flag. Adds a vendor-reactflow Vite chunk and a feature changeset.
Audit of all shared-member merge + self-healing finalize paths: routing,
merger finalize-success, and the 6 self-healing recovery paths were already
group-branch-safe (FN-5846). Found a residual of the 2026-05-23 lost-work
incident bug #2: already-merged-detector's ancestry strategy used bare
git log --grep first-hit, and the ownership regex made the conventional scope
optional (bare 'feat:' matched). Anchor attribution on trailers or task-scoped
subject; scan candidates instead of accepting the first grep hit. Adds real-git
characterization tests.
Wires the ACP runtime plugin into the published CLI (RUNTIME_PLUGIN_IDS in
tsup.config) and the on-demand BUILTIN_PLUGINS catalog (experimental), matching
the untrusted-subprocess security posture. Adds the Risk S1 default-policy
safety: an acpAllowUnrestricted acknowledgement (default false) — without it, a
blanket allow on a sensitive category is escalated to approval rather than
auto-approved under the allow-all default policy, applied in both the permission
floor and fs write gating. Adds docs/acp-contract.md (launch/readiness +
failure taxonomy), a README with the AGENTS.md-required upstream evidence
(SDK repo/docs/release/integrity), a bundle-output test for the staged plugin,
and a @runfusion/fusion minor changeset. Package green at 179 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
branch_groups.branchName is globally UNIQUE, but ensureBranchGroupForSource
only looked up an existing group by (sourceType, sourceId). When a second
mission's shared-branch triage resolved to a base branch (e.g. "main") that
another mission already owned a group for, createBranchGroup threw
"UNIQUE constraint failed: branch_groups.branchName". That error escaped
triageFeature and was swallowed by both callers (validation-failure
auto-triage and the reconcile sweep), leaving the mission's "defined"
features — including generated fix features — permanently un-triaged.
ensureBranchGroupForSource now reuses an existing open group for the same
branch name before attempting to create one, matching the established
getBranchGroupByBranchName(...) ?? ensureBranchGroupForSource(...) idiom.
Confirmed by reproducing against a snapshot of the affected mission DB:
triageFeature threw the UNIQUE error before, succeeds after.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Create the @fusion/i18n package as the authored source-of-truth: shared
i18next config (namespace split, script-aware zh-CN/zh-TW fallback, plural
setup), en base catalogs, and a generated CLI static-import map so the
terminal surface is drop-in for new locales. Add the i18next-cli workflow
(extract/sync/types/status/lint) wired as root i18n:* scripts, install the
i18next stack into dashboard + CLI, strip @fusion/i18n from the published CLI
manifest, gitignore the generated dashboard catalog tree, and add a changeset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Minimax usage panel only rendered one model row. The primary `general`
model meters quota purely via `current_interval_remaining_percent` (its
`current_interval_total_count` is 0), so the count-based `total > 0` visibility
filter dropped it entirely. The percent was also derived from count fields
rather than the authoritative `*_remaining_percent` field.
fetchMinimaxUsage now builds windows via a helper that prefers
`*_remaining_percent` (count-based fallback when absent) and skips a window only
when no quota signal exists. Each model's separate weekly quota window is now
surfaced as its own indicator alongside the interval window.
Verified against the live coding_plan/remains endpoint: 2 models (general,
video) now produce 4 windows (interval + weekly each) instead of 1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A mission feature could be left status="done" while its loopState never
advanced past "implementing" and it had no linked board task, so it was
never validated. The slice-completion gate (computeSliceStatus) correctly
refuses to count an assertion-linked done feature until its validator
passes, but nothing re-drove a task-less feature — so the slice, milestone,
and whole mission could never auto-progress.
Active-mission recovery now detects these stranded done features and re-runs
assertion validation directly (read-only judge, no board task): on pass the
feature becomes legitimately complete, on fail the normal fix-feature flow
takes over. Extracted the feature-validation path into a shared
runFeatureValidation helper used by both task-completion and recovery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>