- scope session collections to the active project\n- clean up runtime stage registration in discovery tests\n- model brainstorms and plans as repeatable artifact collections\n- compact the singleton Strategy presentation
Keep Compound Engineering navigation surfaces on the Boxes glyph even when stale plugin metadata is served.
- Route desktop and mobile plugin nav entries through a dashboard-view icon resolver.
- Pin Compound Engineering plugin nav icons to Boxes by plugin id while preserving normal icon fallback behavior for other plugins.
- Align manifest/header icon invariants and add regression coverage for stale Sparkles/Grid3X3 metadata.
- Add a patch changeset for the published Fusion package.
Files changed:
.changeset/FN-7198-compound-engineering-icon-match.md | 7 +++++++
packages/dashboard/app/components/LeftSidebarNav.tsx | 4 ++--
packages/dashboard/app/components/MobileNavBar.tsx | 6 +++---
.../app/components/__tests__/LeftSidebarNav.test.tsx | 6 +++++-
.../app/components/__tests__/MobileNavBar.test.tsx | 10 +++++++---
.../app/components/__tests__/pluginNavIcon.test.ts | 10 +++++++++-
packages/dashboard/app/components/pluginNavIcon.tsx | 12 ++++++++++++
.../src/__tests__/manifest.test.ts | 14 ++++++++++++++
.../src/dashboard/CompoundEngineeringView.tsx | 3 ++-
9 files changed, 61 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7198
Fusion-Task-Lineage: e910bb82-55a7-4c81-a4fe-947a07b3e308
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Give Compound Engineering its own Boxes glyph across plugin navigation and headers.
- Register the Boxes lucide icon for plugin navigation so desktop and mobile sidebars can resolve it.
- Switch the Compound Engineering manifest, plugin definition, and view header from Sparkles to Boxes.
- Update navigation and manifest tests plus the release changeset for the published CLI bundle.
Files changed:
.changeset/FN-7197-compound-engineering-icon.md | 7 +++++++
.../app/components/__tests__/MobileNavBar.test.tsx | 2 +-
.../app/components/__tests__/pluginNavIcon.test.ts | 14 ++++++++++++++
packages/dashboard/app/components/pluginNavIcon.tsx | 7 ++++++-
plugins/fusion-plugin-compound-engineering/manifest.json | 2 +-
.../src/__tests__/manifest.test.ts | 2 +-
.../src/dashboard/CompoundEngineeringView.tsx | 12 +++++++++---
plugins/fusion-plugin-compound-engineering/src/index.ts | 2 +-
8 files changed, 40 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7197
Fusion-Task-Lineage: 03c67bfa-3e28-4a2a-b15e-b2c3576588c1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Keep bundled ce-brainstorm guidance aligned with docs/plans as the durable planning artifact.
- Update the visual probe reference to name docs/plans as the durable artifact.
- Add a recursive skill-content guard that prevents docs/brainstorms from being described as new or durable output.
- Assert legacy docs/brainstorms wording remains limited to ce-plan inputs.
Files changed:
.../__tests__/skill-content-alias-guard.test.ts | 85 ++++++++++++++++++++++
.../ce-brainstorm/references/visual-probes.md | 2 +-
2 files changed, 86 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7104
Fusion-Task-Lineage: dd26a511-816d-479c-99ac-7cfec2b96bd4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Resolves the onLoad conflict in the compound-engineering plugin by keeping both
the bundled ce-* persona-def install (this branch) and main's
recoverStaleSessionsForContext call.
Also addresses PR #1672 review feedback:
- executor: sentinel resume now guards on !live.paused (mirrors
runAwaitInputNode) so a still-paused task can't consume a reply and re-enter
the skill early.
- TaskCard: make the Answer-questions button text/title/aria-label fallbacks
consistent ("Answer questions") for label-in-name a11y; update test.
- ce-work: replace the unshipped `skill: ce-worktree` reference with the real
git worktree commands so Option B can't dead-end.
- ce-resolve-pr-feedback: invoke bundled scripts by absolute path via the new
FUSION_CE_SKILLS_DIR env (sessions run with cwd=projectRoot); add the Fusion
await-input sentinel path instead of AskUserQuestion for workflow steps;
normalize whitespace-only review bodies like PR comments.
- plugin: expose FUSION_CE_SKILLS_DIR (installed skills root) to step sessions.
- plan doc: add language to fenced block (markdownlint MD040).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vendor the 43 ce-* persona definitions (from compound-engineering 3.9.4),
add installBundledCeAgents() mirroring the skill installer (pinned,
plugin-local, idempotent, never a global ~/.claude/agents), install them
in onLoad, and expose the install dir to executor/step sessions via an
executorRuntimeEnv hook (FUSION_CE_AGENTS_DIR).
Together with U2's systemPromptOverride this gives the lightweight
subagent path: a CE skill reads a persona def from FUSION_CE_AGENTS_DIR
and passes its body to fn_spawn_agent. Installer test added (19 plugin
tests green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
U1: workflow-step sessions now carry FUSION_WORKFLOW_STEP=1 (scoped to
the step session, not the main executor) so skills detect autonomous
context and surface questions via await-input instead of a dead blocking
tool.
U8: bundle ce-commit, ce-commit-push-pr, and ce-resolve-pr-feedback
(vendored from compound-engineering 3.9.4) so the CE merge/PR flow has
its skills. Registered in COMPOUND_ENGINEERING_SKILLS; manifest test
updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use per-invocation Vitest worker roots to keep compound-engineering tests from timing out on stale temp fixtures.
- Allocate a fresh FUSION_TEST_WORKER_ROOT during Vitest global setup and remove it during teardown.
- Preserve per-worker fallback root creation when global setup is not available.
- Clean compound-engineering harness project roots on close and cover the setup invariants with regression tests.
Files changed:
packages/core/src/__test-utils__/vitest-setup.ts | 20 +++++++---
.../core/src/__test-utils__/vitest-teardown.ts | 38 +++++++++++--------
.../src/__tests__/_harness.ts | 7 +++-
.../src/__tests__/setup-invariant.test.ts | 43 ++++++++++++++++++++++
4 files changed, 85 insertions(+), 23 deletions(-)
Fusion-Task-Id: FN-6282
Fusion-Task-Lineage: 8b842dd0-4f51-44de-b2db-8e8bfa97239c
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>
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'/'{}'.
- 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.
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.
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.
- 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
Add settingsSchema (default session provider/model, enabled stages, sync
reconcile-on-hooks toggle, reconcile cadence hint) aligned across manifest.json
and the runtime manifest, with typed getters. Wire the consumed getters:
orchestrator passes defaultProvider/defaultModelId into sessions and rejects
disabled stages; hooks gate their reconcile drain on reconcileOnHooks. Add the
plugin README and docs/plugins/compound-engineering.md documenting the hub,
interactive sessions, work bridge, and the sync ownership model.
reconcileIntervalMinutes is exposed as an operator cadence hint but not yet
consumed (no host scheduler; reconcile is on-demand by design).
Add a ce_pipeline_state machine (currentStage/status) kept distinct from
board-task ownership (task column) — separate tables, no shared column, per
FN-5719. onTaskMoved/onTaskCompleted hooks do only an indexed lookup + enqueue
and return well under the 5s budget; advancement happens in an on-demand
reconciler sweep that re-derives correct pipeline state from board truth, so a
dropped hook event still converges (no tight poll loop). Outbound CE-flow
changes create the next-stage board task. Conflict policy: board authoritative
for task state, CE flow authoritative for artifact/pipeline content (inbound
read and outbound write target different rows, so they cannot contend).
Host-scheduler note: no host timer wired; sweeps run on hook-drain and on the
dashboard/route refresh surface (tracked for the host event-publish follow-up).
When the work stage completes with a derived task list, create Fusion tasks via
ctx.taskStore.createTask tagged CE-originated (sourceType workflow_step +
sourceMetadata marker) and record an authoritative ce_pipeline_links row per
task (back-reference lives in the link table, not task-row JSON, per FN-5719).
Tasks then run the normal lifecycle untouched. Zero derived tasks is a clean
no-op. The link store is intentionally minimal for U8 to extend with the
bidirectional pipeline-state machine.
Extend the stage registry with presentation metadata (icon/label/glob) so adding
a stage stays data-only. Add CeFlow renderer for text/single_select/multi_select/
confirm questions over the U5 polling session routes, with a visibly-marked chat
fallback (AE1) that still completes the stage. Wire the artifact-hub launcher to
list and start registered stages. Add the skill-interaction audit test producing
a measured rich-vs-chat coverage ratio (declared classification: 8/8=100% across
brainstorm/ideate/plan), failing on unclassified interactions.
Add ce_sessions schema (onSchemaInit), CeSessionStore (via
ctx.taskStore.getDatabase()), and CeOrchestrator driving a stage's skill on the
U4 interactive seam: streams thinking/text, persists questions as
awaiting_input, writes the stage artifact on complete, and auto-saves +
emits an observable event on interrupt/error (never silent loss). Lifecycle:
launching/active/awaiting_input/completed/error/interrupted with resume and an
interval-relative staleness rubric. Start/answer/resume/get-state routes.
Streaming transport is client polling of the get-session-state route for v1:
plugin routes have no native SSE and the loader emitEvent is a logging stub, so
true server push needs a host publish-to-/api/events seam (tracked follow-up).
Skill discovery is cwd+prompt-based; forwarding install paths through the seam
is a tracked follow-up.
Bundle pinned copies of 7 CE pipeline-stage skills (strategy, ideate,
brainstorm, plan, work, code-review, compound) under src/skills/ and declare
them via PluginSkillContribution. Empirical finding: the skills contribution
alone does not make a SKILL.md resolvable in a session -- the engine ingests it
as a name only. So onLoad runs an idempotent, isolation-guarded physical install
into a plugin-local .fusion-ce-skills/ dir (never a global ~/.claude/skills),
which the engine skill-resolver can then discover. Proven against the real
loadSkills + resolveSessionSkills pipeline.