Tighten the Task Review tab layout on mobile and lock in the responsive header contract.
- override the mobile .task-review-tab__summary-wrap flex rule so the summary block stops reserving excessive blank space
- extend TaskReviewTab coverage to assert the mobile summary-wrap override and prevent regressions between empty and populated review states across reviewer-agent and pull-request sources
- keep the review header structure stable across both review sources and empty/populated states
Files changed:
.../dashboard/app/components/TaskReviewTab.css | 4 +
.../components/__tests__/TaskReviewTab.test.tsx | 98 ++++++++++++++++++++++
2 files changed, 102 insertions(+)
Fusion-Task-Id: FN-5947
Fusion-Task-Lineage: 813e1142-bf28-4687-8510-2f93ec40e582
ce-compound learning from the live-output/steering work: push-channel-
alongside-pull-contract, void-safe detached turns, inactivity watchdog,
transient liveActivity + persisted trace, replay suppression. Also adds a
"Compound Engineering sessions" cluster to CONCEPTS.md and refreshes the
plugin reference doc's Sessions section with the new transport behaviors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
- runFeatureValidation now lazy-ensures a linked assertion (FN-5902) instead
of the removed zero-assertion auto-pass, for both task-completion and the
stranded-feature recovery path
- CONCEPTS.md: union of main's Merge-lifecycle cluster and this branch's
Missions clusters; Contract Assertion entry updated for FN-5902 semantics
- AGENTS.md: take main's docs/solutions + CONCEPTS.md pointer wording
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two compounding bugs made the memory-pressure vitest auto-kill a
30-second SIGKILL sweep of anything mentioning vitest:
1. False pressure: getAvailableMemory probed os.availableMemory, which
does not exist, and silently fell back to os.freemem() — on macOS
that reads ~99% used on an idle 256GB machine, permanently above the
90% threshold. Now reads process.availableMemory() (Node 22+) and
refuses to auto-kill when only the unreliable freemem fallback is
available.
2. Overbroad targeting: pgrep -f vitest matches full command lines, so
the sweep also killed wrapper shells (zsh -c '... npx vitest run'),
monitor loops, and anything else whose argv mentions vitest —
stranding exit handlers and taking out unrelated process trees.
New shared findVitestProcessIds (@fusion/core) filters matches to
actual node executables.
Surface enumeration (all vitest-process kill/count surfaces):
- TUI memory-pressure auto-kill (controller.killVitestProcesses)
- TUI manual kill-vitest command (same method)
- dashboard POST /api/kill-vitest
- dashboard GET /api/system-stats vitestProcessCount (display)
All four now route through findVitestProcessIds.
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.
Document the UNIQUE(branch_groups.branchName) collision that silently
stranded mission triage, in docs/solutions/logic-errors/, cross-linked to
the sibling PR #1345 mission-stall learning.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
branch_groups.branchName is globally UNIQUE, but ensureBranchGroupForSource
only looked up an existing group by (sourceType, sourceId). When a second
mission's shared-branch triage resolved to a base branch (e.g. "main") that
another mission already owned a group for, createBranchGroup threw
"UNIQUE constraint failed: branch_groups.branchName". That error escaped
triageFeature and was swallowed by both callers (validation-failure
auto-triage and the reconcile sweep), leaving the mission's "defined"
features — including generated fix features — permanently un-triaged.
ensureBranchGroupForSource now reuses an existing open group for the same
branch name before attempting to create one, matching the established
getBranchGroupByBranchName(...) ?? ensureBranchGroupForSource(...) idiom.
Confirmed by reproducing against a snapshot of the affected mission DB:
triageFeature threw the UNIQUE error before, succeeds after.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Document the stranded done+implementing feature stall in
docs/solutions/logic-errors/, seed CONCEPTS.md with the mission domain
vocabulary, and surface both from AGENTS.md's reference-docs list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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'/'{}'.
A mission feature could be left status="done" while its loopState never
advanced past "implementing" and it had no linked board task, so it was
never validated. The slice-completion gate (computeSliceStatus) correctly
refuses to count an assertion-linked done feature until its validator
passes, but nothing re-drove a task-less feature — so the slice, milestone,
and whole mission could never auto-progress.
Active-mission recovery now detects these stranded done features and re-runs
assertion validation directly (read-only judge, no board task): on pass the
feature becomes legitimately complete, on fail the normal fix-feature flow
takes over. Extracted the feature-validation path into a shared
runFeatureValidation helper used by both task-completion and recovery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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