A long-running agent session holds its OAuth access token in memory. When
the token rotates mid-run (Claude Max tokens have an ~8h lifetime), the next
API call fails with 401 authentication_error and withRateLimitRetry re-throws
it immediately — the task is marked failed and the operator is paged, even
though the refreshed credentials make the very next call succeed. Observed
recurring at every ~8h token boundary, hitting whatever task or heartbeat is
in flight.
Add isTransientAuthError (authentication_error / invalid authentication
credentials / token_expired / oauth scope) with its own small retry budget:
2 retries at a flat ~5s delay (credential refresh completes within seconds,
so the 30s -> 2min rate-limit backoff curve would just prolong the outage).
Auth retries decrement the loop counter so they never consume rate-limit
attempts, and the existing rate-limit path is byte-for-byte unchanged.
Genuinely bad credentials still propagate after ~10s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corrects the FN-7521 mobile breakpoint test suite, which asserted flat inline testids that no longer exist after FN-7545 moved the oversight quick-controls (level select, nudge, stop, explain) into a mobile overflow menu.
- Open the `detail-oversight-menu-trigger` before querying menu items in all three mobile tests
- Assert the level select's current value (`observe`) instead of just truthiness, tightening the regression check
- Rename describe/it blocks to reflect the real FN-7545 overflow-menu affordance instead of the retired CSS-only wrap
- Update the FNXC:PlannerOversight comment to document the FN-7545 mount-time `updateOversightMenuMobile()` behavior driving `isOversightMenuMobile`
Files changed:
.../TaskDetailModal.oversight-controls.test.tsx | 56 +++++++++++++++-------
1 file changed, 40 insertions(+), 16 deletions(-)
Fusion-Task-Id: FN-7558
Fusion-Task-Lineage: df312f9e-e9e0-4fd0-9d53-ce3ff13a74b5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Changes the project-wide plan approval default from deferring to per-workflow settings to auto-approving all task plans, so new/unset projects skip the manual awaiting-approval gate by default.
- Change DEFAULT_PROJECT_SETTINGS.planApprovalMode default from "workflow" to "auto-approve-all" in settings-schema.ts, with FNXC comments documenting the requirement change
- Update ProjectSettings.planApprovalMode JSDoc in types.ts to reflect the new default
- Update useAppSettings hook's initial state and hydration fallback to default to "auto-approve-all" while still honoring an explicit stored "workflow" value
- Update MergeSection UI: move the "(default)" label from the "Use workflow setting" option to "Auto-approve all tasks", keeping the select's fallback value in sync
- Update settings-reference.md docs and i18n locale/resource strings to match the new default label
- Update existing tests (MergeSection legacy auto-merge cleanup, settings default descriptions, useAppSettings) to assert the new default, and add coverage for the updated hydration/fallback behavior
- Add changeset fn-7557-plan-auto-approve-default.md documenting the behavior change
Files changed:
.changeset/fn-7557-plan-auto-approve-default.md | 7 +++++
docs/settings-reference.md | 2 +-
packages/core/src/settings-schema.ts | 6 +++-
packages/core/src/types.ts | 3 ++
.../dashboard/app/components/SettingsModal.tsx | 3 +-
.../components/settings/sections/MergeSection.tsx | 9 ++++--
.../MergeSection.legacy-automerge-cleanup.test.tsx | 4 +--
.../settings-default-descriptions.test.tsx | 3 +-
.../app/hooks/__tests__/useAppSettings.test.ts | 35 +++++++++++++++++++---
packages/dashboard/app/hooks/useAppSettings.ts | 12 ++++++--
packages/i18n/locales/en/app.json | 4 +--
packages/i18n/src/resources.d.ts | 2 +-
12 files changed, 71 insertions(+), 19 deletions(-)
Fusion-Task-Id: FN-7557
Fusion-Task-Lineage: 7dcfe339-6088-4ebc-8387-eb81258a693d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add a visible group label and an in-DOM disabled-reason helper to the task-detail oversight controls so the Nudge/Explain cluster is no longer confusing when greyed out, and make Explain always-openable since it is read-only.
- Add a `detail-oversight-controls-label` group label above the Nudge/Stop/Explain buttons, gated by the same visibility condition as the buttons (mobile and desktop clusters)
- Add a `detail-overseer-nudge-disabled-reason` in-DOM helper line explaining why Nudge is unavailable, instead of relying only on a hover title
- Remove the disabled gate from Explain (it's read-only/non-mutating) and update its title copy to reflect that it always opens and shows last-known state when inactive
- Add regression tests covering the new label/helper text and Explain's always-enabled behavior
- Add a patch changeset and a docs/dashboard-guide.md note
Files changed:
.changeset/FN-7546-oversight-controls-clarity.md | 7 +++
docs/dashboard-guide.md | 2 +
.../dashboard/app/components/TaskDetailModal.css | 42 +++++++++++++
.../dashboard/app/components/TaskDetailModal.tsx | 41 +++++++++++--
.../TaskDetailModal.oversight-controls.test.tsx | 69 ++++++++++++++++++++++
5 files changed, 157 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7546
Fusion-Task-Lineage: d1f342ea-2de0-4b54-9930-9b3d540c7af6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix agent-created artifacts not showing live in the dashboard because a second TaskStore instance (e.g. engine writing while dashboard polls) never detected or re-emitted artifact:registered for rows it did not insert itself.
- Track a per-instance lastArtifactRowId cursor seeded from the max artifacts rowid at watch() startup
- In checkForChanges(), pick up artifact rows with rowid > cursor written by other instances and re-emit artifact:registered, advancing the cursor
- Advance the cursor on local inserts (insertArtifactRow) so this instance never double-emits its own writes
- Call db.bumpLastModified() in registerArtifact() so other instances' pollers actually look at the artifacts table
- Add regression tests covering cross-instance artifact registration in core store and dashboard artifacts route integration
- Add changeset and doc note for the cross-instance live-refresh fix
Files changed:
.changeset/fn-7544-artifact-cross-instance-live-refresh.md | 7 ++
docs/storage.md | 1 +
packages/core/src/__tests__/artifacts.test.ts | 85 +++++++++++++++
packages/core/src/store.ts | 63 ++++++++++-
packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts | 120 ++++++++++++++++++++-
5 files changed, 274 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7544
Fusion-Task-Lineage: 76570505-082e-4f9a-8b06-29591c06c435
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.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>
Adds a mobile-friendly overflow menu for the task-detail planner-overseer controls, keeping the desktop inline cluster unchanged.
- Below 768px, replace the inline oversight-level select/nudge/stop/explain controls with a single "Oversight" overflow-menu trigger (MoreVertical icon, aria-haspopup="menu") mirroring the existing move-action dropdown pattern
- Popover lists the same controls as full-width, tappable role="menuitem" entries with identical testids and enablement/visibility rules as desktop
- Trigger is withheld entirely when no controls would render (no empty-shell button)
- Desktop (>768px) inline cluster is unchanged
- Adds TaskDetailModal.oversight-mobile.test.tsx covering trigger visibility, menu contents, and parity with desktop controls
- Updates docs/dashboard-guide.md to document the mobile overflow-menu behavior
Files changed:
docs/dashboard-guide.md | 2 +
packages/dashboard/app/components/TaskDetailModal.css | 118 ++++++
packages/dashboard/app/components/TaskDetailModal.tsx | 383 ++++++++++++++-----
packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-mobile.test.tsx | 413 +++++++++++++++++++++
packages/dashboard/app/components/__tests__/TaskDetailModal.test-helpers.ts | 2 +
5 files changed, 830 insertions(+), 88 deletions(-)
Fusion-Task-Id: FN-7545
Fusion-Task-Lineage: 63cb778e-f17d-43d3-a3de-864683d4e8f1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the mobile Settings section picker showing bare "Authentication" instead of "Global — Authentication" for the storage-less (scope: undefined) Authentication section.
- Add buildSettingsSectionGroupLabelMap to derive each section's owning group label (Global/Runtimes/Project) from SETTINGS_SECTIONS order
- Extend resolveSettingsSectionOptionLabel to fall back to a Global-derived prefix for storage-less sections belonging to the Global group, without affecting sections that already declare a scope
- Add regression test asserting the Authentication option renders "Global — Authentication" alongside existing scoped siblings
- Add changeset (patch) documenting the fix
Files changed:
.changeset/fn-7552-mobile-authentication-global-prefix.md | 7 +++
packages/dashboard/app/components/SettingsModal.tsx | 40 +++++++++++++
packages/dashboard/app/components/__tests__/settings-mobile.test.tsx | 16 +++++
3 files changed, 63 insertions(+)
Fusion-Task-Id: FN-7552
Fusion-Task-Lineage: c389f556-c7d3-48a2-a51d-926a70bdaa0e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the terminal quick-shortcut bar so it actually scrolls horizontally on mobile instead of clipping keys off-screen.
- Add min-width: 0 to .terminal-shortcut-panel to defeat the flex min-width:auto trap (panel's automatic min-width equalled the sum of all nowrap buttons, overriding overflow-x: auto and letting the modal's overflow: hidden clip the rightmost shortcuts)
- Add regression test asserting the base rule keeps min-width: 0, overflow-x: auto, and flex-wrap: nowrap, and that the mobile media-query override doesn't reintroduce a conflicting min-width
- Add changeset (patch) documenting the fix for @runfusion/fusion
Files changed:
.changeset/fn-7550-terminal-shortcut-scroll.md | 7 +++++++
packages/dashboard/app/components/TerminalModal.css | 5 +++++
.../app/components/__tests__/TerminalModal.test.tsx | 18 ++++++++++++++++++
3 files changed, 30 insertions(+)
Fusion-Task-Id: FN-7550
Fusion-Task-Lineage: a9eed0b8-d0ce-49cb-8ff5-a24d1f2d786e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Removes the recently-added executor/reviewer/merger overseer-state chip from TaskCard, which fired as noise on nearly every in-progress card, keeping the separate oversight-level badge untouched.
- Deletes deriveOverseerCardWatchedStage and its OVERSEER_STATE_LABEL/MODIFIER maps from TaskCard.tsx
- Removes showOverseerStateBadge gating and its contribution to hasCardMetaBadges/areTaskCardPropsEqual
- Strips the now-unused .task-card-overseer-state-badge CSS rules from TaskCard.css
- Rewrites TaskCard.oversight.test.tsx to drop overseer-state-badge coverage while keeping oversight-level badge tests
- Updates docs/dashboard-guide.md reference and adds a changeset documenting the removal
Files changed:
.changeset/fn-7542-remove-overseer-state-badge.md | 7 +
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskCard.css | 63 +-------
packages/dashboard/app/components/TaskCard.tsx | 161 +++------------------
.../__tests__/TaskCard.oversight.test.tsx | 149 +++++++------------
5 files changed, 77 insertions(+), 305 deletions(-)
Fusion-Task-Id: FN-7542
Fusion-Task-Lineage: dabaf72a-a7b0-4054-8a72-7c3d99fa7bb1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a canonical emission layer over recordPlannerIntervention so overseer decision points (observation, steering, recovery attempt, retry, confirmation, escalation) emit consistent overseer:intervention run-audit events without inlining action/outcome logic at each call-site.
- Add packages/core/src/planner-overseer-events.ts with emitOverseerObservation, emitOverseerSteering, emitOverseerRecoveryAttempt, emitOverseerRetry, emitOverseerConfirmation, and emitOverseerEscalation, each fixing its category's intervention action/default outcome and delegating to recordPlannerIntervention.
- Export the new emitters and OverseerEventInput type from packages/core/src/index.ts.
- Add unit tests covering each emitter's action/outcome mapping and metadata pass-through.
- Add a minor changeset documenting the new run-audit emission facade for planner-overseer events.
- Update docs/architecture.md's Run Audit API section to describe the FN-7520 emission facade and its relationship to FN-7519's overseer:intervention mutation type.
Files changed:
.changeset/fn-7520-planner-overseer-events.md | 7 +
docs/architecture.md | 2 +-
packages/core/src/__tests__/planner-overseer-events.test.ts | 236 +++++++++++++++++++++
packages/core/src/index.ts | 9 +
packages/core/src/planner-overseer-events.ts | 128 +++++++++++
5 files changed, 381 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7520
Fusion-Task-Lineage: 85e0d761-e4f3-437e-abe2-031e6cf89c1c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Prevent the Auto-recovery/oversight badge from rendering on virtually every task card when the effective planner oversight level is just the inherited schema default.
- TaskCard: track whether the effective oversight level was reached purely via inheritance (no per-task override, no explicit non-default workflow tier), and suppress the badge in that case
- Preserve badge visibility when a task explicitly overrides oversight to "autonomous" or a workflow tier explicitly resolves to a non-default level
- Update TaskCard oversight tests to cover inherited-default suppression vs explicit-override rendering
- Add changeset documenting the fix
- Update dashboard guide docs
Files changed:
.changeset/fn-7539-oversight-badge-default.md | 7 ++++
docs/dashboard-guide.md | 3 +-
packages/dashboard/app/components/TaskCard.tsx | 21 +++++++++-
.../__tests__/TaskCard.oversight.test.tsx | 48 +++++++++++++++++++---
.../app/components/__tests__/TaskCard.test.tsx | 11 +++--
5 files changed, 76 insertions(+), 14 deletions(-)
Fusion-Task-Id: FN-7539
Fusion-Task-Lineage: 8ac9a60c-69c6-4095-b6b2-18c79718b62b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Introduces a persisted planner-overseer intervention timeline surfaced in the task-detail Planner Oversight cluster, recording stage, reason, action taken, outcome, attempt count/limit, and source links for each intervention.
- Add core `PlannerInterventionEntry` type plus `recordPlannerIntervention`/`getPlannerInterventionTimeline` helpers that persist entries via the run-audit store under the `overseer:intervention` mutation
- Add `PlannerInterventionTimeline` dashboard component rendering the timeline (stage/reason/action/outcome/attempts/links) with associated styles
- Wire the new API route/legacy handler and TaskDetailModal integration to expose and render the timeline
- Add unit tests for the core helpers and the new UI component
- Add changeset for the new minor feature and update architecture/dashboard-guide docs
Files changed:
$(cat /tmp/diffstat_7519.txt)
Fusion-Task-Id: FN-7519
Fusion-Task-Lineage: 3c4fcda3-9eb2-46d3-b142-b0c7d6334cd0
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes splitSettingsSave so the five global GitLab keys are diffed only against the scoped global initial values, not the project-merged effective values, so a real global edit is no longer dropped when it happens to match a project override.
- Add GLOBAL_GITLAB_SCOPED_ONLY_KEYS (gitlabEnabled, gitlabInstanceUrl, gitlabApiBaseUrl, gitlabAuthToken, gitlabAuthTokenType) that never fall back to merged initialValues when computing the save diff
- Update splitSettingsSave to treat these keys as scoped-global-only, treating a missing scoped-global initial as undefined rather than falling back to the merged value
- Add regression tests in settings-save-split.test.ts covering the scoped-vs-merged diff behavior
- Add regression tests in SettingsModal.general.test.tsx covering the save flow end-to-end
- Add changeset fn-7535-global-gitlab-setting-save.md (patch, fix)
Files changed:
.changeset/fn-7535-global-gitlab-setting-save.md | 7 +++
.../app/__tests__/settings-save-split.test.ts | 62 ++++++++++++++++++++++
.../__tests__/SettingsModal.general.test.tsx | 36 +++++++++++++
.../app/components/settings/save-split.ts | 26 ++++++++-
4 files changed, 129 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7535
Fusion-Task-Lineage: f9cd212e-a691-4cd9-8946-48c59537d583
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Route window resize/orientationchange/scroll through the same opening-viewport guard used by visualViewport so a same-gesture echo repositions the Activity menu instead of closing it, fixing the dropdown not showing on mobile.
- Add handleGuardedViewportChange shared by resize/orientationchange/visualViewport listeners so an opening-gesture echo only repositions the menu instead of closing it
- Add handleScrollChange to treat scrolls originating inside the .detail-tabs horizontal tab strip as benign (reposition-only), since window scroll listens with capture and would otherwise see nested scroller scrolls as a close signal
- Add regression tests covering mobile Activity dropdown open/scroll/resize behavior
- Add changeset for the fix
- Update dashboard guide docs
Files changed:
.changeset/fn-7536-activity-dropdown-mobile.md | 7 +
docs/dashboard-guide.md | 1 +
.../dashboard/app/components/TaskDetailModal.tsx | 38 +++--
.../TaskDetailModal.task-activity-chat.test.tsx | 161 +++++++++++++++++++++
4 files changed, 196 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7536
Fusion-Task-Lineage: c8a3b11e-9bd2-4a61-b49e-038c93cfe4d2
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>
Adds two display-only badges to TaskCard so operators can see planner oversight configuration and live overseer activity at a glance.
- Resolve and display the workflow's effective `plannerOversightLevel` (observe/steer/autonomous) per task, with a module-level cache keyed by (projectId, workflowId) and in-flight de-duplication to avoid redundant `/api/workflows/:id/setting-values` fetches across cards sharing a workflow.
- Gate oversight badge rendering until the workflow-effective value resolves (or a per-task override is known) so the very first render never shows a guessed schema-default badge.
- Derive a card-local "active overseer state" indicator (Executor/Reviewer/Merger/Pull request/Workflow gate) that mirrors the engine's `resolveWatchedStage` precedence using only fields already present on the Task payload (column, paused, pausedReason, prInfo, reviewState, workflowTransitionNotification), since the real in-memory monitor state has no persisted/API surface today.
- Add corresponding CSS badge modifiers and update dashboard-guide.md documentation.
- Add TaskCard.oversight.test.tsx covering the new badges and update existing TaskCard tests/badge-wrap test for the new markup.
Files changed:
docs/dashboard-guide.md | 4 +
packages/dashboard/app/components/TaskCard.css | 106 +++++-
packages/dashboard/app/components/TaskCard.tsx | 369 +++++++++++++++++++-
.../__tests__/TaskCard.badge-wrap.test.tsx | 1 +
.../__tests__/TaskCard.oversight.test.tsx | 383 +++++++++++++++++++++
.../app/components/__tests__/TaskCard.test.tsx | 32 +-
6 files changed, 889 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7516
Fusion-Task-Lineage: ce909409-862b-4697-a19e-d6735a3b572d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## The bug
`GET /api/command-center/tokens` documents and accepts `groupBy=task`,
but the `task` dimension was never actually wired through the
aggregator. Four enumeration sites all stopped at `model | provider |
node | agent`:
- `TokenGroupBy` (core `token-analytics.ts`) did not include `"task"`.
- `groupKeyFor()` had no `case "task"`.
- `VALID_GROUP_BY` (dashboard route) rejected the value, so
`resolveGroupBy()` returned `undefined`.
- `groupAttributes()` (core `otel-metrics.ts`) emitted no attribute for
it.
Net effect: a caller asking for a per-task rollup silently fell back to
**ungrouped grand totals** — the per-task breakdown returned zero
groups, even though every task carries `tokenUsage*` and the data was
right there.
## The fix
Thread `"task"` through all four sites (5 functional lines + doc/test):
- Add `"task"` to the `TokenGroupBy` union — this makes the two `switch`
statements **compiler-exhaustive**, so `tsc` forces the two new cases
(no silent gaps).
- `groupKeyFor`: task rows group by their task id; chat rows have no
task and return `null`, mirroring the existing `node` case.
- `groupAttributes`: emit `task.id` for OTLP export, matching `node.id`
/ `agent.id`.
- `VALID_GROUP_BY`: accept `"task"`.
No schema or migration change — the task id is already on the row.
## Verification
- `pnpm --filter @fusion/core typecheck` and `@fusion/dashboard
typecheck` — clean.
- Extended the existing `groups by provider, node, agent` core test with
a `groupBy: "task"` assertion (two tasks → two groups keyed by task id,
100 / 200 tokens). Full suites green: **core 25/25**, **dashboard
325/325**.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added task-based token analytics grouping, alongside existing grouping
options.
* Analytics views and metrics now include task-level breakdowns when
available.
* **Bug Fixes**
* Improved grouping behavior so task totals are reported correctly in
analytics results.
* **Documentation**
* Updated supported analytics options to reflect task grouping in
endpoint and metric descriptions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## 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 -->
## The bug
Per-task token usage (and the cost figures derived from it) is
**silently dropped the moment a task is archived**. A task that burned
millions of tokens shows `0` — or nothing — everywhere once it leaves
the live board.
Root cause is a field whitelist plus a missing type field:
- `TaskStore.taskToArchiveEntry()` (`packages/core/src/store.ts`)
constructs the archived record from an **explicit property whitelist**.
It copies `modelId` / `modelProvider` / `planningModelId` / … but never
`task.tokenUsage`.
- `ArchivedTaskEntry` (`packages/core/src/types.ts`) has no `tokenUsage`
field, so even a stray copy would be dropped by the type.
At archival time the task is DB-hydrated and still carries `tokenUsage`,
and the live `tasks` row (with its `tokenUsage*` columns) is then
deleted — so the whitelist is the only place the data survives or dies.
Result: `archive.db` (`archived_tasks.taskJson`) never contains token
stats. Any tool that reports token/cost usage can only ever see the
small live working set, never the hundreds of finished tasks.
## The fix
Thread `tokenUsage` through the archive round-trip (5 lines, all
pass-through of the already-typed `TaskTokenUsage`):
- `ArchivedTaskEntry` gains an optional `tokenUsage?: TaskTokenUsage`
field.
- `taskToArchiveEntry()` copies `tokenUsage: task.tokenUsage` (write
path).
- `archiveEntryToTask()` and `unarchiveTask()` copy `tokenUsage:
entry.tokenUsage` (both restore paths), so restored tasks keep their
history too.
Because the archived entry is serialized into `taskJson`, no DB
migration/column is needed — the counts land in the existing JSON blob
and read back via `json_extract(taskJson, '$.tokenUsage.totalTokens')`
(and the `inputTokens` / `outputTokens` / `cachedTokens` /
`cacheWriteTokens` breakdown).
## Verification
Applied the equivalent change to the bundled `dist/bin.js` on a live
install and archived a 9.9M-token task into an isolated copy of the
store. The full breakdown survived into `archive.db`:
```
inputTokens=119 outputTokens=26100 cachedTokens=9637483 cacheWriteTokens=233674
totalTokens=9897376 modelId=claude-sonnet-4-6 (+ per-model split intact)
```
Without the change the same archival leaves `tokenUsage` absent from the
entry.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Archived tasks now keep token usage details when saved and restored,
so task history remains accurate across archive flows.
* Restored archived tasks now display the same usage accounting they had
before being archived.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- Derive the Start button target from the workflow's ordered move columns
instead of hard-coding 'todo', so a manual-intake workflow whose first
working stage has a different id transitions correctly. Falls back to
'todo' when column metadata is unavailable.
- 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.
Restore the stable extension suite by quarantining only the dist-barrel recompilation case.
- Move the built @fusion/core dist-barrel extension test into its own file.
- Re-admit extension.test.ts while keeping the isolated dist-barrel file quarantined.
- Update the quarantine ledger and velocity baseline to reflect the narrowed quarantine.
Files changed:
docs/test-velocity-baseline.md | 10 +-
.../src/__tests__/extension-dist-barrel.test.ts | 230 +++++++++++++++++++++
packages/cli/src/__tests__/extension.test.ts | 103 +--------
packages/cli/vitest.config.ts | 5 +-
scripts/lib/test-quarantine.json | 4 +-
5 files changed, 247 insertions(+), 105 deletions(-)
Fusion-Task-Id: FN-7530
Fusion-Task-Lineage: 7b07540f-689b-4133-b590-a39427095397
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Update the App test to assert the open desktop search panel state after rerender.\n\n- Avoid re-clicking the desktop search toggle after the non-mobile panel stays open across rerender.\n- Assert the branch-filter selects directly while the search panel remains rendered.\n\nFiles changed:\n packages/dashboard/app/components/__tests__/App.test.tsx | 9 +++++++--\n 1 file changed, 7 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7529
Fusion-Task-Lineage: 1495f872-d262-4ea4-83f2-5a53ecf7a202
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The command-center token endpoint accepts groupBy=task, but the
dimension was never wired through: TokenGroupBy did not include "task",
groupKeyFor had no task case, VALID_GROUP_BY rejected it, and
groupAttributes emitted no attribute. As a result groupBy=task silently
fell back to ungrouped totals — the per-task rollup returned zero groups
even though tasks.tokenUsage* is populated per task.
Thread "task" through all four enumeration sites (the union type makes
the two switches compiler-exhaustive). Task rows group by task id; chat
rows have no task and return null, mirroring the existing node case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>