The release-authorization classifier substring-matched release signals
(notably `scripts/release.mjs`) even inside disclaimer clauses that
explicitly say the task performs NO release/publish. AI-authored specs
routinely append such disclaimers, so revert/undo/UI tasks (FN-7525,
FN-7554, FN-7556) were parked in awaiting-release-authorization with no
in-band exit — their non-user sources (agent_heartbeat/api) make the
authorization marker inert.
classifyReleaseTask now strips negated release-disclaimer clauses before
signal matching. Genuine "run pnpm release"/"publish @runfusion/fusion"
intent lives in a non-negated clause and still trips the gate. Tests
cover all three real repro shapes plus every documented signal in both
its negated (not release-class) and actionable (still release-class) form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unifies OAuth expiry detection so expired Claude subscription logins correctly show as disconnected with a re-login prompt, and adds a proactive engine-side scheduler that refreshes tokens before they expire.
- Share expiry-detection logic between OAuthExpiryMonitor and the /api/auth/status route so both agree on when a token is expired.
- Add engine-side oauth-refresh-scheduler that proactively refreshes OAuth tokens ahead of expiry, wired into project-engine (guarded by skipNotifier).
- Extend auth-storage with the helpers needed for expiry checks/refresh.
- Add tests covering routes-auth status detection, auth-storage expiry helpers, and the new refresh scheduler.
- Document the new behavior in dashboard-guide.md and settings-reference.md.
- Add changeset for the user-facing fix.
Files changed:
.../fn-7574-oauth-expiry-detection-refresh.md | 7 +
docs/dashboard-guide.md | 4 +
docs/settings-reference.md | 4 +
.../dashboard/src/__tests__/routes-auth.test.ts | 76 +++++++++++
.../dashboard/src/routes/register-auth-routes.ts | 25 +++-
packages/engine/src/__tests__/auth-storage.test.ts | 60 +++++++++
packages/engine/src/auth-storage.ts | 14 +-
.../__tests__/oauth-refresh-scheduler.test.ts | 141 ++++++++++++++++++++
packages/engine/src/notification/index.ts | 3 +
.../src/notification/oauth-refresh-scheduler.ts | 143 +++++++++++++++++++++
packages/engine/src/project-engine.ts | 14 +-
11 files changed, 488 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7574
Fusion-Task-Lineage: 59996eac-c070-4992-9727-d066c6934b69
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Manual plan approval now skips re-asking for approval when a re-specification produces an identical plan to one already approved.
- Add nullable Task.approvedPlanFingerprint field with DB migration 139 to track the approved PROMPT.md fingerprint
- Skip re-parking at awaiting-approval when replan/plan-review-retry/self-healing rebound yields the same plan fingerprint as before
- Require fresh approval when the plan content changes or when a plan is rejected
- Leave Release Authorization, Workflow Plan Review, and auto-approve-all behavior unchanged
- Add/extend tests across core (db, plan-approval, store-persistence), engine (triage), and dashboard (routes-github) to cover fingerprint comparison and idempotent re-approval
- Update docs (settings-reference.md, workflow-steps.md) to describe the idempotent approval behavior
- Add changeset for @runfusion/fusion (patch)
Files changed:
.changeset/fn-7569-plan-approval-idempotent.md | 7 +
docs/settings-reference.md | 2 +-
docs/workflow-steps.md | 2 +
packages/core/src/__tests__/db.test.ts | 54 +++++++
packages/core/src/__tests__/plan-approval.test.ts | 31 +++-
.../core/src/__tests__/store-persistence.test.ts | 39 +++++
packages/core/src/db.ts | 22 ++-
packages/core/src/index.ts | 2 +-
packages/core/src/plan-approval.ts | 23 +++
packages/core/src/store.ts | 20 ++-
packages/core/src/types.ts | 13 ++
.../dashboard/src/__tests__/routes-github.test.ts | 69 +++++++-
.../src/routes/register-task-workflow-routes.ts | 37 ++++-
packages/engine/src/__tests__/triage.test.ts | 178 ++++++++++++++++++++-
packages/engine/src/triage.ts | 58 +++++--
15 files changed, 527 insertions(+), 30 deletions(-)
Fusion-Task-Id: FN-7569
Fusion-Task-Lineage: 7d3855ae-6f45-4571-90db-cf1ae3b541dd
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds all-or-nothing git-revert support for multi-repo workspace tasks and wires it into the existing revert route/AI-undo/per-sha machinery.
- Add `resolveWorkspaceTaskRevertCommits` and `revertWorkspaceTask` to `packages/engine/src/task-revert.ts`, dry-run classifying every sub-repo first and only committing per-repo revert commits when every sub-repo is clean/already-reverted; any conflicting sub-repo rolls back every already-committed sub-repo.
- Extract shared `applyAndCommitRevert" apply/commit machinery (built on the existing `applyRevertNoCommit` primitive) so the workspace path reuses the same commit-message/trailer contract as the single-repo path.
- Add a defensive `isWorkspaceTask` guard to `performTaskRevert` so workspace tasks can never be silently reverted through the single-repo path.
- Wire `POST /api/tasks/:id/revert` (register-task-workflow-routes.ts) to dispatch workspace tasks to `revertWorkspaceTask`, preserving the existing `mode` (git/ai/auto) and AI-undo-fallback contract for workspace conflicts.
- Export the new workspace revert types/functions from `packages/engine/src/index.ts`.
- Add route-dispatch and real-git workspace revert test coverage; update docs and add a changeset.
Files changed:
.changeset/fn-7547-workspace-task-revert.md | 7 +
docs/task-management.md | 8 +-
packages/dashboard/src/__tests__/task-revert-route.test.ts | 102 +++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 76 +++-
packages/engine/src/__tests__/task-revert.workspace.real-git.test.ts | 272 +++++++++++++
packages/engine/src/index.ts | 6 +
packages/engine/src/task-revert.ts | 448 ++++++++++++++++++++-
7 files changed, 897 insertions(+), 22 deletions(-)
Fusion-Task-Id: FN-7547
Fusion-Task-Lineage: b1b5eeda-06fd-43c1-8163-74b62c77b000
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Disambiguate release-authorization approval holds from manual plan-approval holds so auto-approve no longer appears broken.
- Add `Task.awaitingApprovalReason` (`"release-authorization" | null`) to distinguish the release-authorization gate from the independent manual plan-approval gate, both of which set `status: "awaiting-approval"`.
- Stamp `awaitingApprovalReason: "release-authorization"` when the release gate blocks a task, and explicitly clear it (`null`) when the manual plan-approval gate parks the task, so a stale reason never survives a replan.
- Add DB migration/persistence support for the new column in `db.ts`/`store.ts`/`types.ts`.
- TaskCard/TaskDetailModal now render a distinct status for release-authorization holds and suppress the generic manual Approve/Reject affordance for them.
- Add i18n string and docs updates (`settings-reference.md`, `workflow-steps.md`) plus a changeset.
- Extend regression tests in db, triage, TaskCard, and TaskDetailModal to cover the new reason field and disambiguated UI.
Files changed:
$(git diff --cached --stat)
Fusion-Task-Id: FN-7559
Fusion-Task-Lineage: 0b37cbf0-40a4-4165-8088-482ed365ba19
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds an opt-in per-sha commit granularity mode to the task-revert git path, alongside its default squash behavior, and merges it cleanly with the existing FN-7524 AI-undo mode support.
- Add `TaskRevertGranularity` ("squash" | "per-sha") and thread an optional `granularity` option through `performTaskRevert`/`PerformTaskRevertOptions`.
- Factor a shared `applyRevertNoCommit` primitive (stage + no-op/conflict detection) used by both the squash and new per-sha apply paths.
- `"per-sha"` creates one attributed `revert(FN-xxxx): ...` commit per original sha (each with its own `Fusion-Task-Id` trailer and audit line), skipping no-op shas without empty commits; a mid-batch conflict rolls the whole batch back to the pre-call HEAD.
- Extend `TaskRevertResult`'s clean shape with `revertCommitShas: string[]` (all created commits) alongside the existing `revertCommitSha`.
- `POST /api/tasks/:id/revert` accepts an optional `granularity` request-body field (default `"squash"`, validated, 400 on unknown values) and forwards it to the engine service; documented alongside the existing `mode` (git/ai/auto) contract.
- Add real-git and route-level test coverage for per-sha creation, no-op skipping, default-squash behavior, and mid-batch conflict rollback.
- Update docs/task-management.md's revert section and add a changeset.
Files changed:
.changeset/fn-7548-per-sha-revert-granularity.md | 7 +
docs/task-management.md | 3 +-
packages/dashboard/src/__tests__/task-revert-route.test.ts | 46 +++++-
packages/dashboard/src/routes/register-task-workflow-routes.ts | 51 ++++--
packages/engine/src/__tests__/task-revert.real-git.test.ts | 124 +++++++++++++++
packages/engine/src/index.ts | 2 +
packages/engine/src/task-revert.ts | 176 +++++++++++++++++----
7 files changed, 359 insertions(+), 50 deletions(-)
Fusion-Task-Id: FN-7548
Fusion-Task-Lineage: b9548f5e-fcc2-45d4-98e0-dd7340928208
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds an AI-undo fallback to the revert route: when a git-based revert conflicts or is unsupported, an ordinary board task is created to perform the undo via AI instead of a forced/failed git write.
- POST /tasks/:id/revert now accepts an optional `{ mode?: "git" | "ai" | "auto" }` body (default "auto"); unknown values reject with 400.
- "git" preserves the FN-7523 git-only contract unchanged; "ai" always creates the AI-undo task; "auto" tries git first and falls back to AI only on a conflicting or unsupported (e.g. workspace) result — needsHuman (autoMerge:false) never triggers the fallback.
- New engine helpers in task-revert.ts: `createAiUndoTask`, `buildAiUndoTaskDescription`, `REVERT_OF_METADATA_KEY`, plus `AiUndoTaskResult`/`CreateAiUndoTaskDeps` types, exported from packages/engine/src/index.ts.
- The AI-undo task is created via the normal triage-column `store.createTask` path with no dependency on the source task, referencing the source task's mission, id, and landed files, and instructing an undo commit using the `revert(FN-xxxx): ...` convention.
- New core `TaskStore.findOpenRevertTaskForSource` backs an idempotency guard: a repeated call while an AI-undo task is still open returns the same `createdTaskId` with `alreadyOpen: true` instead of creating a duplicate.
- Updated docs/task-management.md's revert section to document the git path + AI-undo fallback contract.
- Added a minor changeset for the @runfusion/fusion release notes.
- Added/extended tests: packages/engine/src/__tests__/task-revert-ai-undo.test.ts (new) and packages/dashboard/src/__tests__/task-revert-route.test.ts (extended) covering mode validation, auto-fallback-on-conflict, forced "ai" mode, and the duplicate-open-task guard.
Files changed:
.changeset/fn-7524-ai-undo-revert.md | 7 +
docs/task-management.md | 13 +-
packages/core/src/store.ts | 31 +++++
packages/dashboard/src/__tests__/task-revert-route.test.ts | 143 ++++++++++++++++++++-
packages/dashboard/src/routes/register-task-workflow-routes.ts | 75 +++++++++--
packages/engine/src/__tests__/task-revert-ai-undo.test.ts | 114 ++++++++++++++++
packages/engine/src/index.ts | 5 +
packages/engine/src/task-revert.ts | 117 ++++++++++++++++-
8 files changed, 487 insertions(+), 18 deletions(-)
Fusion-Task-Id: FN-7524
Fusion-Task-Lineage: 64dfedcf-c286-4c46-8cf8-51ec5e668bf7
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Two related mission-loop fixes, both observed wedging a live autonomous
board.
### 1. Triage ignores `mission.branchStrategy` when `branchAssignment`
is omitted (dashboard)
`resolveBranchAssignmentContext` fabricated `{ mode: "shared" }` for
absent input, so the mission triage routes (`triage`, `triage-all`)
always passed an explicit `assignmentMode` into
`missionStore.triageFeature`/`triageSlice`. That defeats the store's
fallback — `branchOptions?.assignmentMode ??
strategyDefaults.assignmentMode` — so a mission configured with
`branchStrategy: auto-per-task` still produced a **shared** branch
group, named after the base branch.
docs/missions.md documents the intended behavior: missions "can also
persist a `branchStrategy` used whenever triage is triggered without
explicit branch options."
**Fix:** absent input resolves to `{ mode: undefined }`; callers pick
their own default. The mission routes need no change (undefined now
flows through to the strategy fallback). The two planning-subtask call
sites keep their historical `shared` default via a destructure default,
since they have no strategy to fall back to. Explicit
`branchAssignment.mode` is unchanged and still overrides the strategy.
**Observed impact:** with `baseBranch: main`, every triaged task joined
a shared group literally named `main` — tasks tried to push to `main` /
open PRs with head=main base=main, and the whole group wedged in
`merge-retries-exhausted`. The only workaround was remembering to send
`{"branchAssignment": {"mode": "per-task-derived"}}` on every triage
call, which silently ignores the mission's configured strategy the rest
of the time.
### 2. Task-completion validation runs for parked missions (engine)
`MissionExecutionLoop.processTaskOutcome` validated every completed
feature-linked task with no mission-status check — unlike
`recoverActiveMissions`, which already skips missions with `status !==
"active"`. A parked mission (`status: planning`) kept minting validator
runs, and on validator failure, new "Fix:" features — for tasks that
completed after parking. On our board a stale validator workspace
produced a `Fix: → Fix: Fix: → Fix: Fix: Fix:` spiral of bogus features
for already-merged work; the only mitigation was re-parking the mission
after every release and manually archiving the minted features.
**Fix:** gate `processTaskOutcome` on the resolved mission being active,
mirroring the `recoverActiveMissions` guard. The gate sits before the
`needs_fix → implementing` transition so an inactive mission's features
get zero state mutation; the skip logs a `warning` mission event
(`validation_skipped_mission_inactive`) so it's visible in the mission
log. Features that don't resolve to a mission keep the current behavior.
(Out of scope but worth noting: the validator that triggered the spiral
was judging merged work against a stale workspace checkout — that
freshness issue is a separate problem this PR doesn't attempt.)
## Tests
- `branch-selection.test.ts` — updated: absent/`{}` input resolves
`mode: undefined`; explicit modes and the bad-mode error unchanged.
- `mission-execution-loop.test.ts` — two new tests: parked mission skips
validation and logs the warning event; active mission still validates.
- Existing `mission-store.test.ts` coverage ("uses mission
branchStrategy … when branch options are omitted", explicit `shared`
override still creates a group) pins the store side end-to-end — those
pass unchanged, as do the planning/branch-group route suites (182 tests)
and full workspace `pnpm typecheck`.
Changeset included (`patch`, category `fix`).
🤖 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**
* Branch selection now keeps an unspecified mode unset and falls back to
the mission’s configured branch strategy where appropriate.
* Task outcome processing now skips validation for missions that are not
active, preventing unnecessary follow-up actions.
* Added coverage for branch selection and mission execution behavior to
verify the updated handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Wires PlannerOverseerMonitor/PlannerRecoveryController decision points (human-control withholds, confirmation requests/resolutions, and related overseer stages) to the FN-7520 emitOverseer* façade using the real TaskStore, so the planner-oversight intervention timeline now populates from real engine activity instead of staying empty.
- Add onConfirmationResolved handler to PlannerRecoveryController, invoked (best-effort, audit-only) from resolveConfirmation for both approved and denied outcomes.
- Wire project-engine.ts to call emitOverseerObservation/emitOverseerEscalation/emitOverseerConfirmation at the real engine decision points, deduped per (task, stage[, signal]).
- Add planner-overseer-intervention-wiring.test.ts covering the new wiring end-to-end.
- Update docs/architecture.md to reflect the wiring.
- Add changeset fn-7551-overseer-timeline-wiring.md (patch).
Files changed:
.changeset/fn-7551-overseer-timeline-wiring.md | 7 +
docs/architecture.md | 2 +-
.../planner-overseer-intervention-wiring.test.ts | 319 +++++++++++++++++++++
packages/engine/src/planner-recovery-controller.ts | 36 +++
packages/engine/src/project-engine.ts | 248 +++++++++++++++-
5 files changed, 607 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7551
Fusion-Task-Lineage: 8bcd103e-8797-4ef5-9b68-bd2daec8d26b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Branch-group completion no longer silently drops archived-but-unlanded members, which previously let genuinely-incomplete groups be flagged complete and promoted.
- listTasksByBranchGroup now scans with includeArchived:true so archived members stay counted in the group's total instead of dropping out silently
- ArchivedTaskEntry gains a persisted mergeDetails snapshot so an archived member that had already landed is still distinguished from one that never landed
- store.ts archival paths (task->archive projection) now carry mergeDetails through so isBranchGroupMemberLanded keeps working post-archival
- Added regression coverage in branch-group-store.test.ts and group-merge-coordinator.test.ts for archived-landed and archived-unlanded gating
- Added changeset documenting the fix as a patch-level bug fix
Files changed:
.changeset/fn-7534-branch-group-archived-member.md | 7 +
docs/dashboard-guide.md | 2 +
packages/core/src/__tests__/branch-group-store.test.ts | 77 +++++++++++
packages/core/src/store.ts | 30 ++++-
packages/core/src/types.ts | 11 ++
packages/engine/src/__tests__/group-merge-coordinator.test.ts | 148 ++++++++++++++++++++-
6 files changed, 273 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7534
Fusion-Task-Lineage: 510af857-ce08-49a0-a2a1-41b3ad473804
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
processTaskOutcome validated every completed feature-linked task with no
mission-status check, unlike recoverActiveMissions (which already skips
missions with status !== "active"). A parked mission (status=planning) kept
minting validator runs — and "Fix:" features on failure — for tasks completed
after parking, spiraling Fix^n features from a stale validator workspace.
Gate processTaskOutcome on the resolved mission being active, before the
needs_fix -> implementing transition so parked missions get zero feature
state mutation. The skip logs a warning mission event
(validation_skipped_mission_inactive). Features that don't resolve to a
mission keep the current behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What
Adds a new built-in workflow **Coding (Ideas)** — a capture-first
variant of the default coding pipeline that puts a manual **Ideas (no
AI)** intake in front of a merged **Todo** (planner + capacity) column.
The board becomes five stages, each staffed by a distinct role:
```
Ideas (no AI) → Todo (planner) → In-progress (coder) → In-review (reviewer) → Done
```
## Why
The current board parks un-worked cards in a passive **Todo** column
where no agent is active. Operators asked for a way to (1) capture ideas
without the engine auto-planning them, and (2) collapse the triage/todo
split so every visible column has an agent working it — planning now
happens *in* Todo. A "Ready" badge distinguishes planned cards waiting
for a capacity slot from freshly promoted unplanned ones.
## How it works
1. **Create** a task against Coding (Ideas) → it lands in **Ideas**
(`autoTriage:false` intake). The triage service ignores it — no AI runs.
2. **Start** (button on the card, or drag) moves it to **Todo**. The
triage poll discovers the unplanned card (bootstrap-stub PROMPT.md) and
plans it in place.
3. While planning the card shows **Planning**; once the spec is written
it shows **Ready** and waits for an in-progress slot under the normal
capacity hold.
4. From Todo onward the graph is identical to the default Coding
workflow (stepwise execution → optional code review → merge).
## Engine changes
| Surface | Change |
|---|---|
| `createTask` (`store.ts`) | Lands cards in the workflow's intake
column (`resolvedEntryColumn`) instead of hardcoding `"triage"`. Default
workflow is byte-identical (intake resolves to `"triage"`).
Bootstrap-prompt check generalized to all pre-planning columns. |
| Triage poll (`triage.ts`) | Also discovers unplanned `todo` tasks
(bootstrap-stub prompt); `finalizeApprovedTask` skips the redundant
triage→todo move for in-place planning; planning-concurrency counter
covers both columns. |
| Scheduler (`scheduler.ts`) | Skips `todo` tasks with
`status:"planning"` or a bootstrap-stub prompt so unplanned cards are
never dispatched. |
| TaskCard (`TaskCard.tsx`) | **Start** button on ideas cards; **Ready**
badge on planned todo tasks. |
| Board (`board-workflows.ts`) | `ideas` column label. |
All engine changes are **gated** — they only affect workflows whose
intake is not `"triage"`, so the default Coding workflow and every
existing built-in are byte-identical in behavior.
## Tests
- `builtin-coding-ideas-workflow-ir.test.ts` *(new)* — column set,
intake trait (`autoTriage:false`), merged todo traits, node re-homing
(start→ideas, planning→todo), optional-group defaults, round-trip.
- `store-create-intake-column.test.ts` *(new)* — createTask lands in
`ideas` for explicit + default selection, `triage` for the default
workflow, writes a bootstrap prompt.
- Updated `builtin-workflows.test.ts` catalog-order assertion for the
new entry.
## Verification
- Typecheck: core ✓ engine ✓ dashboard ✓
- Lint ✓ · Changeset format ✓ (`minor`)
- Merge gate (`test:gate`): 321 engine-core + 63 CI-shape ✓
- Regression suites: triage (39), concurrency (165),
movement/migration/hooks (259), builtin workflows (65), store-create
(54) — all green
- `verify:fast`: workspace build + CLI build + boot smoke (`fn --help` +
real `/api/health`) ✓
## Changeset
`.changeset/fn-coding-ideas-workflow.md` — `@runfusion/fusion: minor`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a new “Coding (Ideas)” builtin workflow with an Ideas intake
stage and merged planning flow.
* Updated task cards to support a **Start** action and show a **Ready**
badge for qualifying planning-stage tasks.
* **Bug Fixes**
* Tasks created for the Ideas workflow now persist into the correct
entry column and get the right prompt bootstrapping.
* Scheduler and triage avoid promoting/releasing unplanned todo tasks
that still contain the bootstrap prompt stub, and stale planning is
cleaned up across the merged intake flow.
* **Tests**
* Added coverage for the new builtin workflow IR and create-task intake
wiring.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Gate bootstrap prompt to entry column/triage only, not every non-execution
column, so direct createTask({column:'todo'}) keeps generateSpecifiedPrompt.
- Guard the workflow-column hold-release dispatch path (reserveSlot) against
planning-status and bootstrap-stub todo tasks, matching the legacy filter.
- Extend clearStaleSpecifyingStatuses startup sweep to the todo column so a
restarted in-place planning task does not hold a maxTriageConcurrent slot.
- Gate the Start button on the intake column flag instead of the literal
'ideas' id, so any manual-intake workflow gets the affordance.
- Add regression test: direct todo create must not get a bootstrap stub.
Require generated task specs to summarize the requested before-to-after transformation near the top.
- Add a Before → After Transformation section to standard and fast planning prompt templates.\n- Document the new task definition section and cover it with prompt regression tests.\n- Add a patch changeset for the published Fusion package.\n\nFiles changed:\n .changeset/fn-7499-before-after-transformation.md | 7 +++++++\n docs/task-management.md | 1 +\n packages/core/src/__tests__/agent-prompts.test.ts | 17 +++++++++++++++\n packages/core/src/agent-prompts.ts | 25 +++++++++++++++++++----\n packages/engine/src/__tests__/triage.test.ts | 20 ++++++++++++++++++\n 5 files changed, 66 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7499
Fusion-Task-Lineage: d049b5d6-a4bc-40dd-831d-04a11f9dc2cf
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add builtin:coding-ideas, a capture-first variant of the default coding
pipeline. New cards land in a manual Ideas intake (autoTriage:false) and are
not auto-planned until an operator promotes them into the merged Todo
planner+capacity column, where the triage service plans them in place.
Engine foundation:
- createTask lands cards in the workflow intake column (resolvedEntryColumn)
instead of hardcoding triage; default workflow is byte-identical.
- Triage poll discovers unplanned todo tasks (bootstrap-stub prompt) and
plans them in place; finalizeApprovedTask skips the redundant move.
- Scheduler skips todo tasks that are planning or still carry a bootstrap
prompt, so unplanned cards are never dispatched.
Dashboard:
- Start button on ideas cards (ideas -> todo move triggers planning).
- Ready badge on planned todo tasks waiting for an in-progress slot.
- ideas column label in board-workflows.
Tests: workflow IR round-trip/column/node-placement, createTask intake wiring,
and updated builtin catalog order assertion.
- probeWorktrunk: also refuse a bare wt/wt.exe override on Windows (resolves to
Windows Terminal via PATH), and tighten the package-dir match from a broad
'windowsterminal' substring to 'microsoft.windowsterminal' so a genuine
worktrunk under an unrelated *windowsterminal* folder is still probed.
- Fix worktrunk enable deadlock/save-race: probe status when the user views the
Worktrees section (not gated on 'enabled', which deadlocked since the toggle is
disabled until status==installed), and re-verify on Save so a fast enable+save
can't silently persist enabled:false. Hook 'refresh' now returns the fetched
status and is exposed.
- Tests: bare-wt refusal, forward-slash Windows Terminal path, unrelated
windowsterminal-folder is probed, and a rerender enabled false->true probe-once
transition; update SettingsModal mocks for the new refresh().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 1882/1883 terminal-auto-create guard fixed the wrong subsystem; the
embedded terminal was already guarded. The actual trigger: worktrunk's CLI is
named 'wt', colliding with Windows Terminal (wt.exe) on PATH, so probing it with
'wt --version' launched Windows Terminal and popped its native version dialog —
fired automatically by the Settings worktrunk-status fetch on mount.
- useWorktrunkInstallStatus: only auto-fetch /api/worktrunk/status when the
integration is enabled (user opt-in), never on a plain Settings/dashboard mount.
- probeWorktrunk: refuse to exec a resolved 'wt' that is the Windows Terminal
alias (WindowsApps / WindowsTerminal package dir), covering every resolution
surface (cached/override/PATH/install/settings-route). Basename computed
host-independently so the guard holds when the build host is POSIX.
- Tests for both guards; report updated with corrected root cause + Symptom
Verification + Surface Enumeration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace real-timer waits with a deterministic bounded microtask flush for
the fire-and-forget task lifecycle hook chain (FN-5048: no slow tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Windows: local runtime hung at "Starting local Fusion runtime…"
### Root cause
Desktop startup had two independent persisted sources of truth that
could disagree:
- `desktop-launch-mode.json` — decides whether **main** *starts* the
embedded local runtime
- `shell-connections.json` (`desktopMode`) — decides whether the
renderer **launch gate** *waits* for it
`shell:setDesktopMode` persists shell settings **before** the fallible
`startLocalRuntimeOnce()` / `saveDesktopLaunchMode()`. So a first
"local" selection whose runtime start threw or was interrupted left
`shell=local` / `launch-mode=choose` **permanently**. Every later launch
then sat at "Starting local Fusion runtime…" polling a runtime nobody
started → 30s timeout.
### Fix (defense in depth) — `78f0bc31`
- `initializeApp` reconciles: a completed shell `local` selection is
authoritative → heals the launch-mode file and starts the runtime.
- `onDesktopModeChange` / `onDesktopLaunchModeChange` persist
launch-mode **before** the fallible start so it can't re-desync.
- `DesktopLaunchGate` no longer assumes main started the runtime — if
it's not running/starting it actively `setDesktopMode("local")` before
polling.
- Env-gated startup trace (`FUSION_STARTUP_TRACE`) so packaged builds
(which log nothing) are diagnosable.
- Regression tests: split-brain → runtime starts + file heals;
agreement-on-choose → no start.
**Verified end-to-end under real Electron 35 / Node 22.16**: from the
exact split-brain state the runtime now reaches `RUNNING` and the
launch-mode file heals.
### Also: Windows root-build breakages — `bd24bd4c8`
- `scripts/build-workspace.mjs` "run as main" guard compared
`import.meta.url` to `` `file://${process.argv[1]}` ``, which never
matches on Windows → root `pnpm build` silently no-opped (exit 0, no
dist). Now uses `pathToFileURL(process.argv[1]).href`.
- `spawn('pnpm', …)` without `shell:true` (ENOENT on Windows) in
`build-workspace.mjs` and `packages/cli/tsup.config.ts` → pass `shell`
on win32.
### Notes
- `@fusion/desktop` and `@fusion/dashboard` are private → no changeset.
- Build the Windows installer via the `desktop-windows` workflow
(`electron-builder --projectDir deploy`); local `pnpm deploy` staging
hits an unrelated directory-rename race on managed-workspace
filesystems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added optional desktop runtime startup tracing (enabled via
environment variable).
* **Bug Fixes**
* Improved local desktop handoff to prevent reload loops and navigate
directly to the embedded local runtime.
* Added “split-brain” healing between persisted launch mode and shell
settings.
* Prevented auto-registration of runtime root/CWD during
desktop/dashboard startup.
* Improved Windows compatibility for CLI/workspace command spawning and
npm install process handling.
* **Tests**
* Expanded local/Electron integration, navigation, and onboarding
regression coverage; improved async flushing for more reliable
initialization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
createServer's modelRegistry is optional and, when absent, the /api/models endpoint returns an
empty list — so after connecting a provider (e.g. Anthropic) the onboarding model picker showed
"no models". The desktop wired authStorage but no ModelRegistry.
Add a shared createFusionModelRegistry(authStorage) factory to @fusion/engine (which already depends
on @earendil-works/pi-coding-agent) so non-CLI hosts don't need that dep directly, and pass its
result as modelRegistry from both desktop server paths. Verified against a clean embedded server:
/api/models now returns 24 Claude models instead of an empty list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a task adds/removes a dependency without regenerating the lockfile,
the inferred `pnpm install --frozen-lockfile` (and yarn/bun equivalents)
in the AI-merge clean room fails with ERR_PNPM_OUTDATED_LOCKFILE, dead-
ending the merge. Detect that specific frozen-refusal and retry once
non-frozen (pnpm gets explicit --no-frozen-lockfile to override any CI
default), regenerating the lockfile and recomputing the install marker.
A configured worktreeInitCommand keeps its authoritative frozen intent
and still hard-fails. Surfaced via the merge:ai-deps-sync run-audit event.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.