mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-09 22:18:19 +00:00
Fix/websocket streaming issues (#748)
This commit is contained in:
@@ -95,6 +95,19 @@ export const projectsDb = {
|
||||
`).all() as ProjectRepositoryRow[];
|
||||
},
|
||||
|
||||
/**
|
||||
* Archived rows are queried separately so archive-focused UIs can present
|
||||
* hidden workspaces without reintroducing them into the active sidebar list.
|
||||
*/
|
||||
getArchivedProjectPaths(): ProjectRepositoryRow[] {
|
||||
const db = getConnection();
|
||||
return db.prepare(`
|
||||
SELECT project_id, project_path, custom_project_name, isStarred, isArchived
|
||||
FROM projects
|
||||
WHERE isArchived = 1
|
||||
`).all() as ProjectRepositoryRow[];
|
||||
},
|
||||
|
||||
getCustomProjectName(projectPath: string): string | null {
|
||||
const db = getConnection();
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import { closeConnection } from '@/modules/database/connection.js';
|
||||
import { initializeDatabase } from '@/modules/database/init-db.js';
|
||||
import { sessionsDb } from '@/modules/database/repositories/sessions.db.js';
|
||||
|
||||
async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promise<void> {
|
||||
const previousDatabasePath = process.env.DATABASE_PATH;
|
||||
const tempDirectory = await mkdtemp(path.join(tmpdir(), 'sessions-db-'));
|
||||
const databasePath = path.join(tempDirectory, 'auth.db');
|
||||
|
||||
closeConnection();
|
||||
process.env.DATABASE_PATH = databasePath;
|
||||
await initializeDatabase();
|
||||
|
||||
try {
|
||||
await runTest();
|
||||
} finally {
|
||||
closeConnection();
|
||||
if (previousDatabasePath === undefined) {
|
||||
delete process.env.DATABASE_PATH;
|
||||
} else {
|
||||
process.env.DATABASE_PATH = previousDatabasePath;
|
||||
}
|
||||
await rm(tempDirectory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test('session archive queries hide archived rows from active project views', async () => {
|
||||
await withIsolatedDatabase(() => {
|
||||
sessionsDb.createSession('session-active', 'claude', '/workspace/demo-project', 'Active Session');
|
||||
sessionsDb.createSession('session-archived', 'claude', '/workspace/demo-project', 'Archived Session');
|
||||
sessionsDb.updateSessionIsArchived('session-archived', true);
|
||||
|
||||
const activeSessions = sessionsDb.getAllSessions();
|
||||
const archivedSessions = sessionsDb.getArchivedSessions();
|
||||
const activeProjectSessions = sessionsDb.getSessionsByProjectPath('/workspace/demo-project');
|
||||
const allProjectSessions = sessionsDb.getSessionsByProjectPathIncludingArchived('/workspace/demo-project');
|
||||
|
||||
assert.deepEqual(activeSessions.map((session) => session.session_id), ['session-active']);
|
||||
assert.deepEqual(archivedSessions.map((session) => session.session_id), ['session-archived']);
|
||||
assert.deepEqual(activeProjectSessions.map((session) => session.session_id), ['session-active']);
|
||||
assert.deepEqual(
|
||||
allProjectSessions.map((session) => session.session_id).sort(),
|
||||
['session-active', 'session-archived'],
|
||||
);
|
||||
assert.equal(sessionsDb.countSessionsByProjectPath('/workspace/demo-project'), 1);
|
||||
});
|
||||
});
|
||||
|
||||
test('createSession reactivates archived rows when the session becomes active again', async () => {
|
||||
await withIsolatedDatabase(() => {
|
||||
sessionsDb.createSession('session-reused', 'claude', '/workspace/demo-project', 'First Name');
|
||||
sessionsDb.updateSessionIsArchived('session-reused', true);
|
||||
|
||||
sessionsDb.createSession('session-reused', 'claude', '/workspace/demo-project', 'Updated Name');
|
||||
|
||||
const activeSessions = sessionsDb.getAllSessions();
|
||||
const archivedSessions = sessionsDb.getArchivedSessions();
|
||||
const restoredSession = sessionsDb.getSessionById('session-reused');
|
||||
|
||||
assert.equal(activeSessions.length, 1);
|
||||
assert.equal(activeSessions[0]?.session_id, 'session-reused');
|
||||
assert.equal(activeSessions[0]?.custom_name, 'Updated Name');
|
||||
assert.equal(archivedSessions.length, 0);
|
||||
assert.equal(restoredSession?.isArchived, 0);
|
||||
});
|
||||
});
|
||||
@@ -8,13 +8,14 @@ type SessionRow = {
|
||||
project_path: string | null;
|
||||
jsonl_path: string | null;
|
||||
custom_name: string | null;
|
||||
isArchived: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
type SessionMetadataLookupRow = Pick<
|
||||
SessionRow,
|
||||
'session_id' | 'provider' | 'project_path' | 'jsonl_path' | 'custom_name' | 'created_at' | 'updated_at'
|
||||
'session_id' | 'provider' | 'project_path' | 'jsonl_path' | 'custom_name' | 'isArchived' | 'created_at' | 'updated_at'
|
||||
>;
|
||||
|
||||
function normalizeTimestamp(value?: string): string | null {
|
||||
@@ -53,13 +54,14 @@ export const sessionsDb = {
|
||||
projectsDb.createProjectPath(normalizedProjectPath);
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO sessions (session_id, provider, custom_name, project_path, jsonl_path, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
||||
`INSERT INTO sessions (session_id, provider, custom_name, project_path, jsonl_path, isArchived, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 0, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
||||
ON CONFLICT(session_id) DO UPDATE SET
|
||||
provider = excluded.provider,
|
||||
updated_at = excluded.updated_at,
|
||||
project_path = excluded.project_path,
|
||||
jsonl_path = excluded.jsonl_path,
|
||||
isArchived = 0,
|
||||
custom_name = COALESCE(excluded.custom_name, sessions.custom_name)`
|
||||
).run(
|
||||
sessionId,
|
||||
@@ -87,7 +89,7 @@ export const sessionsDb = {
|
||||
const db = getConnection();
|
||||
const row = db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, created_at, updated_at
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE session_id = ?
|
||||
ORDER BY updated_at DESC
|
||||
@@ -102,8 +104,25 @@ export const sessionsDb = {
|
||||
const db = getConnection();
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, created_at, updated_at
|
||||
FROM sessions`
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE isArchived = 0`
|
||||
)
|
||||
.all() as SessionRow[];
|
||||
},
|
||||
|
||||
/**
|
||||
* Archived rows are intentionally queried separately so the caller can render
|
||||
* them in a dedicated view without reintroducing them into active session lists.
|
||||
*/
|
||||
getArchivedSessions(): SessionRow[] {
|
||||
const db = getConnection();
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE isArchived = 1
|
||||
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
||||
)
|
||||
.all() as SessionRow[];
|
||||
},
|
||||
@@ -113,7 +132,24 @@ export const sessionsDb = {
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, created_at, updated_at
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE project_path = ?
|
||||
AND isArchived = 0`
|
||||
)
|
||||
.all(normalizedProjectPath) as SessionRow[];
|
||||
},
|
||||
|
||||
/**
|
||||
* Permanent project deletion must see every session row for the path,
|
||||
* including archived ones, so their transcript files can be cleaned up.
|
||||
*/
|
||||
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
||||
const db = getConnection();
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE project_path = ?`
|
||||
)
|
||||
@@ -125,9 +161,10 @@ export const sessionsDb = {
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
return db
|
||||
.prepare(
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, created_at, updated_at
|
||||
`SELECT session_id, provider, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at
|
||||
FROM sessions
|
||||
WHERE project_path = ?
|
||||
AND isArchived = 0
|
||||
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
|
||||
LIMIT ? OFFSET ?`
|
||||
)
|
||||
@@ -141,7 +178,8 @@ export const sessionsDb = {
|
||||
.prepare(
|
||||
`SELECT COUNT(*) AS count
|
||||
FROM sessions
|
||||
WHERE project_path = ?`
|
||||
WHERE project_path = ?
|
||||
AND isArchived = 0`
|
||||
)
|
||||
.get(normalizedProjectPath) as { count: number } | undefined;
|
||||
|
||||
@@ -167,6 +205,19 @@ export const sessionsDb = {
|
||||
return row?.custom_name ?? null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Soft-delete and restore both use the same flag update so callers keep the
|
||||
* row, metadata, and file path intact while toggling visibility.
|
||||
*/
|
||||
updateSessionIsArchived(sessionId: string, isArchived: boolean): void {
|
||||
const db = getConnection();
|
||||
db.prepare(
|
||||
`UPDATE sessions
|
||||
SET isArchived = ?
|
||||
WHERE session_id = ?`
|
||||
).run(isArchived ? 1 : 0, sessionId);
|
||||
},
|
||||
|
||||
deleteSessionById(sessionId: string): boolean {
|
||||
const db = getConnection();
|
||||
return db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId).changes > 0;
|
||||
|
||||
Reference in New Issue
Block a user