-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: auto-name new sessions and support API-key renames #643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,7 @@ console.log('SERVER_PORT from env:', process.env.SERVER_PORT); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import express from 'express'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { WebSocketServer, WebSocket } from 'ws'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { connectedClients, broadcastProgress, broadcastSessionNameUpdated } from './utils/websocket-clients.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os from 'os'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import http from 'http'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import cors from 'cors'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -68,7 +69,7 @@ import pluginsRoutes from './routes/plugins.js'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import messagesRoutes from './routes/messages.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createNormalizedMessage } from './providers/types.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { initializeDatabase, sessionNamesDb, apiKeysDb, applyCustomSessionNames } from './database/db.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { configureWebPush } from './services/vapid-keys.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { IS_PLATFORM } from './constants/config.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -96,22 +97,8 @@ const WATCHER_IGNORED_PATTERNS = [ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const WATCHER_DEBOUNCE_MS = 300; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let projectsWatchers = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let projectsWatcherDebounceTimer = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const connectedClients = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let isGetProjectsRunning = false; // Flag to prevent reentrant calls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Broadcast progress to all connected WebSocket clients | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function broadcastProgress(progress) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const message = JSON.stringify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: 'loading_progress', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...progress | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connectedClients.forEach(client => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (client.readyState === WebSocket.OPEN) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client.send(message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Setup file system watchers for Claude, Cursor, and Codex project/session folders | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function setupProjectsWatcher() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const chokidar = (await import('chokidar')).default; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -398,12 +385,55 @@ app.use('/api/gemini', authenticateToken, geminiRoutes); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Plugins API Routes (protected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.use('/api/plugins', authenticateToken, pluginsRoutes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Rename session endpoint (JWT or API key header) — must be registered | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // before the /api/sessions router so the API-key auth path is not blocked | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // by the global authenticateToken middleware. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.put('/api/sessions/:sessionId/rename', (req, res, next) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // JWT-first: if Authorization header present, use JWT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const authHeader = req.headers['authorization']; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (authHeader) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return authenticateToken(req, res, next); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback: DB API key via x-api-key header only (no query string for security) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const apiKey = req.headers['x-api-key']; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (apiKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = apiKeysDb.validateApiKey(apiKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (user) { req.user = user; return next(); } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(401).json({ error: 'Invalid or inactive API key' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(401).json({ error: 'Authentication required (Authorization or x-api-key header)' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+397
to
+404
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback: DB API key via x-api-key header only (no query string for security) | |
| const apiKey = req.headers['x-api-key']; | |
| if (apiKey) { | |
| const user = apiKeysDb.validateApiKey(apiKey); | |
| if (user) { req.user = user; return next(); } | |
| return res.status(401).json({ error: 'Invalid or inactive API key' }); | |
| } | |
| return res.status(401).json({ error: 'Authentication required (Authorization or x-api-key header)' }); | |
| // Fallback: DB API key via x-api-key header. | |
| // When the global API_KEY gate is enabled, allow ?apiKey= as an alternate | |
| // transport so clients can satisfy both the global gate and per-user key auth. | |
| const headerApiKey = req.headers['x-api-key']; | |
| const queryApiKey = process.env.API_KEY ? req.query?.apiKey : undefined; | |
| const apiKey = typeof headerApiKey === 'string' && headerApiKey | |
| ? headerApiKey | |
| : (typeof queryApiKey === 'string' && queryApiKey ? queryApiKey : undefined); | |
| if (apiKey) { | |
| const user = apiKeysDb.validateApiKey(apiKey); | |
| if (user) { req.user = user; return next(); } | |
| return res.status(401).json({ error: 'Invalid or inactive API key' }); | |
| } | |
| const authError = process.env.API_KEY | |
| ? 'Authentication required (Authorization, x-api-key header, or apiKey query parameter)' | |
| : 'Authentication required (Authorization or x-api-key header)'; | |
| return res.status(401).json({ error: authError }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broadcast manual renames after persisting them.
Line 422 only updates session_names. Those SQLite writes do not hit the project watchers, so JWT/API-key renames never emit session_name_updated and other open clients stay stale until a refresh. The new live-update path needs to be triggered here as well.
💡 Suggested fix
sessionNamesDb.setName(safeSessionId, provider, summary.trim());
+ broadcastSessionNameUpdated(safeSessionId, provider, summary.trim());
res.json({ success: true });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sessionNamesDb.setName(safeSessionId, provider, summary.trim()); | |
| res.json({ success: true }); | |
| sessionNamesDb.setName(safeSessionId, provider, summary.trim()); | |
| broadcastSessionNameUpdated(safeSessionId, provider, summary.trim()); | |
| res.json({ success: true }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/index.js` around lines 422 - 423, After persisting the rename with
sessionNamesDb.setName(safeSessionId, provider, summary.trim()), also trigger
the live-update path so other clients get the change (emit the
"session_name_updated" event). Call the project/watchers notifier (e.g. the
existing emitter or websocket broadcast function) with the same identifiers and
the trimmed name — include safeSessionId, provider and summary.trim() — before
sending res.json({ success: true }); so JWT/API-key sessions and open clients
receive the update immediately.
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,99 @@ | ||||||||||||||||
| import { query } from '@anthropic-ai/claude-agent-sdk'; | ||||||||||||||||
| import { sessionNamesDb } from './database/db.js'; | ||||||||||||||||
|
|
||||||||||||||||
| const inFlight = new Set(); | ||||||||||||||||
|
|
||||||||||||||||
| function cleanUserMessage(text) { | ||||||||||||||||
| if (!text || typeof text !== 'string') return ''; | ||||||||||||||||
| return text | ||||||||||||||||
| .replace(/<command-name>[\s\S]*?<\/command-name>/g, '') | ||||||||||||||||
| .replace(/<command-message>[\s\S]*?<\/command-message>/g, '') | ||||||||||||||||
| .replace(/<command-args>[\s\S]*?<\/command-args>/g, '') | ||||||||||||||||
| .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '') | ||||||||||||||||
| .replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, '') | ||||||||||||||||
| .trim(); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| function sanitizeSummary(text) { | ||||||||||||||||
| if (!text || typeof text !== 'string') return null; | ||||||||||||||||
| let s = text | ||||||||||||||||
| .replace(/[\n\r]/g, ' ') | ||||||||||||||||
| .replace(/^["'`]+|["'`]+$/g, '') // strip surrounding quotes | ||||||||||||||||
| .replace(/^(Session name|Name|Title|Summary):\s*/i, '') // strip common prefixes | ||||||||||||||||
| .trim(); | ||||||||||||||||
| if (!s) return null; | ||||||||||||||||
| return s.substring(0, 60); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| async function generateSessionName(userMessage, assistantResponse) { | ||||||||||||||||
| const cleaned = cleanUserMessage(userMessage); | ||||||||||||||||
| if (!cleaned) return null; | ||||||||||||||||
|
|
||||||||||||||||
| const prompt = `Given this coding assistant conversation, generate a concise session name (max 60 chars). | ||||||||||||||||
|
|
||||||||||||||||
| User: ${cleaned.substring(0, 500)} | ||||||||||||||||
| Assistant: ${(assistantResponse || '').substring(0, 500)} | ||||||||||||||||
|
|
||||||||||||||||
| Rules: | ||||||||||||||||
| - Return ONLY the session name, no quotes, no explanation | ||||||||||||||||
| - Focus on the actual task, ignore XML tags and boilerplate content | ||||||||||||||||
| - Be specific (e.g. "Fix auth token refresh bug" not "Code changes") | ||||||||||||||||
| - Max 60 characters`; | ||||||||||||||||
|
|
||||||||||||||||
| const q = query({ | ||||||||||||||||
| prompt, | ||||||||||||||||
| options: { | ||||||||||||||||
| model: 'haiku', | ||||||||||||||||
| tools: [], | ||||||||||||||||
| maxTurns: 1, | ||||||||||||||||
| persistSession: false, | ||||||||||||||||
| permissionMode: 'plan', | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| const timeout = setTimeout(() => q.return?.(), 30000); | ||||||||||||||||
| try { | ||||||||||||||||
| for await (const msg of q) { | ||||||||||||||||
| if (msg.type === 'result' && msg.result) { | ||||||||||||||||
| return sanitizeSummary(msg.result); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } finally { | ||||||||||||||||
| clearTimeout(timeout); | ||||||||||||||||
| } | ||||||||||||||||
| return null; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Generate a session name using Claude SDK and persist it to SQLite. | ||||||||||||||||
| * Skips if a custom name (manual rename) already exists for this session. | ||||||||||||||||
| * @param {string} sessionId | ||||||||||||||||
| * @param {string} userMessage - First user message | ||||||||||||||||
| * @param {string|null} assistantResponse - First assistant response | ||||||||||||||||
| * @param {function|null} broadcastFn - Optional callback to broadcast projects_updated | ||||||||||||||||
|
||||||||||||||||
| * @param {function|null} broadcastFn - Optional callback to broadcast projects_updated | |
| * @param {function|null} broadcastFn - Optional callback to broadcast session_name_updated |
Copilot
AI
Apr 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generateAndPersistSessionName can still clobber a manual rename due to a race between the second getName() check and sessionNamesDb.setName() (which is an unconditional upsert). To make the “manual rename always wins” guarantee true, persist the auto-generated name atomically only if no name exists (e.g., INSERT ... ON CONFLICT DO NOTHING / conditional update), rather than check-then-set in JS.
| // Re-check: manual rename may have occurred during LLM call | |
| if (sessionNamesDb.getName(sessionId, 'claude')) return; | |
| sessionNamesDb.setName(sessionId, 'claude', name); | |
| // Persist atomically so a concurrent manual rename always wins. | |
| const didPersist = sessionNamesDb.setNameIfAbsent(sessionId, 'claude', name); | |
| if (!didPersist) return; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import WebSocket from 'ws'; | ||
|
|
||
| // Shared set of connected WebSocket clients, used by both the main server | ||
| // (index.js) and the agent API routes to broadcast real-time updates. | ||
| export const connectedClients = new Set(); | ||
|
|
||
| // Broadcast loading/progress updates to all connected clients. | ||
| export function broadcastProgress(progress) { | ||
| const message = JSON.stringify({ | ||
| type: 'loading_progress', | ||
| ...progress | ||
| }); | ||
| connectedClients.forEach(client => { | ||
| if (client.readyState === WebSocket.OPEN) { | ||
| client.send(message); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| // Broadcast a lightweight session-name-updated event to all connected clients. | ||
| // Unlike the old broadcastProjectsUpdated(), this does NOT re-scan projects — | ||
| // the frontend patches its local state directly. | ||
| export function broadcastSessionNameUpdated(sessionId, provider, name) { | ||
| const msg = JSON.stringify({ | ||
| type: 'session_name_updated', | ||
| sessionId, provider, name, | ||
| timestamp: new Date().toISOString() | ||
| }); | ||
| connectedClients.forEach(client => { | ||
| if (client.readyState === WebSocket.OPEN) client.send(msg); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,8 @@ import type { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Project, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ProjectSession, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ProjectsUpdatedMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SessionNameUpdatedMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SessionProvider, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '../types/app'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type UseProjectsStateArgs = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -226,6 +228,54 @@ export function useProjectsState({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (latestMessage.type === 'session_name_updated') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { sessionId: updatedId, provider, name } = latestMessage as SessionNameUpdatedMessage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sessionKey = (p: SessionProvider): keyof Project => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (p) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'cursor': return 'cursorSessions'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'codex': return 'codexSessions'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'gemini': return 'geminiSessions'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: return 'sessions'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setProjects(prev => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prev.map(project => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const key = sessionKey(provider); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sessions = project[key] as ProjectSession[] | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!sessions) return project; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const idx = sessions.findIndex(s => s.id === updatedId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (idx === -1) return project; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updated = [...sessions]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated[idx] = { ...updated[idx], summary: name }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { ...project, [key]: updated }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Also update selectedProject / selectedSession if they match | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (selectedProject) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const key = sessionKey(provider); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sessions = selectedProject[key] as ProjectSession[] | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (sessions) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const idx = sessions.findIndex(s => s.id === updatedId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (idx !== -1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updated = [...sessions]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated[idx] = { ...updated[idx], summary: name }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSelectedProject({ ...selectedProject, [key]: updated }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (selectedSession?.id === updatedId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSelectedSession({ ...selectedSession, summary: name }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+231
to
+276
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard Line 243 always produces a new 💡 Suggested fix if (latestMessage.type === 'session_name_updated') {
const { sessionId: updatedId, provider, name } = latestMessage as SessionNameUpdatedMessage;
const sessionKey = (p: SessionProvider): keyof Project => {
switch (p) {
case 'cursor': return 'cursorSessions';
case 'codex': return 'codexSessions';
case 'gemini': return 'geminiSessions';
default: return 'sessions';
}
};
- setProjects(prev =>
- prev.map(project => {
- const key = sessionKey(provider);
- const sessions = project[key] as ProjectSession[] | undefined;
- if (!sessions) return project;
-
- const idx = sessions.findIndex(s => s.id === updatedId);
- if (idx === -1) return project;
-
- const updated = [...sessions];
- updated[idx] = { ...updated[idx], summary: name };
- return { ...project, [key]: updated };
- })
- );
+ const key = sessionKey(provider);
+ setProjects(prev => {
+ let changed = false;
+ const next = prev.map(project => {
+ const sessions = project[key] as ProjectSession[] | undefined;
+ if (!sessions) return project;
+
+ const idx = sessions.findIndex(s => s.id === updatedId);
+ if (idx === -1 || sessions[idx]?.summary === name) return project;
+
+ changed = true;
+ const updated = [...sessions];
+ updated[idx] = { ...updated[idx], summary: name };
+ return { ...project, [key]: updated };
+ });
+
+ return changed ? next : prev;
+ });
- // Also update selectedProject / selectedSession if they match
- if (selectedProject) {
- const key = sessionKey(provider);
- const sessions = selectedProject[key] as ProjectSession[] | undefined;
- if (sessions) {
- const idx = sessions.findIndex(s => s.id === updatedId);
- if (idx !== -1) {
- const updated = [...sessions];
- updated[idx] = { ...updated[idx], summary: name };
- setSelectedProject({ ...selectedProject, [key]: updated });
-
- if (selectedSession?.id === updatedId) {
- setSelectedSession({ ...selectedSession, summary: name });
- }
- }
- }
- }
+ setSelectedProject(prev => {
+ if (!prev) return prev;
+ const sessions = prev[key] as ProjectSession[] | undefined;
+ if (!sessions) return prev;
+
+ const idx = sessions.findIndex(s => s.id === updatedId);
+ if (idx === -1 || sessions[idx]?.summary === name) return prev;
+
+ const updated = [...sessions];
+ updated[idx] = { ...updated[idx], summary: name };
+ return { ...prev, [key]: updated };
+ });
+
+ setSelectedSession(prev =>
+ prev?.id === updatedId && prev.summary !== name
+ ? { ...prev, summary: name }
+ : prev
+ );
return;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (latestMessage.type !== 'projects_updated') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
req.headers['x-api-key']can be a string array in Node/Express; passing an array through toapiKeysDb.validateApiKey()will break validation (and could lead to unexpected behavior). Normalize to a single string (or reject arrays) before validating.