feat(sessions): add API endpoints for hiding and unhiding sessions

- Implemented new POST endpoints for hiding and unhiding individual and bulk sessions in the configuration.
- Added error handling and validation for project and session IDs in the new API routes.
- Enhanced the existing session management functionality in the ConfigManager to support these operations.
This commit is contained in:
matt 2026-02-16 23:33:56 +09:00
parent da1a8998fc
commit fb66b14d62
3 changed files with 118 additions and 22 deletions

View file

@ -382,6 +382,94 @@ export function registerConfigRoutes(app: FastifyInstance): void {
}
);
// Hide session
app.post<{ Body: { projectId: string; sessionId: string } }>(
'/api/config/hide-session',
async (request) => {
try {
const { projectId, sessionId } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!sessionId || typeof sessionId !== 'string') {
return { success: false, error: 'Session ID is required and must be a string' };
}
configManager.hideSession(projectId, sessionId);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/hide-session:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Unhide session
app.post<{ Body: { projectId: string; sessionId: string } }>(
'/api/config/unhide-session',
async (request) => {
try {
const { projectId, sessionId } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!sessionId || typeof sessionId !== 'string') {
return { success: false, error: 'Session ID is required and must be a string' };
}
configManager.unhideSession(projectId, sessionId);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/unhide-session:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Bulk hide sessions
app.post<{ Body: { projectId: string; sessionIds: string[] } }>(
'/api/config/hide-sessions',
async (request) => {
try {
const { projectId, sessionIds } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!Array.isArray(sessionIds) || sessionIds.some((id) => typeof id !== 'string')) {
return { success: false, error: 'Session IDs must be an array of strings' };
}
configManager.hideSessions(projectId, sessionIds);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/hide-sessions:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Bulk unhide sessions
app.post<{ Body: { projectId: string; sessionIds: string[] } }>(
'/api/config/unhide-sessions',
async (request) => {
try {
const { projectId, sessionIds } = request.body;
if (!projectId || typeof projectId !== 'string') {
return { success: false, error: 'Project ID is required and must be a string' };
}
if (!Array.isArray(sessionIds) || sessionIds.some((id) => typeof id !== 'string')) {
return { success: false, error: 'Session IDs must be an array of strings' };
}
configManager.unhideSessions(projectId, sessionIds);
return { success: true };
} catch (error) {
logger.error('Error in POST /api/config/unhide-sessions:', error);
return { success: false, error: getErrorMessage(error) };
}
}
);
// Select folders - no-op in browser mode
app.post('/api/config/select-folders', async (): Promise<ConfigResult<string[]>> => {
return { success: true, data: [] };

View file

@ -562,14 +562,18 @@ export async function analyzeSessionFileMetadata(
}
// Last phase: final tokens - last post-compaction
// Guard: if the last compaction had no subsequent assistant message, post is 0.
// In that case, skip the final phase to avoid double-counting.
const lastPhase = compactionPhases[compactionPhases.length - 1];
const lastContribution = lastMainAssistantInputTokens - lastPhase.post;
total += lastContribution;
phaseBreakdown.push({
phaseNumber: compactionPhases.length + 1,
contribution: lastContribution,
peakTokens: lastMainAssistantInputTokens,
});
if (lastPhase.post > 0) {
const lastContribution = lastMainAssistantInputTokens - lastPhase.post;
total += lastContribution;
phaseBreakdown.push({
phaseNumber: compactionPhases.length + 1,
contribution: lastContribution,
peakTokens: lastMainAssistantInputTokens,
});
}
contextConsumption = total;
}

View file

@ -413,23 +413,27 @@ export const ChatHistory = ({ tabId }: ChatHistoryProps): JSX.Element => {
const prevItem = aiItemIndex > 0 ? conversation.items[aiItemIndex - 1] : null;
if (prevItem?.type !== 'user') return;
const groupId = prevItem.group.id;
const element = chatItemRefs.current.get(groupId);
if (!element) return;
const run = async (): Promise<void> => {
const groupId = prevItem.group.id;
await ensureGroupVisible(groupId);
const element = chatItemRefs.current.get(groupId);
if (!element) return;
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
setHighlightedGroupId(groupId);
setIsNavigationHighlight(true);
if (navigationHighlightTimerRef.current) {
clearTimeout(navigationHighlightTimerRef.current);
}
navigationHighlightTimerRef.current = setTimeout(() => {
setHighlightedGroupId(null);
setIsNavigationHighlight(false);
navigationHighlightTimerRef.current = null;
}, 2000);
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
setHighlightedGroupId(groupId);
setIsNavigationHighlight(true);
if (navigationHighlightTimerRef.current) {
clearTimeout(navigationHighlightTimerRef.current);
}
navigationHighlightTimerRef.current = setTimeout(() => {
setHighlightedGroupId(null);
setIsNavigationHighlight(false);
navigationHighlightTimerRef.current = null;
}, 2000);
};
void run();
},
[conversation, setHighlightedGroupId]
[conversation, ensureGroupVisible, setHighlightedGroupId]
);
// Handler to navigate to a specific tool within a turn from context panel