Users can now watch everything the agent does while a CE stage works, steer
it mid-stage, and read the whole conversation as a proper chat surface.
Live output:
- New host capability: CreateInteractiveAiSessionOptions.onProgress — the
engine adapter streams thinking/text deltas + tool start/end markers from
the pi agent hooks (any plugin can use this).
- Orchestrator buffers per-session live activity (merged deltas, discrete
tool lines, capped), emits throttled progress events over SSE, and
GET /sessions/:id attaches it as liveActivity for the polling fallback.
- Routes detach turn execution: start/answer/resume return immediately
(status active) and clients converge via push/poll — the turn is watchable
instead of hidden inside a blocking POST.
- Turn timeout is now INACTIVITY-based: an actively-working long turn is
never killed; a quiet one interrupts with its working trace preserved.
- On settle the trace persists into history as a condensed record.
Steering:
- Stage protocol: responses may be a direct answer, {value, comment}
(answer + guidance), or {feedback} (guidance without answering); the
system prompt instructs agents to treat steering as first-class input.
- CeFlow: guidance textarea alongside selectable questions — attach to the
clicked answer, or "Send guidance" on its own.
Q&A UI:
- Transcript no longer hides control records: past questions/answers render
as chat bubbles (option ids → labels), steering turns marked, working
traces as collapsible "Agent work" blocks, completion marker.
- Live working pane (pulse + streaming thinking/tool lines) while a turn runs.
Tests: 130 plugin tests green (14 new: live buffer/flush ordering, inactivity
watchdog survives active work, detached convergence, steering payload shapes,
transcript rendering, live pane). Engine seam tests green; plugin/core/
engine/dashboard tsc clean. Core full suite OOMs locally (known orchestrator-
shell issue) — covered by CI shards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The store/orchestrator were already multi-session (independent rows + live
handles per session); this surfaces it end to end:
- Sessions panel in the dashboard view: lists every session with stage,
status badge ("needs your input" for awaiting_input), and last activity;
stays visible while a flow is open so switching is one click. Closing a
flow returns to the overview without stopping the session.
- useCeSession.open(): adopt an existing session (pins its projectId for
answer/resume/poll); useCeSessions list hook with push-event refresh and
poll fallback while any session is mid-turn.
- DELETE /sessions/:id + orchestrator.discard(): dispose the live handle
before deleting the row (pipeline-link rows kept for task provenance);
Discard affordance on settled sessions.
- Tests: cross-session independence through one orchestrator, store delete,
route list/delete, hook open/list/remove/push/poll, view panel
open/switch/discard. 116 tests green; plugin + dashboard tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose operational log retention as a project setting in the dashboard.
- add an Operational log retention selector to the Project General settings section with supported retention options
- validate operationalLogRetentionDays in the settings API and cover accepted and rejected values in tests
- document the constrained retention values and assert project-scope/default parity for the setting
Files changed:
docs/settings-reference.md | 2 +-
packages/core/src/__tests__/settings-parity.test.ts | 7 ++++
packages/dashboard/app/components/SettingsModal.tsx | 47 ++++++++++------------
packages/dashboard/app/components/__tests__/SettingsModal.test.tsx | 8 ++++
packages/dashboard/src/__tests__/routes-settings.test.ts | 21 ++++++++++
packages/dashboard/src/routes/register-settings-memory-routes.ts | 10 +++++
6 files changed, 69 insertions(+), 26 deletions(-)
Fusion-Task-Id: FN-5939
Fusion-Task-Lineage: 2148dd88-1cef-4c6d-9696-31148fce97d3
- Add behavior-level tests for the shared merge-enqueue funnel
(enqueueEligibleInReviewTasks) with a Surface Enumeration of all
in-review entry surfaces, per review
- Seed real stale in-review fixtures in the FN-5147 no-mutation
regression block so sweeps enumerate candidates and the assertions
are non-vacuous
- Keep per-task auto-merge gating uniform across reclaim/contamination
candidate columns: the suggested in-review-only scoping broke the
FN-5704 regression contract (reclaim short-circuits when autoMerge
is off); documented the tension in code comments and the learning doc
- Drop hardcoded commit hash from the learning doc
Document the trigger-layer gating bug fixed in this PR under
docs/solutions/logic-errors/, seed CONCEPTS.md with the merge-lifecycle
vocabulary, and surface both knowledge stores in AGENTS.md's reference
docs index.
Tasks with autoMerge explicitly enabled never auto-merged when the
project-level setting was disabled: the merge enqueue gate
(allowInReviewMergeProcessing) and all 19 in-review self-healing sweeps
checked only settings.autoMerge, and the board stall-signal hydration
passed the raw global into the diagnostic gates.
Introduce allowsAutoMergeProcessing(task, settings) in core — additive
relative to the global setting so configs with global auto-merge ON are
unchanged (explicit autoMerge:false tasks still flow to the merger's
manual-required parking) — and use it at the enqueue gate, every
self-healing sweep, and the store's stall/stalled signal contexts.
With @fusion/dashboard removed from the CE plugin's deps (cycle fix), a literal
import() of its dashboard-view made the dashboard typecheck the plugin source and
fail to resolve the plugin's type-only @fusion/dashboard import. Use the
moduleId-variable + @vite-ignore pattern (as cli-printing-press does) so tsc
treats it as dynamic; Vite still resolves it at runtime.
A new workspace-acyclicity invariant on main (run via the PR merge) flagged
compound-engineering -> @fusion/dashboard -> compound-engineering: the plugin is
listed in @fusion/dashboard's deps (for view loading) AND declared @fusion/dashboard
as a runtime dependency, which the cycle check (deps+devDeps) and the
'bundled plugins must not depend on host packages' check both reject.
The plugin's only @fusion/dashboard use is the type-only PluginDashboardViewContext
import. Drop the @fusion/dashboard dependency and resolve that type via an ambient
dashboard-interop.d.ts + tsconfig paths mapping (the fusion-plugin-dependency-graph
interop pattern). Breaks the cycle; the host passes the real context at runtime.
The Minimax usage panel only rendered one model row. The primary `general`
model meters quota purely via `current_interval_remaining_percent` (its
`current_interval_total_count` is 0), so the count-based `total > 0` visibility
filter dropped it entirely. The percent was also derived from count fields
rather than the authoritative `*_remaining_percent` field.
fetchMinimaxUsage now builds windows via a helper that prefers
`*_remaining_percent` (count-based fallback when absent) and skips a window only
when no quota signal exists. Each model's separate weekly quota window is now
surfaced as its own indicator alongside the interval window.
Verified against the live coding_plan/remains endpoint: 2 models (general,
video) now produce 4 windows (interval + weekly each) instead of 1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document why PluginSkillContribution.skillFiles silently fails to load bundled
skills in live sessions (resolver only filters disk-discovered skills) and the
physical-install + additionalSkillPaths-forwarding fix, as a searchable
docs/solutions/ learning. Surface docs/solutions/ in AGENTS.md so agents
discover it.
safeParse() previously only caught JSON syntax errors, so a semantically-wrong
but valid column ('null', '{}', a string) would rehydrate a non-array
conversationHistory that later crashed appendHistory's spread (and a bogus
currentQuestion). safeParse now takes a shape validator and falls back to []/null
on invalid shapes too. + regression test covering 'null'/'{}'.
Remove the duplicate merger override from the SettingsModal test fixture.
- delete the earlier legacy merger object from the mocked settings payload
- keep the deterministic merger override as the single effective fixture value
Files changed:
packages/dashboard/app/components/__tests__/SettingsModal.test.tsx | 1 -
1 file changed, 1 deletion(-)
Fusion-Task-Id: FN-5923
Fusion-Task-Lineage: 206239b0-af4a-4e45-93b8-2940a42d431a
Align the dependency graph plugin's dashboard interop declarations with the current dashboard contract.
- import ReactNode for plugin task card rendering support
- add DetailTaskTab, PluginToastType, and PluginTaskView type exports
- update PluginDashboardViewContext to require workflowSteps and the expanded openTaskDetail signature
- add optional renderTaskCard and addToast hooks to match dashboard expectations
Files changed:
.../src/dashboard-interop.d.ts | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-5935
Fusion-Task-Lineage: 32313a96-1008-4de6-a7cb-e6bfbc385534
Keep research settings controls inside their containment wrappers so inputs stay onscreen across breakpoints.
- wrap advanced provider controls in a dedicated research settings body container
- tighten research grid and field min-width behavior in desktop and mobile styles
- add regression coverage for global and project research sections on desktop and mobile
Files changed:
packages/dashboard/app/components/SettingsModal.css | 37 +++++++-
packages/dashboard/app/components/SettingsModal.tsx | 104 +++++++++++----------
packages/dashboard/app/components/__tests__/SettingsModal.test.tsx | 65 +++++++++++++
packages/dashboard/app/components/__tests__/settings-mobile.test.tsx | 48 ++++++++++
4 files changed, 201 insertions(+), 53 deletions(-)
Fusion-Task-Id: FN-5932
Fusion-Task-Lineage: 22dff41f-b563-40d0-97ea-1ca6d1e09466
Document and enforce pnpm build-script review decisions to prevent ignored-script install warnings.
- add reviewed ignoredBuiltDependencies entries to the root pnpm config and mirror the effective policy in pnpm-workspace.yaml
- add a regression test that verifies reviewed dependencies are categorized exactly once and stay aligned across both config files
- document the pnpm build-script approval policy in contributing docs and link plugin authoring guidance from AGENTS.md and PLUGIN_AUTHORING.md
Files changed:
AGENTS.md | 5 ++
docs/PLUGIN_AUTHORING.md | 3 +-
docs/contributing.md | 14 ++++
package.json | 9 +++
pnpm-workspace.yaml | 14 ++++
scripts/__tests__/pnpm-build-scripts-config.test.mjs | 74 ++++++++++++++++++++++
6 files changed, 118 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5927
Fusion-Task-Lineage: 192bbed9-c5ed-45cb-b4bd-fb18514e2783
- session-store: rowToSession parses JSON columns via a safeParse helper, so a
corrupted currentQuestion/conversationHistory column degrades to null/[] instead
of throwing and crashing reads of an otherwise-valid row (+regression test)
- session-routes: validate the ?status= list filter against CE_SESSION_STATUSES
(asCeSessionStatus) instead of casting an arbitrary query string with 'as never'
- _harness: makeScriptedSession throws on an empty script rather than yielding
undefined, surfacing test mistakes loudly
- useCeSession tests: use fake timers (advanceTimersByTimeAsync) instead of real
setTimeout waits for deterministic poll-interval assertions
Skipped: 3 doc nits in src/skills/ce-*/references/** — those are pinned upstream
ce-* skill copies (KTD5 vendored snapshot), not this repo's content.
Refine the task review tab layout for clearer hierarchy and better mobile behavior.
- group the review summary and decision badge into a stacked header layout
- reorganize review items with dedicated selection, status, and metadata regions
- restyle review cards, status badges, auto-merge controls, and body containers for improved spacing and overflow handling
- extend TaskReviewTab coverage for populated layout hooks and mobile CSS regressions
Files changed:
packages/dashboard/app/components/TaskReviewTab.css | 220 +++++++++++++++------
packages/dashboard/app/components/TaskReviewTab.tsx | 24 ++-
packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx | 32 ++-
3 files changed, 206 insertions(+), 70 deletions(-)
Fusion-Task-Id: FN-5925
Fusion-Task-Lineage: fdb9d882-abda-48b0-aed0-a1ff4262af8f
Store hidden usage windows with per-instance identities while keeping legacy labels restorable.
- persist hidden usage windows with an index-qualified identity instead of label-only keys
- treat legacy label-only entries as matches for restore/show-hidden behavior
- guard hidden-window counts when provider windows are empty or undefined
- add regression coverage for duplicate labels, multiple hidden windows, and legacy persistence
Files changed:
packages/dashboard/app/components/UsageIndicator.tsx | 71 +++++++++----
packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx | 113 ++++++++++++++++++++-
2 files changed, 159 insertions(+), 25 deletions(-)
Fusion-Task-Id: FN-5929
Fusion-Task-Lineage: eb48988e-80b8-4d6c-90b9-0d324564f1aa
Keep queued chat composer state when users move between sessions and only clear it when the session is truly discarded.
- stop clearing persisted pending chat messages during session switches in useChat and useQuickChat
- clear persisted queued messages only when creating a fresh session or archiving/deleting an existing session
- add regression coverage for navigation, reselection, pre-session queueing, and cleanup behavior across chat and quick chat hooks
Files changed:
.../dashboard/app/hooks/__tests__/useChat.test.ts | 246 ++++++++++++++++++++-
.../app/hooks/__tests__/useQuickChat.test.ts | 150 +++++++++++++
packages/dashboard/app/hooks/useChat.ts | 9 +-
packages/dashboard/app/hooks/useQuickChat.ts | 5 +-
4 files changed, 396 insertions(+), 14 deletions(-)
Fusion-Task-Id: FN-5921
Fusion-Task-Lineage: 8916f862-77f5-4d5b-be9a-412e8a71a47d
Fix NewChatDialog to resolve the model from the dropdown default when the user has not
explicitly picked a model, allowing chat creation with the default model.
- Introduce `resolvedModel` that falls back to `defaultModelValue` when `selectedModel` is empty
- Use `resolvedModel` in submit handler and submit-disabled guard so the default model is accepted
- Add test for creating a session with the default model ("Use default" selected)
- Add test for creating a session with an explicitly chosen non-default model
- Update existing no-default test to verify the submit button stays disabled and no session is created
Files changed:
packages/dashboard/app/components/ChatView.tsx | 12 ++--
packages/dashboard/app/components/__tests__/ChatView.test.tsx | 69 ++++++++++++++++++++--
2 files changed, 72 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-5922
Fusion-Task-Lineage: 30b2aecb-af73-4f6d-b697-cc0b5630d5d8
Keep the usage indicator's Show hidden action available when persisted hidden rows no longer render.
- count hidden usage rows from both rendered windows and orphaned persisted labels
- preserve Show hidden visibility on mobile modal and desktop popover surfaces
- add regression coverage for orphaned hidden labels across both UI surfaces
Files changed:
packages/dashboard/app/components/UsageIndicator.tsx | 22 ++++++-
packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx | 75 ++++++++++++++++++++++
2 files changed, 96 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5919
Fusion-Task-Lineage: 6e4827a6-0d6f-424d-a4df-ec7185adf841
Use a committed launcher for published CLI bin links to avoid fresh-install warnings.
- point the published fn and fusion bin entries at a committed bin.mjs launcher
- add a launcher that checks for dist/bin.js and forwards execution to the built CLI
- cover bin target invariants across workspace packages and update CLI package config tests
- add a patch changeset for the published @runfusion/fusion package
Files changed:
.changeset/fn-5916-cli-bin-launcher.md | 5 ++
packages/cli/bin.mjs | 20 ++++++
packages/cli/package.json | 5 +-
packages/cli/src/__tests__/bin-targets.test.ts | 85 +++++++++++++++++++++++
packages/cli/src/__tests__/package-config.test.ts | 8 ++-
5 files changed, 118 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-5916
Fusion-Task-Lineage: 59d40654-ff77-4655-b9ce-3f3421521b2c
The FN-5930 squash merge combined two wait-for-exit strategies,
leaving a shadowed 'const exited' and double await. Collapse to
a single register-before-kill pattern.
Fusion-Task-Id: FN-5930
Reduce flakiness in the real-git verification spawn supervision test.\n\n- write the child PID to stdout with an awaited newline flush before the parent exits\n- collapse the scenario branching so the SIGTERM path is mutually exclusive with crash handling\n- await parent process exit during cleanup and document coverage across normal, signal, and crash teardown paths\n\nFiles changed:\n .../verification-spawn-supervision.real-git.test.ts | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-5930
Fusion-Task-Lineage: 20c3fe73-a640-4c34-8a07-edf5bdda4b25
Replaces polling-only with a true server-push seam (any plugin benefits):
- core: createRouteContext accepts an emitEvent override (default still logs)
- dashboard: emitPluginCustomSseEvent forwards a plugin's ctx.emitEvent calls to
connected /api/events clients as a project-scoped 'plugin:custom' event; the
plugin route context's emitEvent is wired to it
- dashboard: PluginDashboardViewContext gains subscribePluginEvents so views
consume push via a host capability (no raw EventSource, no deep app import)
- CE view subscribes its session to push and refetches on each event; polling
stays as the fallback when push isn't wired or an event is missed
Also: skill-reachability test installs into a temp dir (no repo-dir writes).
Tests: dashboard sse +2 (plugin:custom relay + project scoping), plugin 99.
The session's owning store (and its live in-process handle) is selected per
request by projectId. start() sent projectId but answer/resume/getSession did
not, so any project-scoped session broke on the first answer (a different store
resolved → session not found / no live handle). The client now captures the
start projectId and reuses it on every subsequent call. Closes the multi-project
session-identity residual.
Closes the U2/U5 skill-discovery carry-forward so the plugin's interactive ce-*
sessions actually load the stage's bundled skill in a live agent (not just in
scripted-fake tests).
Root cause: createFnAgent built its DefaultResourceLoader without forwarding any
skill-discovery path, and the interactive seam options couldn't carry one. The
loader's skillsOverride only *filters* skills already discovered from cwd's
standard roots, so the plugin-local .fusion-ce-skills/<id>/SKILL.md was never
discoverable.
Fix (end-to-end):
- AgentOptions.additionalSkillPaths forwarded into DefaultResourceLoader
- CreateInteractiveAiSessionOptions gains requestedSkillNames + additionalSkillPaths
- the interactive engine adapter forwards them to createFnAgent (skills +
additionalSkillPaths)
- the orchestrator runs the session with cwd on the real project root and hands
it [stage.skillId] + the install root
Proven: a real DefaultResourceLoader with additionalSkillPaths discovers ce-plan
and filters out ce-work; the orchestrator passes the right id/path/cwd. Plugin 96,
engine 136, core 99 tests green.
Keep planning summary actions responsive with operation-specific loading indicators.
- pass separate single-task and breakdown loading flags into the planning summary view
- show the Creating spinner only on Create Single Task and the Breaking down spinner only on Break into Tasks while keeping the sibling action disabled
- add regression coverage for both pending-action paths and normalize the restart integration test temp worktree root under /private/tmp
Files changed:
.../dashboard/app/components/PlanningModeModal.tsx | 14 ++-
.../PlanningModeModal.planning-flow.test.tsx | 138 +++++++++++++++++++++
.../src/__tests__/restart.integration.test.ts | 7 +-
3 files changed, 151 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-5912
Fusion-Task-Lineage: b93da566-0c2b-4ff8-83ea-b6e009dfd650
- reconciler: a deleted current-stage board task no longer wedges the pipeline
in 'running' forever (terminality computed over existing tasks only; all-deleted
is a no-op, not a wedge)
- session-store: a human-slow awaiting_input session is no longer misclassified
stale (interval rubric applies only to in-flight active/launching turns)
- stage-registry: pipeline progression uses an explicit order ordinal instead of
registry insertion order, so out-of-order registration can't corrupt advancement
- orchestrator.answer(): validate questionId before mutating state, so a stale id
can't destroy the persisted currentQuestion recovery anchor
- orchestrator.resume(): rehydrate a live interactive session by replaying
persisted history (side effects suppressed) so a resumed session is actually
answerable instead of dead-ending; honest interrupted+error fallback when no
factory is available
95 tests (6 new regression tests, each confirmed failing pre-fix).
Quality cleanup across the 9-unit build (behavior-preserving, 89 tests green):
- extract createCeTaskWithLink so the work bridge and reconciler share one
provenance+link contract (prevents drift)
- discovery list scan probes readability via accessSync instead of reading and
discarding full file bytes
- makeError helper replaces ~7 duplicated CeArtifactError literals
- shared asString route helper; drop dead pipelineIds set; collapse a double
pipeline-state write and a redundant link re-query in advance
- resolveStageSkillCwd no longer takes params it ignores