## 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.
Reviews no longer fail on formatting. Three changes to how reviewer/gate
verdicts are parsed and how retries reset state:
- Approval leniency: a review that clearly approves in prose passes even
without a structured verdict (proseSignalsClearApproval, with a
revise/reject/negated-approval guard so a rejection is never flipped). Any
APPROVE*/APPROVAL verdict token classifies as approved. Shared by the
reviewer/plan-review parser and the code-review/browser-verification gate.
- Prose + trailing JSON: extractJsonObjectCandidates does a string-aware
balanced-brace scan and prefers the last object, so a model that emits
reasoning prose then a trailing {"verdict":...} payload parses correctly.
An explicit "Verdict:" heading/line still takes precedence over an
incidental/example JSON object.
- Malformed handling: executeWorkflowStep retries the fallback model on
malformed output (not just timeout); malformed gate output becomes a
non-blocking advisory (a genuine parsed REVISE still blocks).
- Retry clears prior terminal step failures (incl. optional gate nodes like
code-review) after the task leaves the mergeable in-review column, so a
retry starts clean without an auto-merge race.
Fail-closed merge / PR-review / mission-verification gates are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add TaskExecutor.blockOuterDispatchWhenEphemeralDisabled, gating all three
workflow dispatch paths (graph / authoritative / work-engine) on
ephemeralAgentsEnabled at the top of execute(). Previously the toggle was
enforced only on the legacy scheduler/EphemeralWorkerManager path — whose
onTaskStart spawn refusal is a fire-and-forget callback that runs after
execution begins — so unassigned tasks reaching execute() off a non-scheduler
path still ran. Unassigned tasks are now re-queued for permanent-agent
assignment; permanent-agent-bound tasks still run. Adds regression coverage
across all three entry points.
Also includes the ephemeralAgentsCanCreateTasks project setting (default on)
gating fn_task_create for ephemeral callers in both the pi extension and the
executor task-worker tool.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A pre-merge-remediation/plan-replan node (e.g. code-review-remediation) is a
fire-and-forget async scheduler with no failure out-edge. When its schedule call
can't re-arm (missing rehydrated failureContext after restart,
remediation-not-scheduled, or an exhausted rework budget), the failure bubbled
out as the terminal graph outcome and handleGraphFailure stamped status:"failed"
— surfacing a spurious "Task Failed" even while the previously-scheduled
fix/reviewer session was still live.
Guard the terminal sink: skip the failed park when the failed node is a
remediation node AND a live agent session surface is still registered for the
task. Scoped via isRemediationGraphNode (IR workflowAction + built-in node-id
fallback) and hasLiveTaskSessionSurface; genuine execute/merge failures and
remediation failures with no live session still park failed unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the pnpm-lock.yaml conflict. Main's #1865 (review-checkout routing)
auto-merged cleanly with the completion-summary backstop in executor.ts.
Main independently pinned pi-claude-cli's pi-ai/pi-coding-agent to ^0.80.3
(e15489259) but kept the top-level `getModels` import, which 0.80.3 removed —
this branch's migration to `getBuiltinModels` from `/providers/all` is retained
as the working fix. Lockfile regenerated against the merged package.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
- Add explicit `sourceMetadata.externalReviewCheckout` review routing
with fail-closed validation.
- Log the selected review checkout so operator/runtime tasks can verify
where review ran.
- Cover metadata extraction, invalid metadata fallback,
`fn_review_step`, and workflow `stepReview` routing.
## Test Plan
- `cd packages/engine && corepack pnpm exec vitest run
src/__tests__/review-checkout.test.ts
src/__tests__/reviewer-workspace.test.ts`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Review routing now supports explicit external checkout overrides via
review checkout metadata, using the resolved external checkout as the
review working directory when valid.
* Logging now highlights the selected review routing target and warns
when external checkout metadata is present but invalid.
* **Bug Fixes**
* Missing/blank/invalid/relative/nonexistent/non-git paths now fail
closed to the task worktree instead of falling back.
* Metadata priority is enforced strictly (custom fields → branch context
→ source metadata → root), preventing lower-priority fallback when
higher-priority data is invalid.
* **Tests**
* Added/expanded automated coverage for override and fail-closed
behavior, including workspace-mode routing semantics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Two distinct v0.52.0 regressions reported in issue #1863.
1. Triage loop (engine): the best-effort completion-summary graph node is
wired into every built-in workflow with a success-only edge. A thrown
handler exception or a failed summary projection write bypassed the
advisory `!blocking -> success` coercion, terminated the graph at
'completion-summary', and routeGraphFailureToExecutionResume bounced the
in-review task back to todo forever (token usage 0, execution NOT STARTED).
The graph executor now degrades a completion-summary node failure to
success (ensureWorkflowCompletionSummary still backfills task.summary), with
a routeGraphFailureToExecutionResume backstop. Shared isCompletionSummaryNode
predicate exported from @fusion/core.
2. i18n object-key crashes (dashboard): three views called t() with keys that
resolve to nested objects (taskDetail.executionMode, routing.source,
nodes.dockerHost), so i18next returned "returned an object instead of
string" and crashed the render. Added leaf label keys across all locales and
switched the callers.
Tests: engine non-fatal completion-summary regression (fails without the fix),
dashboard invariant guard scanning t("literal") callers against real en/app.json,
and a Stats-panel reproduction against the real bundle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sonnet 5 had disappeared from every surface: pi-ai 0.79.9 (the installed
version) lacks it, and FN-7374 removed the static row expecting the live
registry to carry it. Live-verified that claude-sonnet-5 returns 200 on
api.anthropic.com/v1 with a raw ANTHROPIC_API_KEY and runs via the Claude
CLI (it 403s on subscription-OAuth /v1 — scope-gated; runtime fallback
applies). Note: pi-ai 0.80.3 ships sonnet-5 natively, so this SUPPLEMENTAL
row dedupes once the install catches up.
- core: re-add claude-sonnet-5 to SUPPLEMENTAL_ANTHROPIC_PROVIDER_REGISTRATION
and restore its static pricing (revert FN-7374); update pricing tests.
- engine/dashboard tests: flip the FN-7374 "withheld" assertions to the
restored "advertised" behavior.
PR feedback:
- Trim the two FNXC comments (auth-storage.ts, pi.ts) to concise
requirement prose per coding guidelines (CodeRabbit).
- Replace the now-inert getApiKey mock in two subscription routing tests
with a clarifying note (Greptile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude subscription (Max/Pro) chats regressed to 404/502/429 because
FN-7396 rerouted subscription OAuth to a /v1-based `anthropic-subscription`
runtime provider — reintroducing issue #1857 that FN-7391 had fixed. Both
routed the OAuth token to api.anthropic.com/v1, the surface that broke.
Proven in code that v0.51.0 (working) sent subscription OAuth directly to
/v1 via pi-ai's built-in `anthropic` provider (Claude Code impersonation:
Bearer + anthropic-beta oauth headers), NOT through the CLI. Restore that:
- auth-storage: getApiKey("anthropic") resolves subscription/legacy OAuth
again (raw API key still wins), so the built-in provider gets the token.
- pi.ts: remove the runtime reroute and the /v1 `anthropic-subscription`
execution provider so anthropic/* selections stay on the built-in provider.
- register-model-routes: advertise `anthropic` for OAuth users so direct
OAuth is selectable in the picker.
Three independent surfaces, no rerouting: direct OAuth, raw ANTHROPIC_API_KEY
(precedence), and explicit pi-claude-cli.
Fixes#1857
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep Anthropic OAuth subscription credentials away from the direct /v1 provider while preserving raw API-key routing.
- Treat direct anthropic auth as raw API-key-only in auth storage and model discovery.
- Re-route persisted anthropic model selections to the Claude CLI provider when no raw Anthropic API key is configured.
- Add regression coverage for OAuth-only routing, raw API-key preservation, and provider list separation.
- Document the distinct raw API key, OAuth subscription, and Claude CLI routes.
Files changed:
.../fn-7391-anthropic-subscription-cli-routing.md | 7 ++
docs/settings-reference.md | 2 +-
.../dashboard/src/__tests__/routes-auth.test.ts | 74 ++++++++++++++
.../dashboard/src/routes/register-model-routes.ts | 27 +++++-
packages/engine/src/__tests__/auth-storage.test.ts | 108 +++++++--------------
.../src/__tests__/pi-create-fn-agent.test.ts | 100 +++++++++++++++++++
packages/engine/src/auth-storage.ts | 45 ++++-----
packages/engine/src/pi.ts | 61 ++++++++++++
8 files changed, 319 insertions(+), 105 deletions(-)
Fusion-Task-Id: FN-7391
Fusion-Task-Lineage: d1fecc2d-7f31-408c-bf65-0e65b7fd88b1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
- add an opt-in `allowAnswerQuestionIdDrift` flag for interactive AI
sessions
- keep strict question-id validation by default
- enable the tolerance only for Compound Engineering recovered sessions
so persisted session rows can answer after dashboard
restarts/non-deterministic rehydration
## Test Plan
- `corepack pnpm --filter @fusion/engine exec vitest run
src/__tests__/interactive-ai-session.test.ts --silent=passed-only
--reporter=dot`
- `corepack pnpm --filter @fusion/engine typecheck`
- `corepack pnpm --filter @fusion-plugin-examples/compound-engineering
build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved interactive session recovery so persisted answers can
continue after dashboard restarts, even if the question ID changes
during rehydration.
* Keeps strict question-ID validation by default; mismatches still fail
unless drift is explicitly allowed.
* **New Features**
* Added `allowAnswerQuestionIdDrift` option to permit accepting the
persisted question ID during recovered session answering.
* **Tests**
* Added/updated coverage for strict mismatch error behavior and the
successful completion path when drift is enabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->