feat: show session name and workspace path in heading

This commit is contained in:
Haileyesus
2026-04-08 17:13:15 +03:00
parent e297921d31
commit d4366e3ad2
8 changed files with 284 additions and 24 deletions

View File

@@ -3,6 +3,7 @@ import fsp, { readFile } from 'node:fs/promises';
import { scanStateDb } from '@/shared/database/repositories/scan-state.db.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
import type { LLMProvider } from '@/shared/types/app.js';
import { AppError } from '@/shared/utils/app-error.js';
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
@@ -118,7 +119,11 @@ export const llmSessionsService = {
});
}
return session;
const workspace = workspaceOriginalPathsDb.getWorkspacePath(session.workspace_path);
return {
...session,
workspace_id: workspace?.workspace_id ?? null,
};
},
/**

View File

@@ -7,6 +7,7 @@ import test from 'node:test';
import { AppError } from '@/shared/utils/app-error.js';
import { scanStateDb } from '@/shared/database/repositories/scan-state.db.js';
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
import { llmProviderRegistry } from '@/modules/ai-runtime/ai-runtime.registry.js';
import { llmSessionsService } from '@/modules/ai-runtime/services/sessions.service.js';
import { conversationSearchService } from '@/modules/conversations/conversation-search.service.js';
@@ -102,6 +103,7 @@ test('llmSessionsService.updateSessionCustomName validates existence before upda
provider: 'claude',
workspace_path: '/tmp/workspace',
jsonl_path: null,
custom_name: null,
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-01T00:00:00.000Z',
}
@@ -140,11 +142,22 @@ test('llmSessionsService.getIndexedSession returns DB session metadata', { concu
provider: 'claude',
workspace_path: '/tmp/workspace',
jsonl_path: '/tmp/workspace/session.jsonl',
custom_name: 'Custom Session Name',
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-02T00:00:00.000Z',
}
: null
));
const restoreGetWorkspacePath = patchMethod(workspaceOriginalPathsDb, 'getWorkspacePath', (workspacePath: string) => (
workspacePath === '/tmp/workspace'
? {
workspace_id: 'workspace-123',
workspace_path: workspacePath,
custom_workspace_name: 'Workspace Custom Name',
isStarred: 0,
}
: null
));
try {
const session = llmSessionsService.getIndexedSession('known-session');
@@ -153,8 +166,10 @@ test('llmSessionsService.getIndexedSession returns DB session metadata', { concu
provider: 'claude',
workspace_path: '/tmp/workspace',
jsonl_path: '/tmp/workspace/session.jsonl',
custom_name: 'Custom Session Name',
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-02T00:00:00.000Z',
workspace_id: 'workspace-123',
});
assert.throws(
@@ -165,6 +180,7 @@ test('llmSessionsService.getIndexedSession returns DB session metadata', { concu
error.statusCode === 404,
);
} finally {
restoreGetWorkspacePath();
restoreGetById();
}
});
@@ -183,6 +199,7 @@ test('llmSessionsService.deleteSessionArtifacts validates ids and deletes disk/d
provider: 'cursor',
workspace_path: '/tmp/workspace',
jsonl_path: transcriptPath,
custom_name: null,
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-01T00:00:00.000Z',
}
@@ -233,6 +250,7 @@ test('llmSessionsService.getSessionHistory parses JSONL and Gemini JSON correctl
provider: 'cursor',
workspace_path: '/tmp/workspace',
jsonl_path: jsonlPath,
custom_name: null,
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-01T00:00:00.000Z',
};
@@ -244,6 +262,7 @@ test('llmSessionsService.getSessionHistory parses JSONL and Gemini JSON correctl
provider: 'gemini',
workspace_path: '/tmp/workspace',
jsonl_path: jsonPath,
custom_name: null,
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-01T00:00:00.000Z',
};
@@ -255,6 +274,7 @@ test('llmSessionsService.getSessionHistory parses JSONL and Gemini JSON correctl
provider: 'claude',
workspace_path: '/tmp/workspace',
jsonl_path: null,
custom_name: null,
created_at: '2026-04-01T00:00:00.000Z',
updated_at: '2026-04-01T00:00:00.000Z',
};

View File

@@ -29,6 +29,18 @@ const parseWorkspaceIdFromBody = (req: Request): string => {
return workspaceId;
};
const parseWorkspaceIdFromParams = (req: Request): string => {
const workspaceId = getTrimmedString(req.params.workspaceId);
if (!workspaceId) {
throw new AppError('workspaceId is required.', {
code: 'WORKSPACE_ID_REQUIRED',
statusCode: 400,
});
}
return workspaceId;
};
const parseWorkspaceCustomNameFromBody = (req: Request): string | null => {
const body = req.body as Record<string, unknown> | undefined;
const customName = getTrimmedString(body?.workspaceCustomName);
@@ -43,6 +55,15 @@ router.get(
}),
);
router.get(
'/:workspaceId',
asyncHandler(async (req: Request, res: Response) => {
const workspaceId = parseWorkspaceIdFromParams(req);
const workspace = workspaceService.getWorkspaceById(workspaceId);
res.json(createApiSuccessResponse({ workspace }));
}),
);
router.patch(
'/star',
asyncHandler(async (req: Request, res: Response) => {

View File

@@ -86,6 +86,30 @@ const sortWorkspacesByLastActivity = (
return left.workspaceDisplayName.localeCompare(right.workspaceDisplayName);
});
const toWorkspaceRecord = (
workspaceId: string,
workspacePath: string,
workspaceCustomName: string | null,
isStarred: boolean,
sessions: WorkspaceSessionRecord[],
): WorkspaceRecord => {
const sortedSessions = sortSessionsByLastActivity(sessions);
const lastActivity = sortedSessions[0]?.lastActivity || null;
return {
workspaceId,
workspaceOriginalPath: workspacePath,
workspaceCustomName,
workspaceDisplayName:
workspaceCustomName ||
path.basename(workspacePath) ||
workspacePath,
isStarred,
lastActivity,
sessions: sortedSessions,
};
};
/**
* Groups indexed sessions by workspace and returns a deterministic catalog shape.
*/
@@ -102,23 +126,14 @@ const buildWorkspaceSessionCollection = (): WorkspaceRecord[] => {
}
const workspaceRecords = workspaceRows.map((workspaceRow) => {
const sessions = sortSessionsByLastActivity(
sessionsByWorkspace.get(workspaceRow.workspace_path) || [],
);
const lastActivity = sessions[0]?.lastActivity || null;
return {
workspaceId: workspaceRow.workspace_id,
workspaceOriginalPath: workspaceRow.workspace_path,
workspaceCustomName: workspaceRow.custom_workspace_name,
workspaceDisplayName:
workspaceRow.custom_workspace_name ||
path.basename(workspaceRow.workspace_path) ||
workspaceRow.workspace_path,
isStarred: workspaceRow.isStarred === 1,
lastActivity,
const sessions = sessionsByWorkspace.get(workspaceRow.workspace_path) || [];
return toWorkspaceRecord(
workspaceRow.workspace_id,
workspaceRow.workspace_path,
workspaceRow.custom_workspace_name,
workspaceRow.isStarred === 1,
sessions,
};
);
});
return sortWorkspacesByLastActivity(workspaceRecords);
@@ -132,6 +147,28 @@ export const workspaceService = {
return buildWorkspaceSessionCollection();
},
getWorkspaceById(workspaceId: string): WorkspaceRecord {
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
if (!workspaceRow) {
throw new AppError('Workspace not found.', {
code: 'WORKSPACE_NOT_FOUND',
statusCode: 404,
});
}
const sessions = sessionsDb
.getSessionsByWorkspacePath(workspaceRow.workspace_path)
.map(toWorkspaceSessionRecord);
return toWorkspaceRecord(
workspaceRow.workspace_id,
workspaceRow.workspace_path,
workspaceRow.custom_workspace_name,
workspaceRow.isStarred === 1,
sessions,
);
},
toggleWorkspaceStar(workspaceId: string): boolean {
const workspaceRow = workspaceOriginalPathsDb.getWorkspaceById(workspaceId);
if (!workspaceRow) {

View File

@@ -14,7 +14,7 @@ type SessionNameLookupRow = {
type SessionMetadataLookupRow = Pick<
SessionsRow,
'session_id' | 'provider' | 'workspace_path' | 'jsonl_path' | 'created_at' | 'updated_at'
'session_id' | 'provider' | 'workspace_path' | 'jsonl_path' | 'custom_name' | 'created_at' | 'updated_at'
>;
function normalizeTimestamp(value?: string): string | null {
@@ -119,7 +119,7 @@ export const sessionsDb = {
const db = getConnection();
const row = db
.prepare(
`SELECT session_id, provider, workspace_path, jsonl_path, created_at, updated_at
`SELECT session_id, provider, workspace_path, jsonl_path, custom_name, created_at, updated_at
FROM sessions
WHERE session_id = ?`
)