Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/frontend/src/main/ipc-handlers/env-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { parseEnvFile } from './utils';
import { getClaudeCliInvocation, getClaudeCliInvocationAsync } from '../claude-cli-utils';
import { debugError } from '../../shared/utils/debug-logger';
import { getSpawnOptions, getSpawnCommand } from '../env-utils';
import { getMemoriesDir } from '../config-paths';

// GitLab environment variable keys
const GITLAB_ENV_KEYS = {
Expand Down Expand Up @@ -336,7 +337,7 @@ ${existingVars['OLLAMA_EMBEDDING_DIM'] ? `OLLAMA_EMBEDDING_DIM=${existingVars['O

# LadybugDB Database (embedded - no Docker required)
${existingVars['GRAPHITI_DATABASE'] ? `GRAPHITI_DATABASE=${existingVars['GRAPHITI_DATABASE']}` : '# GRAPHITI_DATABASE=auto_claude_memory'}
${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_DB_PATH']}` : '# GRAPHITI_DB_PATH=~/.auto-claude/memories'}
${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_DB_PATH']}` : `# GRAPHITI_DB_PATH=${getMemoriesDir()}`}
`;

return content;
Expand Down
22 changes: 19 additions & 3 deletions apps/frontend/src/main/ipc-handlers/memory-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
GraphitiConnectionTestResult,
} from '../../shared/types';
import {
getMemoryServiceStatus,
getMemoryServiceStatusAsync,
getMemoryService,
getDefaultDbPath,
isKuzuAvailable,
Expand Down Expand Up @@ -380,7 +380,7 @@ export function registerMemoryHandlers(): void {
IPC_CHANNELS.MEMORY_STATUS,
async (_): Promise<IPCResult<InfrastructureStatus>> => {
try {
const status = getMemoryServiceStatus();
const status = await getMemoryServiceStatusAsync();
return {
success: true,
data: {
Expand All @@ -402,7 +402,7 @@ export function registerMemoryHandlers(): void {
IPC_CHANNELS.MEMORY_LIST_DATABASES,
async (_, dbPath?: string): Promise<IPCResult<string[]>> => {
try {
const status = getMemoryServiceStatus(dbPath);
const status = await getMemoryServiceStatusAsync(dbPath);
return { success: true, data: status.databases };
} catch (error) {
return {
Expand All @@ -413,6 +413,22 @@ export function registerMemoryHandlers(): void {
}
);

// Get platform-specific memories directory path
ipcMain.handle(
IPC_CHANNELS.MEMORY_GET_DIR,
async (): Promise<IPCResult<string>> => {
try {
const memoriesDir = getDefaultDbPath();
return { success: true, data: memoriesDir };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'errors:memory.getDirFailed',
};
}
}
);

// Test memory database connection
ipcMain.handle(
IPC_CHANNELS.MEMORY_TEST_CONNECTION,
Expand Down
231 changes: 227 additions & 4 deletions apps/frontend/src/main/memory-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ export class MemoryService {
if (!result.success) {
return {
success: false,
message: result.error || 'Failed to check database status',
message: result.error || LADYBUG_ERROR_KEYS.statusCheckFailed,
};
}

Expand All @@ -605,21 +605,22 @@ export class MemoryService {
if (!data.available) {
return {
success: false,
message: 'LadybugDB (real_ladybug) not installed. Requires Python 3.12+',
message: LADYBUG_ERROR_KEYS.notInstalled,
};
}

if (!data.databaseExists) {
return {
success: false,
message: `Database not found at ${data.databasePath}/${data.database}`,
message: LADYBUG_ERROR_KEYS.databaseNotFound,
// Path info available via data.databasePath for logging if needed
};
}

if (!data.connected) {
return {
success: false,
message: data.error || 'Failed to connect to database',
message: data.error || LADYBUG_ERROR_KEYS.connectionFailed,
};
}

Expand Down Expand Up @@ -744,16 +745,232 @@ export function isKuzuAvailable(): boolean {
return scriptPath !== null;
}

/**
* Check if LadybugDB (real_ladybug) Python package is installed
* Returns detailed status about the installation
*/
export interface LadybugInstallStatus {
installed: boolean;
pythonAvailable: boolean;
error?: string;
}

let ladybugInstallCache: LadybugInstallStatus | null = null;

// Track if an async check is in progress to avoid duplicate checks
let ladybugCheckInProgress: Promise<LadybugInstallStatus> | null = null;

/**
* Error key constants for i18n translation.
* These keys should be defined in errors.json translation files.
*/
export const LADYBUG_ERROR_KEYS = {
pythonNotFound: 'errors:ladybug.pythonNotFound',
notInstalled: 'errors:ladybug.notInstalled',
buildTools: 'errors:ladybug.buildTools',
checkFailed: 'errors:ladybug.checkFailed',
databaseNotFound: 'errors:ladybug.databaseNotFound',
statusCheckFailed: 'errors:ladybug.statusCheckFailed',
connectionFailed: 'errors:ladybug.connectionFailed',
} as const;

/**
* Check if LadybugDB (real_ladybug) Python package is installed (async version).
* Uses child_process.spawn wrapped in a Promise to avoid blocking the main process.
* Returns detailed status about the installation.
*/
export async function checkLadybugInstalledAsync(): Promise<LadybugInstallStatus> {
// Return cached result if available (avoid repeated slow checks)
if (ladybugInstallCache !== null) {
return ladybugInstallCache;
}

// If a check is already in progress, wait for it
if (ladybugCheckInProgress !== null) {
return ladybugCheckInProgress;
}

const pythonCmd = findPythonCommand();
if (!pythonCmd) {
ladybugInstallCache = {
installed: false,
pythonAvailable: false,
error: LADYBUG_ERROR_KEYS.pythonNotFound,
};
return ladybugInstallCache;
}

// Start the async check and track the promise
ladybugCheckInProgress = new Promise<LadybugInstallStatus>((resolve) => {
const [cmd, args] = parsePythonCommand(pythonCmd);
const checkArgs = [...args, '-c', 'import real_ladybug; print("OK")'];

// Use getMemoryPythonEnv() to ensure real_ladybug can be found
const pythonEnv = getMemoryPythonEnv();

Comment on lines +793 to +810
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the definitions of the two functions mentioned
echo "=== Searching for findPythonCommand ==="
rg -n "findPythonCommand" apps/frontend/src/main -S -A 3

echo ""
echo "=== Searching for getBackendPythonPath ==="
rg -n "getBackendPythonPath|getConfiguredPythonPath" apps/frontend/src/main -S -A 3

Repository: AndyMik90/Auto-Claude

Length of output: 17230


🏁 Script executed:

#!/bin/bash
# Get the file structure to understand the layout
ls -la apps/frontend/src/main/

Repository: AndyMik90/Auto-Claude

Length of output: 3211


🏁 Script executed:

#!/bin/bash
# Check the memory-service.ts file around the specified lines
wc -l apps/frontend/src/main/memory-service.ts

Repository: AndyMik90/Auto-Claude

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Read the implementations of the three functions
echo "=== findPythonCommand in python-detector.ts (lines 54-100) ==="
sed -n '54,100p' apps/frontend/src/main/python-detector.ts

echo ""
echo "=== getBackendPythonPath in memory-service.ts (lines 127-158) ==="
sed -n '127,158p' apps/frontend/src/main/memory-service.ts

echo ""
echo "=== getConfiguredPythonPath in python-env-manager.ts (lines 830-841) ==="
sed -n '830,841p' apps/frontend/src/main/python-env-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 3535


🏁 Script executed:

#!/bin/bash
# Read the specific lines mentioned in the review
echo "=== Lines 793-810 in memory-service.ts ==="
sed -n '793,810p' apps/frontend/src/main/memory-service.ts

echo ""
echo "=== Lines 953-962 in memory-service.ts ==="
sed -n '953,962p' apps/frontend/src/main/memory-service.ts

echo ""
echo "=== Context around line 907 ==="
sed -n '900,920p' apps/frontend/src/main/memory-service.ts

Repository: AndyMik90/Auto-Claude

Length of output: 1914


🏁 Script executed:

#!/bin/bash
# Verify the key difference: check what pythonEnvManager does
echo "=== pythonEnvManager in python-env-manager.ts ==="
rg -n "class PythonEnvManager" apps/frontend/src/main/python-env-manager.ts -A 20 | head -40

echo ""
echo "=== isEnvReady and getPythonPath methods ==="
rg -n "isEnvReady\(\)|getPythonPath\(\)" apps/frontend/src/main/python-env-manager.ts -B 2 -A 5 | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 1954


🏁 Script executed:

#!/bin/bash
# Check where memory queries actually run and what Python they use
echo "=== queryMemories function (uses getBackendPythonPath) ==="
sed -n '190,210p' apps/frontend/src/main/memory-service.ts

echo ""
echo "=== checkLadybugInstalledAsync context ==="
sed -n '775,830p' apps/frontend/src/main/memory-service.ts | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 2442


🏁 Script executed:

#!/bin/bash
# Check if getBackendPythonPath always returns a string (never null)
echo "=== Full getBackendPythonPath and getConfiguredPythonPath implementations ==="
sed -n '127,158p' apps/frontend/src/main/memory-service.ts
echo ""
sed -n '830,841p' apps/frontend/src/main/python-env-manager.ts

echo ""
echo "=== Check what happens at line 954 in context ==="
sed -n '950,970p' apps/frontend/src/main/memory-service.ts

Repository: AndyMik90/Auto-Claude

Length of output: 2477


🏁 Script executed:

#!/bin/bash
# Verify: does getBackendPythonPath return a string or can be null?
echo "=== Return type of getBackendPythonPath ==="
sed -n '125,130p' apps/frontend/src/main/memory-service.ts

echo ""
echo "=== Return type of getConfiguredPythonPath ==="
sed -n '828,835p' apps/frontend/src/main/python-env-manager.ts

Repository: AndyMik90/Auto-Claude

Length of output: 760


🏁 Script executed:

#!/bin/bash
# Check the logic flow one more time - in dev mode, what does each function find?
echo "=== In dev mode, getBackendPythonPath looks for backend venv ==="
sed -n '140,157p' apps/frontend/src/main/memory-service.ts

echo ""
echo "=== findPythonCommand checks bundled first, then system ==="
sed -n '54,90p' apps/frontend/src/main/python-detector.ts

Repository: AndyMik90/Auto-Claude

Length of output: 2116


🏁 Script executed:

#!/bin/bash
# Verify: the issue is that in dev mode, the two functions resolve to DIFFERENT Python interpreters
# Let's confirm the backend venv is supposed to have real_ladybug
echo "=== Check backend setup in the codebase ==="
rg -n "real_ladybug" apps/frontend/src/main -A 2 -B 2 | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 3877


🏁 Script executed:

#!/bin/bash
# Final verification: confirm the different resolution paths
echo "=== Summary of resolution differences ==="
echo "Development mode:"
echo "1. getBackendPythonPath() -> looks for backend/.venv/bin/python (has real_ladybug)"
echo "2. findPythonCommand() -> checks bundled/system Python (may NOT have real_ladybug)"
echo ""
echo "This means in dev mode, the install check (findPythonCommand) could fail"
echo "while memory queries actually work (using getBackendPythonPath with real_ladybug)"
echo ""
echo "=== Verify: does the install check try to import real_ladybug? ==="
sed -n '793,830p' apps/frontend/src/main/memory-service.ts | grep -A 5 "import real_ladybug"

Repository: AndyMik90/Auto-Claude

Length of output: 744


🏁 Script executed:

#!/bin/bash
# Check if there's any note about needing to align these
echo "=== Search for any comments about this in memory-service ==="
rg -n "findPythonCommand|getBackendPythonPath" apps/frontend/src/main/memory-service.ts -B 3 | grep -E "(findPythonCommand|getBackendPythonPath|real_ladybug)" | head -30

Repository: AndyMik90/Auto-Claude

Length of output: 1214


🏁 Script executed:

#!/bin/bash
# Verify one last detail: what happens with the null check
# getBackendPythonPath always returns string, so the null check at line 794 would never trigger
echo "=== Check if getBackendPythonPath can ever return null or empty ==="
sed -n '127,158p' apps/frontend/src/main/memory-service.ts | grep -E "return|null|undefined"

Repository: AndyMik90/Auto-Claude

Length of output: 209


Use getBackendPythonPath() for the install check to match memory query resolution.

In development mode, findPythonCommand() (line 793) may resolve to system Python without real_ladybug installed, while memory queries correctly use getBackendPythonPath() to find the backend venv that includes the package. This causes the install check to fail even when memory queries work (false negative). Since the check attempts to import real_ladybug, it must use the same resolver as the actual queries.

Replace findPythonCommand() with getBackendPythonPath() at line 793. Note that getBackendPythonPath() always returns a string and never null, so the null check at lines 794–800 can be removed.

Also apply the same fix at lines 954 and 982 where pythonAvailable uses findPythonCommand() for status reporting.

🤖 Prompt for AI Agents
In `@apps/frontend/src/main/memory-service.ts` around lines 793 - 810, Replace
calls to findPythonCommand() with getBackendPythonPath() when performing the
real_ladybug import check so the install probe uses the same backend venv
resolver as memory queries; remove the now-unnecessary null check around python
command existence in the ladybug install flow (the block that sets
ladybugInstallCache when pythonCmd is falsy) and always proceed to
parsePythonCommand(getBackendPythonPath()) inside the ladybugCheckInProgress
promise (the area using parsePythonCommand, checkArgs and
pythonEnv/getMemoryPythonEnv()). Also update the status-reporting locations that
set pythonAvailable (previously using findPythonCommand()) to use
getBackendPythonPath() at the other two spots mentioned (the code around where
pythonAvailable is computed at the later status checks).

const proc = spawn(cmd, checkArgs, {
stdio: ['ignore', 'pipe', 'pipe'],
env: pythonEnv,
});

let stdout = '';
let stderr = '';
let resolved = false;

proc.stdout.on('data', (data) => {
stdout += data.toString();
});

proc.stderr.on('data', (data) => {
stderr += data.toString();
});

// Timeout after 10 seconds
const timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
proc.kill();
ladybugInstallCache = {
installed: false,
pythonAvailable: true,
error: LADYBUG_ERROR_KEYS.checkFailed,
};
ladybugCheckInProgress = null;
resolve(ladybugInstallCache);
}
}, 10000);

proc.on('close', (code) => {
if (resolved) return;
resolved = true;
clearTimeout(timeoutId);
ladybugCheckInProgress = null;

if (code === 0 && stdout.includes('OK')) {
ladybugInstallCache = {
installed: true,
pythonAvailable: true,
};
} else {
// Parse error to provide helpful message (using i18n keys)
let error: string = LADYBUG_ERROR_KEYS.notInstalled;

if (stderr.includes('ModuleNotFoundError') || stderr.includes('No module named')) {
error = LADYBUG_ERROR_KEYS.notInstalled;
} else if (stderr.includes('WinError 2') || stderr.includes('system cannot find')) {
error = LADYBUG_ERROR_KEYS.buildTools;
}

ladybugInstallCache = {
installed: false,
pythonAvailable: true,
error,
};
}

resolve(ladybugInstallCache);
});

proc.on('error', (err) => {
if (resolved) return;
resolved = true;
clearTimeout(timeoutId);
ladybugCheckInProgress = null;

// Handle spawn failures (ENOENT/EACCES for missing/inaccessible Python)
const errorCode = (err as NodeJS.ErrnoException).code;
const pythonNotAvailable = errorCode === 'ENOENT' || errorCode === 'EACCES';
ladybugInstallCache = {
installed: false,
pythonAvailable: !pythonNotAvailable,
error: pythonNotAvailable ? LADYBUG_ERROR_KEYS.pythonNotFound : LADYBUG_ERROR_KEYS.checkFailed,
};
resolve(ladybugInstallCache);
});
});

return ladybugCheckInProgress;
}

/**
* Synchronous version that returns cached result or a "checking" status.
* Use checkLadybugInstalledAsync() for accurate results.
* @deprecated Use checkLadybugInstalledAsync() instead
*/
export function checkLadybugInstalled(): LadybugInstallStatus {
// Return cached result if available
if (ladybugInstallCache !== null) {
return ladybugInstallCache;
}

// Quick sync check for Python availability (doesn't block, just checks PATH)
const pythonAvailable = findPythonCommand() !== null;

// Return "checking" status and kick off async check
// This prevents blocking the main process
checkLadybugInstalledAsync().catch(() => {
// Error is handled inside the async function
});

// Return a temporary status indicating check is in progress
return {
installed: false,
pythonAvailable,
// No error - check is still in progress
};
}

/**
* Clear the LadybugDB installation cache (useful after installation attempt)
*/
export function clearLadybugInstallCache(): void {
ladybugInstallCache = null;
}

/**
* Get memory service status
*/
export interface MemoryServiceStatus {
kuzuInstalled: boolean;
ladybugInstalled: boolean;
ladybugError?: string;
databasePath: string;
databaseExists: boolean;
databases: string[];
}

/**
* Get memory service status (async version).
* This is the preferred version that doesn't block the main process.
*/
export async function getMemoryServiceStatusAsync(dbPath?: string): Promise<MemoryServiceStatus> {
const basePath = dbPath || getDefaultDbPath();

const databases = fs.existsSync(basePath)
? fs.readdirSync(basePath).filter((name) => !name.startsWith('.'))
: [];

// Check if Python and script are available (findPythonCommand can return null)
const pythonAvailable = findPythonCommand() !== null;
const scriptAvailable = getQueryScriptPath() !== null;

// Check if LadybugDB is actually installed (async to avoid blocking)
const ladybugStatus = await checkLadybugInstalledAsync();

return {
kuzuInstalled: pythonAvailable && scriptAvailable,
ladybugInstalled: ladybugStatus.installed,
ladybugError: ladybugStatus.error,
databasePath: basePath,
databaseExists: databases.length > 0,
databases,
};
}

/**
* Get memory service status (sync version, may return cached/incomplete status).
* @deprecated Use getMemoryServiceStatusAsync() instead to avoid blocking the main process.
*/
export function getMemoryServiceStatus(dbPath?: string): MemoryServiceStatus {
const basePath = dbPath || getDefaultDbPath();

Expand All @@ -765,8 +982,14 @@ export function getMemoryServiceStatus(dbPath?: string): MemoryServiceStatus {
const pythonAvailable = findPythonCommand() !== null;
const scriptAvailable = getQueryScriptPath() !== null;

// Check if LadybugDB is actually installed
// Note: This may return cached result or trigger async check
const ladybugStatus = checkLadybugInstalled();

return {
kuzuInstalled: pythonAvailable && scriptAvailable,
ladybugInstalled: ladybugStatus.installed,
ladybugError: ladybugStatus.error,
databasePath: basePath,
databaseExists: databases.length > 0,
databases,
Expand Down
Loading
Loading