## 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 — <project name>" 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>
121 lines
3.6 KiB
CSS
121 lines
3.6 KiB
CSS
/*
|
|
FNXC:ChatMemoryFocusSelector 2026-08-13:
|
|
Styling for the per-conversation memory focus selector chip + popover. Uses only
|
|
design tokens (--space-*, --color/--surface/--border/--text-muted/--accent,
|
|
--radius-*, --shadow-lg, --font-size-*, --font-weight-*) per the styling guide —
|
|
no hardcoded px (except 0), no hex/rgba. A null/empty session focus renders a
|
|
cleared "Focus" chip (never a dangling topic chip); a set topic renders an active
|
|
chip whose label is the topic. Mobile (max-width 768px) widens the popover to the
|
|
viewport minus padding so the inline input stays usable on narrow screens. The
|
|
chip matches --chat-input-control-size (the composer's send/attach control
|
|
height) so it centers with the single-line textarea.
|
|
|
|
FNXC:ChatMemoryFocusSelector 2026-08-21-13:35:
|
|
RUFU-146 review (PRRT_kwDOSA-8Y86a7RZo): raw literals replaced with existing
|
|
semantic tokens — --btn-border-width (borders), --transition-fast
|
|
(chip transition), --opacity-disabled (disabled chip), --focus-ring-strong
|
|
(focus-visible, previously an invalid `outline: 2px solid var(--focus-ring)`
|
|
declaration since --focus-ring is a box-shadow token), calc(var(--space-xl) * 8)
|
|
(12rem label cap), --z-popover (popover layer), --line-height-normal (help text).
|
|
The 768px breakpoint stays literal by design: --mobile-breakpoint is documented
|
|
in styles.css as documentation-only (custom properties cannot appear in
|
|
@media conditions) and every other component CSS uses the same literal.
|
|
*/
|
|
|
|
.chat-focus-root {
|
|
position: relative;
|
|
flex: none;
|
|
}
|
|
|
|
.chat-focus-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
min-inline-size: var(--chat-input-control-size, 2.25rem);
|
|
min-block-size: var(--chat-input-control-size, 2.25rem);
|
|
block-size: var(--chat-input-control-size, 2.25rem);
|
|
padding: 0 var(--space-sm);
|
|
color: var(--text-muted);
|
|
background: var(--surface);
|
|
border: var(--btn-border-width) solid var(--border);
|
|
border-radius: var(--radius-pill, var(--radius-md));
|
|
cursor: pointer;
|
|
transition: color var(--transition-fast), border-color var(--transition-fast);
|
|
}
|
|
|
|
.chat-focus-chip:disabled {
|
|
opacity: var(--opacity-disabled, 0.5);
|
|
cursor: default;
|
|
}
|
|
|
|
.chat-focus-chip:focus-visible {
|
|
box-shadow: var(--focus-ring-strong);
|
|
}
|
|
|
|
.chat-focus-chip--active {
|
|
color: var(--accent);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.chat-focus-chip-topic,
|
|
.chat-focus-chip-label {
|
|
max-inline-size: calc(var(--space-xl) * 8);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: var(--font-size-xs);
|
|
}
|
|
|
|
.chat-focus-popover {
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: calc(100% + var(--space-xs));
|
|
width: min(calc(var(--space-xl) * 15), calc(100vw - (var(--space-lg) * 2)));
|
|
max-width: calc(100vw - (var(--space-lg) * 2));
|
|
max-inline-size: calc(100vw - (var(--space-lg) * 2));
|
|
padding: var(--space-sm);
|
|
background: var(--surface);
|
|
border: var(--btn-border-width) solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
box-shadow: var(--shadow-lg);
|
|
z-index: var(--z-popover);
|
|
}
|
|
|
|
.chat-focus-title {
|
|
color: var(--text);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: var(--font-weight-semibold);
|
|
margin-bottom: var(--space-xs);
|
|
}
|
|
|
|
.chat-focus-help {
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-2xs);
|
|
line-height: var(--line-height-normal);
|
|
margin: 0 0 var(--space-sm);
|
|
}
|
|
|
|
.chat-focus-input {
|
|
width: 100%;
|
|
margin-bottom: var(--space-sm);
|
|
}
|
|
|
|
.chat-focus-actions {
|
|
display: flex;
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.chat-focus-save,
|
|
.chat-focus-clear {
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.chat-focus-popover {
|
|
left: var(--space-md);
|
|
right: var(--space-md);
|
|
width: auto;
|
|
max-width: none;
|
|
max-inline-size: none;
|
|
}
|
|
} |