Refactor worker port handling and improve logging
- Replaced hardcoded migration port with dynamic port retrieval using `getWorkerPort()` in worker-cli.ts. - Updated context generator to clarify error handling comments. - Introduced timeout constants in ProcessManager for better maintainability. - Configured SQLite settings using constants for mmap size and cache size in DatabaseManager. - Added timeout constants for Git and NPM commands in BranchManager. - Enhanced error logging in FormattingService and SearchManager to provide more context on failures. - Removed deprecated silentDebug function and replaced its usage with logger.debug. - Updated tests to use dynamic worker port retrieval instead of hardcoded values.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,11 +1,8 @@
|
||||
import { ProcessManager } from '../services/process/ProcessManager.js';
|
||||
|
||||
// During migration, use port 38888 to run alongside the PM2-managed worker on 37777
|
||||
// Once migration is complete (Phase 3+), this will switch to using settings
|
||||
const MIGRATION_PORT = 38888;
|
||||
import { getWorkerPort } from '../shared/worker-utils.js';
|
||||
|
||||
const command = process.argv[2];
|
||||
const port = MIGRATION_PORT;
|
||||
const port = getWorkerPort();
|
||||
|
||||
async function main() {
|
||||
switch (command) {
|
||||
|
||||
@@ -335,7 +335,7 @@ export async function generateContext(input?: ContextInput, useColors: boolean =
|
||||
priorAssistantMessage = messages.assistantMessage;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
// Expected: Transcript file may not exist or be readable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,13 @@ const PID_FILE = join(DATA_DIR, 'worker.pid');
|
||||
const LOG_DIR = join(DATA_DIR, 'logs');
|
||||
const MARKETPLACE_ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
|
||||
// Timeout constants
|
||||
const PROCESS_STOP_TIMEOUT_MS = 5000;
|
||||
const HEALTH_CHECK_TIMEOUT_MS = 10000;
|
||||
const HEALTH_CHECK_INTERVAL_MS = 200;
|
||||
const HEALTH_CHECK_FETCH_TIMEOUT_MS = 1000;
|
||||
const PROCESS_EXIT_CHECK_INTERVAL_MS = 100;
|
||||
|
||||
interface PidInfo {
|
||||
pid: number;
|
||||
port: number;
|
||||
@@ -124,7 +131,7 @@ export class ProcessManager {
|
||||
}
|
||||
}
|
||||
|
||||
static async stop(timeout: number = 5000): Promise<boolean> {
|
||||
static async stop(timeout: number = PROCESS_STOP_TIMEOUT_MS): Promise<boolean> {
|
||||
const info = this.getPidInfo();
|
||||
if (!info) return true;
|
||||
|
||||
@@ -202,9 +209,8 @@ export class ProcessManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static async waitForHealth(pid: number, port: number, timeoutMs: number = 10000): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||
private static async waitForHealth(pid: number, port: number, timeoutMs: number = HEALTH_CHECK_TIMEOUT_MS): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||
const startTime = Date.now();
|
||||
const checkInterval = 200;
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
// Check if process is still alive
|
||||
@@ -215,7 +221,7 @@ export class ProcessManager {
|
||||
// Try health check
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
||||
signal: AbortSignal.timeout(1000)
|
||||
signal: AbortSignal.timeout(HEALTH_CHECK_FETCH_TIMEOUT_MS)
|
||||
});
|
||||
if (response.ok) {
|
||||
return { success: true, pid };
|
||||
@@ -224,7 +230,7 @@ export class ProcessManager {
|
||||
// Not ready yet
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||
await new Promise(resolve => setTimeout(resolve, HEALTH_CHECK_INTERVAL_MS));
|
||||
}
|
||||
|
||||
return { success: false, error: 'Health check timed out' };
|
||||
@@ -232,13 +238,12 @@ export class ProcessManager {
|
||||
|
||||
private static async waitForExit(pid: number, timeout: number): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
const checkInterval = 100;
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
if (!this.isProcessAlive(pid)) {
|
||||
return;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||
await new Promise(resolve => setTimeout(resolve, PROCESS_EXIT_CHECK_INTERVAL_MS));
|
||||
}
|
||||
|
||||
throw new Error('Process did not exit within timeout');
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
|
||||
|
||||
// SQLite configuration constants
|
||||
const SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024; // 256MB
|
||||
const SQLITE_CACHE_SIZE_PAGES = 10_000;
|
||||
|
||||
export interface Migration {
|
||||
version: number;
|
||||
up: (db: Database) => void;
|
||||
@@ -51,8 +55,8 @@ export class DatabaseManager {
|
||||
this.db.run('PRAGMA synchronous = NORMAL');
|
||||
this.db.run('PRAGMA foreign_keys = ON');
|
||||
this.db.run('PRAGMA temp_store = memory');
|
||||
this.db.run('PRAGMA mmap_size = 268435456'); // 256MB
|
||||
this.db.run('PRAGMA cache_size = 10000');
|
||||
this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
||||
this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
||||
|
||||
// Initialize schema_versions table
|
||||
this.initializeSchemaVersions();
|
||||
|
||||
@@ -13,6 +13,11 @@ import { logger } from '../../utils/logger.js';
|
||||
|
||||
const INSTALLED_PLUGIN_PATH = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
|
||||
// Timeout constants
|
||||
const GIT_COMMAND_TIMEOUT_MS = 30_000;
|
||||
const NPM_INSTALL_TIMEOUT_MS = 120_000;
|
||||
const DEFAULT_SHELL_TIMEOUT_MS = 60_000;
|
||||
|
||||
export interface BranchInfo {
|
||||
branch: string | null;
|
||||
isBeta: boolean;
|
||||
@@ -36,7 +41,7 @@ function execGit(command: string): string {
|
||||
return execSync(`git ${command}`, {
|
||||
cwd: INSTALLED_PLUGIN_PATH,
|
||||
encoding: 'utf-8',
|
||||
timeout: 30000,
|
||||
timeout: GIT_COMMAND_TIMEOUT_MS,
|
||||
windowsHide: true
|
||||
}).trim();
|
||||
}
|
||||
@@ -44,7 +49,7 @@ function execGit(command: string): string {
|
||||
/**
|
||||
* Execute shell command in installed plugin directory
|
||||
*/
|
||||
function execShell(command: string, timeoutMs: number = 60000): string {
|
||||
function execShell(command: string, timeoutMs: number = DEFAULT_SHELL_TIMEOUT_MS): string {
|
||||
return execSync(command, {
|
||||
cwd: INSTALLED_PLUGIN_PATH,
|
||||
encoding: 'utf-8',
|
||||
@@ -165,7 +170,7 @@ export async function switchBranch(targetBranch: string): Promise<SwitchResult>
|
||||
}
|
||||
|
||||
logger.debug('BRANCH', 'Running npm install');
|
||||
execShell('npm install', 120000); // 2 minute timeout for npm
|
||||
execShell('npm install', NPM_INSTALL_TIMEOUT_MS);
|
||||
|
||||
logger.success('BRANCH', 'Branch switch complete', {
|
||||
branch: targetBranch
|
||||
@@ -223,7 +228,7 @@ export async function pullUpdates(): Promise<SwitchResult> {
|
||||
if (existsSync(installMarker)) {
|
||||
unlinkSync(installMarker);
|
||||
}
|
||||
execShell('npm install', 120000);
|
||||
execShell('npm install', NPM_INSTALL_TIMEOUT_MS);
|
||||
|
||||
logger.success('BRANCH', 'Updates pulled', { branch: info.branch });
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
export type FormatType = 'index' | 'full';
|
||||
|
||||
@@ -102,7 +103,9 @@ Other tips:
|
||||
if (facts.length > 0) {
|
||||
metadata.push(`Facts: ${facts.join('; ')}`);
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
logger.warn('FORMAT', 'Invalid JSON in facts field', { obsId: obs.id });
|
||||
}
|
||||
}
|
||||
|
||||
if (obs.concepts) {
|
||||
@@ -111,7 +114,9 @@ Other tips:
|
||||
if (concepts.length > 0) {
|
||||
metadata.push(`Concepts: ${concepts.join(', ')}`);
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
logger.warn('FORMAT', 'Invalid JSON in concepts field', { obsId: obs.id });
|
||||
}
|
||||
}
|
||||
|
||||
if (obs.files_read || obs.files_modified) {
|
||||
@@ -119,12 +124,16 @@ Other tips:
|
||||
if (obs.files_read) {
|
||||
try {
|
||||
files.push(...JSON.parse(obs.files_read));
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
logger.warn('FORMAT', 'Invalid JSON in files_read field', { obsId: obs.id });
|
||||
}
|
||||
}
|
||||
if (obs.files_modified) {
|
||||
try {
|
||||
files.push(...JSON.parse(obs.files_modified));
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
logger.warn('FORMAT', 'Invalid JSON in files_modified field', { obsId: obs.id });
|
||||
}
|
||||
}
|
||||
if (files.length > 0) {
|
||||
metadata.push(`Files: ${[...new Set(files)].join(', ')}`);
|
||||
@@ -190,12 +199,16 @@ Other tips:
|
||||
if (session.files_read) {
|
||||
try {
|
||||
files.push(...JSON.parse(session.files_read));
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
logger.warn('FORMAT', 'Invalid JSON in session files_read field', { sessionId: session.sdk_session_id });
|
||||
}
|
||||
}
|
||||
if (session.files_edited) {
|
||||
try {
|
||||
files.push(...JSON.parse(session.files_edited));
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
logger.warn('FORMAT', 'Invalid JSON in session files_edited field', { sessionId: session.sdk_session_id });
|
||||
}
|
||||
}
|
||||
if (files.length > 0) {
|
||||
metadata.push(`Files: ${[...new Set(files)].join(', ')}`);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ChromaSync } from '../sync/ChromaSync.js';
|
||||
import { FormattingService } from './FormattingService.js';
|
||||
import { TimelineService, TimelineItem } from './TimelineService.js';
|
||||
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
||||
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
const COLLECTION_NAME = 'cm__claude-mem';
|
||||
|
||||
@@ -97,7 +97,7 @@ export class SearchManager {
|
||||
// PATH 1: FILTER-ONLY (no query text) - Skip Chroma/FTS5, use direct SQLite filtering
|
||||
// This path enables date filtering which Chroma cannot do (requires direct SQLite access)
|
||||
if (!query) {
|
||||
happy_path_error__with_fallback(`[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)`);
|
||||
logger.debug('SEARCH', 'Filter-only query (no query text), using direct SQLite filtering', { enablesDateFilters: true });
|
||||
const obsOptions = { ...options, type: obs_type, concepts, files };
|
||||
if (searchObservations) {
|
||||
observations = this.sessionSearch.searchObservations(undefined, obsOptions);
|
||||
@@ -113,7 +113,7 @@ export class SearchManager {
|
||||
else if (this.chromaSync) {
|
||||
let chromaSucceeded = false;
|
||||
try {
|
||||
happy_path_error__with_fallback(`[mcp-server] Using ChromaDB semantic search (type filter: ${type || 'all'})`);
|
||||
logger.debug('SEARCH', 'Using ChromaDB semantic search', { typeFilter: type || 'all' });
|
||||
|
||||
// Build Chroma where filter for doc_type
|
||||
let whereFilter: Record<string, any> | undefined;
|
||||
@@ -128,7 +128,7 @@ export class SearchManager {
|
||||
// Step 1: Chroma semantic search with optional type filter
|
||||
const chromaResults = await this.queryChroma(query, 100, whereFilter);
|
||||
chromaSucceeded = true; // Chroma didn't throw error
|
||||
happy_path_error__with_fallback(`[mcp-server] ChromaDB returned ${chromaResults.ids.length} semantic matches`);
|
||||
logger.debug('SEARCH', 'ChromaDB returned semantic matches', { matchCount: chromaResults.ids.length });
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -139,7 +139,7 @@ export class SearchManager {
|
||||
isRecent: meta && meta.created_at_epoch > ninetyDaysAgo
|
||||
})).filter(item => item.isRecent);
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentMetadata.length} results within 90-day window`);
|
||||
logger.debug('SEARCH', 'Results within 90-day window', { count: recentMetadata.length });
|
||||
|
||||
// Step 3: Categorize IDs by document type
|
||||
const obsIds: number[] = [];
|
||||
@@ -157,7 +157,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] Categorized: ${obsIds.length} obs, ${sessionIds.length} sessions, ${promptIds.length} prompts`);
|
||||
logger.debug('SEARCH', 'Categorized results by type', { observations: obsIds.length, sessions: sessionIds.length, prompts: promptIds.length });
|
||||
|
||||
// Step 4: Hydrate from SQLite with additional filters
|
||||
if (obsIds.length > 0) {
|
||||
@@ -172,14 +172,14 @@ export class SearchManager {
|
||||
prompts = this.sessionStore.getUserPromptsByIds(promptIds, { orderBy: 'date_desc', limit: options.limit });
|
||||
}
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${observations.length} obs, ${sessions.length} sessions, ${prompts.length} prompts from SQLite`);
|
||||
logger.debug('SEARCH', 'Hydrated results from SQLite', { observations: observations.length, sessions: sessions.length, prompts: prompts.length });
|
||||
} else {
|
||||
// Chroma returned 0 results - this is the correct answer, don't fall back to FTS5
|
||||
happy_path_error__with_fallback(`[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)`);
|
||||
logger.debug('SEARCH', 'ChromaDB found no matches (final result, no FTS5 fallback)', {});
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):', chromaError.message);
|
||||
happy_path_error__with_fallback('[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/');
|
||||
logger.debug('SEARCH', 'ChromaDB failed - returning empty results (FTS5 fallback removed)', { error: chromaError.message });
|
||||
logger.debug('SEARCH', 'Install UVX/Python to enable vector search', { url: 'https://docs.astral.sh/uv/getting-started/installation/' });
|
||||
// Return empty results - no fallback
|
||||
observations = [];
|
||||
sessions = [];
|
||||
@@ -188,8 +188,8 @@ export class SearchManager {
|
||||
}
|
||||
// ChromaDB not initialized - return empty results (no fallback)
|
||||
else {
|
||||
happy_path_error__with_fallback(`[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)`);
|
||||
happy_path_error__with_fallback(`[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/`);
|
||||
logger.debug('SEARCH', 'ChromaDB not initialized - returning empty results (FTS5 fallback removed)', {});
|
||||
logger.debug('SEARCH', 'Install UVX/Python to enable vector search', { url: 'https://docs.astral.sh/uv/getting-started/installation/' });
|
||||
observations = [];
|
||||
sessions = [];
|
||||
prompts = [];
|
||||
@@ -312,9 +312,9 @@ export class SearchManager {
|
||||
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
|
||||
logger.debug('SEARCH', 'Using hybrid semantic search for timeline query', {});
|
||||
const chromaResults = await this.queryChroma(query, 100);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults?.ids?.length ?? 0} semantic matches`);
|
||||
logger.debug('SEARCH', 'Chroma returned semantic matches for timeline', { matchCount: chromaResults?.ids?.length ?? 0 });
|
||||
|
||||
if (chromaResults?.ids && chromaResults.ids.length > 0) {
|
||||
const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
|
||||
@@ -328,7 +328,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ export class SearchManager {
|
||||
const topResult = results[0];
|
||||
anchorId = topResult.id;
|
||||
anchorEpoch = topResult.created_at_epoch;
|
||||
happy_path_error__with_fallback(`[mcp-server] Query mode: Using observation #${topResult.id} as timeline anchor`);
|
||||
logger.debug('SEARCH', 'Query mode: Using observation as timeline anchor', { observationId: topResult.id });
|
||||
timelineData = this.sessionStore.getTimelineAroundObservation(topResult.id, topResult.created_at_epoch, depth_before, depth_after, project);
|
||||
}
|
||||
// MODE 2: Anchor-based timeline
|
||||
@@ -621,7 +621,7 @@ export class SearchManager {
|
||||
try {
|
||||
if (query) {
|
||||
// Semantic search filtered to decision type
|
||||
happy_path_error__with_fallback('[mcp-server] Using Chroma semantic search with type=decision filter');
|
||||
logger.debug('SEARCH', 'Using Chroma semantic search with type=decision filter', {});
|
||||
const chromaResults = await this.queryChroma(query, Math.min((filters.limit || 20) * 2, 100), { type: 'decision' });
|
||||
const obsIds = chromaResults.ids;
|
||||
|
||||
@@ -632,7 +632,7 @@ export class SearchManager {
|
||||
}
|
||||
} else {
|
||||
// No query: get all decisions, rank by "decision" keyword
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for decisions');
|
||||
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for decisions', {});
|
||||
const metadataResults = this.sessionSearch.findByType('decision', filters);
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
@@ -653,7 +653,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma search failed, using SQLite fallback:', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma search failed, using SQLite fallback', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ export class SearchManager {
|
||||
// Search for change-type observations and change-related concepts
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid search for change-related observations');
|
||||
logger.debug('SEARCH', 'Using hybrid search for change-related observations', {});
|
||||
|
||||
// Get all observations with type="change" or concepts containing change
|
||||
const typeResults = this.sessionSearch.findByType('change', filters);
|
||||
@@ -737,7 +737,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,7 +807,7 @@ export class SearchManager {
|
||||
// Search for how-it-works concept observations
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for how-it-works');
|
||||
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for how-it-works', {});
|
||||
const metadataResults = this.sessionSearch.findByConcept('how-it-works', filters);
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
@@ -827,7 +827,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,11 +883,11 @@ export class SearchManager {
|
||||
// Vector-first search via ChromaDB
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search (Chroma + SQLite)');
|
||||
logger.debug('SEARCH', 'Using hybrid semantic search (Chroma + SQLite)', {});
|
||||
|
||||
// Step 1: Chroma semantic search (top 100)
|
||||
const chromaResults = await this.queryChroma(query, 100);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
logger.debug('SEARCH', 'Chroma returned semantic matches', { matchCount: chromaResults.ids.length });
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -897,17 +897,17 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
|
||||
|
||||
// Step 3: Hydrate from SQLite in temporal order
|
||||
if (recentIds.length > 0) {
|
||||
const limit = options.limit || 20;
|
||||
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit });
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
|
||||
logger.debug('SEARCH', 'Hydrated observations from SQLite', { count: results.length });
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,11 +960,11 @@ export class SearchManager {
|
||||
// Vector-first search via ChromaDB
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for sessions');
|
||||
logger.debug('SEARCH', 'Using hybrid semantic search for sessions', {});
|
||||
|
||||
// Step 1: Chroma semantic search (top 100)
|
||||
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'session_summary' });
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
logger.debug('SEARCH', 'Chroma returned semantic matches for sessions', { matchCount: chromaResults.ids.length });
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -974,17 +974,17 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
|
||||
|
||||
// Step 3: Hydrate from SQLite in temporal order
|
||||
if (recentIds.length > 0) {
|
||||
const limit = options.limit || 20;
|
||||
results = this.sessionStore.getSessionSummariesByIds(recentIds, { orderBy: 'date_desc', limit });
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} sessions from SQLite`);
|
||||
logger.debug('SEARCH', 'Hydrated sessions from SQLite', { count: results.length });
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,11 +1037,11 @@ export class SearchManager {
|
||||
// Vector-first search via ChromaDB
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for user prompts');
|
||||
logger.debug('SEARCH', 'Using hybrid semantic search for user prompts', {});
|
||||
|
||||
// Step 1: Chroma semantic search (top 100)
|
||||
const chromaResults = await this.queryChroma(query, 100, { doc_type: 'user_prompt' });
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
logger.debug('SEARCH', 'Chroma returned semantic matches for prompts', { matchCount: chromaResults.ids.length });
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Step 2: Filter by recency (90 days)
|
||||
@@ -1051,17 +1051,17 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
|
||||
|
||||
// Step 3: Hydrate from SQLite in temporal order
|
||||
if (recentIds.length > 0) {
|
||||
const limit = options.limit || 20;
|
||||
results = this.sessionStore.getUserPromptsByIds(recentIds, { orderBy: 'date_desc', limit });
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} user prompts from SQLite`);
|
||||
logger.debug('SEARCH', 'Hydrated user prompts from SQLite', { count: results.length });
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1114,11 +1114,11 @@ export class SearchManager {
|
||||
// Metadata-first, semantic-enhanced search
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for concept search');
|
||||
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for concept search', {});
|
||||
|
||||
// Step 1: SQLite metadata filter (get all IDs with this concept)
|
||||
const metadataResults = this.sessionSearch.findByConcept(concept, filters);
|
||||
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with concept "${concept}"`);
|
||||
logger.debug('SEARCH', 'Found observations with concept', { concept, count: metadataResults.length });
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
// Step 2: Chroma semantic ranking (rank by relevance to concept)
|
||||
@@ -1133,7 +1133,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
|
||||
logger.debug('SEARCH', 'Chroma ranked results by semantic relevance', { count: rankedIds.length });
|
||||
|
||||
// Step 3: Hydrate in semantic rank order
|
||||
if (rankedIds.length > 0) {
|
||||
@@ -1143,14 +1143,14 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
|
||||
// Fall through to SQLite fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to SQLite-only if Chroma unavailable or failed
|
||||
if (results.length === 0) {
|
||||
happy_path_error__with_fallback('[mcp-server] Using SQLite-only concept search');
|
||||
logger.debug('SEARCH', 'Using SQLite-only concept search', {});
|
||||
results = this.sessionSearch.findByConcept(concept, filters);
|
||||
}
|
||||
|
||||
@@ -1204,11 +1204,11 @@ export class SearchManager {
|
||||
// Metadata-first, semantic-enhanced search for observations
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for file search');
|
||||
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for file search', {});
|
||||
|
||||
// Step 1: SQLite metadata filter (get all results with this file)
|
||||
const metadataResults = this.sessionSearch.findByFile(filePath, filters);
|
||||
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.observations.length} observations, ${metadataResults.sessions.length} sessions for file "${filePath}"`);
|
||||
logger.debug('SEARCH', 'Found results for file', { file: filePath, observations: metadataResults.observations.length, sessions: metadataResults.sessions.length });
|
||||
|
||||
// Sessions: Keep as-is (already summarized, no semantic ranking needed)
|
||||
sessions = metadataResults.sessions;
|
||||
@@ -1227,7 +1227,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} observations by semantic relevance`);
|
||||
logger.debug('SEARCH', 'Chroma ranked observations by semantic relevance', { count: rankedIds.length });
|
||||
|
||||
// Step 3: Hydrate in semantic rank order
|
||||
if (rankedIds.length > 0) {
|
||||
@@ -1237,14 +1237,14 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
|
||||
// Fall through to SQLite fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to SQLite-only if Chroma unavailable or failed
|
||||
if (observations.length === 0 && sessions.length === 0) {
|
||||
happy_path_error__with_fallback('[mcp-server] Using SQLite-only file search');
|
||||
logger.debug('SEARCH', 'Using SQLite-only file search', {});
|
||||
const results = this.sessionSearch.findByFile(filePath, filters);
|
||||
observations = results.observations;
|
||||
sessions = results.sessions;
|
||||
@@ -1323,11 +1323,11 @@ export class SearchManager {
|
||||
// Metadata-first, semantic-enhanced search
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using metadata-first + semantic ranking for type search');
|
||||
logger.debug('SEARCH', 'Using metadata-first + semantic ranking for type search', {});
|
||||
|
||||
// Step 1: SQLite metadata filter (get all IDs with this type)
|
||||
const metadataResults = this.sessionSearch.findByType(type, filters);
|
||||
happy_path_error__with_fallback(`[mcp-server] Found ${metadataResults.length} observations with type "${typeStr}"`);
|
||||
logger.debug('SEARCH', 'Found observations with type', { type: typeStr, count: metadataResults.length });
|
||||
|
||||
if (metadataResults.length > 0) {
|
||||
// Step 2: Chroma semantic ranking (rank by relevance to type)
|
||||
@@ -1342,7 +1342,7 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma ranked ${rankedIds.length} results by semantic relevance`);
|
||||
logger.debug('SEARCH', 'Chroma ranked results by semantic relevance', { count: rankedIds.length });
|
||||
|
||||
// Step 3: Hydrate in semantic rank order
|
||||
if (rankedIds.length > 0) {
|
||||
@@ -1352,14 +1352,14 @@ export class SearchManager {
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma ranking failed, using SQLite order:', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma ranking failed, using SQLite order', { error: chromaError.message });
|
||||
// Fall through to SQLite fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to SQLite-only if Chroma unavailable or failed
|
||||
if (results.length === 0) {
|
||||
happy_path_error__with_fallback('[mcp-server] Using SQLite-only type search');
|
||||
logger.debug('SEARCH', 'Using SQLite-only type search', {});
|
||||
results = this.sessionSearch.findByType(type, filters);
|
||||
}
|
||||
|
||||
@@ -1815,9 +1815,9 @@ export class SearchManager {
|
||||
// Use hybrid search if available
|
||||
if (this.chromaSync) {
|
||||
try {
|
||||
happy_path_error__with_fallback('[mcp-server] Using hybrid semantic search for timeline query');
|
||||
logger.debug('SEARCH', 'Using hybrid semantic search for timeline query', {});
|
||||
const chromaResults = await this.queryChroma(query, 100);
|
||||
happy_path_error__with_fallback(`[mcp-server] Chroma returned ${chromaResults.ids.length} semantic matches`);
|
||||
logger.debug('SEARCH', 'Chroma returned semantic matches for timeline', { matchCount: chromaResults.ids.length });
|
||||
|
||||
if (chromaResults.ids.length > 0) {
|
||||
// Filter by recency (90 days)
|
||||
@@ -1827,15 +1827,15 @@ export class SearchManager {
|
||||
return meta && meta.created_at_epoch > ninetyDaysAgo;
|
||||
});
|
||||
|
||||
happy_path_error__with_fallback(`[mcp-server] ${recentIds.length} results within 90-day window`);
|
||||
logger.debug('SEARCH', 'Results within 90-day window', { count: recentIds.length });
|
||||
|
||||
if (recentIds.length > 0) {
|
||||
results = this.sessionStore.getObservationsByIds(recentIds, { orderBy: 'date_desc', limit: mode === 'auto' ? 1 : limit });
|
||||
happy_path_error__with_fallback(`[mcp-server] Hydrated ${results.length} observations from SQLite`);
|
||||
logger.debug('SEARCH', 'Hydrated observations from SQLite', { count: results.length });
|
||||
}
|
||||
}
|
||||
} catch (chromaError: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] Chroma query failed - no results (FTS5 fallback removed):', chromaError.message);
|
||||
logger.debug('SEARCH', 'Chroma query failed - no results (FTS5 fallback removed)', { error: chromaError.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1886,7 +1886,7 @@ export class SearchManager {
|
||||
} else {
|
||||
// Auto mode: Use top result as timeline anchor
|
||||
const topResult = results[0];
|
||||
happy_path_error__with_fallback(`[mcp-server] Auto mode: Using observation #${topResult.id} as timeline anchor`);
|
||||
logger.debug('SEARCH', 'Auto mode: Using observation as timeline anchor', { observationId: topResult.id });
|
||||
|
||||
// Get timeline around this observation
|
||||
const timelineData = this.sessionStore.getTimelineAroundObservation(
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { DatabaseManager } from './DatabaseManager.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { happy_path_error__with_fallback } from '../../utils/silent-debug.js';
|
||||
import type { ActiveSession, PendingMessage, ObservationData } from '../worker-types.js';
|
||||
|
||||
export class SessionManager {
|
||||
@@ -43,7 +42,7 @@ export class SessionManager {
|
||||
// in the database but the in-memory session still has the stale empty value
|
||||
const dbSession = this.dbManager.getSessionById(sessionDbId);
|
||||
if (dbSession.project && dbSession.project !== session.project) {
|
||||
happy_path_error__with_fallback('[SessionManager] Updating project from database', {
|
||||
logger.debug('SESSION', 'Updating project from database', {
|
||||
sessionDbId,
|
||||
oldProject: session.project,
|
||||
newProject: dbSession.project
|
||||
@@ -53,7 +52,7 @@ export class SessionManager {
|
||||
|
||||
// Update userPrompt for continuation prompts
|
||||
if (currentUserPrompt) {
|
||||
happy_path_error__with_fallback('[SessionManager] Updating userPrompt for continuation', {
|
||||
logger.debug('SESSION', 'Updating userPrompt for continuation', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
oldPrompt: session.userPrompt.substring(0, 80),
|
||||
@@ -62,7 +61,7 @@ export class SessionManager {
|
||||
session.userPrompt = currentUserPrompt;
|
||||
session.lastPromptNumber = promptNumber || session.lastPromptNumber;
|
||||
} else {
|
||||
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for existing session', {
|
||||
logger.debug('SESSION', 'No currentUserPrompt provided for existing session', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
usingCachedPrompt: session.userPrompt.substring(0, 80)
|
||||
@@ -78,13 +77,13 @@ export class SessionManager {
|
||||
const userPrompt = currentUserPrompt || dbSession.user_prompt;
|
||||
|
||||
if (!currentUserPrompt) {
|
||||
happy_path_error__with_fallback('[SessionManager] No currentUserPrompt provided for new session, using database', {
|
||||
logger.debug('SESSION', 'No currentUserPrompt provided for new session, using database', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
dbPrompt: dbSession.user_prompt.substring(0, 80)
|
||||
});
|
||||
} else {
|
||||
happy_path_error__with_fallback('[SessionManager] Initializing session with fresh userPrompt', {
|
||||
logger.debug('SESSION', 'Initializing session with fresh userPrompt', {
|
||||
sessionDbId,
|
||||
promptNumber,
|
||||
userPrompt: currentUserPrompt.substring(0, 80)
|
||||
|
||||
@@ -4,22 +4,45 @@ import { spawnSync } from "child_process";
|
||||
import { logger } from "../utils/logger.js";
|
||||
import { HOOK_TIMEOUTS, getTimeout } from "./hook-constants.js";
|
||||
import { ProcessManager } from "../services/process/ProcessManager.js";
|
||||
import { SettingsDefaultsManager } from "./SettingsDefaultsManager.js";
|
||||
|
||||
const MARKETPLACE_ROOT = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
|
||||
// Named constants for health checks
|
||||
const HEALTH_CHECK_TIMEOUT_MS = getTimeout(HOOK_TIMEOUTS.HEALTH_CHECK);
|
||||
|
||||
// MIGRATION: Hardcoded port to avoid conflicts with PM2 worker on 37777
|
||||
// TODO: Switch to settings-based port after PM2 is fully removed
|
||||
const MIGRATION_PORT = 38888;
|
||||
// Port cache to avoid repeated settings file reads
|
||||
let cachedPort: number | null = null;
|
||||
|
||||
/**
|
||||
* Get the worker port number
|
||||
* Currently hardcoded for migration; will use settings after PM2 removal
|
||||
* Get the worker port number from settings
|
||||
* Uses CLAUDE_MEM_WORKER_PORT from settings file or default (37777)
|
||||
* Caches the port value to avoid repeated file reads
|
||||
*/
|
||||
export function getWorkerPort(): number {
|
||||
return MIGRATION_PORT;
|
||||
if (cachedPort !== null) {
|
||||
return cachedPort;
|
||||
}
|
||||
|
||||
try {
|
||||
const settingsPath = path.join(SettingsDefaultsManager.get('CLAUDE_MEM_DATA_DIR'), 'settings.json');
|
||||
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
|
||||
cachedPort = parseInt(settings.CLAUDE_MEM_WORKER_PORT, 10);
|
||||
return cachedPort;
|
||||
} catch (error) {
|
||||
// Fallback to default if settings load fails
|
||||
logger.debug('SYSTEM', 'Failed to load port from settings, using default', { error });
|
||||
cachedPort = parseInt(SettingsDefaultsManager.get('CLAUDE_MEM_WORKER_PORT'), 10);
|
||||
return cachedPort;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cached port value
|
||||
* Call this when settings are updated to force re-reading from file
|
||||
*/
|
||||
export function clearPortCache(): void {
|
||||
cachedPort = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,12 +83,7 @@ export function clearSilentLog(): void {
|
||||
try {
|
||||
appendFileSync(LOG_FILE, `\n${'='.repeat(80)}\n[${new Date().toISOString()}] Log cleared\n${'='.repeat(80)}\n\n`);
|
||||
} catch (error) {
|
||||
// Ignore errors
|
||||
// Expected: Log file may not be writable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use happy_path_error__with_fallback instead
|
||||
* Backward compatibility alias for silentDebug
|
||||
*/
|
||||
export const silentDebug = happy_path_error__with_fallback;
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { sampleObservation, featureObservation } from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Context Injection (SessionStart)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
const PROJECT_NAME = 'claude-mem';
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,9 +14,10 @@ import {
|
||||
grepScenario,
|
||||
sessionScenario
|
||||
} from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Observation Capture (PostToolUse)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { sampleObservation, featureObservation } from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Search (MCP Tools)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { sessionScenario } from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Session Cleanup (SessionEnd)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { bashCommandScenario, sessionScenario } from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Session Initialization (UserPromptSubmit)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { sessionSummaryScenario, sessionScenario } from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Session Summary (Stop)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -11,9 +11,10 @@ import {
|
||||
sessionScenario,
|
||||
sampleObservation
|
||||
} from '../helpers/scenarios.js';
|
||||
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
||||
|
||||
describe('Full Observation Lifecycle', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
let sessionId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user