Root cause of "the TUI keeps rendering after I get my terminal back": on
quit, dispose() called logSink.releaseConsole() (re-pointing console.* at
the real terminal) and then tui.stop() left the alt-screen and restored the
user's shell. Every log line from the slow engine/mesh/dev-server teardown
that followed then painted over the recovered prompt.
dispose() now calls a new logSink.silence() instead, which drops all sink
and console.* output from quit through process exit. Shutdown-step
diagnostics (timeShutdownStep + the watchdog stall line) are gated behind
FUSION_DEBUG_SHUTDOWN so a normal quit is pristine; the 3s hard-exit
watchdog still guarantees the process dies.
Adds a silence() regression guard to log-sink.test.ts asserting sink
methods and captured console.* both go silent across surfaces.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
runSkillsInstall already awaits the spawned child exit event, so the
fixed 100ms post-await sleeps were redundant wall-clock time (FN-5048).
Removes ~400ms of dead waits; assertions unchanged, 22 tests still pass.
Fix PR-mode auto-merge status checks to query GitHub with the project repository.
- Resolve the current project owner/repo once from the task cwd before PR status checks.
- Pass owner/repo/number to getPrMergeStatus for shared-group, task, and retry paths.
- Cover repository resolution and PR status argument behavior in lifecycle tests.
- Add a patch changeset for the published CLI fix.
Files changed:
.changeset/FN-7133-pr-merge-status-repo-args.md | 7 ++++
.../src/commands/__tests__/task-lifecycle.test.ts | 39 +++++++++++++++++++++-
packages/cli/src/commands/task-lifecycle.ts | 17 +++++++---
3 files changed, 58 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7133
Fusion-Task-Lineage: 3f9bfd65-6950-40dc-9505-53140bd6a6a1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Each graceful-shutdown teardown step (dev servers, hybrid executor,
engine manager, peer exchange, mesh, central-core) now runs through
timeShutdownStep, which records the in-flight step name. A hang leaves
that name set, so the hard-exit watchdog reports the exact culprit on
stderr before force-exiting — no repro needed. Per-step timings print to
stderr under FUSION_DEBUG_SHUTDOWN=1; otherwise only steps slower than
1s are surfaced. Folds the per-step try/catch into the wrapper so a
throwing step logs and continues instead of stranding the process.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pressing q/Ctrl+C in the TUI routes through SIGINT so the dashboard's
graceful shutdown runs (kills dev-server process groups, engines, mesh,
central-core). That shutdown awaits several teardown steps with no
timeout, so a single hung step left process.exit(0) unreachable: the
process never exited and the still-alive dashboard kept writing output
onto the restored shell. The shutdownInProgress guard also swallowed
repeat signals, so mashing q could not escape.
Both shutdown() and devShutdown() now arm an unref'd 3s hard-exit
watchdog on the first signal and force an immediate process.exit(0) on a
second signal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- merger-ai: resolve+persist concrete landedSha when a sub-repo is recognized
already-landed via the Fusion-Task-Id trailer fallback, so finalize no longer
drops it and mis-finalizes a fully-landed workspace task as a no-op
- project-engine: manual-merge land-lease busy errors reject the resolver without
burning mergeRetries; clear stale busy-reenqueue counter on real partial land;
persist retry count before arming the backoff timer (fail closed on write error)
- cli/dashboard + task: use shared isWorkspaceTask predicate instead of inlining
- base-commit-capture: POSIX single-quote shell escaping for integration ref
- git-repository: validate workspace.json repos elements are strings
- merger-ai: drop dead store param from landOneRepo
- tests: assert the 60s backoff cap across cycles; exercise the real runAiMerge
merge door; fix non-git-root assertion; re-export real workspace error classes
in the merger-ai mock (fixes 24 pre-existing instanceof-undefined failures);
remove generic fake-timer smoke test now covered by the live engine assertion
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5-persona review of the Phase-C per-repo merge loop. No P0; the no-push invariant
and retry/park accounting verified clean. Fixed:
Land mechanics (merger-ai.ts / active-session-registry.ts):
- persistRepoLandedSha no longer swallows the DB write: a failed landedSha write
after the ref advanced now escalates to WorkspacePartialLandError so the engine
parks/retries instead of silently re-landing (duplicate squash). isRepoLanded
gains a landedSha-independent fallback — it scans the integration ref for this
task's Fusion-Task-Id trailer (a squash commit is NOT a branch descendant, so a
branch-ancestor check is provably wrong), so an actually-landed repo is skipped
on retry.
- The land lease is now taskId-aware across kinds: any foreign-task holder on a
sub-repo path is contention (a merging task can't run over an executing task's
acquire lease), and registerPath throws ActiveSessionPathHeldByForeignTaskError
instead of silently clobbering a different task's entry.
- The per-repo loop is wrapped in try/finally(setStatus(null)) so the busy/partial
throws can't leave the task stuck 'merging'. WorkspacePartialLandError is a real
exported class (not a .name-mutated Error). finalizeWorkspaceTask re-reads fresh
and no longer swallows the mergeDetails write (TOCTOU). isRepoLanded exported for
Phase D.
Dispatch + doors (project-engine.ts / dashboard.ts / task.ts / @fusion/core):
- getTask-null in the partial-land catch fails closed (park) instead of defaulting
retries to 0 and scheduling an indefinite retry storm.
- The merge-confirmed reachability fast-path skips workspace tasks (its
representative commitSha is a sub-repo squash sha, unreachable in the root cwd —
it was demoting fully-merged tasks); they're verified by per-repo landedSha.
- The CLI/dashboard merge doors now return merged:true on full land (were hardcoded
merged:false). WorkspaceRepoLandBusyError re-enqueues with backoff WITHOUT burning
the mergeRetries quota (bounded busy counter) so contention can't park a healthy
task. Backoff capped at 60s. shouldRetryWorkspacePartialLand folded into
shouldRetryAutoMergeConflict. Catch switched to instanceof. New canonical
isWorkspaceTask predicate in @fusion/core.
Gate green: build, typecheck, lint, test:gate (649+58); workspace-merger + oracle
+ project-engine 174.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts the per-repo land mechanics out of runAiMerge's inline clean-room
closure into an exported landOneRepo(store, repoRootDir, branch, integrationBranch,
ctx): pre-merge prune (rooted at the sub-repo), the clean-room temp worktree,
mergeAndReview, landSquash, and the CAS concurrent-advance retry that advances ONE
local integration ref — no remote push. runAiMerge is rewired as the single-repo
caller (its task-global finalization unchanged); the merger-ai suite (56 tests)
stays green as the byte-for-byte oracle.
landWorkspaceTask loops a workspace task's acquired sub-repos (sorted keys),
re-resolving each repo's integration branch with the shared override stripped
({...settings, integrationBranch: undefined, baseBranch: undefined}) so each
sub-repo lands on its own origin/HEAD, calls landOneRepo per repo, and aggregates
repo-tagged results — land-as-you-go on each repo's LOCAL ref (D2/D5). It does NOT
finalize/move the task (finalize-once + landed-tracking + idempotent retry are U2).
Door routing (KTD2): the engine dispatch and the user-facing CLI `fn task merge`
+ dashboard merge doors route workspace tasks to landWorkspaceTask so manual merge
works; store.mergeTask, aiMergeTask, and the runAiMerge chokepoint guard keep
throwing WorkspaceTaskMergeError as defense-in-depth.
New two-repo fixture tests: both repos land + no-push assertion, per-repo
override-stripped resolution onto distinct branches, repo-B conflict partial land
(task not moved), defense-in-depth throws. Gate green: typecheck, lint, build,
test:gate (649+58).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applies ce-code-review (autofix) feedback — 5 reviewers, P1s corroborated.
F1 [P1, ×4 reviewers] Guard the merge chokepoint, not just the 4 doors. The
per-caller `getTask().catch(()=>null); if(t) assert` pattern failed open on a
transient read, and runAiMerge re-read the task unguarded — so a workspace
task could reach git work against the non-git root. Added a named
WorkspaceTaskMergeError and call assertNotWorkspaceTaskMerge inside runAiMerge
(the sole merge path) and the deprecated aiMergeTask body; door guards remain
as fast-fail defense-in-depth.
F2 [P1] The dispatch catch treated the guard throw as a merge failure and set
mergeRetries=MAX, permanently blocking manual retry. It now recognizes
WorkspaceTaskMergeError and parks without burning retries.
F3 [P2] Deprecation-warning test asserted toBeLessThanOrEqual(1) — vacuously
true on zero emissions. Now resets the per-project flag and asserts the
warning fires exactly once and not again on a second deterministic merge.
F6 [P2] The once-per-process warning flag suppressed the notice for all other
projects in a multi-project host; now keyed per project (Set by cwd).
F5/F7/F8 [P3] @deprecated propagated to the aiMergeTask barrel re-export; CLI
runTaskMerge guard moved inside the formatted try/catch; FNXC placeholder
timestamps corrected; test .at(-1) -> length index.
Documented as residual (deferred to master-plan U8, not bugs in U0's window):
self-healing auto-finalize + store.mergeTask are additional merge-completing
paths not hardened here — workspace tasks are not end-to-end runnable until
master-plan Phase A, and U8 makes self-healing workspace-aware.
Gate green: typecheck (29 projects), lint, build, test:gate (649+58),
affected tests (206+4).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make runAiMerge (the FN-5633 clean-room AI merge path, already the default)
the sole merge path; soft-deprecate the legacy aiMergeTask pipeline. Phase 0
of the workspace-mode master plan — a standalone merge-consolidation refactor
that lands first so downstream workspace work targets one merge function.
U1 — collapse the dispatch: project-engine.ts now calls runAiMerge
unconditionally; the two direct callers that bypassed the dispatch
(dashboard.ts onMergeImpl in --no-engine mode, task.ts runTaskMerge /
`fn task merge`) now route to runAiMerge too. Export runAiMerge from
@fusion/engine.
U2 — soft-deprecate: aiMergeTask is @deprecated (body retained for a later
deletion pass; shared helpers runAiMerge imports, e.g.
captureSingleCommitLandedMetadata, left intact). merger.mode "deterministic"
is annotated deprecated and made inert (type + field kept — published
@runfusion/fusion surface); the dispatch logs a one-time deprecation warning
and routes to runAiMerge. Changeset added (minor).
U3 — R7 workspace merge-boundary guard: shared @fusion/core predicate
assertNotWorkspaceTaskMerge(task) rejects tasks with populated
workspaceWorktrees at all four merge entry points (dispatch, store.mergeTask,
onMergeImpl, runTaskMerge) with an error naming master-plan U6. Covers the
window until per-repo merge support lands; U6 removes it.
U4 — deterministic-mode blast-radius audit: no production project, CI config,
or seeded setting pins merger.mode "deterministic"; only four engine tests
used it to drive the dispatch to aiMergeTask as a mockable seam — migrated to
mock runAiMerge instead. Other module-level aiMergeTask mocks were dead under
the default "ai" mode or test aiMergeTask directly (body retained), so they
are unaffected.
Also removes an unused acquireWorkspaceRepoWorktree import inherited from the
foundation branch (executor.ts) that was failing lint; master-plan U1 re-adds
it with its per-repo usage.
Merge gate green: lint, typecheck (29 projects), build, test:gate
(649 + 58), plus the migrated (114) and new predicate (4) tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dashboard's discovered-skills catalog was built only from the
disk-scanning package manager, so plugin-contributed skills (e.g.
compound-engineering ce-*) — which the engine materializes for executor
sessions separately — never appeared in the editor. Built-in workflow
nodes that reference them (builtin:compound-engineering) showed
"— select skill —" / unresolved.
- skills-adapter: merge plugin skill contributions into the discovered
list (deduped by bare name) via an optional getPluginSkills thunk;
add shared bareSkillName normalizer.
- wire getPluginSkills into all three server entry points: serve,
daemon, and dashboard (the UI-serving command — verified via live
end-to-end that omitting it left the editor catalog empty).
- node-summary + WorkflowNodeEditor: resolve namespaced skillNames
(compound-engineering:ce-work) against the catalog's two-segment
names (ce-work/SKILL.md) so nodes display and select the right skill.
Verified: dashboard + CLI typecheck, 136 dashboard tests, and a live
dashboard E2E (discovered skills 0→11; Plan node resolves to "ce-plan"
in both the canvas label and the inspector dropdown).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep the Agents view focused when starting the selected agent from the dashboard TUI.
- Treat `s` as the selected-agent start command inside the Agents interactive view.
- Preserve `m` as the universal Main/status shortcut and keep the `s` alias outside Agents.
- Add regression coverage for Agents, non-Agents, status, and empty Agents shortcut behavior.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-6491-tui-agents-start-key.md | 5 +
.../commands/dashboard-tui/__tests__/app.test.tsx | 102 ++++++++++++++++++++-
packages/cli/src/commands/dashboard-tui/app.tsx | 9 +-
3 files changed, 112 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-6491
Fusion-Task-Lineage: 82a9acab-6e4a-400c-80ee-54d051245b57
Refresh stored Claude OAuth credentials before reporting dashboard auth status or resolving model auth so users do not need to repeatedly re-login after access-token expiry.
Coalesce concurrent refresh attempts, prevent stale refreshes from overwriting newer logins, and route CLI dashboard/serve/daemon/onboard auth wiring through the shared refresh-capable storage.
Restore Pi-upgraded workspaces to keep Fusion resources accessible and recover title generation when configured models go stale.
- Mark read-only Fusion/Pi provider settings views as project-trusted so extension loading keeps working after Pi upgrades.
- Retry task title summarization with automatic model resolution when the configured provider model is missing from the Pi registry.
- Cover provider settings trust behavior and stale summarizer fallback paths with regression tests.
- Document the read-only settings trust contract and add a patch changeset.
Files changed:
.changeset/fn-6218-pi-upgrade-regressions.md | 5 +
docs/settings-reference.md | 2 +
docs/task-management.md | 2 +
.../commands/__tests__/provider-settings.test.ts | 16 ++++
packages/cli/src/commands/provider-settings.ts | 5 +
packages/core/src/__tests__/ai-summarize.test.ts | 102 +++++++++++++++++++++
packages/core/src/ai-summarize.ts | 85 ++++++++++++-----
.../src/__tests__/pi-create-fn-agent.test.ts | 32 +++++++
packages/engine/src/pi.ts | 7 +-
9 files changed, 233 insertions(+), 23 deletions(-)
Fusion-Task-Id: FN-6218
Fusion-Task-Lineage: 3d6aca32-a56a-43a0-a57b-8e6abc839ce9
Handle in-review tasks with status none as retryable when execution or merge retry state is stranded.
- treat in-review tasks with status "none" plus incomplete execution or prior merge attempts as retryable across the CLI, extension, and dashboard routes
- send stranded execution retries back to todo with preserved progress and keep stranded merge retries in-review while clearing merge retry state
- add CLI, extension, and dashboard regression coverage and publish a patch changeset for @runfusion/fusion
Files changed:
.changeset/fuzzy-ravens-retry.md | 5 +
packages/cli/src/__tests__/extension.test.ts | 147 +++++++++++++++++++++
packages/cli/src/commands/__tests__/task.test.ts | 147 +++++++++++++++++++--
packages/cli/src/commands/task.ts | 59 ++++++++-
packages/cli/src/extension.ts | 39 ++++--
packages/dashboard/src/__tests__/routes-tasks-ops.test.ts | 134 +++++++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 30 +++--
7 files changed, 528 insertions(+), 33 deletions(-)
Fusion-Task-Id: FN-6166
Fusion-Task-Lineage: cf78b180-ded4-4b6f-8e98-35e6f309e1d4
Restore mobile auto-merge dashboard stability and broaden regression coverage.
- share the mobile media query between viewport detection and board stabilization so landscape phones stay on the mobile path
- add an integration regression suite for toggling auto-merge across mobile, tablet, desktop, rollback, and task review surfaces
- expand dashboard and CLI tests, keep the new published changeset, and preserve related vitest coverage lists and safety comments
Files changed:
$(git diff --cached --stat)
Fusion-Task-Id: FN-5972
Fusion-Task-Lineage: f3a3bbae-21c2-451b-b24f-848cc12d0542
Adds the fn pr noun — create/show/list/respond/approve/retry/merge/close/
automerge — routing to the same store/engine/release paths as the U7
dashboard routes (surface-parity pinned by a consistency test). Retires
fn task pr-create (dispatch removed; deprecated re-export kept for
importers). Changeset: @runfusion/fusion minor. 22 command tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the fix-or-disagree agent loop behind pr-respond: batched
one-run-per-cycle over actionable threads (filtering resolved/outdated/
self/bot), with prompt-injection delimiting of untrusted comment bodies,
viewer-authenticated anti-spoof markers, a pre-push secret scan, fast-
forward-only push (no force-push path) with non-ff abort+re-batch, reply+
resolve, commit-last thread-state persistence with marker+SHA crash
recovery (R15), and an iteration cap on responseRounds (R8). GitHub/git/
agent ops injected; engine stays dashboard-import-free. Adds GraphQL
getPrReviewThreadsDetailed + getViewerLogin to the client. 23 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds PrReconciler — a per-repo, self-owned polling loop (started from the
runtime layer in project-engine.ts, NOT the scheduler) that ETag-probes
GitHub, deep-fetches on change, persists mirror state, clears unverified
on first reconcile, and fires releaseHeldTaskByEvent(github:pr-<event>)
for transitions (changes-requested/approved/conflict/conflict-cleared/
merged/closed). Drops terminal entities; persists an audit event on error.
GitHub ops injected via PrReconcileGithubOps at the 3 CLI sites; engine
never imports the dashboard client. scheduler.ts stays PR-free (R20),
pinned by a regression test. 8 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>