Files
fusion/plugins/fusion-plugin-dependency-graph/README.md
ischindl 8fcf4bdbaa feat: Stash memory backend — session capture, per-chat backfill, opt-in vector search (#3494)
## Summary

Adds the **Stash memory backend** (`memory.backendType=stash`) that
connects Fusion's agent memory to the
[Stash](https://github.com/Fergana-Labs/stash) product — *knowledge
bases for the agent era* ([product site:
joinstash.ai](https://joinstash.ai)). Fusion becomes a first-class Stash
client: complete chat sessions and finished tasks are captured into
Stash, memory is recalled during chat, and Stash sessions are kept in
sync with the dashboard (including deletes and archival).

**Product:** <https://github.com/Fergana-Labs/stash> ·
[joinstash.ai](https://joinstash.ai)

## What's included

### 1. Stash memory backend (RUFU-068 / RUFU-121)
- New `StashMemoryBackend` (`memory.backendType=stash`) with `stashUrl`
/ `stashApiKey` settings (global secrets-store `stash-api-key` +
per-project override).
- **Complete-chat-session capture** keyed by ChatSession id.
- Sessions are classified into **per-project folders** (get-or-create,
`external_key fusion-<projectId>`, 1h per-process cache) and
**soft-deleted with their chat** via `DELETE /api/chat/sessions/:id`.
- Per-conversation **memory-focus** read-time scoping (new
`0066_chat_session_memory_focus.sql` migration — sequence renumbered
0059→0060→0061→0065→0066 as origin/main claimed the lower numbers);
event metadata enriched with `project` / `project_name` / `chat_title`.
- Recall queries normalized to single-keyword / explicit-OR ASCII (≤100
chars); shared normalizer export reused by per-turn recall.

### 2. Per-task executor transcript capture (RUFU-122)
Finished or failed tasks upload their executor transcript
(`agent-log.jsonl`) to Stash as a task session.

### 3. Bulk archive Stash sync (RUFU-125)
Archived task-planner chats soft-delete their Stash sessions on bulk
archival (paged). The snapshot of doomed session ids is taken *before*
the local bulk delete, and the Stash sync runs fire-and-forget so a
Stash stall can never delay local archival.

### 4. Per-chat "Preserve to Stash" backfill (RUFU-136)
A per-chat action that backfills a chat's full history into Stash, with
client-side idempotency and a pre-check that skips already-uploaded
content (fail-closed, no duplicate upload on transport failure).
- **Session-folder naming fix:** the first project folder is now named
"Fusion — &lt;project name&gt;" instead of the bare "Fusion" fallback
(the backfill now resolves the central-registry project name,
best-effort, never blocking the upload).

### 5. Opt-in semantic (vector) recall (RUFU-126)
`stashVectorSearch` setting (default `false` — **zero behavior change
until enabled**). For multi-word queries the backend tries Stash's
semantic-search endpoint first, then falls back byte-identically to the
keyword path. Definitive 404/405/501/503 responses are negatively cached
per process. Requires a patched Stash server (new endpoint +
`sentence-transformers` + embedding backfill); unpatched servers are
transparently bypassed after the first 404.

## Safety
- **Opt-in / inert by default:** the default backend remains `qmd`; the
Stash backend is inert until `memoryBackendType=stash` + `stashUrl` are
set.
- All Stash I/O is **best-effort, fail-closed, and non-blocking** — a
Stash outage never blocks chat, task completion, or archival. No
run-audit content is emitted.

## Testing
- Backfill + delete-sync suites (20/20), Stash backend suite (68/68),
executor memory / session capture suites, `memory-focus-recalling`,
description-guard — all green.
- `tsc` clean across core / engine / dashboard.
- Live verification: bulk backfill of 21/24 chats completed; the
"Preserve to Stash" action is idempotent on re-run.

## Changesets
- `@runfusion/fusion` **minor** — Stash memory backend + capture
(RUFU-068/121), per-task transcript (RUFU-122), bulk archive sync
(RUFU-125), per-chat backfill (RUFU-136), opt-in vector search
(RUFU-126)
- `@runfusion/fusion` **patch** — backfill session-folder naming fix


## Rebase Note (2026-08-23)

Rebased onto `origin/main` `3f448f7292` (v0.77.0-beta.7). Conflicts
resolved additively:
- `packages/core/src/postgres/schema-applier.ts` + test — upstream's
0062-0065 migrations (task/subtask splitting removal, AI merge review
reconciliation, task repository scope, FN-149 review convergence)
unioned with this PR's `chat_sessions.memory_focus` migration, which is
**renumbered 0065 → 0066** (upstream's FN-149 shipped 0065 canonically
on origin/main); `SCHEMA_BASELINE_VERSION` advances to `0066`.
- `packages/dashboard/app/components/ChatView.tsx` — upstream's docked
chat sidebar resize handlers unioned with the RUFU-136 "Preserve to
Stash" backfill handler.
- New commit: `settings.memory.*` stash-backend i18n keys added to all 6
secondary locales (RUFU-121/122 parity fix; `pnpm i18n:status` no longer
reports any violation introduced by this PR).

**Deploy note (operator environments that already ran a pre-rebase build
of this PR):** the memory-focus SQL may already be in the schema under
ledger row `0065`. Remap that row to `0066` (`UPDATE
fusion_schema_migrations SET version = '0066' WHERE version = '0065';`)
*before* first boot of a 0066-ceiling binary — otherwise the fresh
upstream `0065_fn_149_review_convergence_stage.sql` would be skipped as
"already applied". Clean databases (no prior memory-focus row) need no
action.

**CI note — Lint (lifecycle-column census) is red on the merge base:**
`pnpm check:lifecycle-columns --strict` fails identically on pure
`origin/main` `3f448f7292` with
`packages/core/src/db/legacy-adoption.ts: 0 -> 3` (3 column guards in
the U9b legacy-adoption table without a baseline entry or
`DELIBERATE-LITERAL` marker). Verified by running the census on a clean
origin/main checkout — inherited from the base, not introduced by this
PR. Fix belongs upstream; tracked separately.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Stash memory integration with project configuration and optional
semantic search.
  * Added per-chat memory focus controls and a `/focus` command.
  * Added “Preserve to Stash” for uploading complete chat history.
  * Added automatic chat, task transcript, and completion-event capture.
* Added project-specific Stash session folders and archive/delete
synchronization.
* **Bug Fixes**
* Improved Stash folder naming and handling of missing branches during
no-commit tasks.
* **Documentation**
* Added setup, configuration, integration, vector-search, and
performance guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Fusion <noreply@runfusion.ai>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-08-23 16:46:14 -07:00

7.5 KiB

fusion-plugin-dependency-graph

Plugin-provided top-level Graph dashboard view for Fusion.

Host registration and bundled loading

The dependency graph view is registered as a bundled plugin view in the dashboard. This means:

  • registerBundledPluginViews() (called at dashboard startup) registers the view component in the static plugin view registry under key plugin:fusion-plugin-dependency-graph:graph.
  • The view component is resolved via a literal-specifier lazy import (import("@fusion-plugin-examples/dependency-graph/dashboard-view")) — this is critical for Vite/esbuild to emit a production chunk.
  • In Vite dev/build and Vitest, @fusion-plugin-examples/dependency-graph/dashboard-view is aliased to plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx (source, not dist/). This avoids stale dist artifacts breaking the dashboard view.
  • The App.tsx graph route resolves to the bundled view via isPluginViewRegistered fallback, so the graph view works even when the plugin is not installed/loaded through the PluginLoader API (e.g. fresh DB).
  • The canonical route ID is plugin:fusion-plugin-dependency-graph:graph.

Rendering approach

  • Filtering: includes triage, todo, in-progress, in-review; excludes done, archived
  • Graph build: edges are resolved only from task.dependencies as source=dependent, target=dependency
  • Orphan dependency handling: if a visible task depends on an excluded/missing dependency (for example done/archived after filtering), the missing edge is silently dropped and graph rendering continues without broken connectors
  • Auto-layout: Sugiyama-style layered layout (computeAutoLayout) groups nodes by dependency depth; both vertical and horizontal orientations use measured per-node card heights to compute stacked offsets (preventing card overlap), and orientation still flips responsively (height > width or width < 768) so depth flows left-to-right on tall/narrow viewports
  • Edge drawing: SVG bezier curves from source bottom-center to target top-center, with vertical mode anchoring source tails to each node's measured rendered height and arrowheads showing dependent → dependency direction
  • Interaction: drag-to-pan canvas background, scroll/two-finger pan, Ctrl/Cmd-wheel zoom, pinch-to-zoom with stationary midpoint, drag-to-reposition nodes, keyboard shortcuts, zoom toolbar, reset, and fit-to-graph
  • Zoomed navigation reachability: pan clamping now scales with the full rendered graph bounds (min/max node extents) at the active zoom level, so zooming in no longer traps off-screen content behind fixed viewport-only limits
  • Fit-to-graph: computes node bounding box from both minimum and maximum node coordinates (including negative auto-layout origins) with layout node dimensions and applies zoom/pan so the graph fits in viewport with padding
  • Initial auto-fit: when no saved scoped positions exist, the first non-empty render auto-fits once; subsequent updates preserve user navigation state
  • Position persistence: dragged node positions are stored per project in browser localStorage and restored on reload
  • Animated transitions: fit/reset operations animate transform (var(--transition-normal)), while continuous drag/wheel/pinch stays transition-free for responsiveness
  • Node rendering: each graph node renders the real dashboard TaskCard via GraphTaskNode (no duplicated card markup)
  • Task detail integration: a primary non-drag click on a graph node surface opens the native dashboard task detail modal exactly once through host context (openTaskDetail), matching board/list behavior
  • In-progress behavior: steps are visible by default and active-task glow (agent-active) is preserved because node cards reuse TaskCard directly
  • Active-state indicator bar: active nodes render a compact top bar (.graph-task-active-indicator) with the current execution status label (for example Executing, Planning) and pulsing --in-progress emphasis
  • Current-step highlighting: active nodes set data-current-step for valid native step indices so CSS selectors highlight the currently executing .card-step-item and pulse its step dot
  • Zoom-out differentiation: .graph-task-node--active adds amplified glow and subtle scale/border tint so active nodes remain distinguishable at reduced zoom levels
  • In-review visual treatment: in-review nodes get a static .graph-task-node--in-review left accent in --in-review to distinguish waiting-review work from active execution nodes
  • Graph node classes: .graph-task-node, .graph-task-node--active, .graph-task-node--in-review, .graph-task-node--highlighted, .graph-task-node--dimmed, .graph-node--highlighted, and .graph-node--dimmed are available for graph-specific layering/highlight states while card internals remain owned by TaskCard.css
  • Graph edge classes: .graph-edge--highlighted and .graph-edge--dimmed are applied during dependency-chain emphasis states
  • Drag behavior: native task dragging was removed from TaskCard entirely (FN-051), so graph nodes no longer pass any drag prop — canvas pan/zoom never conflicts with card-level HTML5 drag

Position persistence

  • Canonical base key: fusion-plugin-dependency-graph:positions
  • Storage key format: kb:${projectId}:fusion-plugin-dependency-graph:positions (falls back to fusion-plugin-dependency-graph:positions when no project is selected)
  • Read path: positions load on graph mount and whenever projectId changes, then merge with fresh auto-layout so new tasks still receive layout defaults
  • Write path: positions persist on drag end only (not on every drag frame), filtered to currently visible tasks for stale cleanup
  • Reset behavior: Fit to graph / Reset view clear persisted positions and re-apply auto-layout
  • Implementation detail: the plugin now reuses dashboard projectStorage helpers (getScopedItem / setScopedItem / removeScopedItem) instead of duplicating scoped localStorage logic

Dependency chain highlighting

  • Hover a node to highlight the full transitive upstream + downstream chain for that task.
  • Click a node to persist selection highlighting until the same node is clicked again or the canvas pane is clicked; this same click also opens task detail once through the host detail callback.
  • Priority: hover state overrides selected state; when hover leaves, selected highlighting reappears.
  • Dimming: when a chain is active, unrelated nodes and edges are dimmed.
  • Neutral state: when nothing is hovered/selected, no highlight/dim classes are applied.
  • Drag suppression: drag movements above the node drag threshold suppress the post-drag click, preventing accidental detail opens on pointer release.
  • Edge rule: an edge is highlighted only when both its source and target nodes are in the active chain.

Controls

Toolbar (bottom-right)

  • Zoom in (ZoomIn) — button + Ctrl+= / Cmd+=
  • Zoom out (ZoomOut) — button + Ctrl+- / Cmd+-
  • Zoom percent label — live readout (for example 100%, 75%, 250%)
  • Fit to graph (Maximize) — button + Ctrl+Shift+F / Cmd+Shift+F
  • Reset view (RotateCcw) — button + Ctrl+0 / Cmd+0

Additional keyboard behavior

  • Escape resets to default view (zoom=1, pan=0,0)
  • Shortcuts are suppressed when focus is inside input, textarea, select, or contentEditable elements

All controls are rendered as floating .btn-icon actions in the bottom-right corner, with mobile-friendly 44px touch targets in the @media (max-width: 768px) override.