UNPKG

mcp-chrome-bridge

Version:

Chrome Native-Messaging host (Node)

292 lines 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSession = createSession; exports.getSession = getSession; exports.getSessionsByProject = getSessionsByProject; exports.getAllSessions = getAllSessions; exports.getSessionsByProjectAndEngine = getSessionsByProjectAndEngine; exports.updateSession = updateSession; exports.deleteSession = deleteSession; exports.updateEngineSessionId = updateEngineSessionId; exports.touchSessionActivity = touchSessionActivity; exports.updateManagementInfo = updateManagementInfo; exports.getOrCreateDefaultSession = getOrCreateDefaultSession; /** * Session Service - Database-backed implementation using Drizzle ORM. * * Provides CRUD operations for agent sessions with: * - Type-safe database queries * - Engine-agnostic session configuration storage * - JSON config and management info caching */ const node_crypto_1 = require("node:crypto"); const drizzle_orm_1 = require("drizzle-orm"); const db_1 = require("./db"); // ============================================================ // JSON Parsing Utilities // ============================================================ function parseJson(value) { if (!value) return undefined; try { return JSON.parse(value); } catch (_a) { return undefined; } } function stringifyJson(value) { if (value === null || value === undefined) return null; return JSON.stringify(value); } // ============================================================ // Type Conversion // ============================================================ function rowToSession(row) { var _a, _b, _c; return { id: row.id, projectId: row.projectId, engineName: row.engineName, engineSessionId: (_a = row.engineSessionId) !== null && _a !== void 0 ? _a : undefined, name: (_b = row.name) !== null && _b !== void 0 ? _b : undefined, model: (_c = row.model) !== null && _c !== void 0 ? _c : undefined, permissionMode: row.permissionMode, allowDangerouslySkipPermissions: row.allowDangerouslySkipPermissions === '1', systemPromptConfig: parseJson(row.systemPromptConfig), optionsConfig: parseJson(row.optionsConfig), managementInfo: parseJson(row.managementInfo), createdAt: row.createdAt, updatedAt: row.updatedAt, }; } // ============================================================ // Public API // ============================================================ /** * Create a new session for a project. */ async function createSession(projectId, engineName, options = {}) { var _a, _b, _c, _d, _e; const db = (0, db_1.getDb)(); const now = new Date().toISOString(); // Resolve permission mode - AgentChat defaults to bypassPermissions for headless operation const resolvedPermissionMode = ((_a = options.permissionMode) === null || _a === void 0 ? void 0 : _a.trim()) || 'bypassPermissions'; // SDK requires allowDangerouslySkipPermissions=true when using bypassPermissions mode // If explicitly provided, use that value; otherwise infer from permission mode const resolvedAllowDangerouslySkipPermissions = typeof options.allowDangerouslySkipPermissions === 'boolean' ? options.allowDangerouslySkipPermissions : resolvedPermissionMode === 'bypassPermissions'; const sessionData = { id: ((_b = options.id) === null || _b === void 0 ? void 0 : _b.trim()) || (0, node_crypto_1.randomUUID)(), projectId, engineName, engineSessionId: ((_c = options.engineSessionId) === null || _c === void 0 ? void 0 : _c.trim()) || null, name: ((_d = options.name) === null || _d === void 0 ? void 0 : _d.trim()) || null, model: ((_e = options.model) === null || _e === void 0 ? void 0 : _e.trim()) || null, permissionMode: resolvedPermissionMode, allowDangerouslySkipPermissions: resolvedAllowDangerouslySkipPermissions ? '1' : null, systemPromptConfig: stringifyJson(options.systemPromptConfig), optionsConfig: stringifyJson(options.optionsConfig), managementInfo: null, createdAt: now, updatedAt: now, }; await db.insert(db_1.sessions).values(sessionData); return rowToSession(sessionData); } /** * Get a session by ID. */ async function getSession(sessionId) { const db = (0, db_1.getDb)(); const rows = await db.select().from(db_1.sessions).where((0, drizzle_orm_1.eq)(db_1.sessions.id, sessionId)).limit(1); return rows.length > 0 ? rowToSession(rows[0]) : undefined; } /** Maximum length for preview text */ const MAX_PREVIEW_LENGTH = 50; /** * Truncate text to max length with ellipsis. */ function truncatePreview(text, maxLength = MAX_PREVIEW_LENGTH) { const trimmed = text.trim().replace(/\s+/g, ' '); if (trimmed.length <= maxLength) return trimmed; return trimmed.slice(0, maxLength - 1) + '…'; } /** * Add preview to sessions by fetching first user message for each. * Shared helper to avoid code duplication. */ async function addPreviewsToSessions(rows) { const db = (0, db_1.getDb)(); return Promise.all(rows.map(async (row) => { const session = rowToSession(row); // Query first user message for this session (include metadata for special rendering) const firstUserMessages = await db .select({ content: db_1.messages.content, metadata: db_1.messages.metadata }) .from(db_1.messages) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.messages.sessionId, row.id), (0, drizzle_orm_1.eq)(db_1.messages.role, 'user'))) .orderBy((0, drizzle_orm_1.asc)(db_1.messages.createdAt)) .limit(1); if (firstUserMessages.length > 0 && firstUserMessages[0].content) { const content = firstUserMessages[0].content; const metadataJson = firstUserMessages[0].metadata; session.preview = truncatePreview(content); // Parse metadata to extract clientMeta/displayText for special rendering if (metadataJson) { try { const parsed = JSON.parse(metadataJson); // Type-safe extraction with validation const rawClientMeta = parsed.clientMeta; const rawDisplayText = parsed.displayText; // Validate displayText is a string const displayText = typeof rawDisplayText === 'string' ? rawDisplayText : undefined; // Validate clientMeta structure const clientMeta = rawClientMeta && typeof rawClientMeta === 'object' && 'kind' in rawClientMeta && (rawClientMeta.kind === 'web_editor_apply_batch' || rawClientMeta.kind === 'web_editor_apply_single') ? rawClientMeta : undefined; // Only set previewMeta if we have valid special metadata if (clientMeta || displayText) { session.previewMeta = { displayText: displayText || truncatePreview(content), clientMeta, // Truncate fullContent to avoid payload bloat (200 chars max) fullContent: truncatePreview(content, 200), }; } } catch (_a) { // Ignore JSON parse errors, just use plain preview } } } return session; })); } /** * Get all sessions for a project, sorted by most recently updated. * Includes preview from first user message for each session. */ async function getSessionsByProject(projectId) { const db = (0, db_1.getDb)(); const rows = await db .select() .from(db_1.sessions) .where((0, drizzle_orm_1.eq)(db_1.sessions.projectId, projectId)) .orderBy((0, drizzle_orm_1.desc)(db_1.sessions.updatedAt)); return addPreviewsToSessions(rows); } /** * Get all sessions across all projects, sorted by most recently updated. * Includes preview from first user message for each session. */ async function getAllSessions() { const db = (0, db_1.getDb)(); const rows = await db.select().from(db_1.sessions).orderBy((0, drizzle_orm_1.desc)(db_1.sessions.updatedAt)); return addPreviewsToSessions(rows); } /** * Get sessions for a project filtered by engine name. */ async function getSessionsByProjectAndEngine(projectId, engineName) { const db = (0, db_1.getDb)(); const rows = await db .select() .from(db_1.sessions) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(db_1.sessions.projectId, projectId), (0, drizzle_orm_1.eq)(db_1.sessions.engineName, engineName))) .orderBy((0, drizzle_orm_1.desc)(db_1.sessions.updatedAt)); return rows.map(rowToSession); } /** * Update an existing session. */ async function updateSession(sessionId, updates) { var _a, _b, _c, _d; const db = (0, db_1.getDb)(); const now = new Date().toISOString(); const updateData = { updatedAt: now, }; if (updates.engineSessionId !== undefined) { updateData.engineSessionId = ((_a = updates.engineSessionId) === null || _a === void 0 ? void 0 : _a.trim()) || null; } if (updates.name !== undefined) { updateData.name = ((_b = updates.name) === null || _b === void 0 ? void 0 : _b.trim()) || null; } if (updates.model !== undefined) { updateData.model = ((_c = updates.model) === null || _c === void 0 ? void 0 : _c.trim()) || null; } if (updates.permissionMode !== undefined) { updateData.permissionMode = ((_d = updates.permissionMode) === null || _d === void 0 ? void 0 : _d.trim()) || 'bypassPermissions'; } if (updates.allowDangerouslySkipPermissions !== undefined) { updateData.allowDangerouslySkipPermissions = updates.allowDangerouslySkipPermissions ? '1' : null; } if (updates.systemPromptConfig !== undefined) { updateData.systemPromptConfig = stringifyJson(updates.systemPromptConfig); } if (updates.optionsConfig !== undefined) { updateData.optionsConfig = stringifyJson(updates.optionsConfig); } if (updates.managementInfo !== undefined) { updateData.managementInfo = stringifyJson(updates.managementInfo); } await db.update(db_1.sessions).set(updateData).where((0, drizzle_orm_1.eq)(db_1.sessions.id, sessionId)); } /** * Delete a session by ID. * Note: Messages associated with this session are NOT automatically deleted. * The caller should handle message cleanup if needed. */ async function deleteSession(sessionId) { const db = (0, db_1.getDb)(); await db.delete(db_1.sessions).where((0, drizzle_orm_1.eq)(db_1.sessions.id, sessionId)); } /** * Update the engine session ID (e.g., Claude SDK session_id). */ async function updateEngineSessionId(sessionId, engineSessionId) { await updateSession(sessionId, { engineSessionId }); } /** * Touch session activity - updates the updatedAt timestamp. * Used when a message is sent to move the session to the top of the list. */ async function touchSessionActivity(sessionId) { const db = (0, db_1.getDb)(); const now = new Date().toISOString(); await db.update(db_1.sessions).set({ updatedAt: now }).where((0, drizzle_orm_1.eq)(db_1.sessions.id, sessionId)); } /** * Update the cached management information. */ async function updateManagementInfo(sessionId, info) { // Add timestamp to management info const infoWithTimestamp = info ? { ...info, lastUpdated: new Date().toISOString() } : null; await updateSession(sessionId, { managementInfo: infoWithTimestamp }); } /** * Get or create a default session for a project and engine. * Useful for backwards compatibility - creates a session if none exists. */ async function getOrCreateDefaultSession(projectId, engineName, options = {}) { const existingSessions = await getSessionsByProjectAndEngine(projectId, engineName); if (existingSessions.length > 0) { // Return the most recently updated session return existingSessions[0]; } // Create a new default session return createSession(projectId, engineName, { ...options, name: options.name || `Default ${engineName} session`, }); } //# sourceMappingURL=session-service.js.map