Store Assistent chats, UI state, and taste in sqlite.

Runtime session data belongs in one DB so History can FTS-search and follow the volume; persona overlays and sidecar cards stay files.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 01:10:57 +03:00
co-authored by Cursor
parent 8702b64e12
commit ac8e30c7c8
10 changed files with 582 additions and 155 deletions
+17
View File
@@ -427,6 +427,23 @@
letter-spacing: 0.03em;
}
.sa-chats-search {
appearance: none;
width: 100%;
box-sizing: border-box;
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
background: color-mix(in srgb, #000 28%, transparent);
color: inherit;
border-radius: 0.35rem;
padding: 0.35rem 0.5rem;
font: inherit;
font-size: 0.82rem;
}
.sa-chats-search:focus {
outline: 1px solid color-mix(in srgb, #6cf 55%, currentColor);
}
.sa-chats-panel-hint {
font-size: 0.72rem;
opacity: 0.65;
+2 -2
View File
@@ -4047,7 +4047,7 @@
}
/**
* Fills fields the browser has never seen from Assistent/ui-state.json, so a fresh
* Fills fields the browser has never seen from sqlite ui_state, so a fresh
* browser on the same volume inherits the previous session. Existing localStorage wins.
* auto_download is only ever restored when it is off the danger flag stays opt-in.
*/
@@ -5361,7 +5361,7 @@
}
const remoteUpdated = remote.updated || 0;
const localUpdated = state.taste?.updated || 0;
// Disk taste.json is the source of truth; localStorage only wins when it is strictly newer.
// sqlite taste is the source of truth; localStorage only wins when it is strictly newer.
const localEmpty = !localUpdated
&& !(state.taste?.styles?.length || state.taste?.likes?.length || state.taste?.avoid?.length);
if (localEmpty || remoteUpdated >= localUpdated) {
+13 -2
View File
@@ -1,5 +1,5 @@
/**
* Swarm Assistent — disk persistence for chats + UI state (Assistent/chats/, Assistent/ui-state.json).
* Swarm Assistent — sqlite persistence for chats + UI state (Assistent/memory/assistent.sqlite).
* Loaded after assistent.api.js and before assistent.js.
*/
window.SA = window.SA || {};
@@ -40,6 +40,7 @@ window.SA = window.SA || {};
createdAt: Number(raw.createdAt) || Date.now(),
updatedAt: Number(raw.updatedAt) || Number(raw.createdAt) || Date.now(),
messages: Array.isArray(raw.messages) ? raw.messages : [],
messages_count: Number(raw.messages_count) || (Array.isArray(raw.messages) ? raw.messages.length : 0),
params: raw.params && typeof raw.params === 'object' ? raw.params : null,
};
}
@@ -74,7 +75,7 @@ window.SA = window.SA || {};
return local;
}
/** Disk chats, newest first. Falls back to a localStorage migration when the volume is empty. */
/** Sqlite chats, newest first. Falls back to a localStorage migration when the store is empty. */
async function loadChats() {
let chats = [];
try {
@@ -103,6 +104,15 @@ window.SA = window.SA || {};
return normalizeChat(data?.chat);
}
async function searchChats(q) {
const query = String(q || '').trim();
if (query.length < 2) {
return [];
}
const data = await request('AssistentListChats', { q: query, with_messages: false, limit: 40 });
return (data?.chats || []).map(normalizeChat).filter(Boolean);
}
function saveChat(chat, { immediate = false } = {}) {
const clean = normalizeChat(chat);
if (!clean) {
@@ -183,6 +193,7 @@ window.SA = window.SA || {};
LS_CHATS,
loadChats,
getChat,
searchChats,
saveChat,
deleteChat,
loadUiState,