Merge remote-tracking branch 'refs/remotes/origin/main'

# Conflicts:
#	apps/ui/src/routes/__root.tsx
This commit is contained in:
antdev
2026-01-07 01:48:34 +08:00
309 changed files with 24966 additions and 5040 deletions

View File

@@ -1,6 +1,7 @@
export { useAutoMode } from './use-auto-mode';
export { useBoardBackgroundSettings } from './use-board-background-settings';
export { useElectronAgent } from './use-electron-agent';
export { useGuidedPrompts } from './use-guided-prompts';
export { useKeyboardShortcuts } from './use-keyboard-shortcuts';
export { useMessageQueue } from './use-message-queue';
export { useOSDetection, type OperatingSystem, type OSDetectionResult } from './use-os-detection';

View File

@@ -1,9 +1,12 @@
import { useEffect, useCallback, useMemo } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { createLogger } from '@automaker/utils/logger';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import type { AutoModeEvent } from '@/types/electron';
const logger = createLogger('AutoMode');
// Type guard for plan_approval_required event
function isPlanApprovalEvent(
event: AutoModeEvent
@@ -67,7 +70,7 @@ export function useAutoMode() {
if (!api?.autoMode) return;
const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => {
console.log('[AutoMode Event]', event);
logger.info('Event:', event);
// Events include projectPath from backend - use it to look up project ID
// Fall back to current projectId if not provided in event
@@ -84,7 +87,7 @@ export function useAutoMode() {
// Skip event if we couldn't determine the project
if (!eventProjectId) {
console.warn('[AutoMode] Could not determine project for event:', event);
logger.warn('Could not determine project for event:', event);
return;
}
@@ -103,7 +106,7 @@ export function useAutoMode() {
case 'auto_mode_feature_complete':
// Feature completed - remove from running tasks and UI will reload features on its own
if (event.featureId) {
console.log('[AutoMode] Feature completed:', event.featureId, 'passes:', event.passes);
logger.info('Feature completed:', event.featureId, 'passes:', event.passes);
removeRunningTask(eventProjectId, event.featureId);
addAutoModeActivity({
featureId: event.featureId,
@@ -121,7 +124,7 @@ export function useAutoMode() {
// Check if this is a user-initiated cancellation or abort (not a real error)
if (event.errorType === 'cancellation' || event.errorType === 'abort') {
// User cancelled/aborted the feature - just log as info, not an error
console.log('[AutoMode] Feature cancelled/aborted:', event.error);
logger.info('Feature cancelled/aborted:', event.error);
// Remove from running tasks
if (eventProjectId) {
removeRunningTask(eventProjectId, event.featureId);
@@ -130,7 +133,7 @@ export function useAutoMode() {
}
// Real error - log and show to user
console.error('[AutoMode Error]', event.error);
logger.error('Error:', event.error);
// Check for authentication errors and provide a more helpful message
const isAuthError =
@@ -182,7 +185,7 @@ export function useAutoMode() {
case 'auto_mode_phase':
// Log phase transitions (Planning, Action, Verification)
if (event.featureId && event.phase && event.message) {
console.log(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
logger.debug(`[AutoMode] Phase: ${event.phase} for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: event.phase,
@@ -195,7 +198,7 @@ export function useAutoMode() {
case 'plan_approval_required':
// Plan requires user approval before proceeding
if (isPlanApprovalEvent(event)) {
console.log(`[AutoMode] Plan approval required for ${event.featureId}`);
logger.debug(`[AutoMode] Plan approval required for ${event.featureId}`);
setPendingPlanApproval({
featureId: event.featureId,
projectPath: event.projectPath || currentProject?.path || '',
@@ -208,7 +211,7 @@ export function useAutoMode() {
case 'planning_started':
// Log when planning phase begins
if (event.featureId && event.mode && event.message) {
console.log(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`);
logger.debug(`[AutoMode] Planning started (${event.mode}) for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: 'planning',
@@ -221,7 +224,7 @@ export function useAutoMode() {
case 'plan_approved':
// Log when plan is approved by user
if (event.featureId) {
console.log(`[AutoMode] Plan approved for ${event.featureId}`);
logger.debug(`[AutoMode] Plan approved for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: 'action',
@@ -236,7 +239,7 @@ export function useAutoMode() {
case 'plan_auto_approved':
// Log when plan is auto-approved (requirePlanApproval=false)
if (event.featureId) {
console.log(`[AutoMode] Plan auto-approved for ${event.featureId}`);
logger.debug(`[AutoMode] Plan auto-approved for ${event.featureId}`);
addAutoModeActivity({
featureId: event.featureId,
type: 'action',
@@ -253,7 +256,7 @@ export function useAutoMode() {
AutoModeEvent,
{ type: 'plan_revision_requested' }
>;
console.log(
logger.debug(
`[AutoMode] Plan revision requested for ${event.featureId} (v${revisionEvent.planVersion})`
);
addAutoModeActivity({
@@ -269,7 +272,7 @@ export function useAutoMode() {
// Task started - show which task is being worked on
if (event.featureId && 'taskId' in event && 'taskDescription' in event) {
const taskEvent = event as Extract<AutoModeEvent, { type: 'auto_mode_task_started' }>;
console.log(
logger.debug(
`[AutoMode] Task ${taskEvent.taskId} started for ${event.featureId}: ${taskEvent.taskDescription}`
);
addAutoModeActivity({
@@ -284,7 +287,7 @@ export function useAutoMode() {
// Task completed - show progress
if (event.featureId && 'taskId' in event) {
const taskEvent = event as Extract<AutoModeEvent, { type: 'auto_mode_task_complete' }>;
console.log(
logger.debug(
`[AutoMode] Task ${taskEvent.taskId} completed for ${event.featureId} (${taskEvent.tasksCompleted}/${taskEvent.tasksTotal})`
);
addAutoModeActivity({
@@ -302,7 +305,7 @@ export function useAutoMode() {
AutoModeEvent,
{ type: 'auto_mode_phase_complete' }
>;
console.log(
logger.debug(
`[AutoMode] Phase ${phaseEvent.phaseNumber} completed for ${event.featureId}`
);
addAutoModeActivity({
@@ -330,18 +333,18 @@ export function useAutoMode() {
// Start auto mode - UI only, feature pickup is handled in board-view.tsx
const start = useCallback(() => {
if (!currentProject) {
console.error('No project selected');
logger.error('No project selected');
return;
}
setAutoModeRunning(currentProject.id, true);
console.log(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`);
logger.debug(`[AutoMode] Started with maxConcurrency: ${maxConcurrency}`);
}, [currentProject, setAutoModeRunning, maxConcurrency]);
// Stop auto mode - UI only, running tasks continue until natural completion
const stop = useCallback(() => {
if (!currentProject) {
console.error('No project selected');
logger.error('No project selected');
return;
}
@@ -350,14 +353,14 @@ export function useAutoMode() {
// Stopping auto mode only turns off the toggle to prevent new features
// from being picked up. Running tasks will complete naturally and be
// removed via the auto_mode_feature_complete event.
console.log('[AutoMode] Stopped - running tasks will continue');
logger.info('Stopped - running tasks will continue');
}, [currentProject, setAutoModeRunning]);
// Stop a specific feature
const stopFeature = useCallback(
async (featureId: string) => {
if (!currentProject) {
console.error('No project selected');
logger.error('No project selected');
return;
}
@@ -371,7 +374,7 @@ export function useAutoMode() {
if (result.success) {
removeRunningTask(currentProject.id, featureId);
console.log('[AutoMode] Feature stopped successfully:', featureId);
logger.info('Feature stopped successfully:', featureId);
addAutoModeActivity({
featureId,
type: 'complete',
@@ -379,11 +382,11 @@ export function useAutoMode() {
passes: false,
});
} else {
console.error('[AutoMode] Failed to stop feature:', result.error);
logger.error('Failed to stop feature:', result.error);
throw new Error(result.error || 'Failed to stop feature');
}
} catch (error) {
console.error('[AutoMode] Error stopping feature:', error);
logger.error('Error stopping feature:', error);
throw error;
}
},

View File

@@ -1,8 +1,11 @@
import { useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger';
import { useAppStore } from '@/store/app-store';
import { getHttpApiClient } from '@/lib/http-api-client';
import { toast } from 'sonner';
const logger = createLogger('BoardBackground');
/**
* Hook for managing board background settings with automatic persistence to server
*/
@@ -19,11 +22,11 @@ export function useBoardBackgroundSettings() {
});
if (!result.success) {
console.error('Failed to persist settings:', result.error);
logger.error('Failed to persist settings:', result.error);
toast.error('Failed to save settings');
}
} catch (error) {
console.error('Failed to persist settings:', error);
logger.error('Failed to persist settings:', error);
toast.error('Failed to save settings');
}
},

View File

@@ -0,0 +1,46 @@
import { useEffect, useRef } from 'react';
import { useSetupStore } from '@/store/setup-store';
import { getHttpApiClient } from '@/lib/http-api-client';
/**
* Hook to initialize Cursor CLI status on app startup.
* This ensures the cursorCliStatus is available in the setup store
* before the user opens feature dialogs.
*/
export function useCursorStatusInit() {
const { setCursorCliStatus, cursorCliStatus } = useSetupStore();
const initialized = useRef(false);
useEffect(() => {
// Only initialize once per session
if (initialized.current || cursorCliStatus !== null) {
return;
}
initialized.current = true;
const initCursorStatus = async () => {
try {
const api = getHttpApiClient();
const statusResult = await api.setup.getCursorStatus();
if (statusResult.success) {
setCursorCliStatus({
installed: statusResult.installed ?? false,
version: statusResult.version ?? undefined,
auth: statusResult.auth?.authenticated
? {
authenticated: true,
method: statusResult.auth.method || 'unknown',
}
: undefined,
});
}
} catch (error) {
// Silently fail - cursor is optional
console.debug('[CursorStatusInit] Failed to check cursor status:', error);
}
};
initCursorStatus();
}, [setCursorCliStatus, cursorCliStatus]);
}

View File

@@ -4,11 +4,15 @@ import { useMessageQueue } from './use-message-queue';
import type { ImageAttachment, TextFileAttachment } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
import { sanitizeFilename } from '@/lib/image-utils';
import { createLogger } from '@automaker/utils/logger';
const logger = createLogger('ElectronAgent');
interface UseElectronAgentOptions {
sessionId: string;
workingDirectory?: string;
model?: string;
thinkingLevel?: string;
onToolUse?: (toolName: string, toolInput: unknown) => void;
}
@@ -18,6 +22,7 @@ interface QueuedPrompt {
message: string;
imagePaths?: string[];
model?: string;
thinkingLevel?: string;
addedAt: string;
}
@@ -64,6 +69,7 @@ export function useElectronAgent({
sessionId,
workingDirectory,
model,
thinkingLevel,
onToolUse,
}: UseElectronAgentOptions): UseElectronAgentResult {
const [messages, setMessages] = useState<Message[]>([]);
@@ -91,7 +97,7 @@ export function useElectronAgent({
setError(null);
try {
console.log('[useElectronAgent] Sending message directly', {
logger.info('Sending message directly', {
hasImages: images && images.length > 0,
imageCount: images?.length || 0,
hasTextFiles: textFiles && textFiles.length > 0,
@@ -121,9 +127,9 @@ export function useElectronAgent({
);
if (result.success && result.path) {
imagePaths.push(result.path);
console.log('[useElectronAgent] Saved image to .automaker/images:', result.path);
logger.info('Saved image to .automaker/images:', result.path);
} else {
console.error('[useElectronAgent] Failed to save image:', result.error);
logger.error('Failed to save image:', result.error);
}
}
}
@@ -133,7 +139,8 @@ export function useElectronAgent({
messageContent,
workingDirectory,
imagePaths,
model
model,
thinkingLevel
);
if (!result.success) {
@@ -143,13 +150,13 @@ export function useElectronAgent({
// Note: We don't set isProcessing to false here because
// it will be set by the "complete" or "error" stream event
} catch (err) {
console.error('[useElectronAgent] Failed to send message:', err);
logger.error('Failed to send message:', err);
setError(err instanceof Error ? err.message : 'Failed to send message');
setIsProcessing(false);
throw err;
}
},
[sessionId, workingDirectory, model, isProcessing]
[sessionId, workingDirectory, model, thinkingLevel, isProcessing]
);
// Message queue for queuing messages when agent is busy
@@ -188,13 +195,13 @@ export function useElectronAgent({
setError(null);
try {
console.log('[useElectronAgent] Starting session:', sessionId);
logger.info('Starting session:', sessionId);
const result = await api.agent!.start(sessionId, workingDirectory);
if (!mounted) return;
if (result.success && result.messages) {
console.log('[useElectronAgent] Loaded', result.messages.length, 'messages');
logger.info('Loaded', result.messages.length, 'messages');
setMessages(result.messages);
setIsConnected(true);
@@ -202,7 +209,7 @@ export function useElectronAgent({
const historyResult = await api.agent!.getHistory(sessionId);
if (mounted && historyResult.success) {
const isRunning = historyResult.isRunning || false;
console.log('[useElectronAgent] Session running state:', isRunning);
logger.info('Session running state:', isRunning);
setIsProcessing(isRunning);
}
} else {
@@ -211,7 +218,7 @@ export function useElectronAgent({
}
} catch (err) {
if (!mounted) return;
console.error('[useElectronAgent] Failed to initialize:', err);
logger.error('Failed to initialize:', err);
setError(err instanceof Error ? err.message : 'Failed to initialize');
setIsProcessing(false);
}
@@ -227,7 +234,7 @@ export function useElectronAgent({
// Auto-process queue when agent finishes processing
useEffect(() => {
if (!isProcessing && !isProcessingQueue && queuedMessages.length > 0) {
console.log('[useElectronAgent] Auto-processing next queued message');
logger.info('Auto-processing next queued message');
processNext();
}
}, [isProcessing, isProcessingQueue, queuedMessages.length, processNext]);
@@ -238,21 +245,21 @@ export function useElectronAgent({
if (!api?.agent) return;
if (!sessionId) return; // Don't subscribe if no session
console.log('[useElectronAgent] Subscribing to stream events for session:', sessionId);
logger.info('Subscribing to stream events for session:', sessionId);
const handleStream = (event: StreamEvent) => {
// CRITICAL: Only process events for our specific session
if (event.sessionId !== sessionId) {
console.log('[useElectronAgent] Ignoring event for different session:', event.sessionId);
logger.info('Ignoring event for different session:', event.sessionId);
return;
}
console.log('[useElectronAgent] Stream event for', sessionId, ':', event.type);
logger.info('Stream event for', sessionId, ':', event.type);
switch (event.type) {
case 'started':
// Agent started processing (including from queue)
console.log('[useElectronAgent] Agent started processing for session:', sessionId);
logger.info('Agent started processing for session:', sessionId);
setIsProcessing(true);
break;
@@ -297,13 +304,13 @@ export function useElectronAgent({
case 'tool_use':
// Tool being used
console.log('[useElectronAgent] Tool use:', event.tool.name);
logger.info('Tool use:', event.tool.name);
onToolUse?.(event.tool.name, event.tool.input);
break;
case 'complete':
// Agent finished processing for THIS session
console.log('[useElectronAgent] Processing complete for session:', sessionId);
logger.info('Processing complete for session:', sessionId);
setIsProcessing(false);
if (event.messageId) {
setMessages((prev) =>
@@ -316,7 +323,7 @@ export function useElectronAgent({
case 'error':
// Error occurred for THIS session
console.error('[useElectronAgent] Agent error for session:', sessionId, event.error);
logger.error('Agent error for session:', sessionId, event.error);
setIsProcessing(false);
setError(event.error);
if (event.message) {
@@ -327,13 +334,13 @@ export function useElectronAgent({
case 'queue_updated':
// Server queue was updated
console.log('[useElectronAgent] Queue updated:', event.queue);
logger.info('Queue updated:', event.queue);
setServerQueue(event.queue || []);
break;
case 'queue_error':
// Error processing a queued prompt
console.error('[useElectronAgent] Queue error:', event.error);
logger.error('Queue error:', event.error);
setError(event.error);
break;
}
@@ -343,7 +350,7 @@ export function useElectronAgent({
return () => {
if (unsubscribeRef.current) {
console.log('[useElectronAgent] Unsubscribing from stream events for session:', sessionId);
logger.info('Unsubscribing from stream events for session:', sessionId);
unsubscribeRef.current();
unsubscribeRef.current = null;
}
@@ -360,7 +367,7 @@ export function useElectronAgent({
}
if (isProcessing) {
console.warn('[useElectronAgent] Already processing a message');
logger.warn('Already processing a message');
return;
}
@@ -368,7 +375,7 @@ export function useElectronAgent({
setError(null);
try {
console.log('[useElectronAgent] Sending message', {
logger.info('Sending message', {
hasImages: images && images.length > 0,
imageCount: images?.length || 0,
hasTextFiles: textFiles && textFiles.length > 0,
@@ -398,9 +405,9 @@ export function useElectronAgent({
);
if (result.success && result.path) {
imagePaths.push(result.path);
console.log('[useElectronAgent] Saved image to .automaker/images:', result.path);
logger.info('Saved image to .automaker/images:', result.path);
} else {
console.error('[useElectronAgent] Failed to save image:', result.error);
logger.error('Failed to save image:', result.error);
}
}
}
@@ -410,7 +417,8 @@ export function useElectronAgent({
messageContent,
workingDirectory,
imagePaths,
model
model,
thinkingLevel
);
if (!result.success) {
@@ -420,12 +428,12 @@ export function useElectronAgent({
// Note: We don't set isProcessing to false here because
// it will be set by the "complete" or "error" stream event
} catch (err) {
console.error('[useElectronAgent] Failed to send message:', err);
logger.error('Failed to send message:', err);
setError(err instanceof Error ? err.message : 'Failed to send message');
setIsProcessing(false);
}
},
[sessionId, workingDirectory, model, isProcessing]
[sessionId, workingDirectory, model, thinkingLevel, isProcessing]
);
// Stop current execution
@@ -437,7 +445,7 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Stopping execution');
logger.info('Stopping execution');
const result = await api.agent!.stop(sessionId);
if (!result.success) {
@@ -446,7 +454,7 @@ export function useElectronAgent({
setIsProcessing(false);
}
} catch (err) {
console.error('[useElectronAgent] Failed to stop:', err);
logger.error('Failed to stop:', err);
setError(err instanceof Error ? err.message : 'Failed to stop execution');
}
}, [sessionId]);
@@ -460,7 +468,7 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Clearing history');
logger.info('Clearing history');
const result = await api.agent!.clear(sessionId);
if (result.success) {
@@ -470,7 +478,7 @@ export function useElectronAgent({
setError(result.error || 'Failed to clear history');
}
} catch (err) {
console.error('[useElectronAgent] Failed to clear:', err);
logger.error('Failed to clear:', err);
setError(err instanceof Error ? err.message : 'Failed to clear history');
}
}, [sessionId]);
@@ -512,18 +520,24 @@ export function useElectronAgent({
}
}
console.log('[useElectronAgent] Adding to server queue');
const result = await api.agent.queueAdd(sessionId, messageContent, imagePaths, model);
logger.info('Adding to server queue');
const result = await api.agent.queueAdd(
sessionId,
messageContent,
imagePaths,
model,
thinkingLevel
);
if (!result.success) {
setError(result.error || 'Failed to add to queue');
}
} catch (err) {
console.error('[useElectronAgent] Failed to add to queue:', err);
logger.error('Failed to add to queue:', err);
setError(err instanceof Error ? err.message : 'Failed to add to queue');
}
},
[sessionId, workingDirectory, model]
[sessionId, workingDirectory, model, thinkingLevel]
);
// Remove a prompt from the server queue
@@ -536,14 +550,14 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Removing from server queue:', promptId);
logger.info('Removing from server queue:', promptId);
const result = await api.agent.queueRemove(sessionId, promptId);
if (!result.success) {
setError(result.error || 'Failed to remove from queue');
}
} catch (err) {
console.error('[useElectronAgent] Failed to remove from queue:', err);
logger.error('Failed to remove from queue:', err);
setError(err instanceof Error ? err.message : 'Failed to remove from queue');
}
},
@@ -559,14 +573,14 @@ export function useElectronAgent({
}
try {
console.log('[useElectronAgent] Clearing server queue');
logger.info('Clearing server queue');
const result = await api.agent.queueClear(sessionId);
if (!result.success) {
setError(result.error || 'Failed to clear queue');
}
} catch (err) {
console.error('[useElectronAgent] Failed to clear queue:', err);
logger.error('Failed to clear queue:', err);
setError(err instanceof Error ? err.message : 'Failed to clear queue');
}
}, [sessionId]);

View File

@@ -0,0 +1,86 @@
/**
* Hook for fetching guided prompts from the backend API
*
* This hook provides the single source of truth for guided prompts,
* fetched from the backend /api/ideation/prompts endpoint.
*/
import { useState, useEffect, useCallback } from 'react';
import type { IdeationPrompt, PromptCategory, IdeaCategory } from '@automaker/types';
import { getElectronAPI } from '@/lib/electron';
interface UseGuidedPromptsReturn {
prompts: IdeationPrompt[];
categories: PromptCategory[];
isLoading: boolean;
error: string | null;
refetch: () => Promise<void>;
getPromptsByCategory: (category: IdeaCategory) => IdeationPrompt[];
getPromptById: (id: string) => IdeationPrompt | undefined;
getCategoryById: (id: IdeaCategory) => PromptCategory | undefined;
}
export function useGuidedPrompts(): UseGuidedPromptsReturn {
const [prompts, setPrompts] = useState<IdeationPrompt[]>([]);
const [categories, setCategories] = useState<PromptCategory[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchPrompts = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const api = getElectronAPI();
const result = await api.ideation?.getPrompts();
if (result?.success) {
setPrompts(result.prompts || []);
setCategories(result.categories || []);
} else {
setError(result?.error || 'Failed to fetch prompts');
}
} catch (err) {
console.error('Failed to fetch guided prompts:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch prompts');
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetchPrompts();
}, [fetchPrompts]);
const getPromptsByCategory = useCallback(
(category: IdeaCategory): IdeationPrompt[] => {
return prompts.filter((p) => p.category === category);
},
[prompts]
);
const getPromptById = useCallback(
(id: string): IdeationPrompt | undefined => {
return prompts.find((p) => p.id === id);
},
[prompts]
);
const getCategoryById = useCallback(
(id: IdeaCategory): PromptCategory | undefined => {
return categories.find((c) => c.id === id);
},
[categories]
);
return {
prompts,
categories,
isLoading,
error,
refetch: fetchPrompts,
getPromptsByCategory,
getPromptById,
getCategoryById,
};
}

View File

@@ -1,5 +1,5 @@
import { useEffect, useCallback } from 'react';
import { useAppStore, parseShortcut } from '@/store/app-store';
import { useEffect, useCallback, useMemo } from 'react';
import { useAppStore, parseShortcut, DEFAULT_KEYBOARD_SHORTCUTS } from '@/store/app-store';
export interface KeyboardShortcut {
key: string; // Can be simple "K" or with modifiers "Shift+N", "Cmd+K"
@@ -237,8 +237,18 @@ export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
/**
* Hook to get current keyboard shortcuts from store
* This replaces the static constants and allows customization
* Merges with defaults to ensure new shortcuts are always available
*/
export function useKeyboardShortcutsConfig() {
const keyboardShortcuts = useAppStore((state) => state.keyboardShortcuts);
return keyboardShortcuts;
// Merge with defaults to ensure new shortcuts are available
// even if user's persisted state predates them
return useMemo(
() => ({
...DEFAULT_KEYBOARD_SHORTCUTS,
...keyboardShortcuts,
}),
[keyboardShortcuts]
);
}

View File

@@ -1,6 +1,9 @@
import { useState, useCallback } from 'react';
import { createLogger } from '@automaker/utils/logger';
import type { ImageAttachment, TextFileAttachment } from '@/store/app-store';
const logger = createLogger('MessageQueue');
export interface QueuedMessage {
id: string;
content: string;
@@ -72,7 +75,7 @@ export function useMessageQueue({ onProcessNext }: UseMessageQueueOptions): UseM
// Remove the processed message from queue
setQueuedMessages((prev) => prev.slice(1));
} catch (error) {
console.error('Error processing queued message:', error);
logger.error('Error processing queued message:', error);
// Keep the message in queue for retry or manual removal
} finally {
setIsProcessingQueue(false);

View File

@@ -18,11 +18,14 @@
*/
import { useEffect, useState, useRef } from 'react';
import { createLogger } from '@automaker/utils/logger';
import { getHttpApiClient, waitForApiKeyInit } from '@/lib/http-api-client';
import { isElectron } from '@/lib/electron';
import { getItem, removeItem } from '@/lib/storage';
import { useAppStore } from '@/store/app-store';
const logger = createLogger('SettingsMigration');
/**
* State returned by useSettingsMigration hook
*/
@@ -109,7 +112,7 @@ export function useSettingsMigration(): MigrationState {
const status = await api.settings.getStatus();
if (!status.success) {
console.error('[Settings Migration] Failed to get status:', status);
logger.error('Failed to get status:', status);
setState({
checked: true,
migrated: false,
@@ -120,7 +123,7 @@ export function useSettingsMigration(): MigrationState {
// If settings files already exist, no migration needed
if (!status.needsMigration) {
console.log('[Settings Migration] Settings files exist, no migration needed');
logger.info('Settings files exist, no migration needed');
setState({ checked: true, migrated: false, error: null });
return;
}
@@ -128,12 +131,12 @@ export function useSettingsMigration(): MigrationState {
// Check if we have localStorage data to migrate
const automakerStorage = getItem('automaker-storage');
if (!automakerStorage) {
console.log('[Settings Migration] No localStorage data to migrate');
logger.info('No localStorage data to migrate');
setState({ checked: true, migrated: false, error: null });
return;
}
console.log('[Settings Migration] Starting migration...');
logger.info('Starting migration...');
// Collect all localStorage data
const localStorageData: Record<string, string> = {};
@@ -148,7 +151,7 @@ export function useSettingsMigration(): MigrationState {
const result = await api.settings.migrate(localStorageData);
if (result.success) {
console.log('[Settings Migration] Migration successful:', {
logger.info('Migration successful:', {
globalSettings: result.migratedGlobalSettings,
credentials: result.migratedCredentials,
projects: result.migratedProjectCount,
@@ -161,7 +164,7 @@ export function useSettingsMigration(): MigrationState {
setState({ checked: true, migrated: true, error: null });
} else {
console.warn('[Settings Migration] Migration had errors:', result.errors);
logger.warn('Migration had errors:', result.errors);
setState({
checked: true,
migrated: false,
@@ -169,7 +172,7 @@ export function useSettingsMigration(): MigrationState {
});
}
} catch (error) {
console.error('[Settings Migration] Migration failed:', error);
logger.error('Migration failed:', error);
setState({
checked: true,
migrated: false,
@@ -224,14 +227,13 @@ export async function syncSettingsToServer(): Promise<boolean> {
muteDoneSound: state.muteDoneSound,
enhancementModel: state.enhancementModel,
validationModel: state.validationModel,
phaseModels: state.phaseModels,
autoLoadClaudeMd: state.autoLoadClaudeMd,
enableSandboxMode: state.enableSandboxMode,
skipSandboxWarning: state.skipSandboxWarning,
keyboardShortcuts: state.keyboardShortcuts,
aiProfiles: state.aiProfiles,
mcpServers: state.mcpServers,
mcpAutoApproveTools: state.mcpAutoApproveTools,
mcpUnrestrictedTools: state.mcpUnrestrictedTools,
promptCustomization: state.promptCustomization,
projects: state.projects,
trashedProjects: state.trashedProjects,
@@ -243,7 +245,7 @@ export async function syncSettingsToServer(): Promise<boolean> {
const result = await api.settings.updateGlobal(updates);
return result.success;
} catch (error) {
console.error('[Settings Sync] Failed to sync settings:', error);
logger.error('Failed to sync settings:', error);
return false;
}
}
@@ -270,7 +272,7 @@ export async function syncCredentialsToServer(apiKeys: {
const result = await api.settings.updateCredentials({ apiKeys });
return result.success;
} catch (error) {
console.error('[Settings Sync] Failed to sync credentials:', error);
logger.error('Failed to sync credentials:', error);
return false;
}
}
@@ -311,7 +313,7 @@ export async function syncProjectSettingsToServer(
const result = await api.settings.updateProject(projectPath, updates);
return result.success;
} catch (error) {
console.error('[Settings Sync] Failed to sync project settings:', error);
logger.error('Failed to sync project settings:', error);
return false;
}
}
@@ -331,22 +333,20 @@ export async function loadMCPServersFromServer(): Promise<boolean> {
const result = await api.settings.getGlobal();
if (!result.success || !result.settings) {
console.error('[Settings Load] Failed to load settings:', result.error);
logger.error('Failed to load settings:', result.error);
return false;
}
const mcpServers = result.settings.mcpServers || [];
const mcpAutoApproveTools = result.settings.mcpAutoApproveTools ?? true;
const mcpUnrestrictedTools = result.settings.mcpUnrestrictedTools ?? true;
// Clear existing and add all from server
// We need to update the store directly since we can't use hooks here
useAppStore.setState({ mcpServers, mcpAutoApproveTools, mcpUnrestrictedTools });
useAppStore.setState({ mcpServers });
console.log(`[Settings Load] Loaded ${mcpServers.length} MCP servers from server`);
logger.info(`Loaded ${mcpServers.length} MCP servers from server`);
return true;
} catch (error) {
console.error('[Settings Load] Failed to load MCP servers:', error);
logger.error('Failed to load MCP servers:', error);
return false;
}
}